开启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);
else
set_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);
}
下图的是linux3.16的slub deubg可以看到它相比于4. 16就没有left padding这个东西
slub分配器将内存刚开始划分出来的时候长这个样子,如下图所示:
2、上面的red zone有0xbb和0xcc两种情况。bb表示对象被释放了是空闲的,0xcc表示正在被使用
0x921a5e80往前是0xcc这个是left pad,然后开始是obj内容(这个obj前面一部分被写了,后面仍然保留6b,可能是用不掉这么多把)接下来就是最后一字节的0xa5。接下来4自己是fp,然后是32字节的alloc/free trace,+ padding(0x5a)
详细的分析可看文章最后部分
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();
} else
object_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
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
因篇幅问题不能全部显示,请点此查看更多更全内容