]> git.ipfire.org Git - thirdparty/kernel/stable.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)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Wed, 29 Oct 2025 13:08:56 +0000 (14:08 +0100)
commit 6ed8bfd24ce1cb31742b09a3eb557cd008533eec upstream.

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>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
mm/slub.c

index 24e65d7048ba237e79c9c99d861ddb189a1995a4..c4e05a6c7c861183b609a17440ac1b8c16f6e0a4 100644 (file)
--- a/mm/slub.c
+++ b/mm/slub.c
@@ -1943,7 +1943,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,
@@ -2008,6 +2008,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) {
@@ -2017,8 +2018,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
@@ -2027,6 +2027,9 @@ int alloc_slab_obj_exts(struct slab *slab, struct kmem_cache *s,
                mark_objexts_empty(vec);
                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;
        }
 
        kmemleak_not_leak(vec);