搜索
您的当前位置:首页正文

slub debug(linux4.16.1)

来源:步旅网

开启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=0xee057680

Bytes 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=0x6b

FIX 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

因篇幅问题不能全部显示,请点此查看更多更全内容

Top