net: skb: isolate skb data area allocations into a separate bucket
Subsystems (4)
v4 cover net: skb: isolate skb data area allocations into a separate bucket
Hi!
I wanted to get this rolling again, and got the buckets able to handle memcg (GFP_KERNEL_ACCOUNT) with isolation (since it's common due to AF_UNIX), and GFP_DMA with fall back (since it's rare). It gave me an excuse to build out bucket kunit tests too, and that (and LLM review) found a couple other issues that needed fixing too.
The bulk of this is mm/slab, but the final patch is netdev, but Jakub had acked it before. I've dropped that Ack since time has passed, but I'm hoping it will get a re-Ack and this whole series can go via slab.
Thanks!
-Kees
v4:
- fix a bunch of little things wrong with kmem_buckets
- provide cache-type fallback when using buckets
- handle memcg when using buckets
v3: https://lore.kernel.org/all/[email protected]/
Kees Cook (6):
mm/slab: Mark the kmem_buckets_create() context as a Context: section mm/slab: Give bucket caches the alignment of the caches they mirror mm/slab: Add kmem_buckets_destroy()
mm/slab: Add tests for the existing kmem_buckets behaviour mm/slab: Provide kmalloc type fallback for bucket allocations mm/slab: Let a bucket set handle __GFP_ACCOUNT
Pedro Falcato (1):
net: skb: isolate skb data area allocations into a separate bucket
include/linux/slab.h | 59 +++++++- mm/slab.h | 44 +++++- lib/tests/slub_kunit.c | 316 +++++++++++++++++++++++++++++++++++++++++ mm/slab_common.c | 167 ++++++++++++++++++---- net/core/skbuff.c | 11 +- 5 files changed, 566 insertions(+), 31 deletions(-)
Signature · 1 line
2.34.1
PATCH v4 1/7 mm/slab: Mark the kmem_buckets_create() context as a Context: section
kmem_buckets_create() had the same sentence about calling context as kmem_cache_create(), but lacked the "Context:" prefix, so kernel-doc rendered in the body instead of as a "context" section.
Give it the missing prefix. Additionally fix the "a interrupt" typo __kmem_cache_create_args() had.
---
Cc: Vlastimil Babka <[email protected]>
Cc: Harry Yoo <[email protected]>
Cc: Andrew Morton <[email protected]>
Cc: Hao Li <[email protected]>
Cc: Christoph Lameter <[email protected]>
Cc: David Rientjes <[email protected]>
Cc: Roman Gushchin <[email protected]>
Cc: <[email protected]>
Cc: Pedro Falcato <[email protected]>
Cc: Kuniyuki Iwashima <[email protected]>
Cc: <[email protected]>
--- mm/slab_common.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-)
mm/slab_common.c +2 -2
@@ -311,7 +311,7 @@ __kmem_cache_alias(const char *name, unsigned int size, slab_flags_t flags,* &SLAB_TYPESAFE_BY_RCU - Slab page (not individual objects) freeing delayed * by a grace period - see the full description before using. * -* Context: Cannot be called within a interrupt, but can be interrupted. +* Context: Cannot be called within an interrupt, but can be interrupted. * * Return: a pointer to the cache on success, NULL on failure. */
@@ -422,7 +422,7 @@ static struct kmem_cache *kmem_buckets_cache __ro_after_init;* to/from userspace. * @ctor: A constructor for the objects, run when new allocations are made. * -* Cannot be called within an interrupt, but can be interrupted. +* Context: Cannot be called within an interrupt, but can be interrupted. * * Return: a pointer to the cache on success, NULL on failure. When * CONFIG_SLAB_BUCKETS is not enabled, ZERO_SIZE_PTR is returned, and
Signature · 1 line
2.34.1
Re: [PATCH v4 1/7] mm/slab: Mark the kmem_buckets_create() context as a Context: section
Quoted 11 lines · Kees Cook
kmem_buckets_create()had the same sentence about calling context askmem_cache_create(), but lacked the "Context:" prefix, so kernel-doc rendered in the body instead of as a "context" section.Give it the missing prefix. Additionally fix the "a interrupt" typo
__kmem_cache_create_args()had.Fixes b32801d1255be ("mm/slab: Introduce kmem_buckets_create() and family")Assisted-by LLMSigned-off-by Kees Cook <[email protected]>---
Looks good to me,
Signature · 2 lines
Cheers, Harry / Hyeonggon
Re: [PATCH v4 1/7] mm/slab: Mark the kmem_buckets_create() context as a Context: section
In reply to Kees CookQuoted 10 lines · Kees Cook
kmem_buckets_create()had the same sentence about calling context askmem_cache_create(), but lacked the "Context:" prefix, so kernel-doc rendered in the body instead of as a "context" section.Give it the missing prefix. Additionally fix the "a interrupt" typo
__kmem_cache_create_args()had.Fixes b32801d1255be ("mm/slab: Introduce kmem_buckets_create() and family")Assisted-by LLMSigned-off-by Kees Cook <[email protected]>
Signature · 1 line
Pedro
Re: [PATCH v4 1/7] mm/slab: Mark the kmem_buckets_create() context as a Context: section
In reply to Kees CookQuoted 11 lines · Kees Cook
kmem_buckets_create()had the same sentence about calling context askmem_cache_create(), but lacked the "Context:" prefix, so kernel-doc rendered in the body instead of as a "context" section.Give it the missing prefix. Additionally fix the "a interrupt" typo
__kmem_cache_create_args()had.Fixes b32801d1255be ("mm/slab: Introduce kmem_buckets_create() and family")Assisted-by LLMSigned-off-by Kees Cook <[email protected]>---
PATCH v4 2/7 mm/slab: Give bucket caches the alignment of the caches they mirror
In reply to Kees CookA bucket set is created with kmem_cache_create_usercopy(..., align = 0), so calculate_alignment() falls back to arch_slab_minalign(), typically 8 bytes. The general kmalloc caches it stands in for are created through create_boot_cache(), which starts from ARCH_KMALLOC_MINALIGN and raises it to the largest power-of-two divisor of the size:
if (flags & SLAB_KMALLOC) align = max(align, 1U << (ffs(size) - 1));
This is only a problem when slab metadata is enabled with CONFIG_KASAN=y, CONFIG_SLUB_DEBUG_ON=y, or "slab_debug=...", because metadata changes the stride size off a power of two, for example:
size 128: bucket align=8 size=224 | kmalloc align=128 size=384 size 512: bucket align=8 size=608 | kmalloc align=512 size=1536 size 2048: bucket align=8 size=2144 | kmalloc align=2048 size=6144
So bucket allocations will fail the IS_ALIGNED(p, ARCH_DMA_MINALIGN) check, potentially creating problems for non-coherent DMA situation.
Take the alignment from the cache being mirrored, which is where the size and the name suffix already come from. Nothing changes where the alignment was already implied by the size.
---
Cc: Vlastimil Babka <[email protected]>
Cc: Harry Yoo <[email protected]>
Cc: Andrew Morton <[email protected]>
Cc: Hao Li <[email protected]>
Cc: Christoph Lameter <[email protected]>
Cc: David Rientjes <[email protected]>
Cc: Roman Gushchin <[email protected]>
Cc: <[email protected]>
Cc: Pedro Falcato <[email protected]>
Cc: Kuniyuki Iwashima <[email protected]>
Cc: <[email protected]>
--- mm/slab_common.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-)
mm/slab_common.c +2 -1
@@ -487,7 +487,8 @@ kmem_buckets *kmem_buckets_create(const char *name, slab_flags_t flags,if (WARN_ON (!cache_name)) goto fail; (*b)[aligned_idx] =kmem_cache_create_usercopy (cache_name, size, -0 , flags, cache_useroffset, + kmalloc_caches[KMALLOC_NORMAL ][idx]->align, + flags, cache_useroffset, cache_usersize, ctor);kfree (cache_name);if (WARN_ON (!(*b)[aligned_idx]))
Signature · 1 line
2.34.1
Harry Yoo
maintainerRe: [PATCH v4 2/7] mm/slab: Give bucket caches the alignment of the caches they mirror
Quoted 9 lines · Kees Cook
A bucket set is created with
kmem_cache_create_usercopy(..., align = 0), socalculate_alignment()falls back toarch_slab_minalign(), typically 8 bytes. The general kmalloc caches it stands in for are created throughcreate_boot_cache(), which starts fromARCH_KMALLOC_MINALIGNand raises it to the largest power-of-two divisor of the size:if (flags & SLAB_KMALLOC) align = max(align, 1U << (ffs(size) - 1));
Quoted 7 lines
This is only a problem when slab metadata is enabled with
CONFIG_KASAN=y,CONFIG_SLUB_DEBUG_ON=y, or "slab_debug=...", because metadata changes the stride size off a power of two, for example:size 128: bucket align=8 size=224 | kmalloc align=128 size=384 size 512: bucket align=8 size=608 | kmalloc align=512 size=1536 size 2048: bucket align=8 size=2144 | kmalloc align=2048 size=6144
Hmm... I think what adds confusion here is that in new_kmalloc_cache() we adjust the size based on alignment, but in create_boot_cache() we don't do that. Perhaps let's make it consistent and move it to new_kmalloc_cache()?
Quoted 2 lines
So bucket allocations will fail the
IS_ALIGNED(p, ARCH_DMA_MINALIGN)check, potentially creating problems for non-coherent DMA situation.
I was wondering "Why should they respect kmalloc alignment..." but yeah, It makes sense if the users were using kmalloc and depended on its alignment.
Well, but that's already done in new_kmalloc_cache() and kmem_buckets_create() should already honor ARCH_KMALLOC_MINALIGN?
The largest-power-of-two-divisor-alignment guarantee was introduced by commit ad59baa31695 ("slab, rust: extend kmalloc() alignment guarantees to remove Rust padding")
...which makes me wonder what you're trying to fix here?
Quoted 39 lines
Take the alignment from the cache being mirrored, which is where the size and the name suffix already come from. Nothing changes where the alignment was already implied by the size.
Fixes b32801d1255be ("mm/slab: Introduce kmem_buckets_create() and family")Assisted-by LLMSigned-off-by Kees Cook <[email protected]>---
Cc: Vlastimil Babka <[email protected]>
Cc: Harry Yoo <[email protected]>
Cc: Andrew Morton <[email protected]>
Cc: Hao Li <[email protected]>
Cc: Christoph Lameter <[email protected]>
Cc: David Rientjes <[email protected]>
Cc: Roman Gushchin <[email protected]>
Cc: <[email protected]>
Cc: Pedro Falcato <[email protected]>
Cc: Kuniyuki Iwashima <[email protected]>
Cc: <[email protected]>--- mm/slab_common.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-)mm/slab_common.c +2
-1@@ -487,7 +487,8 @@ kmem_buckets *kmem_buckets_create(const char *name, slab_flags_t flags,if (WARN_ON (!cache_name)) goto fail; (*b)[aligned_idx] =kmem_cache_create_usercopy (cache_name, size, -0 , flags, cache_useroffset, + kmalloc_caches[KMALLOC_NORMAL ][idx]->align, + flags, cache_useroffset, cache_usersize, ctor);kfree (cache_name);if (WARN_ON (!(*b)[aligned_idx]))Signature · 1 line
2.34.1
Signature · 2 lines
Cheers, Harry / Hyeonggon
Re: [PATCH v4 2/7] mm/slab: Give bucket caches the alignment of the caches they mirror
Quoted 14 lines · Harry Yoo
Quoted 13 lines · Kees Cook
A bucket set is created with
kmem_cache_create_usercopy(..., align = 0), socalculate_alignment()falls back toarch_slab_minalign(), typically 8 bytes. The general kmalloc caches it stands in for are created throughcreate_boot_cache(), which starts fromARCH_KMALLOC_MINALIGNand raises it to the largest power-of-two divisor of the size:if (flags & SLAB_KMALLOC) align = max(align, 1U << (ffs(size) - 1));This is only a problem when slab metadata is enabled with
CONFIG_KASAN=y,CONFIG_SLUB_DEBUG_ON=y, or "slab_debug=...", because metadata changes the stride size off a power of two, for example:
Quoted 8 lines
Quoted 3 lines
size 128: bucket align=8 size=224 | kmalloc align=128 size=384 size 512: bucket align=8 size=608 | kmalloc align=512 size=1536 size 2048: bucket align=8 size=2144 | kmalloc align=2048 size=6144Hmm... I think what adds confusion here is that in
new_kmalloc_cache()we adjust the size based on alignment, but increate_boot_cache()we don't do that. Perhaps let's make it consistent and move it tonew_kmalloc_cache()?
Yeah, I really couldn't figure out what was "correct" here.
Quoted 6 lines
Quoted 2 lines
So bucket allocations will fail the
IS_ALIGNED(p, ARCH_DMA_MINALIGN)check, potentially creating problems for non-coherent DMA situation.I was wondering "Why should they respect kmalloc alignment..." but yeah, It makes sense if the users were using kmalloc and depended on its alignment.
Right, it was a "visible" change between standard kmalloc and bucketed kmalloc, so I figured the right action was to be (bug?) identical.
Quoted 8 lines
Well, but that's already done in
new_kmalloc_cache()andkmem_buckets_create()should already honorARCH_KMALLOC_MINALIGN?The largest-power-of-two-divisor-alignment guarantee was introduced by commit
ad59baa31695("slab, rust: extendkmalloc()alignment guarantees to remove Rust padding")...which makes me wonder what you're trying to fix here?
What Sashiko noticed was that alignment might not match under certain configs, and then I verified it at runtime, and figured I'd best fix it just on the basis that it was a difference from what a user might expect, and it might be especially important for skb data.
Quoted 9 lines
mm/slab_common.c kmem_buckets *kmem_buckets_create(const char *name, slab_flags_t flags, · line 494
Quoted 9 lines
mm/slab_common.c +2
-1if (WARN_ON (!cache_name)) goto fail; (*b)[aligned_idx] =kmem_cache_create_usercopy (cache_name, size, -0 , flags, cache_useroffset, + kmalloc_caches[KMALLOC_NORMAL ][idx]->align, + flags, cache_useroffset, cache_usersize, ctor);kfree (cache_name);if (WARN_ON (!(*b)[aligned_idx]))
It looks "obviously correct", but I probably failed to correctly describe it. I'm happy to do whatever here.
Signature · 1 line
Kees Cook
Harry Yoo
maintainerRe: [PATCH v4 2/7] mm/slab: Give bucket caches the alignment of the caches they mirror
Quoted 14 lines · Kees Cook
Quoted 10 lines · Harry Yoo
mm/slab_common.c kmem_buckets *kmem_buckets_create(const char *name, slab_flags_t flags, · line 494
Quoted 9 lines · Kees Cook
mm/slab_common.c +2
-1if (WARN_ON (!cache_name)) goto fail; (*b)[aligned_idx] =kmem_cache_create_usercopy (cache_name, size, -0 , flags, cache_useroffset, + kmalloc_caches[KMALLOC_NORMAL ][idx]->align, + flags, cache_useroffset, cache_usersize, ctor);kfree (cache_name);if (WARN_ON (!(*b)[aligned_idx]))It looks "obviously correct", but I probably failed to correctly describe it. I'm happy to do whatever here.
That is correct, but is there any reason why kmem_buckets users should not specify their own alignment requirement and inherit the alignment from kmalloc?
For DMA yeah it might be confusing for every user to figure out that the alignment should be __kmalloc_minalign(), but otherwise there isn't really a good enough reason to inherit the alignment from kmalloc, no?
Signature · 2 lines
Cheers, Harry / Hyeonggon
Re: [PATCH v4 2/7] mm/slab: Give bucket caches the alignment of the caches they mirror
Quoted 16 lines · Harry Yoo
Quoted 14 lines · Kees Cook
Quoted 10 lines · Harry Yoo
mm/slab_common.c kmem_buckets *kmem_buckets_create(const char *name, slab_flags_t flags, · line 494
Quoted 9 lines · Kees Cook
mm/slab_common.c +2
-1if (WARN_ON (!cache_name)) goto fail; (*b)[aligned_idx] =kmem_cache_create_usercopy (cache_name, size, -0 , flags, cache_useroffset, + kmalloc_caches[KMALLOC_NORMAL ][idx]->align, + flags, cache_useroffset, cache_usersize, ctor);kfree (cache_name);if (WARN_ON (!(*b)[aligned_idx]))It looks "obviously correct", but I probably failed to correctly describe it. I'm happy to do whatever here.
Quoted 7 lines
That is correct, but is there any reason why
kmem_bucketsusers should not specify their own alignment requirement and inherit the alignment from kmalloc?For DMA yeah it might be confusing for every user to figure out that the alignment should be
__kmalloc_minalign(), but otherwise there isn't really a good enough reason to inherit the alignment from kmalloc, no?
Oh, so you mean explicitly add an alignment arg to kmem_buckets_create()? And perhaps if _that_ one is "0" we can fallback to whatever kmalloc_caches uses?
Signature · 1 line
Kees Cook
PATCH v4 3/7 mm/slab: Add kmem_buckets_destroy()
In reply to Kees Cookkmem_buckets_create() intentionally had no "destroy" counterpart. Every caller has lived in core kernel code and creates its set once at boot, so nothing has needed to take one down. However, KUnit tests may be built module, so we need it now to support the coming tests.
Some caches have size aliases, so the same pointer is stored at more then one index, so we have to save it, clear all matching instances, and then free the saved cache pointer. (This is what the bitmap was tracking before in the "allocation failed" error path.)
When CONFIG_SLAB_BUCKETS=n the whole body compiles away, matching the ZERO_SIZE_PTR that kmem_buckets_create() hands back in that configuration.
---
Cc: Vlastimil Babka <[email protected]>
Cc: Harry Yoo <[email protected]>
Cc: Andrew Morton <[email protected]>
Cc: Hao Li <[email protected]>
Cc: Christoph Lameter <[email protected]>
Cc: David Rientjes <[email protected]>
Cc: Roman Gushchin <[email protected]>
Cc: <[email protected]>
Cc: Pedro Falcato <[email protected]>
Cc: Kuniyuki Iwashima <[email protected]>
Cc: <[email protected]>
--- include/linux/slab.h | 1 + mm/slab_common.c | 49 +++++++++++++++++++++++++++++++++++++------- 2 files changed, 43 insertions(+), 7 deletions(-)
include/linux/slab.h +1 -0
@@ -893,6 +893,7 @@ void kmem_cache_free(struct kmem_cache *s, void *objp);kmem_buckets *kmem_buckets_create (const char *name,slab_flags_t flags,unsigned int useroffset,unsigned int usersize,void (*ctor)(void *)); +void kmem_buckets_destroy (kmem_buckets *bucket);/* * Bulk allocation and freeing operations. These are accelerated in an
mm/slab_common.c +42 -7
75 lines not on this page · raw message
Signature · 1 line
2.34.1
PATCH v4 4/7 mm/slab: Add tests for the existing kmem_buckets behaviour
In reply to Kees Cookkmem_buckets has had no test coverage since it was added. Add tests, including stuff unique to the bucket design:
- A bucket allocation comes from a cache of the set's own, and that cache carries SLAB_NO_MERGE.
- Each size class is served by the set, including 96 and 192, which are not powers of two and are filled in from an aligned index by kmem_buckets_create(). Sizes above KMALLOC_MAX_CACHE_SIZE go to the page allocator instead, bucket set or not.
- With CONFIG_SLAB_BUCKETS=n, kmem_buckets_create() still returns a non-NULL (zero size alloc pointer), so that callers only have to check for failure, and allocations through it come from the general caches.
- Destroying a set takes its caches down rather than only freeing the set, which is what a module creating one on each load depends on. A set freed without its caches would leave the names taken, and the next load would warn about every one of them.
The tests skip rather than compile out, which is useful for testing the CONFIG_SLAB_BUCKETS=n behaviors.
Built and tests passing (with expected skips) on ARCH=x86_64 defconfig with GCC 16.2.0, with CONFIG_SLAB_BUCKETS as y and n.
---
Cc: Vlastimil Babka <[email protected]>
Cc: Harry Yoo <[email protected]>
Cc: Andrew Morton <[email protected]>
Cc: Hao Li <[email protected]>
Cc: Christoph Lameter <[email protected]>
Cc: David Rientjes <[email protected]>
Cc: Roman Gushchin <[email protected]>
Cc: <[email protected]>
Cc: Pedro Falcato <[email protected]>
Cc: Kuniyuki Iwashima <[email protected]>
Cc: <[email protected]>
--- lib/tests/slub_kunit.c | 214 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 214 insertions(+)
lib/tests/slub_kunit.c +214 -0
@@ -1,6 +1,7 @@// SPDX-License-Identifier: GPL-2.0 #include <kunit/test.h> #include <kunit/test-bug.h> +#include <kunit/resource.h> #include <linux/mm.h> #include <linux/slab.h> #include <linux/module.h>
@@ -474,6 +475,214 @@ static int test_init(struct kunit *test)return 0 ; } +/* Destroy buckets on test exit so a failed KUNIT_ASSERT_*() doesn't leak. */ +KUNIT_DEFINE_ACTION_WRAPPER(destroy_buckets, kmem_buckets_destroy, kmem_buckets *); + +#define KUNIT_ASSERT_BUCKETS_CREATED (test, b) \ + do { \ + KUNIT_ASSERT_NOT_NULL(test, b); \ + KUNIT_ASSERT_EQ(test, 0, \ + kunit_add_action_or_reset(test, \ + destroy_buckets, b)); \ + } while (0) + +/* +* The cache an allocation came from, or NULL if it came from no cache at +* all, e.g. a size too big for any of them is served by the page allocator. +*/ +static struct kmem_cache *cache_of (void *p) +{ +struct slab *slab =virt_to_slab (p); + +return slab ? slab->slab_cache :NULL ; +} + +/* +* A bucket set exists to keep its allocations out of the caches everything +* else uses, so check the two things that make that true: they come from a +* cache of the set's own, and that cache is never merged into another. +*/ +static void test_kmem_buckets_isolation (struct kunit *test) +{ +struct kmem_cache *bucket_cache, *general_cache; +kmem_buckets *b; +void *p, *q; + +if (!IS_ENABLED (CONFIG_SLAB_BUCKETS )) +kunit_skip (test,"needs CONFIG_SLAB_BUCKETS" ); + + b =kmem_buckets_create ("isolated_buckets" ,0 ,0 ,INT_MAX ,NULL ); +KUNIT_ASSERT_BUCKETS_CREATED (test, b); + +/* +* Free each allocation before asserting on the next one: the cache +* outlives its objects, so nothing below needs them, and an assertion +* that leaves one behind would make the deferred teardown report a +* cache that is still in use. +*/ + p =kmem_buckets_alloc (b,128 ,GFP_KERNEL ); +KUNIT_ASSERT_NOT_NULL (test, p); + bucket_cache =cache_of (p); +kfree (p); +KUNIT_ASSERT_NOT_NULL (test, bucket_cache); + +KUNIT_EXPECT_TRUE_MSG (test,strstarts (bucket_cache->name,"isolated_buckets-" ), +"expected a bucket cache, got %s" , bucket_cache->name); + +/* +* Cache merging is on by default, and a bucket cache merged into a +* same-sized general one would quietly undo the whole separation. +*/ +KUNIT_EXPECT_TRUE (test, bucket_cache->flags &SLAB_NO_MERGE ); + + q =kmalloc (128 ,GFP_KERNEL ); +KUNIT_ASSERT_NOT_NULL (test, q); + general_cache =cache_of (q); +kfree (q); +KUNIT_ASSERT_NOT_NULL (test, general_cache); + +KUNIT_EXPECT_PTR_NE (test, bucket_cache, general_cache); +} + +/* +* Every size class gets its own cache in the set, including the ones that +* are not powers of two and are filled in from an aligned index. Sizes past +* the largest cache are served by the page allocator, bucket set or not. +*/ +static void test_kmem_buckets_sizes (struct kunit *test) +{ +static const size_t sizes[] = {8 ,96 ,192 ,1024 ,4096 }; +struct kmem_cache *c; +kmem_buckets *b; +void *p; +int i; + +if (!IS_ENABLED (CONFIG_SLAB_BUCKETS )) +kunit_skip (test,"needs CONFIG_SLAB_BUCKETS" ); + + b =kmem_buckets_create ("sized_buckets" ,0 ,0 ,INT_MAX ,NULL ); +KUNIT_ASSERT_BUCKETS_CREATED (test, b); + +for (i =0 ; i <ARRAY_SIZE (sizes); i++) { + p =kmem_buckets_alloc (b, sizes[i],GFP_KERNEL ); +KUNIT_ASSERT_NOT_NULL (test, p); + c =cache_of (p); +kfree (p); +KUNIT_ASSERT_NOT_NULL (test, c); + +KUNIT_EXPECT_TRUE_MSG (test,strstarts (c->name,"sized_buckets-" ), +"size %zu: expected a bucket cache, got %s" , + sizes[i], c->name); +KUNIT_EXPECT_GE (test, c->object_size, sizes[i]); + } + +/* Too big for any cache: a folio from the page allocator, not a slab. */ + p =kmem_buckets_alloc (b,KMALLOC_MAX_CACHE_SIZE +1 ,GFP_KERNEL ); +KUNIT_ASSERT_NOT_NULL (test, p); + c =cache_of (p); +kfree (p); + +KUNIT_EXPECT_NULL (test, c); +} + +/* +* A bucket cache stands in for a kmalloc cache, so it has to be aligned like +* one. The DMA layer decides whether a buffer needs bouncing from its size, +* on the grounds that a kmalloc cache of that size is already aligned for +* the device, so a weaker alignment here is not something a caller can see +* coming. Without slab debugging the size implies the alignment and this +* holds either way; with it, only the cache's own alignment does. +*/ +static void test_kmem_buckets_alignment (struct kunit *test) +{ +static const size_t sizes[] = {128 ,512 ,2048 }; +struct kmem_cache *bucket_cache, *general_cache; +kmem_buckets *b; +void *p; +int i; + +if (!IS_ENABLED (CONFIG_SLAB_BUCKETS )) +kunit_skip (test,"needs CONFIG_SLAB_BUCKETS" ); + + b =kmem_buckets_create ("aligned_buckets" ,0 ,0 ,INT_MAX ,NULL ); +KUNIT_ASSERT_BUCKETS_CREATED (test, b); + +for (i =0 ; i <ARRAY_SIZE (sizes); i++) { + p =kmem_buckets_alloc (b, sizes[i],GFP_KERNEL ); +KUNIT_ASSERT_NOT_NULL (test, p); + bucket_cache =cache_of (p); +KUNIT_EXPECT_TRUE_MSG (test, +IS_ALIGNED ((unsigned long )p,ARCH_DMA_MINALIGN ), +"size %zu: object %p is not %d byte aligned" , + sizes[i], p, (int )ARCH_DMA_MINALIGN ); +kfree (p); + + p =kmalloc (sizes[i],GFP_KERNEL ); +KUNIT_ASSERT_NOT_NULL (test, p); + general_cache =cache_of (p); +kfree (p); + +KUNIT_ASSERT_NOT_NULL (test, bucket_cache); +KUNIT_ASSERT_NOT_NULL (test, general_cache); +KUNIT_EXPECT_EQ_MSG (test, bucket_cache->align, general_cache->align, +"size %zu: bucket cache aligned to %u, %s to %u" , + sizes[i], bucket_cache->align, + general_cache->name, general_cache->align); + } +} + +/* +* With the feature compiled out, kmem_buckets_create() still returns +* something non-NULL so that callers only have to check for failure, and +* allocations through it work (i.e. come from the general caches). +*/ +static void test_kmem_buckets_disabled (struct kunit *test) +{ +kmem_buckets *b; +struct kmem_cache *c; +void *p; + +if (IS_ENABLED (CONFIG_SLAB_BUCKETS )) +kunit_skip (test,"only meaningful without CONFIG_SLAB_BUCKETS" ); + + b =kmem_buckets_create ("disabled_buckets" ,0 ,0 ,INT_MAX ,NULL ); +KUNIT_ASSERT_BUCKETS_CREATED (test, b); + + p =kmem_buckets_alloc (b,128 ,GFP_KERNEL ); +KUNIT_ASSERT_NOT_NULL (test, p); + c =cache_of (p); +kfree (p); +KUNIT_ASSERT_NOT_NULL (test, c); + +KUNIT_EXPECT_TRUE_MSG (test, !strstarts (c->name,"disabled_buckets-" ), +"expected a general cache, got %s" , c->name); +} + +/* Destroying a set has to take its caches down, not just free the set. */ +static void test_kmem_buckets_destroy (struct kunit *test) +{ +kmem_buckets *b; +void *p; + +if (!IS_ENABLED (CONFIG_SLAB_BUCKETS )) +kunit_skip (test,"needs CONFIG_SLAB_BUCKETS" ); + + b =kmem_buckets_create ("destroyed_buckets" ,0 ,0 ,INT_MAX ,NULL ); +KUNIT_ASSERT_NOT_NULL (test, b); + +/* +* Deliberately leaked, as test_leak_destroy() leaks its own: the +* teardown below has to find it. kmem_cache_destroy() unlists the +* cache either way, so the name is still released. +*/ + p =kmem_buckets_alloc (b,128 ,GFP_KERNEL ); +KUNIT_EXPECT_NOT_NULL (test, p); + +kmem_buckets_destroy (b); + +KUNIT_EXPECT_EQ (test,2 , slab_errors); +} +static struct kunit_case test_cases[] = {KUNIT_CASE (test_clobber_zone),
@@ -495,6 +704,11 @@ static struct kunit_case test_cases[] = {#if defined(CONFIG_KPROBES ) && defined(CONFIG_SMP )KUNIT_CASE_SLOW (test_kmalloc_nolock_and_friends_kprobe), #endif +KUNIT_CASE (test_kmem_buckets_isolation), +KUNIT_CASE (test_kmem_buckets_sizes), +KUNIT_CASE (test_kmem_buckets_alignment), +KUNIT_CASE (test_kmem_buckets_disabled), + KUNIT_CASE(test_kmem_buckets_destroy), {} };
Signature · 1 line
2.34.1
PATCH v4 5/7 mm/slab: Provide kmalloc type fallback for bucket allocations
In reply to Kees Cookkmem_buckets_create() clones kmalloc_caches[KMALLOC_NORMAL]. kmalloc_slab() figures out the kmalloc type the caller asks for, but then ignored it whenever a bucket set was in use, returning a normal cache regardless. This would be a problem if a caller asked for GFP_DMA, __GFP_ACCOUNT, etc. None of the current users do this, so there is problem, but it makes adding new users fragile. For example, skb data[1] needs to handle GFP_DMA (rarely) and __GFP_ACCOUNT (often).
Send those allocations to the general caches instead so nothing breaks and regular allocations remain isolated with the bucket. The kmem_bucket_type enum contains only a single item here, but will be expanded in the next patch.
Built and tests pass with ARCH=x86_64 defconfig with GCC 16.2.0, with CONFIG_SLAB_BUCKETS as y and n.
---
Cc: Vlastimil Babka <[email protected]>
Cc: Harry Yoo <[email protected]>
Cc: Andrew Morton <[email protected]>
Cc: Hao Li <[email protected]>
Cc: Christoph Lameter <[email protected]>
Cc: David Rientjes <[email protected]>
Cc: Roman Gushchin <[email protected]>
Cc: <[email protected]>
Cc: Pedro Falcato <[email protected]>
Cc: Kuniyuki Iwashima <[email protected]>
Cc: <[email protected]>
--- include/linux/slab.h | 13 +++++++++++ mm/slab.h | 23 ++++++++++++++++-- lib/tests/slub_kunit.c | 53 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 87 insertions(+), 2 deletions(-)
include/linux/slab.h +13 -0
@@ -742,6 +742,19 @@ typedef struct kmem_cache * kmem_buckets[KMALLOC_SHIFT_HIGH + 1];extern kmem_buckets kmalloc_caches[NR_KMALLOC_TYPES ]; +/* +* The kmalloc types a bucket set can hold a copy of. This is deliberately not +* enum kmalloc_cache_type: the KMALLOC_PARTITION copies are all "normal" to a +* bucket set, which already separates what they were there to separate, so +* indexing by those would mean up to KMALLOC_PARTITION_CACHES_NR unusable +* rows per set. Allocations of any type not listed here are served by the +* general caches. +*/ +enum kmem_bucket_type { +KMEM_BUCKET_NORMAL =0 , +NR_KMEM_BUCKET_TYPES +}; +/* * Define gfp bits that should not be set for KMALLOC_NORMAL. */
mm/slab.h +21 -2
@@ -421,6 +421,26 @@ static inline unsigned int size_index_elem(unsigned int bytes)return (bytes -1 ) /8 ; } +/* +* Which set of buckets to use for the given kmalloc_cache_type. If not +* handled by the kmem_buckets, fall back to general caches. +*/ +static inline kmem_buckets * +kmalloc_choose_bucket (kmem_buckets *bucket,enum kmalloc_cache_type type) +{ +enum kmem_bucket_type btype; + +if (!bucket) +return &kmalloc_caches[type]; + +if (type <=KMALLOC_PARTITION_END ) + btype =KMEM_BUCKET_NORMAL ; +else +return &kmalloc_caches[type];/* No set holds a row for it. */ + +return &bucket[btype]; +} +/* * Find the kmem_cache structure that serves a given size of * allocation
@@ -438,8 +458,7 @@ kmalloc_slab(size_t size, kmem_buckets *b, gfp_t flags, kmalloc_token_t token,if (alloc_flags &SLAB_ALLOC_NO_OBJ_EXT ) type =KMALLOC_NO_OBJ_EXT ; -if (!b) - b = &kmalloc_caches[type]; + b =kmalloc_choose_bucket (b, type);if (size <=192 ) index = kmalloc_size_index[size_index_elem (size)];else
lib/tests/slub_kunit.c +53 -0
65 lines not on this page · raw message
Signature · 1 line
2.34.1
Re: [PATCH v4 5/7] mm/slab: Provide kmalloc type fallback for bucket allocations
Hi Kees,
Big thanks for continuing this effort :))
Quoted 43 lines · Kees Cook
kmem_buckets_create()clones kmalloc_caches[KMALLOC_NORMAL].kmalloc_slab()figures out the kmalloc type the caller asks for, but then ignored it whenever a bucket set was in use, returning a normal cache regardless. This would be a problem if a caller asked forGFP_DMA,__GFP_ACCOUNT, etc. None of the current users do this, so there is problem, but it makes adding new users fragile. For example, skb data[1] needs to handleGFP_DMA(rarely) and__GFP_ACCOUNT(often).Send those allocations to the general caches instead so nothing breaks and regular allocations remain isolated with the bucket. The
kmem_bucket_typeenum contains only a single item here, but will be expanded in the next patch.Built and tests pass with ARCH=x86_64 defconfig with GCC 16.2.0, with
CONFIG_SLAB_BUCKETSas y and n.Assisted-by LLMSigned-off-by Kees Cook <[email protected]>---
Cc: Vlastimil Babka <[email protected]>
Cc: Harry Yoo <[email protected]>
Cc: Andrew Morton <[email protected]>
Cc: Hao Li <[email protected]>
Cc: Christoph Lameter <[email protected]>
Cc: David Rientjes <[email protected]>
Cc: Roman Gushchin <[email protected]>
Cc: <[email protected]>
Cc: Pedro Falcato <[email protected]>
Cc: Kuniyuki Iwashima <[email protected]>
Cc: <[email protected]>--- include/linux/slab.h | 13 +++++++++++ mm/slab.h | 23 ++++++++++++++++-- lib/tests/slub_kunit.c | 53 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 87 insertions(+), 2 deletions(-)include/linux/slab.h +0
-0@@ -742,6 +742,19 @@ typedef struct kmem_cache * kmem_buckets[KMALLOC_SHIFT_HIGH + 1];
include/linux/slab.h typedef struct kmem_cache * kmem_buckets[KMALLOC_SHIFT_HIGH + 1]; · line 752
Quoted 10 lines
include/linux/slab.h +8
-0extern kmem_buckets kmalloc_caches[NR_KMALLOC_TYPES ]; +/* +* The kmalloc types a bucket set can hold a copy of. This is deliberately not +* enum kmalloc_cache_type: the KMALLOC_PARTITION copies are all "normal" to a +* bucket set, which already separates what they were there to separate, so +* indexing by those would mean up to KMALLOC_PARTITION_CACHES_NR unusable +* rows per set. Allocations of any type not listed here are served by the +* general caches. +*/
This sounds odd. Is there a good reason why KMALLOC_PARTITIONs are kmalloc_cache_types? Perhaps that bit should be reworked instead?
Quoted 22 lines
include/linux/slab.h +5
-0+enum kmem_bucket_type { +KMEM_BUCKET_NORMAL =0 , +NR_KMEM_BUCKET_TYPES +}; +/* * Define gfp bits that should not be set for KMALLOC_NORMAL. */ mm/slab.h +6
-0@@ -421,6 +421,26 @@ static inline unsigned int size_index_elem(unsigned int bytes)return (bytes -1 ) /8 ; } +/* +* Which set of buckets to use for the given kmalloc_cache_type. If not +* handled by the kmem_buckets, fall back to general caches. +*/ +static inline kmem_buckets * +kmalloc_choose_bucket (kmem_buckets *bucket,enum kmalloc_cache_type type)
mm/slab.h static inline unsigned int size_index_elem(unsigned int bytes) · line 439
Quoted 10 lines
mm/slab.h +10
-0+{ +enum kmem_bucket_type btype; + +if (!bucket) +return &kmalloc_caches[type]; + +if (type <=KMALLOC_PARTITION_END ) + btype =KMEM_BUCKET_NORMAL ; +else +return &kmalloc_caches[type];/* No set holds a row for it. */
Hitting this case sounds like a bug in the kernel. WARN_ON_ONCE()?
Otherwise LGTM.
Signature · 1 line
Pedro
Re: [PATCH v4 5/7] mm/slab: Provide kmalloc type fallback for bucket allocations
Quoted 1 line · Pedro Falcato
Big thanks for continuing this effort :))
Thanks for starting it! :) I've had a few folks wanting it, so I'm happy to help.
Quoted 11 lines · Kees Cook
[...]
Quoted 8 lines
+/*
+ * The kmalloc types a bucket set can hold a copy of. This is deliberately not
+ * enumkmalloc_cache_type: theKMALLOC_PARTITIONcopies are all "normal" to a
+ * bucket set, which already separates what they were there to separate, so
+ * indexing by those would mean up toKMALLOC_PARTITION_CACHES_NRunusable
+ * rows per set. Allocations of any type not listed here are served by the
+ * general caches.
+ */
Quoted 2 lines
This sounds odd. Is there a good reason why
KMALLOC_PARTITIONsarekmalloc_cache_types? Perhaps that bit should be reworked instead?
I'm not sure I follow. Do you mean the partition copies themselves shouldn't be kmalloc_cache_types? That predates this series. For a bucket set, they're all the same "normal" type, so a set indexed by kmalloc_cache_type would carry rows it can never use: on x86_64 with CONFIG_KMALLOC_PARTITION_CACHES=y, that's 20 rows (2240 bytes) per set instead of 2 (224 bytes). I've put the numbers in the commit log for v5.
Quoted 7 lines
[...]
mm/slab.h static inline unsigned int size_index_elem(unsigned int bytes) · line 439
Quoted 4 lines
mm/slab.h +4
-0+if (type <=KMALLOC_PARTITION_END ) + btype =KMEM_BUCKET_NORMAL ; +else +return &kmalloc_caches[type];/* No set holds a row for it. */ Hitting this case sounds like a bug in the kernel.
WARN_ON_ONCE()?
The next patch warns where a set could have held the row but wasn't created with it (an accounted allocation without KMEM_BUCKET_CGROUP). What's left here are types no set can hold, like DMA and reclaimable, and those already come from caches of their own, so falling back doesn't lose the separation.
Quoted 1 line
Otherwise LGTM.
Thanks!
Signature · 1 line
Kees Cook
PATCH v4 6/7 mm/slab: Let a bucket set handle __GFP_ACCOUNT
In reply to Kees CookA bucket set holds one row of caches, cloned from KMALLOC_NORMAL, and an allocation of any other kmalloc type falls back to the general caches. Extend this to handle __GFP_ACCOUNT, so that a single bucket user can isolate either GFP_KERNEL or GFP_KERNEL_ACCOUNT allocations, as is needed for skb data, where AF_UNIX uses:
sk->sk_allocation = GFP_KERNEL_ACCOUNT;
The coverage is selected at bucket creation time:
b = kmem_buckets_create_types(name, flags, 0, INT_MAX, NULL, BIT(KMEM_BUCKET_NORMAL) | BIT(KMEM_BUCKET_CGROUP));
The prior kmem_buckets_create() function keeps its name and defaults to only KMEM_BUCKET_NORMAL, leaving existing users as-is.
Only the accounted type is offered. Nothing wants a reclaimable or no-obj-ext row, and of the twelve places passing GFP_DMA to an skb allocator, all rare hardware: b44, b43legacy, prestera and s390 ctcm.
The choice is made at creation rather than every set getting every type because the rows, when populated, are not free. Each holds 13 caches, and a cache is a 1208 byte struct plus an unconditional per-cpu allocation, a node struct, and an entry in /proc/slabinfo and under /sys/kernel/slab.
KMEM_BUCKET_CGROUP collapses to KMEM_BUCKET_NORMAL without CONFIG_MEMCG, exactly as KMALLOC_CGROUP does, so NR_KMEM_BUCKET_TYPES is 1 there and a bucket set is the same single row it is today. Where the type is asked for but the system is not creating caches of it (under "cgroup.memory=nokmem") the row is aliased to the normal one, as new_kmalloc_cache() does for the general caches, so those allocations stay isolated rather than falling back to the general caches.
Built and tests pass (and skip as expected) on ARCH=x86_64 defconfig with GCC 16.2.0 in all combinations of CONFIG_SLAB_BUCKETS=y/n and CONFIG_MEMCG=y/n/y+"cgroup.memory=nokmem".
---
Cc: Vlastimil Babka <[email protected]>
Cc: Harry Yoo <[email protected]>
Cc: Andrew Morton <[email protected]>
Cc: Hao Li <[email protected]>
Cc: Christoph Lameter <[email protected]>
Cc: David Rientjes <[email protected]>
Cc: Roman Gushchin <[email protected]>
Cc: <[email protected]>
Cc: Pedro Falcato <[email protected]>
Cc: Kuniyuki Iwashima <[email protected]>
Cc: <[email protected]>
--- include/linux/slab.h | 45 ++++++++++++- mm/slab.h | 23 ++++++- lib/tests/slub_kunit.c | 65 +++++++++++++++--- mm/slab_common.c | 145 ++++++++++++++++++++++++++++++++--------- 4 files changed, 235 insertions(+), 43 deletions(-)
include/linux/slab.h +42 -3
@@ -752,6 +752,11 @@ extern kmem_buckets kmalloc_caches[NR_KMALLOC_TYPES];*/ enum kmem_bucket_type {KMEM_BUCKET_NORMAL =0 , +#ifdef CONFIG_MEMCG +KMEM_BUCKET_CGROUP , +#else +KMEM_BUCKET_CGROUP =KMEM_BUCKET_NORMAL , +#endif NR_KMEM_BUCKET_TYPES };
@@ -903,9 +908,43 @@ void *kmem_cache_alloc_lru_noprof(struct kmem_cache *s, struct list_lru *lru,bool kmem_cache_charge (void *objp,gfp_t gfpflags);void kmem_cache_free (struct kmem_cache *s,void *objp); -kmem_buckets *kmem_buckets_create (const char *name,slab_flags_t flags, -unsigned int useroffset,unsigned int usersize, -void (*ctor)(void *)); +kmem_buckets *kmem_buckets_create_types (const char *name,slab_flags_t flags, +unsigned int useroffset,unsigned int usersize, +void (*ctor)(void *), +unsigned int type_mask); + +/** +* kmem_buckets_create - Create a set of caches that handle dynamic sized +* allocations via kmem_buckets_alloc() +* @name: A prefix string which is used in /proc/slabinfo to identify this +* cache. The individual caches with have their sizes as the suffix. +* @flags: SLAB flags (see kmem_cache_create() for details). +* @useroffset: Starting offset within an allocation that may be copied +* to/from userspace. +* @usersize: How many bytes, starting at @useroffset, may be copied +* to/from userspace. +* @ctor: A constructor for the objects, run when new allocations are made. +* +* Covers KMEM_BUCKET_NORMAL only. Allocations needing another kmalloc type +* are served by the general caches, keeping the type they asked for and +* losing only the isolation. Use kmem_buckets_create_types() to cover more. +* +* Context: Cannot be called within an interrupt, but can be interrupted. +* +* Return: a pointer to the cache on success, NULL on failure. When +* CONFIG_SLAB_BUCKETS is not enabled, ZERO_SIZE_PTR is returned, and +* subsequent calls to kmem_buckets_alloc() will fall back to kmalloc(). +* (i.e. callers only need to check for NULL on failure.) +*/ +static inline kmem_buckets *kmem_buckets_create (const char *name,slab_flags_t flags, +unsigned int useroffset, +unsigned int usersize, +void (*ctor)(void *)) +{ +return kmem_buckets_create_types (name, flags, useroffset, usersize, ctor, +BIT (KMEM_BUCKET_NORMAL )); +} +void kmem_buckets_destroy (kmem_buckets *bucket);/*
mm/slab.h +22 -1
@@ -435,10 +435,31 @@ kmalloc_choose_bucket(kmem_buckets *bucket, enum kmalloc_cache_type type)if (type <=KMALLOC_PARTITION_END ) btype =KMEM_BUCKET_NORMAL ; +else if (IS_ENABLED (CONFIG_MEMCG ) && type ==KMALLOC_CGROUP ) + btype =KMEM_BUCKET_CGROUP ;else return &kmalloc_caches[type];/* No set holds a row for it. */ -return &bucket[btype]; +/* +* Either this row was created, and holds a cache everywhere the +* general caches hold one, or it was never created and holds nothing. +* Test with the KMALLOC_SHIFT_LOW which exists in every configuration. +*/ +if (likely (bucket[btype][KMALLOC_SHIFT_LOW ])) +return &bucket[btype]; + +/* +* A row this set _could_ have held, but was not created with: the type +* mask passed to kmem_buckets_create_types() did not cover what its +* callers actually tried to allocate. Report the mismatch but still +* fall back to the general caches. +* +* At present, only __GFP_ACCOUNT can be missing. +*/ +WARN_ONCE (1 , +"kmem_buckets: __GFP_ACCOUNT needs BIT(KMEM_BUCKET_CGROUP) in create mask\n" ); + +return &kmalloc_caches[type]; }/*
lib/tests/slub_kunit.c +57 -8
78 lines not on this page · raw message
mm/slab_common.c +114 -31
217 lines not on this page · raw message
Signature · 1 line
2.34.1
Harry Yoo
maintainerRe: [PATCH v4 6/7] mm/slab: Let a bucket set handle __GFP_ACCOUNT
Quoted 64 lines · Kees Cook
A bucket set holds one row of caches, cloned from
KMALLOC_NORMAL, and an allocation of any other kmalloc type falls back to the general caches. Extend this to handle__GFP_ACCOUNT, so that a single bucket user can isolate eitherGFP_KERNELorGFP_KERNEL_ACCOUNTallocations, as is needed for skb data, whereAF_UNIXuses:sk->sk_allocation = GFP_KERNEL_ACCOUNT;The coverage is selected at bucket creation time:
b = kmem_buckets_create_types(name, flags, 0, INT_MAX, NULL, BIT(KMEM_BUCKET_NORMAL) | BIT(KMEM_BUCKET_CGROUP));The prior
kmem_buckets_create()function keeps its name and defaults to onlyKMEM_BUCKET_NORMAL, leaving existing users as-is.Only the accounted type is offered. Nothing wants a reclaimable or no-obj-ext row, and of the twelve places passing
GFP_DMAto an skb allocator, all rare hardware: b44, b43legacy, prestera and s390 ctcm.The choice is made at creation rather than every set getting every type because the rows, when populated, are not free. Each holds 13 caches, and a cache is a 1208 byte struct plus an unconditional per-cpu allocation, a node struct, and an entry in
/proc/slabinfoand under/sys/kernel/slab.
KMEM_BUCKET_CGROUPcollapses toKMEM_BUCKET_NORMALwithoutCONFIG_MEMCG, exactly asKMALLOC_CGROUPdoes, soNR_KMEM_BUCKET_TYPESis 1 there and a bucket set is the same single row it is today. Where the type is asked for but the system is not creating caches of it (under "cgroup.memory=nokmem") the row is aliased to the normal one, asnew_kmalloc_cache()does for the general caches, so those allocations stay isolated rather than falling back to the general caches.Built and tests pass (and skip as expected) on ARCH=x86_64 defconfig with GCC 16.2.0 in all combinations of
CONFIG_SLAB_BUCKETS=y/nandCONFIG_MEMCG=y/n/y+"cgroup.memory=nokmem".Assisted-by LLMSigned-off-by Kees Cook <[email protected]>---
Cc: Vlastimil Babka <[email protected]>
Cc: Harry Yoo <[email protected]>
Cc: Andrew Morton <[email protected]>
Cc: Hao Li <[email protected]>
Cc: Christoph Lameter <[email protected]>
Cc: David Rientjes <[email protected]>
Cc: Roman Gushchin <[email protected]>
Cc: <[email protected]>
Cc: Pedro Falcato <[email protected]>
Cc: Kuniyuki Iwashima <[email protected]>
Cc: <[email protected]>--- include/linux/slab.h | 45 ++++++++++++- mm/slab.h | 23 ++++++- lib/tests/slub_kunit.c | 65 +++++++++++++++--- mm/slab_common.c | 145 ++++++++++++++++++++++++++++++++--------- 4 files changed, 235 insertions(+), 43 deletions(-)lib/tests/slub_kunit.c +0
-0
lib/tests/slub_kunit.c static void test_kmem_buckets_type_fallback(struct kunit *test) · line 727
Quoted 5 lines
lib/tests/slub_kunit.c +0
-2@@ -723,15 +723,63 @@ static void test_kmem_buckets_type_fallback(struct kunit *test) "expected a DMA cache, got %s", c->name); } -/* Nor can one that has to be accounted. */ -if (IS_ENABLED (CONFIG_MEMCG ) && !mem_cgroup_kmem_disabled ()) {
Didn't take a deeper look at this yet but this is causing an error:
MODPOST Module.symvers
ERROR: modpost: lib/tests/slub_kunit.ko: symbol 'mem_cgroup_kmem_disabled' undefined!
make[3]: *** [../scripts/Makefile.modpost:147: Module.symvers] Error 1
make[2]: *** [/var/lib/jenkins/agent/workspace/harry-linux/ARCH/x86_64/COMPILER/clang/DEBUG/light/PREEMPT/lazy/src/Makefile:2179: modpost] Error 2 make[1]: *** [/var/lib/jenkins/agent/workspace/harry-linux/ARCH/x86_64/COMPILER/clang/DEBUG/light/PREEMPT/lazy/src/Makefile:248: __sub-make] Error 2 make[1]: Leaving directory '/var/lib/jenkins/agent/workspace/harry-linux/ARCH/x86_64/COMPILER/clang/DEBUG/light/PREEMPT/lazy/src/build'
make: *** [Makefile:248: __sub-make] Error 2
Build step 'Execute shell' marked build as failure
Signature · 2 lines
Cheers, Harry / Hyeonggon
Re: [PATCH v4 6/7] mm/slab: Let a bucket set handle __GFP_ACCOUNT
Quoted 5 lines · Harry Yoo
On Mon, Sep 21, 2026 at 12:58:17AM -0700, Kees Cook wrote:
Didn't take a deeper look at this yet but this is causing an error:MODPOST Module.symvers
ERROR: modpost:lib/tests/slub_kunit.ko: symbol 'mem_cgroup_kmem_disabled' undefined!
Argh, modular build. Of all the things I tested, I didn't actually test kunit modular build. :P It's so easy to use kunit.py that I forget it's doing a monolithic build under the hood. :P
I'll get that fixed.
Signature · 1 line
Kees Cook
Re: [PATCH v4 6/7] mm/slab: Let a bucket set handle __GFP_ACCOUNT
In reply to Kees CookQuoted 14 lines · Kees Cook
A bucket set holds one row of caches, cloned from
KMALLOC_NORMAL, and an allocation of any other kmalloc type falls back to the general caches. Extend this to handle__GFP_ACCOUNT, so that a single bucket user can isolate eitherGFP_KERNELorGFP_KERNEL_ACCOUNTallocations, as is needed for skb data, whereAF_UNIXuses:sk->sk_allocation = GFP_KERNEL_ACCOUNT;The coverage is selected at bucket creation time:
b = kmem_buckets_create_types(name, flags, 0, INT_MAX, NULL, BIT(KMEM_BUCKET_NORMAL) | BIT(KMEM_BUCKET_CGROUP));
Quoted 6 lines
The prior
kmem_buckets_create()function keeps its name and defaults to onlyKMEM_BUCKET_NORMAL, leaving existing users as-is.Only the accounted type is offered. Nothing wants a reclaimable or no-obj-ext row, and of the twelve places passing
GFP_DMAto an skb allocator, all rare hardware: b44, b43legacy, prestera and s390 ctcm.
Well, yes, BUT I don't think silently falling back is a great option. I would rather things be done Properly(tm) and kmem_buckets allowing instantiation of most/all kmalloc cache types. Given it's security stuff, I would prefer being quite explicit over silent.
Perhaps, to save on memory, users could choose to fall back to the default caches. But at kmem_buckets_create time.
Signature · 1 line
Pedro
Re: [PATCH v4 6/7] mm/slab: Let a bucket set handle __GFP_ACCOUNT
Quoted 18 lines · Pedro Falcato
Quoted 17 lines · Kees Cook
A bucket set holds one row of caches, cloned from
KMALLOC_NORMAL, and an allocation of any other kmalloc type falls back to the general caches. Extend this to handle__GFP_ACCOUNT, so that a single bucket user can isolate eitherGFP_KERNELorGFP_KERNEL_ACCOUNTallocations, as is needed for skb data, whereAF_UNIXuses:sk->sk_allocation = GFP_KERNEL_ACCOUNT;The coverage is selected at bucket creation time:
b = kmem_buckets_create_types(name, flags, 0, INT_MAX, NULL, BIT(KMEM_BUCKET_NORMAL) | BIT(KMEM_BUCKET_CGROUP));The prior
kmem_buckets_create()function keeps its name and defaults to onlyKMEM_BUCKET_NORMAL, leaving existing users as-is.
Quoted 8 lines
Quoted 3 lines
Only the accounted type is offered. Nothing wants a reclaimable or no-obj-ext row, and of the twelve places passing
GFP_DMAto an skb allocator, all rare hardware: b44, b43legacy, prestera and s390 ctcm.Well, yes, BUT I don't think silently falling back is a great option. I would rather things be done Properly(tm) and
kmem_bucketsallowing instantiation of most/all kmalloc cache types. Given it's security stuff, I would prefer being quite explicit over silent.
The types that fall back (DMA, reclaimable) are already isolated in caches of their own, away from the normal ones, so the security goal holds for them as-is. The one fallback that would undo the isolation, an accounted allocation on a set made without the cgroup row, will warn now.
Signature · 1 line
Kees Cook
PATCH v4 7/7 net: skb: isolate skb data area allocations into a separate bucket
In reply to Kees CookFrom: Pedro Falcato <[email protected]>
SKB data area allocations (as done from alloc_skb()) use kmalloc(). These allocations can be variably sized and their contents can be more or less controlled from userspace, which makes them useful for attackers that want to overwrite a use-after-free'd object from the same kmalloc slab (which often just requires the sizes to roughly match into the same kmalloc bucket). [0] is an easy example of an exploit that uses netlink skb allocation to target another similarly-sized accidentally freed object.
While other mitigations like CONFIG_RANDOM_KMALLOC_CACHES exist, these are probabilistic. Use the existing kmem buckets API to further isolate these allocations in a guaranteed fashion, when CONFIG_SLAB_BUCKETS=y.
Ask for the accounted kmalloc type as well as the normal one. AF_UNIX sets sk_allocation to GFP_KERNEL_ACCOUNT, so without it every AF_UNIX skb data area would fall back to the general caches, and those are the ones most worth isolating. GFP_DMA is left to fall back, being passed to an skb allocator only by rare devices.
---
Cc: "David S. Miller" <[email protected]>
Cc: Eric Dumazet <[email protected]>
Cc: Jakub Kicinski <[email protected]>
Cc: Paolo Abeni <[email protected]>
Cc: Simon Horman <[email protected]>
Cc: Willem de Bruijn <[email protected]>
Cc: Jason Xing <[email protected]>
Cc: <[email protected]>
Cc: Pedro Falcato <[email protected]>
Cc: Kuniyuki Iwashima <[email protected]>
Cc: <[email protected]>
--- net/core/skbuff.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-)
net/core/skbuff.c +9 -2
@@ -586,6 +586,8 @@ struct sk_buff *napi_build_skb(void *data, unsigned int frag_size) }EXPORT_SYMBOL (napi_build_skb); +static kmem_buckets *skb_data_buckets__ro_after_init ; +static void *kmalloc_pfmemalloc (size_t obj_size,gfp_t flags,int node) {if (!gfp_pfmemalloc_allowed (flags))
@@ -593,7 +595,8 @@ static void *kmalloc_pfmemalloc(size_t obj_size, gfp_t flags, int node)if (!obj_size)return kmem_cache_alloc_node (net_hotdata.skb_small_head_cache, flags, node); -return kmalloc_node_track_caller (obj_size, flags, node); +return kmem_buckets_alloc_node_track_caller (skb_data_buckets, obj_size, + flags, node); }/*
@@ -634,7 +637,7 @@ static void *kmalloc_reserve(unsigned int *size, gfp_t flags, int node,* Try a regular allocation, when that fails and we're not entitled * to the reserves, fail. */ - obj =kmalloc_node_track_caller (obj_size, + obj =kmem_buckets_alloc_node_track_caller (skb_data_buckets, obj_size, flags | __GFP_NOMEMALLOC | __GFP_NOWARN, node);if (likely (obj))
@@ -5235,6 +5238,10 @@ void __init skb_init(void)0 ,SKB_SMALL_HEAD_HEADROOM , NULL); + skb_data_buckets =kmem_buckets_create_types ("skb_data" ,SLAB_PANIC ,0 , +INT_MAX ,NULL , +BIT (KMEM_BUCKET_NORMAL ) | +BIT (KMEM_BUCKET_CGROUP ));skb_extensions_init (); }
Signature · 1 line
2.34.1
Quoted 20 lines · Kees Cook
From: Pedro Falcato <[email protected]>
SKB data area allocations (as done from
alloc_skb()) usekmalloc(). These allocations can be variably sized and their contents can be more or less controlled from userspace, which makes them useful for attackers that want to overwrite a use-after-free'd object from the same kmalloc slab (which often just requires the sizes to roughly match into the same kmalloc bucket). [0] is an easy example of an exploit that uses netlink skb allocation to target another similarly-sized accidentally freed object.While other mitigations like
CONFIG_RANDOM_KMALLOC_CACHESexist, these are probabilistic. Use the existing kmem buckets API to further isolate these allocations in a guaranteed fashion, whenCONFIG_SLAB_BUCKETS=y.Ask for the accounted kmalloc type as well as the normal one.
AF_UNIXsetssk_allocationtoGFP_KERNEL_ACCOUNT, so without it everyAF_UNIXskb data area would fall back to the general caches, and those are the ones most worth isolating.GFP_DMAis left to fall back, being passed to an skb allocator only by rare devices.
Quoted 3 lines
I would be curious to learn how about the memory usage delta. However I see buckets are protected by their own kconfig, small systems can unselect them.
For the networking bits:
Quoted 21 lines · Paolo Abeni
Quoted 20 lines · Kees Cook
From: Pedro Falcato <[email protected]>
SKB data area allocations (as done from
alloc_skb()) usekmalloc(). These allocations can be variably sized and their contents can be more or less controlled from userspace, which makes them useful for attackers that want to overwrite a use-after-free'd object from the same kmalloc slab (which often just requires the sizes to roughly match into the same kmalloc bucket). [0] is an easy example of an exploit that uses netlink skb allocation to target another similarly-sized accidentally freed object.While other mitigations like
CONFIG_RANDOM_KMALLOC_CACHESexist, these are probabilistic. Use the existing kmem buckets API to further isolate these allocations in a guaranteed fashion, whenCONFIG_SLAB_BUCKETS=y.Ask for the accounted kmalloc type as well as the normal one.
AF_UNIXsetssk_allocationtoGFP_KERNEL_ACCOUNT, so without it everyAF_UNIXskb data area would fall back to the general caches, and those are the ones most worth isolating.GFP_DMAis left to fall back, being passed to an skb allocator only by rare devices.
Quoted 7 lines
Quoted 3 lines
I would be curious to learn how about the memory usage delta. However I see buckets are protected by their own kconfig, small systems can unselect them.
Correct. But here's the delta for a 1 node NUMA with memcg, which is dominated by sysfs. 26 caches (13 normal and 13 memcg):
sysfs, 26 caches 113,152
struct kmem_cache, 26 6,656 kmem_cache_node, 26 1,664 node_barn, 26 1,664 names, 26 1,248
the set itself, 2 rows × 14 pointers in kmalloc_buckets 224
per-CPU sheaf structs CPUSx x 832
Quoted 3 lines
For the networking bits:
Acked-by Paolo Abeni <[email protected]>
Thanks!
Signature · 1 line
Kees Cook