]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
slab: Avoid race on slab->obj_exts in alloc_slab_obj_exts
authorHao Ge <gehao@kylinos.cn>
Tue, 21 Oct 2025 01:03:53 +0000 (09:03 +0800)
committerVlastimil Babka <vbabka@suse.cz>
Tue, 21 Oct 2025 13:25:39 +0000 (15:25 +0200)
If two competing threads enter alloc_slab_obj_exts() and one of them
fails to allocate the object extension vector, it might override the
valid slab->obj_exts allocated by the other thread with
OBJEXTS_ALLOC_FAIL. This will cause the thread that lost this race and
expects a valid pointer to dereference a NULL pointer later on.

Update slab->obj_exts atomically using cmpxchg() to avoid
slab->obj_exts overrides by racing threads.

Thanks for Vlastimil and Suren's help with debugging.

Fixes: f7381b911640 ("slab: mark slab->obj_exts allocation failures unconditionally")
Cc: <stable@vger.kernel.org>
Suggested-by: Suren Baghdasaryan <surenb@google.com>
Signed-off-by: Hao Ge <gehao@kylinos.cn>
Reviewed-by: Harry Yoo <harry.yoo@oracle.com>
Reviewed-by: Suren Baghdasaryan <surenb@google.com>
Link: https://patch.msgid.link/20251021010353.1187193-1-hao.ge@linux.dev
Signed-off-by: Vlastimil Babka <vbabka@suse.cz>
mm/slub.c

index a8fcc7e6f25a94d07ec3a4574f9f681f6e9d5864..23d8f54e94866dd8dbda866419af709d4111eaf9 100644 (file)
--- a/mm/slub.c
+++ b/mm/slub.c
@@ -2054,7 +2054,7 @@ static inline void mark_objexts_empty(struct slabobj_ext *obj_exts)
 
 static inline void mark_failed_objexts_alloc(struct slab *slab)
 {
-       slab->obj_exts = OBJEXTS_ALLOC_FAIL;
+       cmpxchg(&slab->obj_exts, 0, OBJEXTS_ALLOC_FAIL);
 }
 
 static inline void handle_failed_objexts_alloc(unsigned long obj_exts,
@@ -2136,6 +2136,7 @@ int alloc_slab_obj_exts(struct slab *slab, struct kmem_cache *s,
 #ifdef CONFIG_MEMCG
        new_exts |= MEMCG_DATA_OBJEXTS;
 #endif
+retry:
        old_exts = READ_ONCE(slab->obj_exts);
        handle_failed_objexts_alloc(old_exts, vec, objects);
        if (new_slab) {
@@ -2145,8 +2146,7 @@ int alloc_slab_obj_exts(struct slab *slab, struct kmem_cache *s,
                 * be simply assigned.
                 */
                slab->obj_exts = new_exts;
-       } else if ((old_exts & ~OBJEXTS_FLAGS_MASK) ||
-                  cmpxchg(&slab->obj_exts, old_exts, new_exts) != old_exts) {
+       } else if (old_exts & ~OBJEXTS_FLAGS_MASK) {
                /*
                 * If the slab is already in use, somebody can allocate and
                 * assign slabobj_exts in parallel. In this case the existing
@@ -2158,6 +2158,9 @@ int alloc_slab_obj_exts(struct slab *slab, struct kmem_cache *s,
                else
                        kfree(vec);
                return 0;
+       } else if (cmpxchg(&slab->obj_exts, old_exts, new_exts) != old_exts) {
+               /* Retry if a racing thread changed slab->obj_exts from under us. */
+               goto retry;
        }
 
        if (allow_spin)