]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
Merge branch 'slab/for-7.2/alloc_token' into slab/for-next
authorVlastimil Babka (SUSE) <vbabka@kernel.org>
Thu, 11 Jun 2026 10:40:05 +0000 (12:40 +0200)
committerVlastimil Babka (SUSE) <vbabka@kernel.org>
Fri, 12 Jun 2026 09:25:12 +0000 (11:25 +0200)
Merge series "slab: support for compiler-assisted type-based slab cache
partitioning" from Marco Elver. From the cover letter [6]:

Rework the general infrastructure around RANDOM_KMALLOC_CACHES into more
flexible KMALLOC_PARTITION_CACHES, with the former being a partitioning
mode of the latter.

Introduce a new mode, KMALLOC_PARTITION_TYPED, which leverages a feature
available in Clang 22 and later, called "allocation tokens" via
__builtin_infer_alloc_token() [1]. Unlike KMALLOC_PARTITION_RANDOM
(formerly RANDOM_KMALLOC_CACHES), this mode deterministically assigns a
slab cache to an allocation of type T, regardless of allocation site.

The builtin __builtin_infer_alloc_token(<malloc-args>, ...) instructs
the compiler to infer an allocation type from arguments commonly passed
to memory-allocating functions and returns a type-derived token ID. The
implementation passes kmalloc-args to the builtin: the compiler performs
best-effort type inference, and then recognizes common patterns such as
`kmalloc(sizeof(T), ...)`, `kmalloc(sizeof(T) * n, ...)`, but also
`(T *)kmalloc(...)`. Where the compiler fails to infer a type the
fallback token (default: 0) is chosen.

Note: kmalloc_obj(..) APIs fix the pattern how size and result type are
expressed, and therefore ensures there's not much drift in which
patterns the compiler needs to recognize. Specifically, kmalloc_obj()
and friends expand to `(TYPE *)KMALLOC(__obj_size, GFP)`, which the
compiler recognizes via the cast to TYPE*.

Clang's default token ID calculation is described as [1]:

   typehashpointersplit: This mode assigns a token ID based on the hash
   of the allocated type's name, where the top half ID-space is reserved
   for types that contain pointers and the bottom half for types that do
   not contain pointers.

Separating pointer-containing objects from pointerless objects and data
allocations can help mitigate certain classes of memory corruption
exploits [2]: attackers who gains a buffer overflow on a primitive
buffer cannot use it to directly corrupt pointers or other critical
metadata in an object residing in a different, isolated heap region.

It is important to note that heap isolation strategies offer a
best-effort approach, and do not provide a 100% security guarantee,
albeit achievable at relatively low performance cost. Note that this
also does not prevent cross-cache attacks: while waiting for future
features like SLAB_VIRTUAL [3] to provide physical page isolation, this
feature should be deployed alongside SHUFFLE_PAGE_ALLOCATOR and
init_on_free=1 to mitigate cross-cache attacks and page-reuse attacks as
much as possible today.

With all that, my kernel (x86 defconfig) shows me a histogram of slab
cache object distribution per /proc/slabinfo (after boot):

  <slab cache>      <objs> <hist>
  kmalloc-part-15    1465  ++++++++++++++
  kmalloc-part-14    2988  +++++++++++++++++++++++++++++
  kmalloc-part-13    1656  ++++++++++++++++
  kmalloc-part-12    1045  ++++++++++
  kmalloc-part-11    1697  ++++++++++++++++
  kmalloc-part-10    1489  ++++++++++++++
  kmalloc-part-09     965  +++++++++
  kmalloc-part-08     710  +++++++
  kmalloc-part-07     100  +
  kmalloc-part-06     217  ++
  kmalloc-part-05     105  +
  kmalloc-part-04    4047  ++++++++++++++++++++++++++++++++++++++++
  kmalloc-part-03     183  +
  kmalloc-part-02     283  ++
  kmalloc-part-01     316  +++
  kmalloc            1422  ++++++++++++++

The above /proc/slabinfo snapshot shows me there are 6673 allocated
objects (slabs 00 - 07) that the compiler claims contain no pointers or
it was unable to infer the type of, and 12015 objects that contain
pointers (slabs 08 - 15). On a whole, this looks relatively sane.

Additionally, when I compile my kernel with -Rpass=alloc-token, which
provides diagnostics where (after dead-code elimination) type inference
failed, I see 186 allocation sites where the compiler failed to identify
a type (down from 966 when I sent the RFC [4]). Some initial review
confirms these are mostly variable sized buffers, but also include
structs with trailing flexible length arrays.

Link: https://clang.llvm.org/docs/AllocToken.html
Link: https://blog.dfsec.com/ios/2025/05/30/blasting-past-ios-18/
Link: https://lwn.net/Articles/944647/
Link: https://lore.kernel.org/all/20250825154505.1558444-1-elver@google.com/
Link: https://discourse.llvm.org/t/rfc-a-framework-for-allocator-partitioning-hints/87434
Link: https://lore.kernel.org/all/20260511200136.3201646-1-elver@google.com/
1  2 
include/linux/slab.h
mm/kfence/kfence_test.c
mm/slub.c

Simple merge
Simple merge
diff --cc mm/slub.c
index 171fa500c7ee262fff83d3fe9ce81b776cbc33de,abbed1d2bd5e503afec5d90a16c3fffe085ae6a1..e2ee8f1aaccf384db5e919a097d3f9795fe566cf
+++ b/mm/slub.c
@@@ -5346,21 -5310,9 +5348,10 @@@ void *__kmalloc_noprof(DECL_TOKEN_PARAM
  }
  EXPORT_SYMBOL(__kmalloc_noprof);
  
- /**
-  * kmalloc_nolock - Allocate an object of given size from any context.
-  * @size: size to allocate
-  * @gfp_flags: GFP flags. Only __GFP_ACCOUNT, __GFP_ZERO, __GFP_NO_OBJ_EXT
-  * allowed.
-  * @node: node number of the target node.
-  *
-  * Return: pointer to the new object or NULL in case of error.
-  * NULL does not mean EBUSY or EAGAIN. It means ENOMEM.
-  * There is no reason to call it again and expect !NULL.
-  */
- void *kmalloc_nolock_noprof(size_t size, gfp_t gfp_flags, int node)
+ void *_kmalloc_nolock_noprof(DECL_TOKEN_PARAMS(size, token), gfp_t gfp_flags, int node)
  {
        gfp_t alloc_gfp = __GFP_NOWARN | __GFP_NOMEMALLOC | gfp_flags;
 +      size_t orig_size = size;
        struct kmem_cache *s;
        bool can_retry = true;
        void *ret;
@@@ -5433,17 -5385,18 +5424,18 @@@ retry
  success:
        maybe_wipe_obj_freeptr(s, ret);
        slab_post_alloc_hook(s, NULL, alloc_gfp, 1, &ret,
 -                           slab_want_init_on_alloc(alloc_gfp, s), size);
 +                           slab_want_init_on_alloc(alloc_gfp, s), orig_size);
  
 -      ret = kasan_kmalloc(s, ret, size, alloc_gfp);
 +      ret = kasan_kmalloc(s, ret, orig_size, alloc_gfp);
        return ret;
  }
- EXPORT_SYMBOL_GPL(kmalloc_nolock_noprof);
+ EXPORT_SYMBOL_GPL(_kmalloc_nolock_noprof);
  
- void *__kmalloc_node_track_caller_noprof(DECL_BUCKET_PARAMS(size, b), gfp_t flags,
+ void *__kmalloc_node_track_caller_noprof(DECL_KMALLOC_PARAMS(size, b, token), gfp_t flags,
                                         int node, unsigned long caller)
  {
-       return __do_kmalloc_node(size, PASS_BUCKET_PARAM(b), flags, node, caller);
+       return __do_kmalloc_node(size, PASS_BUCKET_PARAM(b), flags, node,
+                                caller, PASS_TOKEN_PARAM(token));
  
  }
  EXPORT_SYMBOL(__kmalloc_node_track_caller_noprof);