mm/slub: refactor freelist to use custom type
authorJann Horn <jannh@google.com>
Tue, 4 Jul 2023 13:58:34 +0000 (13:58 +0000)
committerVlastimil Babka <vbabka@suse.cz>
Tue, 11 Jul 2023 07:53:50 +0000 (09:53 +0200)
Currently the SLUB code represents encoded freelist entries as "void*".
That's misleading, those things are encoded under
CONFIG_SLAB_FREELIST_HARDENED so that they're not actually dereferencable.

Give them their own type, and split freelist_ptr() into one function per
direction (one for encoding, one for decoding).

Signed-off-by: Jann Horn <jannh@google.com>
Co-developed-by: Matteo Rizzo <matteorizzo@google.com>
Signed-off-by: Matteo Rizzo <matteorizzo@google.com>
Acked-by: David Rientjes <rientjes@google.com>
Signed-off-by: Vlastimil Babka <vbabka@suse.cz>
mm/slub.c

index e3b5d5c0eb3ad2ec315768813e97c581d1b09c67..f8cc47eff7420dff78742e9db99b52d361151e79 100644 (file)
--- a/mm/slub.c
+++ b/mm/slub.c
@@ -360,13 +360,19 @@ static struct workqueue_struct *flushwq;
  *                     Core slab cache functions
  *******************************************************************/
 
+/*
+ * freeptr_t represents a SLUB freelist pointer, which might be encoded
+ * and not dereferenceable if CONFIG_SLAB_FREELIST_HARDENED is enabled.
+ */
+typedef struct { unsigned long v; } freeptr_t;
+
 /*
  * Returns freelist pointer (ptr). With hardening, this is obfuscated
  * with an XOR of the address where the pointer is held and a per-cache
  * random number.
  */
-static inline void *freelist_ptr(const struct kmem_cache *s, void *ptr,
-                                unsigned long ptr_addr)
+static inline freeptr_t freelist_ptr_encode(const struct kmem_cache *s,
+                                           void *ptr, unsigned long ptr_addr)
 {
 #ifdef CONFIG_SLAB_FREELIST_HARDENED
        /*
@@ -379,25 +385,40 @@ static inline void *freelist_ptr(const struct kmem_cache *s, void *ptr,
         * calls get_freepointer() with an untagged pointer, which causes the
         * freepointer to be restored incorrectly.
         */
-       return (void *)((unsigned long)ptr ^ s->random ^
-                       swab((unsigned long)kasan_reset_tag((void *)ptr_addr)));
+       return (freeptr_t){.v = (unsigned long)ptr ^ s->random ^
+                       swab((unsigned long)kasan_reset_tag((void *)ptr_addr))};
 #else
-       return ptr;
+       return (freeptr_t){.v = (unsigned long)ptr};
 #endif
 }
 
+static inline void *freelist_ptr_decode(const struct kmem_cache *s,
+                                       freeptr_t ptr, unsigned long ptr_addr)
+{
+       void *decoded;
+
+#ifdef CONFIG_SLAB_FREELIST_HARDENED
+       /* See the comment in freelist_ptr_encode */
+       decoded = (void *)(ptr.v ^ s->random ^
+               swab((unsigned long)kasan_reset_tag((void *)ptr_addr)));
+#else
+       decoded = (void *)ptr.v;
+#endif
+       return decoded;
+}
+
 /* Returns the freelist pointer recorded at location ptr_addr. */
 static inline void *freelist_dereference(const struct kmem_cache *s,
                                         void *ptr_addr)
 {
-       return freelist_ptr(s, (void *)*(unsigned long *)(ptr_addr),
+       return freelist_ptr_decode(s, *(freeptr_t *)(ptr_addr),
                            (unsigned long)ptr_addr);
 }
 
 static inline void *get_freepointer(struct kmem_cache *s, void *object)
 {
        object = kasan_reset_tag(object);
-       return freelist_dereference(s, object + s->offset);
+       return freelist_dereference(s, (freeptr_t *)(object + s->offset));
 }
 
 #ifndef CONFIG_SLUB_TINY
@@ -421,15 +442,15 @@ __no_kmsan_checks
 static inline void *get_freepointer_safe(struct kmem_cache *s, void *object)
 {
        unsigned long freepointer_addr;
-       void *p;
+       freeptr_t p;
 
        if (!debug_pagealloc_enabled_static())
                return get_freepointer(s, object);
 
        object = kasan_reset_tag(object);
        freepointer_addr = (unsigned long)object + s->offset;
-       copy_from_kernel_nofault(&p, (void **)freepointer_addr, sizeof(p));
-       return freelist_ptr(s, p, freepointer_addr);
+       copy_from_kernel_nofault(&p, (freeptr_t *)freepointer_addr, sizeof(p));
+       return freelist_ptr_decode(s, p, freepointer_addr);
 }
 
 static inline void set_freepointer(struct kmem_cache *s, void *object, void *fp)
@@ -441,7 +462,7 @@ static inline void set_freepointer(struct kmem_cache *s, void *object, void *fp)
 #endif
 
        freeptr_addr = (unsigned long)kasan_reset_tag((void *)freeptr_addr);
-       *(void **)freeptr_addr = freelist_ptr(s, fp, freeptr_addr);
+       *(freeptr_t *)freeptr_addr = freelist_ptr_encode(s, fp, freeptr_addr);
 }
 
 /* Loop over all objects in a slab */