Hi everyone, I'm debugging some crashes in the KASAN quarantine, and I've noticed that for certain objects something which I assumed to be invariant does not hold. In particular, my understanding was that for an object returned by kmem_cache_zalloc(cache, gfp_flags) the value of virt_to_page(object)->slab_cache must be always equal to |cache|. However this isn't true for at least idr_free_cache in lib/idr.c If I apply the attached patch, build a x86_64 kernel with defconfig, and run the resulting kernel in QEMU, I get the following log: [ 0.007022] HERE: lib/idr.c:198 allocated ffff88001ddc8008 from idr_layer_cache [ 0.007478] idr_layer_cache: ffff88001dc0b6c0, slab_cache: ffff88001dc0b6c0 [ 0.007920] HERE: lib/idr.c:198 allocated ffff88001ddcf1a8 from idr_layer_cache [ 0.008002] idr_layer_cache: ffff88001dc0b6c0, slab_cache: (null) [ 0.008445] ------------[ cut here ]------------ [ 0.008791] kernel BUG at lib/idr.c:200! Am I misunderstanding the purpose of slab_cache in struct page, or is there really a bug in initializing it? Thanks, -- Alexander Potapenko Software Engineer Google Germany GmbH Erika-Mann-Straße, 33 80636 München Geschäftsführer: Matthew Scott Sucherman, Paul Terence Manicle Registergericht und -nummer: Hamburg, HRB 86891 Sitz der Gesellschaft: Hamburg
diff --git a/lib/idr.c b/lib/idr.c index 6098336..7767abe 100644 --- a/lib/idr.c +++ b/lib/idr.c @@ -30,6 +30,7 @@ #include <linux/idr.h> #include <linux/spinlock.h> #include <linux/percpu.h> +#include <linux/mm.h> #define MAX_IDR_SHIFT (sizeof(int) * 8 - 1) #define MAX_IDR_BIT (1U << MAX_IDR_SHIFT) @@ -194,6 +195,9 @@ static int __idr_pre_get(struct idr *idp, gfp_t gfp_mask) while (idp->id_free_cnt < MAX_IDR_FREE) { struct idr_layer *new; new = kmem_cache_zalloc(idr_layer_cache, gfp_mask); + pr_err("HERE: %s:%d allocated %p from idr_layer_cache\n", __FILE__, __LINE__, new); + pr_err("idr_layer_cache: %p, slab_cache: %p\n", idr_layer_cache, virt_to_page(new)->slab_cache); + BUG_ON(virt_to_page(new)->slab_cache != idr_layer_cache); if (new == NULL) return (0); move_to_free_list(idp, new);