在命令行选项中添加slub_debug=UFPZ,以使能相关检测功能,其含义如下:
(1)U:跟踪该slab内存的相关属性,如其创建时所属的cpu、进程、时间(2)F:开启sanity检查功能。该功能用于在slab的某些操作中(如slab分配和释放时),检测是否有对该slab的非法内存访问操作
(3)P:开启poisoning功能,它可被用于检测再次访问释放后的内存问题。当slab被释放后,slab object本身内存将被填充为特定值。因此一旦其释放后再次访问该slab,则通过检查slab内存中的值是否被修改,即可检测到
(4)Z:开启redzoning功能,它可被用于检测内存越界访问问题。其会在slab object的前面和后面分别添加red zone,并在其被分配和被释放时,分别用不同的值填充。当发生访问越界时,就可以通过检查redzone的值检测到
如果命令行添加成功后,通过如下命令可以看到
cat /proc/cmdline
BOOT_IMAGE=/boot/vmlinuz-3.13.0-24-generic root=UUID=10426707-be58-4d00-b49b-f1fe35345d57 ro find_preseed=/preseed.cfg auto noprompt priority=critical locale=en_US quiet
开启slub debug后,obj被分配出来的时候,整个obj会被初始化为特定的数值
以下的都是假定开启了slub deubg
当内存被分配出来的时候全部的区域都被初始化为了POISON_INUSE(0x5a)
#define SLUB_RED_INACTIVE 0xbb
#define SLUB_RED_ACTIVE 0xcc/* ...and for poisoning */
#define POISON_INUSE 0x5a /* for use-uninitialised poisoning */
#define POISON_FREE 0x6b /* for use-after-free poisoning */
#define POISON_END 0xa5 /* end-byte of poisoning */
static struct page *allocate_slab(struct kmem_cache *s, gfp_t flags, int node)
{
............................if (unlikely(s->flags & SLAB_POISON))memset(start, POISON_INUSE, PAGE_SIZE << order);kasan_poison_slab(page);shuffle = shuffle_freelist(s, page);if (!shuffle) {for_each_object_idx(p, idx, s, start, page->objects) {setup_object(s, page, p);if (likely(idx < page->objects))set_freepointer(s, p, p + s->size);elseset_freepointer(s, p, NULL);}page->freelist = fixup_red_left(s, start);}..................return page;
}
setup_object-> setup_object_debug
static void setup_object_debug(struct kmem_cache *s, struct page *page,void *object)
{if (!(s->flags & (SLAB_STORE_USER|SLAB_RED_ZONE|__OBJECT_POISON)))return;init_object(s, object, SLUB_RED_INACTIVE);init_tracking(s, object);
}
static void init_object(struct kmem_cache *s, void *object, u8 val)
{u8 *p = object;if (s->flags & SLAB_RED_ZONE)memset(p - s->red_left_pad, val, s->red_left_pad);if (s->flags & __OBJECT_POISON) {memset(p, POISON_FREE, s->object_size - 1);p[s->object_size - 1] = POISON_END;}if (s->flags & SLAB_RED_ZONE)memset(p + s->object_size, val, s->inuse - s->object_size);
}
slub分配器将内存刚开始划分出来的时候长这个样子,如下图所示:

red left pad用于检查向左写越界的问题。red zone用于检查向右检查写越界的问题。alloc/free track用于根据释放和申请的调用信息。
slub debug检测时机:
1、申请时
___slab_alloc->alloc_debug_processing
可以看到在申请的完了之后先会进行检查alloc_consistency_checks。
检查完毕了,在重新初始化整个块。同时,这个时候red zone区域的值就从SLUB_RED_INACTIVE被修改为了SLUB_RED_ACTIVE(init_object(s, object, SLUB_RED_ACTIVE);)
static noinline int alloc_debug_processing(struct kmem_cache *s,struct page *page,void *object, unsigned long addr)
{if (s->flags & SLAB_CONSISTENCY_CHECKS) {if (!alloc_consistency_checks(s, page, object, addr))goto bad;}/* Success perform special debug activities for allocs */if (s->flags & SLAB_STORE_USER)set_track(s, object, TRACK_ALLOC, addr);trace(s, page, object, 1);init_object(s, object, SLUB_RED_ACTIVE);return 1;bad:if (PageSlab(page)) {/** If this is a slab page then lets do the best we can* to avoid issues in the future. Marking all objects* as used avoids touching the remaining objects.*/slab_fix(s, "Marking all objects used");page->inuse = page->objects;page->freelist = NULL;}return 0;
}
static inline int alloc_consistency_checks(struct kmem_cache *s,struct page *page,void *object, unsigned long addr)
{if (!check_slab(s, page))return 0;if (!check_valid_pointer(s, page, object)) {object_err(s, page, object, "Freelist Pointer check fails");return 0;}/* 检测对象里面的内存布局的值是否被改变这些 */if (!check_object(s, page, object, SLUB_RED_INACTIVE))return 0;return 1;
}
检查各个区域的值,是不是我们设定的固定值。如果不是,将错误信息打印出来,并将错误的值恢复回来。
static int check_object(struct kmem_cache *s, struct page *page,void *object, u8 val)
{u8 *p = object;u8 *endobject = object + s->object_size;if (s->flags & SLAB_RED_ZONE) {/* 检查left padding */if (!check_bytes_and_report(s, page, object, "Redzone",object - s->red_left_pad, val, s->red_left_pad))return 0;if (!check_bytes_and_report(s, page, object, "Redzone",endobject, val, s->inuse - s->object_size))return 0;} else {if ((s->flags & SLAB_POISON) && s->object_size < s->inuse) {check_bytes_and_report(s, page, p, "Alignment padding",endobject, POISON_INUSE,s->inuse - s->object_size);}}if (s->flags & SLAB_POISON) {if (val != SLUB_RED_ACTIVE && (s->flags & __OBJECT_POISON) &&(!check_bytes_and_report(s, page, p, "Poison", p,POISON_FREE, s->object_size - 1) ||!check_bytes_and_report(s, page, p, "Poison",p + s->object_size - 1, POISON_END, 1)))return 0;/** check_pad_bytes cleans up on its own.*/check_pad_bytes(s, page, p);}if (!s->offset && val == SLUB_RED_ACTIVE)/** Object and freepointer overlap. Cannot check* freepointer while object is allocated.*/return 1;/* Check free pointer validity */if (!check_valid_pointer(s, page, get_freepointer(s, p))) {object_err(s, page, p, "Freepointer corrupt");/** No choice but to zap it and thus lose the remainder* of the free objects in this slab. May cause* another error because the object count is now wrong.*/set_freepointer(s, p, NULL);return 0;}return 1;
}
2、释放时
void kfree(const void *x)
{struct page *page;void *object = (void *)x;trace_kfree(_RET_IP_, x);if (unlikely(ZERO_OR_NULL_PTR(x)))return;page = virt_to_head_page(x);if (unlikely(!PageSlab(page))) {BUG_ON(!PageCompound(page));kfree_hook(object);__free_pages(page, compound_order(page));return;}slab_free(page->slab_cache, page, object, NULL, 1, _RET_IP_);
}
static void __slab_free(struct kmem_cache *s, struct page *page,void *head, void *tail, int cnt,unsigned long addr){void *prior;int was_frozen;struct page new;unsigned long counters;struct kmem_cache_node *n = NULL;unsigned long uninitialized_var(flags);stat(s, FREE_SLOWPATH);if (kmem_cache_debug(s) &&!free_debug_processing(s, page, head, tail, cnt, addr))return;...........................
}
slub分配器在释放obj的时候,在这里进行检查。同时释放的时候会重新初始化obj的魔数字。
static noinline int free_debug_processing(struct kmem_cache *s, struct page *page,void *head, void *tail, int bulk_cnt,unsigned long addr)
{
..............................if (s->flags & SLAB_CONSISTENCY_CHECKS) {if (!free_consistency_checks(s, page, object, addr))goto out;}if (s->flags & SLAB_STORE_USER)set_track(s, object, TRACK_FREE, addr);trace(s, page, object, 0);/* Freepointer not overwritten by init_object(), SLAB_POISON moved it */init_object(s, object, SLUB_RED_INACTIVE);
................................return ret;
}
static inline int free_consistency_checks(struct kmem_cache *s,struct page *page, void *object, unsigned long addr)
{if (!check_valid_pointer(s, page, object)) {//检查obj指针是否正确slab_err(s, page, "Invalid object pointer 0x%p", object);return 0;}if (on_freelist(s, page, object)) {object_err(s, page, object, "Object already free");return 0;}if (!check_object(s, page, object, SLUB_RED_ACTIVE))return 0;if (unlikely(s != page->slab_cache)) {if (!PageSlab(page)) {slab_err(s, page, "Attempt to free object(0x%p) outside of slab",object);} else if (!page->slab_cache) {pr_err("SLUB <none>: no slab for object 0x%p.\n",object);dump_stack();} elseobject_err(s, page, object,"page slab pointer corrupt.");return 0;}return 1;
}
总结
slub deubg是利用在obj中填充魔数字,在释放和申请的时候都去检查魔数字是否被改变,进而检测到是否有越界改写对象等问题。
内存越界:可以看到obj的左右都填充了固定的数字。如果写越界了,我们检查左右的魔数字,就能被检测出来;
访问已经释放的内存:obj被释放的时候,整个obj的内存布局会被重新初始化,如果我们去访问已经释放的内存,当该obj被再次分配的时候,就能被检测出来;
重复释放:当obj被释放的时候,会去freelist上面找该对象。如果有找到,稍后会去挂到freelist上面。如果找到了,这个就出现了重复释放的问题。
1、但是可以看到slub debug只能在申请和是否的时候去检查。其他时候检查不到。因此该方法具有迟滞性,不能在出问题的时候,就检测出来。
2、另外开启slub debug会让申请的内存块变大,增加内存开销。
3、只能检查到通过slub 分配器分配的内存块。对于全局变量或者栈的问题无法被检测到。
如何主动去触发slub debug检查呢?
slub debug在发生slab内存非法操作时,不能立即检测出来。而需要手动通过sysfs的validate属性触发检测操作。如要检测128字节kmalloc对象,则可使用以下命令:
echo 1 > /sys/kernel/slab/kmalloc-128/validate
好像是去调用这个函数,去对kmalloc-128池子里面的obj进行检查
static int validate_slab(struct kmem_cache *s, struct page *page,unsigned long *map)
{void *p;void *addr = page_address(page);if (!check_slab(s, page) ||!on_freelist(s, page, NULL))return 0;/* Now we know that a valid freelist exists */bitmap_zero(map, page->objects);get_map(s, page, map);for_each_object(p, s, addr, page->objects) {if (test_bit(slab_index(p, s, addr), map))if (!check_object(s, page, p, SLUB_RED_INACTIVE))return 0;}for_each_object(p, s, addr, page->objects)if (!test_bit(slab_index(p, s, addr), map))if (!check_object(s, page, p, SLUB_RED_ACTIVE))return 0;return 1;
}
样例
qemu-system-arm -M vexpress-a9 -m 1024M -kernel arch/arm/boot/zImage -append "rdinit=/linuxrc console=ttyAMA0 loglevel=8 slub_debug" -dtb arch/arm/boot/dts/vexpress-v2p-ca9.dtb -nographic
马上触发slab检测
/ # echo 1 > /sys/kernel/slab/kmalloc-64/validate 
从kmalloc-64里面申请的内存,释放之后再次去访问
struct obj{int a;int b;char c;short d;int arr[8];
};
void myCallback(void)
{struct obj *p = kmalloc(sizeof(struct obj), GFP_ATOMIC);if (!p)return;kfree(p);p->c = 0x88;
}
用的linux 3.16代码进行演示。。。(没有left red zone)
=============================================================================
BUG kmalloc-64 (Not tainted): Poison overwritten
-----------------------------------------------------------------------------Disabling lock debugging due to kernel taint
INFO: 0xee057f08-0xee057f08. First byte 0x88 instead of 0x6b
INFO: Allocated in myCallback+0xb8/0xf8 age=1021 cpu=0 pid=0
INFO: Freed in myCallback+0xc4/0xf8 age=1021 cpu=0 pid=0
INFO: Slab 0xef5baae0 objects=32 used=32 fp=0x (null) flags=0x0080
INFO: Object 0xee057f00 @offset=3840 fp=0xee057680Bytes b4 ee057ef0: 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a ZZZZZZZZZZZZZZZZ
Object ee057f00: 6b 6b 6b 6b 6b 6b 6b 6b 88 6b 6b 6b 6b 6b 6b 6b kkkkkkkk.kkkkkkk
Object ee057f10: 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b kkkkkkkkkkkkkkkk
Object ee057f20: 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b kkkkkkkkkkkkkkkk
Object ee057f30: 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b a5 kkkkkkkkkkkkkkk.
Redzone ee057f40: bb bb bb bb ....
Padding ee057f68: 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a ZZZZZZZZZZZZZZZZ
Padding ee057f78: 5a 5a 5a 5a 5a 5a 5a 5a ZZZZZZZZ
CPU: 0 PID: 758 Comm: linuxrc Tainted: G B 3.16.0 #144
[<c0013f8c>] (unwind_backtrace) from [<c0010f64>] (show_stack+0x10/0x14)
[<c0010f64>] (show_stack) from [<c044ef5c>] (dump_stack+0x74/0x90)
[<c044ef5c>] (dump_stack) from [<c00c3844>] (check_bytes_and_report+0xb8/0x100)
[<c00c3844>] (check_bytes_and_report) from [<c00c3a14>] (check_object+0x188/0x21c)
[<c00c3a14>] (check_object) from [<c044e094>] (alloc_debug_processing+0x134/0x158)
[<c044e094>] (alloc_debug_processing) from [<c044e390>] (__slab_alloc.constprop.53+0x2d8/0x2f4)
[<c044e390>] (__slab_alloc.constprop.53) from [<c00c4d08>] (kmem_cache_alloc+0xb8/0xe4)
[<c00c4d08>] (kmem_cache_alloc) from [<c03968cc>] (sock_alloc_inode+0x2c/0x98)
[<c03968cc>] (sock_alloc_inode) from [<c00e055c>] (alloc_inode+0x1c/0x9c)
[<c00e055c>] (alloc_inode) from [<c00e2010>] (new_inode_pseudo+0x8/0x4c)
[<c00e2010>] (new_inode_pseudo) from [<c039707c>] (sock_alloc+0x14/0x9c)
[<c039707c>] (sock_alloc) from [<c0397efc>] (__sock_create+0x44/0x198)
[<c0397efc>] (__sock_create) from [<c03980d0>] (sock_create+0x44/0x4c)
[<c03980d0>] (sock_create) from [<c0398594>] (SyS_socket+0x2c/0xac)
[<c0398594>] (SyS_socket) from [<c000e420>] (ret_fast_syscall+0x0/0x30)
FIX kmalloc-64: Restoring 0xee057f08-0xee057f08=0x6bFIX kmalloc-64: Marking all objects used
像这种释放后再访问的问题
1、首先我们知道申请的对象是64字节的
BUG kmalloc-64 (Not tainted): Poison overwritten
2、我们知道是该对象的第9字节被改写
Object ee057f00: 6b 6b 6b 6b 6b 6b 6b 6b 88 6b 6b 6b 6b 6b 6b 6b kkkkkkkk.kkkkkkk
3、该对象申请地方myCallback
INFO: Allocated in myCallback+0xb8/0xf8 age=1021 cpu=0 pid=0
INFO: Freed in myCallback+0xc4/0xf8 age=1021 cpu=0 pid=0
在我这个例子里面知道是申请的 struct obj,再根据第9字节,可以知道修改的是obj的成员c


















