文章目录
背景
前情回顾
关于slab几个结构体的关系和初始化和内存分配和释放的逻辑请见:
[linux kernel]slub内存管理分析(0) 导读
[linux kernel]slub内存管理分析(1) 结构体
[linux kernel]slub内存管理分析(2) 初始化
[linux kernel]slub内存管理分析(2.5) slab重用
[linux kernel]slub内存管理分析(3) kmalloc
[linux kernel]slub内存管理分析(4) 细节操作以及安全加固
[linux kernel]slub内存管理分析(5) kfree
[linux kernel]slub内存管理分析(6) 销毁slab
描述方法约定
PS:为了方便描述,这里我们将一个用来切割分配内存的page 称为一个slab page,而struct kmem_cache
我们这里称为slab管理结构,它管理的真个slab 体系成为slab cache,struct kmem_cache_node
这里就叫node。单个堆块称为object或者堆块或内存对象。
MEMCG总览
省流总结
- 如果开启了memcg,那么kmalloc 申请相同大小的附带ACCOUNT关键字的flag的内存和不带ACCOUNT 关键字的内存可能是从不同slab 中申请的:
- 在kernel 版本小于5.9(不包括5.9) 的情况下,两者申请的内存属于不同slab
- 在kernel 版本属于5.9-5.14(包括5.9,不包括5.14)的情况下,两者申请的内存属于相同slab
- 在kernel 版本大于5.14(包括5.14)的情况下,两者申请的内存属于不同slab,但和第一种情况的实现不同
- 对于跨slab 的漏洞利用,可以考虑使用cross cache attack 或页面级堆风水来做。
其实memcg 本质上来说是一个统计功能,用于统计在cgroup 场景下申请内存的多少什么的,但它的实现方式可能会对我们的漏洞利用带来麻烦,下面简单从源码角度分析一下。
简介
memcg 全称memory cgroup,是cgroup 的一种,其实现了内存资源的隔离与限制功能。在内核中的整体实现是一个包括cgroup模块、内存管理模块等跨模块的实现。而为什么要在这里提到MEMCG呢,是因为我们分析slub 算法源码的主要目的还是为了做内核的漏洞利用。而memcg 的一个在内存管理模块的"记账(统计)"机制会给我们的漏洞利用带来一些麻烦,需要我们注意。
内核编译选项CONFIG_MEMCG 决定是否开启memcg 功能,但这个是默认开启的。
slub 相关 memcg机制
kernel 5.9 版本之前
结构体
struct kmem_cache {
··· ···
#ifdef CONFIG_MEMCG
struct memcg_cache_params memcg_params;//
/* For propagation, maximum size of a stored attr */
unsigned int max_attr_size;
#ifdef CONFIG_SYSFS
struct kset *memcg_kset;
#endif
#endif
··· ···
};
开启CONFIG_MEMCG 的情况下在slab 管理结构kmem_cache中会多出一个比较重要的成员memcg_params,属于memcg_cache_params 结构体,memcg_cache_params 用于记录memcg 在slab 相关的一些参数:
struct memcg_cache_params {
struct kmem_cache *root_cache;//指向根slab
union {
struct {
struct memcg_cache_array __rcu *memcg_caches;//存放若干子memcg slab 管理结构
struct list_head __root_caches_node;
struct list_head children;
bool dying;
};
··· ···
};
};
root_cache 指向根slab,也就是说可以通过根slab的memcg_params 找到子slab,也可以通过子slab 的root_cache 找到所属的跟slab。
memcg_caches 是一个memcg_cache_array 结构的成员,存放子memcg slab 的数组:
struct memcg_cache_array {
struct rcu_head rcu;
struct kmem_cache *entries[0];//子memcg slab 的数组
};
entries 是子memcg slab 的数组,通过memcg id 进行下标寻找。
所以可以理解为每一个根slab 管理结构(根slab 管理结构根据大小分类)都有一个对应的子memcg slab 列表。
初始化
主要的初始化函数是init_memcg_params:
mm\slab_common.c : init_memcg_params
static int init_memcg_params(struct kmem_cache *s,
struct kmem_cache *root_cache)
{
struct memcg_cache_array *arr;
if (root_cache) {
int ret = percpu_ref_init(&s->memcg_params.refcnt,
kmemcg_cache_shutdown,
0, GFP_KERNEL);
if (ret)
return ret;
s->memcg_params.root_cache = root_cache;
INIT_LIST_HEAD(&s->memcg_params.children_node);
INIT_LIST_HEAD(&s->memcg_params.kmem_caches_node);
return 0;
}
slab_init_memcg_params(s);
if (!memcg_nr_cache_ids)
return 0;
arr = kvzalloc(sizeof(struct memcg_cache_array) + //最多含有memcg_nr_cache_ids 个子slab
memcg_nr_cache_ids * sizeof(void *),
GFP_KERNEL);
if (!arr)
return -ENOMEM;
RCU_INIT_POINTER(s->memcg_params.memcg_caches, arr);
return 0;
}
除此之外,memcg 在slab 相关的初始化可以关注调用栈中多出的memcg 相关函数:
kmem_cache_init
- create_boot_cache
- slab_init_memcg_params //初始化memcg
- __kmem_cache_create
- ··· ···
- memcg_propagate_slab_attrs //初始化memcg
- bootstrap
- ··· ···
- slab_init_memcg_params//初始化memcg
- memcg_link_cache //初始化memcg
- create_kmalloc_caches
- new_kmalloc_cache
- create_kmalloc_cache
- ··· ···
- memcg_link_cache //初始化memcg
- create_kmalloc_cache
- new_kmalloc_cache
具体实现
我们这里以kernel 5.4 版本的源码为例,首先看关键的部分,经过之前的分析,我们知道,在kmalloc 的调用栈中有slab_alloc_node 函数,在其中调用的slab_pre_alloc_hook 函数我们没有分析,现在看slab_pre_alloc_hook 函数的实现:
static __always_inline void *slab_alloc_node