LPE exploit for 4.20rc1-rc4.
For educational/research purposes only. Use at your own risk.
syscall: int bpf(int cmd, union bpf_attr *attr, unsigned int size);
开启下列参数:
1. CONFIG_BPF
2. CONFIG_BPF_SYSCALL
3. CONFIG_DEBUG_INFO
a. 漏洞触发路径:bpf -> map_create -> find_and_alloc_map -> queue_stack_map_alloc。
b. 达到漏洞函数的条件。
1. 设置 attr->map_type 为 22。
2. 通过 map_alloc_check 的检查。
3. attr->map_ifindex 为空。
c. 漏洞函数 queue_stack_map_alloc 的漏洞:整数溢出 -> 分配堆空间。
size变量存在整数溢出。这个size在正常情况下应该是:struct bpf_queue_stac 的大小加上 value_size * size 相当于是map中每一项的大小(value_size)乘项数+1(attr->max_entries + 1)。
另外,elements后面是数据区域。
struct bpf_queue_stack {
struct bpf_map map;
raw_spinlock_t lock;
u32 head, tail;
u32 size; /* max_entries + 1 */
char elements[0] __aligned(8);
};
/* map is generic key/value storage optionally accessible by eBPF programs */
struct bpf_map_ops {
/* funcs callable from userspace (via syscall) */
int (*map_alloc_check)(union bpf_attr *attr);
struct bpf_map *(*map_alloc)(union bpf_attr *attr);
void (*map_release)(struct bpf_map *map, struct file *map_file);
...
};
struct bpf_map {
/* The first two cachelines with read-mostly members of which some
* are also accessed in fast-path (e.g. ops, max_entries).
*/
const struct bpf_map_ops *ops ____cacheline_aligned;
struct bpf_map *inner_map_meta;
#ifdef CONFIG_SECURITY
void *security;
#endif
enum bpf_map_type map_type;
u32 key_size;
u32 value_size;
u32 max_entries;
u32 map_flags;
u32 pages;
u32 id;
int numa_node;
u32 btf_key_type_id;
u32 btf_value_type_id;
struct btf *btf;
bool unpriv_array;
/* 55 bytes hole */
/* The 3rd and 4th cacheline with misc members to avoid false sharing
* particularly with refcounting.
*/
struct user_struct *user ____cacheline_aligned;
atomic_t refcnt;
atomic_t usercnt;
struct work_struct work;
char name[BPF_OBJ_NAME_LEN];
};
void *bpf_map_area_alloc(size_t size, int numa_node)
{
......
area = kmalloc_node(size, GFP_USER | flags, numa_node);
if (area != NULL)
return area;
......
}
bpf_map* queue_stack_map_alloc{
......
struct bpf_queue_stack *qs;
u32 size, value_size;
u64 queue_size, cost;
// bugs-> integer overflow. -> 0
size = attr->max_entries + 1;
value_size = attr->value_size;
queue_size = sizeof(*qs) + (u64) value_size * size;
......
qs = bpf_map_area_alloc(queue_size, numa_node); // numa_node存储着attr
......
// 用 bpf_attr 初始化 bpf_map。
bpf_map_init_from_attr(&qs->map, attr);
......
qs->size = size;
return &qs->map;
}
最后从 find_and_alloc_map(attr) 中返回我们的map(struct bpf_map *)。
---->
由于整数溢出,导致只分配了struct bpf_queue_stack 的空间,而没有分配map对应的空间。(个人理解这个bpf_queue_stack类似报文头部(负责管理的),而map对应的空间类似payload(elements数据)),但是map->max_entries = attr->max_entries。
e. 接下来,我们需要找到一块可以造成堆溢出的位置。我们将视角移出 map_create 函数。然而在BPF_MAP_UPDATE_ELEM 分支中我们对此对象进行更新等操作。 触发路径:bpf -> map_update_elem -> queue_stack_map_push_elem.
queue_stack_map_push_elem(struct bpf_map *map, void *value,
u64 flags){
......
dst = &qs->elements[qs->head * qs->map.value_size]; //qs->head代表当前是第几个entry
memcpy(dst, value, qs->map.value_size); //堆溢出位置
......
}
所以,对于0x100大小的map header+map payload, 即struct bpf_queue_stack,如果我们要拷贝的大小(value_size)大于(0x100- sizeof(bpf_map) - 0x10)的大小,就会造成堆溢出,而value_size由输入参数 attr 控制,从而能覆盖到下一个created map的bpf_map_ops虚表。
f. 漏洞利用
struct bpf_queue_stack {
struct bpf_map map;
raw_spinlock_t lock;
u32 head, tail;
u32 size; /* max_entries + 1 */
char elements[0] __aligned(8);
};
struct bpf_map {
/* The first two cachelines with read-mostly members of which some
* are also accessed in fast-path (e.g. ops, max_entries).
*/
const struct bpf_map_ops *ops ____cacheline_aligned;
......
}
- 看到其第一个成员就是虚表指针 ops ,换句话说,在我们kamlloc出的slab中的第一个位置就是指向当前map虚表的指针。
- 如果我们能通过上方的slab堆溢出,劫持下方slab的虚表指针,再fake相应的vtable,就可以实现一套内核的执行流劫持。
- 最终的攻击点我们选择在在fake vtable上伪造fake map_release函数指针,通过close对应的map id触发fake bpf_map_release完成执行流劫持。
- 使用了这样一条在内核中常用的栈迁移gadgets:xchg eax esp; ret;。将该gadget直接放到用户态mmap出来的fake ops table上。
- rop劫持cr4绕过smep,然后ret2usr 提升权限。
- 最后 swapgs; iretq 着陆用户态起shell。
但是这样是没法bypass smap的,因为ret指令涉及到向用户态取数据。
使用kernel rop和swapgs_restore_regs_and_return_to_usermode绕过kpti。
- kernel rop用于提权。
- swapgs_restore_regs_and_return_to_usermode 安全切换到用户态。
使用ret2dir绕过smap保护机制。
1. 未找到类似xchg rsp, reg
的指令,所以不能将内核栈迁移到PHYSMAP
区域。
2. 采用shellcode的方式,真正执行的时候发现PHYSMAP
区域不可执行。
等过一阵子,有时间再探索吧。。
Build for 4.20rc1-rc4.
$ make
To run:
由于当前版本没绕过kaslr,需要手动修改exp的gadget地址以匹配你的内核。
# id
uid=1000(tmp) gid=1000(tmp) groups=1000(tmp)
$ /exp
[+] sizeof(union bpf_attr): 0x48
[+] sizeof(struct bpf_map): 0xc0
[+] sizeof(struct bpf_queue_stack): 0x100
[+] bpf targetFd: 3
[+] map fd: 4
[+] set fake bpf_map_ops table.
[+] set kernel rop.
[+] fake_rsp: 8100e7f8
[+] start closing map fd
[*] welcome to root :-)
$ id
uid=0(root) gid=0(root)
Note: Just a toy. Improve the mitigation please.