slab: Introduce dedicated bucket allocator
Subsystems (6)
Kees Cook
maintainerv6 cover slab: Introduce dedicated bucket allocator
Hi,
v6:
- update commit logs:
- update description of Kconfig default and API details
- call out usage of SLAB_NO_MERGE
- improve description of cross-cache attacks
- fix typos
- add kern-doc parsing of DECL_BUCKET_PARAMS macro
- add kern-doc to kmem_buckets_create()
- have CONFIG_SLAB_BUCKETS default to CONFIG_SLAB_FREELIST_HARDENED
- add CONFIG_SLAB_BUCKETS to hardening.config
- drop alignment argument from kmem_buckets_create()
v5: https://lore.kernel.org/lkml/[email protected]
v4: https://lore.kernel.org/lkml/[email protected]/
v3: https://lore.kernel.org/lkml/[email protected]/
v2: https://lore.kernel.org/lkml/[email protected]/
v1: https://lore.kernel.org/lkml/[email protected]/
For the cover letter, I'm repeating the commit log for patch 4 here, which has the more complete rationale:
Dedicated caches are available for fixed size allocations via kmem_cache_alloc(), but for dynamically sized allocations there is only the global kmalloc API's set of buckets available. This means it isn't possible to separate specific sets of dynamically sized allocations into a separate collection of caches.
This leads to a use-after-free exploitation weakness in the Linux kernel since many heap memory spraying/grooming attacks depend on using userspace-controllable dynamically sized allocations to collide with fixed size allocations that end up in same cache.
While CONFIG_RANDOM_KMALLOC_CACHES provides a probabilistic defense against these kinds of "type confusion" attacks, including for fixed same-size heap objects, we can create a complementary deterministic defense for dynamically sized allocations that are directly user controlled. Addressing these cases is limited in scope, so isolating these kinds of interfaces will not become an unbounded game of whack-a-mole. For example, many pass through memdup_user(), making isolation there very effective.
In order to isolate user-controllable dynamically-sized allocations from the common system kmalloc allocations, introduce kmem_buckets_create(), which behaves like kmem_cache_create(). Introduce kmem_buckets_alloc(), which behaves like kmem_cache_alloc(). Introduce kmem_buckets_alloc_track_caller() for where caller tracking is needed. Introduce kmem_buckets_valloc() for cases where vmalloc fallback is needed. Note that these caches are specifically flagged with SLAB_NO_MERGE, since merging would defeat the entire purpose of the mitigation.
This can also be used in the future to extend allocation profiling's use of code tagging to implement per-caller allocation cache isolation[1] even for dynamic allocations.
Memory allocation pinning[2] is still needed to plug the Use-After-Free cross-allocator weakness (where attackers can arrange to free an entire slab page and have it reallocated to a different cache), but that is an existing and separate issue which is complementary to this improvement. Development continues for that feature via the SLAB_VIRTUAL[3] series (which could also provide guard pages -- another complementary improvement).
After the core implementation are 2 patches that cover the most heavily abused "repeat offenders" used in exploits. Repeating those details here:
The msg subsystem is a common target for exploiting[1][2][3][4][5][6] use-after-free type confusion flaws in the kernel for both read and write primitives. Avoid having a user-controlled size cache share the global kmalloc allocator by using a separate set of kmalloc buckets.
Both memdup_user() and vmemdup_user() handle allocations that are regularly used for exploiting use-after-free type confusion flaws in the kernel (e.g. prctl() PR_SET_VMA_ANON_NAME[1] and setxattr[2][3][4] respectively).
Since both are designed for contents coming from userspace, it allows for userspace-controlled allocation sizes. Use a dedicated set of kmalloc buckets so these allocations do not share caches with the global kmalloc buckets.
Thanks!
-Kees
Kees Cook (6):
mm/slab: Introduce kmem_buckets typedef
mm/slab: Plumb kmem_buckets into __do_kmalloc_node() mm/slab: Introduce kvmalloc_buckets_node() that can take kmem_buckets
argument
mm/slab: Introduce kmem_buckets_create() and family
ipc, msg: Use dedicated slab buckets for alloc_msg() mm/util: Use dedicated slab buckets for memdup_user()
include/linux/slab.h | 48 ++++++++++++--- ipc/msgutil.c | 13 +++- kernel/configs/hardening.config | 1 + mm/Kconfig | 17 ++++++ mm/slab.h | 6 +- mm/slab_common.c | 101 +++++++++++++++++++++++++++++++- mm/slub.c | 20 +++---- mm/util.c | 23 ++++++-- scripts/kernel-doc | 1 + 9 files changed, 200 insertions(+), 30 deletions(-)
Signature · 1 line
2.34.1
Kees Cook
maintainerPATCH v6 1/6 mm/slab: Introduce kmem_buckets typedef
Encapsulate the concept of a single set of kmem_caches that are used for the kmalloc size buckets. Redefine kmalloc_caches as an array of these buckets (for the different global cache buckets).
--- include/linux/slab.h | 5 +++-- mm/slab_common.c | 3 +-- 2 files changed, 4 insertions(+), 4 deletions(-)
include/linux/slab.h +3 -2
@@ -426,8 +426,9 @@ enum kmalloc_cache_type { NR_KMALLOC_TYPES }; -extern struct kmem_cache * -kmalloc_caches[NR_KMALLOC_TYPES ][KMALLOC_SHIFT_HIGH +1 ]; +typedef struct kmem_cache *kmem_buckets [KMALLOC_SHIFT_HIGH +1 ]; + +extern kmem_buckets kmalloc_caches[NR_KMALLOC_TYPES ];/* * Define gfp bits that should not be set for KMALLOC_NORMAL.
mm/slab_common.c +1 -2
@@ -653,8 +653,7 @@ static struct kmem_cache *__init create_kmalloc_cache(const char *name,return s; } -struct kmem_cache * -kmalloc_caches[NR_KMALLOC_TYPES ][KMALLOC_SHIFT_HIGH +1 ]__ro_after_init = +kmem_buckets kmalloc_caches[NR_KMALLOC_TYPES ]__ro_after_init = {/* initialization for https://llvm.org/pr42570 */ };EXPORT_SYMBOL (kmalloc_caches);
Signature · 1 line
2.34.1
Kees Cook
maintainerPATCH v6 2/6 mm/slab: Plumb kmem_buckets into __do_kmalloc_node()
In reply to Kees CookIntroduce CONFIG_SLAB_BUCKETS which provides the infrastructure to support separated kmalloc buckets (in the following kmem_buckets_create() patches and future codetag-based separation). Since this will provide a mitigation for a very common case of exploits, it is recommended to enable this feature for general purpose distros. By default, the new Kconfig will be enabled if CONFIG_SLAB_FREELIST_HARDENED is enabled (and it is added to the hardening.config Kconfig fragment).
To be able to choose which buckets to allocate from, make the buckets available to the internal kmalloc interfaces by adding them as the second argument, rather than depending on the buckets being chosen from the fixed set of global buckets. Where the bucket is not available, pass NULL, which means "use the default system kmalloc bucket set" (the prior existing behavior), as implemented in kmalloc_slab().
To avoid adding the extra argument when !CONFIG_SLAB_BUCKETS, only the top-level macros and static inlines use the buckets argument (where they are stripped out and compiled out respectively). The actual extern functions can then be built without the argument, and the internals fall back to the global kmalloc buckets unconditionally.
--- include/linux/slab.h | 27 ++++++++++++++++++++++----- kernel/configs/hardening.config | 1 + mm/Kconfig | 17 +++++++++++++++++ mm/slab.h | 6 ++++-- mm/slab_common.c | 2 +- mm/slub.c | 20 ++++++++++---------- scripts/kernel-doc | 1 + 7 files changed, 56 insertions(+), 18 deletions(-)
include/linux/slab.h +22 -5
@@ -570,6 +570,21 @@ void *kmem_cache_alloc_node_noprof(struct kmem_cache *s, gfp_t flags, int node) __assume_slab_alignment __malloc;#define kmem_cache_alloc_node (...) alloc_hooks(kmem_cache_alloc_node_noprof(__VA_ARGS__)) +/* +* These macros allow declaring a kmem_buckets * parameter alongside size, which +* can be compiled out with CONFIG_SLAB_BUCKETS=n so that a large number of call +* sites don't have to pass NULL. +*/ +#ifdef CONFIG_SLAB_BUCKETS +#define DECL_BUCKET_PARAMS (_size, _b) size_t (_size), kmem_buckets *(_b) +#define PASS_BUCKET_PARAMS (_size, _b) (_size), (_b) +#define PASS_BUCKET_PARAM (_b) (_b) +#else +#define DECL_BUCKET_PARAMS (_size, _b) size_t (_size) +#define PASS_BUCKET_PARAMS (_size, _b) (_size) +#define PASS_BUCKET_PARAM (_b) NULL +#endif +/* * The following functions are not to be used directly and are intended only * for internal use from kmalloc() and kmalloc_node()
@@ -579,7 +594,7 @@ void *kmem_cache_alloc_node_noprof(struct kmem_cache *s, gfp_t flags,void *__kmalloc_noprof (size_t size,gfp_t flags) __assume_kmalloc_alignment__alloc_size(1) ; -void *__kmalloc_node_noprof (size_t size,gfp_t flags,int node) +void *__kmalloc_node_noprof (DECL_BUCKET_PARAMS (size ,b ),gfp_t flags,int node) __assume_kmalloc_alignment__alloc_size(1) ;void *__kmalloc_cache_noprof (struct kmem_cache *s,gfp_t flags,size_t size)
@@ -679,7 +694,7 @@ static __always_inline __alloc_size(1) void *kmalloc_node_noprof(size_t size, gf kmalloc_caches[kmalloc_type (flags, _RET_IP_)][index], flags, node, size); } -return __kmalloc_node_noprof (size, flags, node); +return __kmalloc_node_noprof (PASS_BUCKET_PARAMS (size,NULL ), flags, node); }#define kmalloc_node (...) alloc_hooks(kmalloc_node_noprof(__VA_ARGS__))
@@ -730,8 +745,10 @@ static inline __realloc_size(2, 3) void * __must_check krealloc_array_noprof(voi*/ #define kcalloc (n, size, flags) kmalloc_array(n, size, (flags) | __GFP_ZERO) -void *kmalloc_node_track_caller_noprof (size_t size,gfp_t flags,int node, -unsigned long caller)__alloc_size(1) ; +void *__kmalloc_node_track_caller_noprof (DECL_BUCKET_PARAMS (size ,b ),gfp_t flags,int node, +unsigned long caller)__alloc_size(1) ; +#define kmalloc_node_track_caller_noprof (size, flags, node, caller) \ + __kmalloc_node_track_caller_noprof(PASS_BUCKET_PARAMS(size, NULL), flags, node, caller)#define kmalloc_node_track_caller (...) \ alloc_hooks(kmalloc_node_track_caller_noprof(__VA_ARGS__, _RET_IP_))
@@ -757,7 +774,7 @@ static inline __alloc_size(1, 2) void *kmalloc_array_node_noprof(size_t n, size_return NULL ;if (__builtin_constant_p (n) &&__builtin_constant_p (size))return kmalloc_node_noprof (bytes, flags, node); -return __kmalloc_node_noprof (bytes, flags, node); +return __kmalloc_node_noprof (PASS_BUCKET_PARAMS (bytes,NULL ), flags, node); }#define kmalloc_array_node (...) alloc_hooks(kmalloc_array_node_noprof(__VA_ARGS__))
kernel/configs/hardening.config +1 -0
@@ -20,6 +20,7 @@ CONFIG_RANDOMIZE_MEMORY=y # Randomize allocator freelists, harden metadata. CONFIG_SLAB_FREELIST_RANDOM=y CONFIG_SLAB_FREELIST_HARDENED=y +CONFIG_SLAB_BUCKETS=y CONFIG_SHUFFLE_PAGE_ALLOCATOR=y CONFIG_RANDOM_KMALLOC_CACHES=y
mm/Kconfig +17 -0
@@ -273,6 +273,23 @@ config SLAB_FREELIST_HARDENED sacrifices to harden the kernel slab allocator against common freelist exploit methods. +config SLAB_BUCKETS + bool "Support allocation from separate kmalloc buckets" + depends on !SLUB_TINY + default SLAB_FREELIST_HARDENED + help + Kernel heap attacks frequently depend on being able to create + specifically-sized allocations with user-controlled contents + that will be allocated into the same kmalloc bucket as a + target object. To avoid sharing these allocation buckets, + provide an explicitly separated set of buckets to be used for + user-controlled allocations. This may very slightly increase + memory fragmentation, though in practice it's only a handful + of extra pages since the bulk of user-controlled allocations + are relatively long-lived. + + If unsure, say Y. + config SLUB_STATS default n bool "Enable performance statistics"
mm/slab.h +4 -2
@@ -403,16 +403,18 @@ static inline unsigned int size_index_elem(unsigned int bytes)* KMALLOC_MAX_CACHE_SIZE and the caller must check that. */ static inline struct kmem_cache * -kmalloc_slab (size_t size,gfp_t flags,unsigned long caller) +kmalloc_slab (size_t size,kmem_buckets *b,gfp_t flags,unsigned long caller) {unsigned int index; +if (!b) + b = &kmalloc_caches[kmalloc_type (flags, caller)];if (size <=192 ) index = kmalloc_size_index[size_index_elem (size)];else index =fls (size -1 ); -return kmalloc_caches[kmalloc_type (flags, caller)][index]; +return (*b)[index]; }gfp_t kmalloc_fix_flags (gfp_t flags);
mm/slab_common.c +1 -1
@@ -702,7 +702,7 @@ size_t kmalloc_size_roundup(size_t size)* The flags don't matter since size_index is common to all. * Neither does the caller for just getting ->object_size. */ -return kmalloc_slab (size,GFP_KERNEL ,0 )->object_size; +return kmalloc_slab (size,NULL ,GFP_KERNEL ,0 )->object_size; }/* Above the smaller buckets, size is a multiple of page size. */
mm/slub.c +10 -10
@@ -4117,7 +4117,7 @@ void *__kmalloc_large_node_noprof(size_t size, gfp_t flags, int node)EXPORT_SYMBOL (__kmalloc_large_node_noprof);static __always_inline -void *__do_kmalloc_node(size_t size, gfp_t flags, int node, +void *__do_kmalloc_node(size_t size, kmem_buckets *b, gfp_t flags, int node,unsigned long caller) {struct kmem_cache *s;
@@ -4133,32 +4133,32 @@ void *__do_kmalloc_node(size_t size, gfp_t flags, int node,if (unlikely (!size))return ZERO_SIZE_PTR ; - s =kmalloc_slab (size, flags, caller); + s =kmalloc_slab (size, b, flags, caller); ret =slab_alloc_node (s,NULL , flags, node, caller, size); ret =kasan_kmalloc (s, ret, size, flags);trace_kmalloc (caller, ret, size, s->size, flags, node);return ret; } - -void *__kmalloc_node_noprof (size_t size,gfp_t flags,int node) +void *__kmalloc_node_noprof (DECL_BUCKET_PARAMS (size ,b ),gfp_t flags,int node) { -return __do_kmalloc_node (size, flags, node, _RET_IP_); +return __do_kmalloc_node (size,PASS_BUCKET_PARAM (b), flags, node, _RET_IP_); }EXPORT_SYMBOL (__kmalloc_node_noprof);void *__kmalloc_noprof (size_t size,gfp_t flags) { -return __do_kmalloc_node (size, flags,NUMA_NO_NODE , _RET_IP_); +return __do_kmalloc_node (size,NULL , flags,NUMA_NO_NODE , _RET_IP_); }EXPORT_SYMBOL (__kmalloc_noprof); -void *kmalloc_node_track_caller_noprof (size_t size,gfp_t flags, -int node,unsigned long caller) +void *__kmalloc_node_track_caller_noprof (DECL_BUCKET_PARAMS (size ,b ),gfp_t flags, +int node,unsigned long caller) { -return __do_kmalloc_node (size, flags, node, caller); +return __do_kmalloc_node (size,PASS_BUCKET_PARAM (b), flags, node, caller); + } -EXPORT_SYMBOL (kmalloc_node_track_caller_noprof); +EXPORT_SYMBOL (__kmalloc_node_track_caller_noprof);void *__kmalloc_cache_noprof (struct kmem_cache *s,gfp_t gfpflags,size_t size) {
scripts/kernel-doc +1 -0
@@ -1729,6 +1729,7 @@ sub dump_function($$) { $prototype =~ s/__printf\s*\(\s*\d*\s*,\s*\d*\s*\) +//; $prototype =~ s/__(?:re)?alloc_size\s*\(\s*\d+\s*(?:,\s*\d+\s*)?\) +//; $prototype =~ s/__diagnose_as\s*\(\s*\S+\s*(?:,\s*\d+\s*)*\) +//; + $prototype =~ s/DECL_BUCKET_PARAMS\s*\(\s*(\S+)\s*,\s*(\S+)\s*\)/$1, $2/; my $define = $prototype =~ s/^#\s*define\s+//; #ak added $prototype =~ s/__attribute_const__ +//; $prototype =~ s/__attribute__\s*\(\(
Signature · 1 line
2.34.1
Kees Cook
maintainerPATCH v6 3/6 mm/slab: Introduce kvmalloc_buckets_node() that can take kmem_buckets argument
In reply to Kees CookPlumb kmem_buckets arguments through kvmalloc_node_noprof() so it is possible to provide an API to perform kvmalloc-style allocations with a particular set of buckets. Introduce kvmalloc_buckets_node() that takes a kmem_buckets argument.
--- include/linux/slab.h | 4 +++- mm/util.c | 9 +++++---- 2 files changed, 8 insertions(+), 5 deletions(-)
include/linux/slab.h +3 -1
@@ -798,7 +798,9 @@ static inline __alloc_size(1) void *kzalloc_noprof(size_t size, gfp_t flags)#define kzalloc (...) alloc_hooks(kzalloc_noprof(__VA_ARGS__))#define kzalloc_node (_size, _flags, _node) kmalloc_node(_size, (_flags)|__GFP_ZERO, _node) -extern void *kvmalloc_node_noprof (size_t size,gfp_t flags,int node)__alloc_size(1) ; +void *__kvmalloc_node_noprof (DECL_BUCKET_PARAMS (size ,b ),gfp_t flags,int node)__alloc_size(1) ; +#define kvmalloc_node_noprof (size, flags, node) \ + __kvmalloc_node_noprof(PASS_BUCKET_PARAMS(size, NULL), flags, node)#define kvmalloc_node (...) alloc_hooks(kvmalloc_node_noprof(__VA_ARGS__))#define kvmalloc (_size, _flags) kvmalloc_node(_size, _flags, NUMA_NO_NODE)
mm/util.c +5 -4
@@ -594,9 +594,10 @@ unsigned long vm_mmap(struct file *file, unsigned long addr,EXPORT_SYMBOL (vm_mmap);/** -* kvmalloc_node - attempt to allocate physically contiguous memory, but upon +* __kvmalloc_node - attempt to allocate physically contiguous memory, but upon * failure, fall back to non-contiguous (vmalloc) allocation. * @size: size of the request. +* @b: which set of kmalloc buckets to allocate from. * @flags: gfp mask for the allocation - must be compatible (superset) with GFP_KERNEL. * @node: numa node to allocate from *
@@ -609,7 +610,7 @@ EXPORT_SYMBOL(vm_mmap);* * Return: pointer to the allocated memory of %NULL in case of failure */ -void *kvmalloc_node_noprof (size_t size,gfp_t flags,int node) +void *__kvmalloc_node_noprof (DECL_BUCKET_PARAMS (size ,b ),gfp_t flags,int node) {gfp_t kmalloc_flags = flags;void *ret;
@@ -631,7 +632,7 @@ void *kvmalloc_node_noprof(size_t size, gfp_t flags, int node) kmalloc_flags &= ~__GFP_NOFAIL; } - ret =kmalloc_node_noprof (size, kmalloc_flags, node); + ret =__kmalloc_node_noprof (PASS_BUCKET_PARAMS (size, b), kmalloc_flags, node);/* * It doesn't really make sense to fallback to vmalloc for sub page
@@ -660,7 +661,7 @@ void *kvmalloc_node_noprof(size_t size, gfp_t flags, int node) flags,PAGE_KERNEL ,VM_ALLOW_HUGE_VMAP , node, __builtin_return_address(0)); } -EXPORT_SYMBOL (kvmalloc_node_noprof); +EXPORT_SYMBOL (__kvmalloc_node_noprof);/** * kvfree() - Free memory.
Signature · 1 line
2.34.1
Kees Cook
maintainerPATCH v6 4/6 mm/slab: Introduce kmem_buckets_create() and family
In reply to Kees CookDedicated caches are available for fixed size allocations via kmem_cache_alloc(), but for dynamically sized allocations there is only the global kmalloc API's set of buckets available. This means it isn't possible to separate specific sets of dynamically sized allocations into a separate collection of caches.
This leads to a use-after-free exploitation weakness in the Linux kernel since many heap memory spraying/grooming attacks depend on using userspace-controllable dynamically sized allocations to collide with fixed size allocations that end up in same cache.
While CONFIG_RANDOM_KMALLOC_CACHES provides a probabilistic defense against these kinds of "type confusion" attacks, including for fixed same-size heap objects, we can create a complementary deterministic defense for dynamically sized allocations that are directly user controlled. Addressing these cases is limited in scope, so isolating these kinds of interfaces will not become an unbounded game of whack-a-mole. For example, many pass through memdup_user(), making isolation there very effective.
In order to isolate user-controllable dynamically-sized allocations from the common system kmalloc allocations, introduce kmem_buckets_create(), which behaves like kmem_cache_create(). Introduce kmem_buckets_alloc(), which behaves like kmem_cache_alloc(). Introduce kmem_buckets_alloc_track_caller() for where caller tracking is needed. Introduce kmem_buckets_valloc() for cases where vmalloc fallback is needed. Note that these caches are specifically flagged with SLAB_NO_MERGE, since merging would defeat the entire purpose of the mitigation.
This can also be used in the future to extend allocation profiling's use of code tagging to implement per-caller allocation cache isolation[1] even for dynamic allocations.
Memory allocation pinning[2] is still needed to plug the Use-After-Free cross-allocator weakness (where attackers can arrange to free an entire slab page and have it reallocated to a different cache), but that is an existing and separate issue which is complementary to this improvement. Development continues for that feature via the SLAB_VIRTUAL[3] series (which could also provide guard pages -- another complementary improvement).
--- include/linux/slab.h | 12 ++++++ mm/slab_common.c | 96 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 108 insertions(+)
include/linux/slab.h +12 -0
@@ -549,6 +549,10 @@ void *kmem_cache_alloc_lru_noprof(struct kmem_cache *s, struct list_lru *lru,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 *)); +/* * Bulk allocation and freeing operations. These are accelerated in an * allocator specific way to avoid taking locks repeatedly or building
@@ -681,6 +685,12 @@ static __always_inline __alloc_size(1) void *kmalloc_noprof(size_t size, gfp_t f }#define kmalloc (...) alloc_hooks(kmalloc_noprof(__VA_ARGS__)) +#define kmem_buckets_alloc (_b, _size, _flags) \ + alloc_hooks(__kmalloc_node_noprof(PASS_BUCKET_PARAMS(_size, _b), _flags, NUMA_NO_NODE)) + +#define kmem_buckets_alloc_track_caller (_b, _size, _flags) \ + alloc_hooks(__kmalloc_node_track_caller_noprof(PASS_BUCKET_PARAMS(_size, _b), _flags, NUMA_NO_NODE, _RET_IP_)) + static __always_inline __alloc_size(1) void *kmalloc_node_noprof(size_t size, gfp_t flags, int node) {if (__builtin_constant_p (size) && size) {
@@ -808,6 +818,8 @@ void *__kvmalloc_node_noprof(DECL_BUCKET_PARAMS(size, b), gfp_t flags, int node)#define kvzalloc (_size, _flags) kvmalloc(_size, (_flags)|__GFP_ZERO)#define kvzalloc_node (_size, _flags, _node) kvmalloc_node(_size, (_flags)|__GFP_ZERO, _node) +#define kmem_buckets_valloc (_b, _size, _flags) \ + alloc_hooks(__kvmalloc_node_noprof(PASS_BUCKET_PARAMS(_size, _b), _flags, NUMA_NO_NODE))static inline __alloc_size(1, 2) void *kvmalloc_array_node_noprof (size_t n,size_t size,gfp_t flags,int node)
mm/slab_common.c +96 -0
108 lines not on this page · raw message
Signature · 1 line
2.34.1
Re: [PATCH v6 4/6] mm/slab: Introduce kmem_buckets_create() and family
mm/slab_common.c void __init create_kmalloc_caches(void) · line 1029
Quoted 11 lines · Kees Cook
#ifdef
SLAB_SUPPORTS_SYSFS
/*
* For a givenkmem_cache,kmem_cache_destroy()should only be calledmm/slab_common.c +4
-0@@ -931,6 +1023,10 @@ void __init create_kmalloc_caches(void)/* Kmalloc array is now usable */ slab_state =UP ; + + kmem_buckets_cache =kmem_cache_create ("kmalloc_buckets" , +sizeof (kmem_buckets), +0 ,SLAB_NO_MERGE ,NULL );
Locally adjusted to put this behind "if (IS_ENABLED(CONFIG_SLAB_BUCKETS))"
Quoted 3 lines
(quoted diff) +0
-0} /**
Kees Cook
maintainerRe: [PATCH v6 4/6] mm/slab: Introduce kmem_buckets_create() and family
Quoted 6 lines · Vlastimil Babka
Quoted 5 lines · Kees Cook
#ifdef
SLAB_SUPPORTS_SYSFS
/*
* For a givenkmem_cache,kmem_cache_destroy()should only be called(quoted diff) +0
-0@@ -931,6 +1023,10 @@ void __init create_kmalloc_caches(void)
Quoted 8 lines
mm/slab_common.c void __init create_kmalloc_caches(void) · line 1029
Quoted 6 lines
mm/slab_common.c +4
-0/* Kmalloc array is now usable */ slab_state =UP ; + + kmem_buckets_cache =kmem_cache_create ("kmalloc_buckets" , +sizeof (kmem_buckets), +0 ,SLAB_NO_MERGE ,NULL );Locally adjusted to put this behind "if (
IS_ENABLED(CONFIG_SLAB_BUCKETS))"
Oops, yes, good catch. Thank you!
Signature · 1 line
Kees Cook
Kees Cook
maintainerPATCH v6 5/6 ipc, msg: Use dedicated slab buckets for alloc_msg()
In reply to Kees CookThe msg subsystem is a common target for exploiting[1][2][3][4][5][6][7] use-after-free type confusion flaws in the kernel for both read and write primitives. Avoid having a user-controlled dynamically-size allocation share the global kmalloc cache by using a separate set of kmalloc buckets via the kmem_buckets API.
--- ipc/msgutil.c | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-)
ipc/msgutil.c +12 -1
@@ -42,6 +42,17 @@ struct msg_msgseg {#define DATALEN_MSG ((size_t)PAGE_SIZE-sizeof(struct msg_msg))#define DATALEN_SEG ((size_t)PAGE_SIZE-sizeof(struct msg_msgseg)) +static kmem_buckets *msg_buckets__ro_after_init ; + +static int __init init_msg_buckets (void ) +{ + msg_buckets =kmem_buckets_create ("msg_msg" ,SLAB_ACCOUNT , +sizeof (struct msg_msg ), +DATALEN_MSG ,NULL ); + +return 0 ; +} +subsys_initcall (init_msg_buckets);static struct msg_msg *alloc_msg (size_t len) {
@@ -50,7 +61,7 @@ static struct msg_msg *alloc_msg(size_t len)size_t alen; alen =min (len,DATALEN_MSG ); - msg =kmalloc (sizeof (*msg) + alen,GFP_KERNEL_ACCOUNT ); + msg =kmem_buckets_alloc (msg_buckets,sizeof (*msg) + alen,GFP_KERNEL );if (msg ==NULL )return NULL ;
Signature · 1 line
2.34.1
Kees Cook
maintainerPATCH v6 6/6 mm/util: Use dedicated slab buckets for memdup_user()
In reply to Kees CookBoth memdup_user() and vmemdup_user() handle allocations that are regularly used for exploiting use-after-free type confusion flaws in the kernel (e.g. prctl() PR_SET_VMA_ANON_NAME[1] and setxattr[2][3][4] respectively).
Since both are designed for contents coming from userspace, it allows for userspace-controlled allocation sizes. Use a dedicated set of kmalloc buckets so these allocations do not share caches with the global kmalloc buckets.
After a fresh boot under Ubuntu 23.10, we can see the caches are already in active use:
# grep ^memdup /proc/slabinfo memdup_user-8k 4 4 8192 4 8 : ... memdup_user-4k 8 8 4096 8 8 : ... memdup_user-2k 16 16 2048 16 8 : ... memdup_user-1k 0 0 1024 16 4 : ... memdup_user-512 0 0 512 16 2 : ... memdup_user-256 0 0 256 16 1 : ... memdup_user-128 0 0 128 32 1 : ... memdup_user-64 256 256 64 64 1 : ... memdup_user-32 512 512 32 128 1 : ... memdup_user-16 1024 1024 16 256 1 : ... memdup_user-8 2048 2048 8 512 1 : ... memdup_user-192 0 0 192 21 1 : ... memdup_user-96 168 168 96 42 1 : ...
--- mm/util.c | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-)
mm/util.c +12 -2
@@ -198,6 +198,16 @@ char *kmemdup_nul(const char *s, size_t len, gfp_t gfp) }EXPORT_SYMBOL (kmemdup_nul); +static kmem_buckets *user_buckets__ro_after_init ; + +static int __init init_user_buckets (void ) +{ + user_buckets =kmem_buckets_create ("memdup_user" ,0 ,0 ,INT_MAX ,NULL ); + +return 0 ; +} +subsys_initcall (init_user_buckets); +/** * memdup_user - duplicate memory region from user space *
@@ -211,7 +221,7 @@ void *memdup_user(const void __user *src, size_t len) {void *p; - p =kmalloc_track_caller (len,GFP_USER | __GFP_NOWARN); + p =kmem_buckets_alloc_track_caller (user_buckets, len,GFP_USER | __GFP_NOWARN);if (!p)return ERR_PTR (-ENOMEM );
@@ -237,7 +247,7 @@ void *vmemdup_user(const void __user *src, size_t len) {void *p; - p =kvmalloc (len,GFP_USER ); + p =kmem_buckets_valloc (user_buckets, len,GFP_USER );if (!p)return ERR_PTR (-ENOMEM );
Signature · 1 line
2.34.1
Vlastimil Babka
AppliedQuoted 9 lines · Kees Cook
Kees Cook (6):
mm/slab: Introducekmem_bucketstypedef
mm/slab: Plumbkmem_bucketsinto__do_kmalloc_node()mm/slab: Introducekvmalloc_buckets_node()that can takekmem_buckets
argument
mm/slab: Introducekmem_buckets_create()and family
ipc, msg: Use dedicated slab buckets foralloc_msg()mm/util: Use dedicated slab buckets formemdup_user()
pushed to slab/for-6.11/buckets, slab/for-next
Thanks!
Quoted 12 lines
include/linux/slab.h | 48 ++++++++++++--- ipc/msgutil.c | 13 +++- kernel/configs/hardening.config | 1 + mm/Kconfig | 17 ++++++ mm/slab.h | 6 +- mm/slab_common.c | 101 +++++++++++++++++++++++++++++++- mm/slub.c | 20 +++---- mm/util.c | 23 ++++++-- scripts/kernel-doc | 1 + 9 files changed, 200 insertions(+), 30 deletions(-)
Kees Cook
maintainerQuoted 11 lines · Vlastimil Babka
Quoted 9 lines · Kees Cook
Kees Cook (6):
mm/slab: Introducekmem_bucketstypedef
mm/slab: Plumbkmem_bucketsinto__do_kmalloc_node()mm/slab: Introducekvmalloc_buckets_node()that can takekmem_buckets
argument
mm/slab: Introducekmem_buckets_create()and family
ipc, msg: Use dedicated slab buckets foralloc_msg()mm/util: Use dedicated slab buckets formemdup_user()
Quoted 1 line
pushed to slab/for-6.11/buckets, slab/for-next
Great! Thanks for the review and improvements! :) I'll get started on the next step getting it hooked up to the codetag bits...
Signature · 1 line
Kees Cook