Linux kernel 4.20 BPF 整数溢出-堆溢出漏洞及其利用
近日,我发现Linux内核BPF模块中一个质量较高linux内核堆溢出漏洞(已向linux社区提交漏洞补丁)。
我们可以100%稳定触发这个漏洞,并且可以利用它来进行本地提权获得root权限。这篇文章主要分析漏洞的成因以及基本漏洞利用方法。
漏洞模块
BPF(Berkeley Packet Filter)模块[1]是用于支持用户态自定义包过滤方法的内核模块,目前各大Linux发行版都默认开启了bpf支持,关于bpf官方文档的介绍如下:
Linux Socket Filtering (LSF) is derived from the Berkeley Packet Filter.Though there are some distinct differences between the BSD and LinuxKernel filtering, but when we speak of BPF or LSF in Linux context, wemean the very same mechanism of filtering in the Linux kernel.
BPF allows a user-space program to attach a filter onto any socket andallow or disallow certain types of data to come through the socket. LSFfollows exactly the same filter code structure as BSD’s BPF, so referringto the BSD bpf.4 manpage is very helpful in creating filters.
漏洞分析
引入时间
该漏洞位于kernel/bpf/queue_stack_maps.c文件中,于2018-10-19在commit
f1a2e44a3aeccb3ff18d3ccc0b0203e70b95bd92中为bpf模块引入了名为queue/stack map的新功能。
Queue/stack maps implement a FIFO/LIFO data storage for ebpf programs.These maps support peek, pop and push operations that are exposed to eBPFprograms through the new bpf_map[peek/pop/push] helpers. Those operationsare exposed to userspace applications through the already existingsyscalls in the following way:
BPF_MAP_LOOKUP_ELEM -> peek
BPF_MAP_LOOKUP_AND_DELETE_ELEM -> pop
BPF_MAP_UPDATE_ELEM -> push
影响范围
该漏洞影响Linux Kernel 4.20rc1-4.20rc4,主要Linux发行版并不受其影响。
整数溢出
这枚漏洞的根本成因是在创建queue_stack_map时发生的整数溢出导致申请出的对象偏小,函数调用链如下:
__x64_sys_bpf()
->map_create()
->find_and_alloc_map()
->queue_stack_map_alloc()
漏洞函数queue_stack_map_alloc如下:
static struct bpf_map *queue_stack_map_alloc(union bpf_attr *attr)
{
int ret, numa_node = bpf_map_attr_numa_node(attr);
struct bpf_queue_stack *qs;
u32 size, value_size;
u64 queue_size, cost;
size = attr->max_entries + 1; //整数溢出漏洞点
value_size = attr->value_size;
queue_size = sizeof(*qs) + (u64) value_size * size;
cost = queue_size;
if (cost >= U32_MAX - PAGE_SIZE)
return ERR_PTR(-E2BIG);
cost = round_up(cost, PAGE_SIZE) >> PAGE_SHIFT;
ret = bpf_map_precharge_memlock(cost);
if (ret 0)
return ERR_PTR(ret);
qs = bpf_map_area_alloc(queue_size, numa_node); //申请过小的堆块
if (!qs)
return ERR_PTR(-ENOMEM);
memset(qs, 0, sizeof(*qs));
bpf_map_init_from_attr(&qs->map, attr);
qs->map.pages = cost;
qs->size = size;
raw_spin_lock_init(&qs->lock);
return &qs->map;
}
上述函数在计算size时,使用了用户传入的参数attr->max_entries。由于size的类型是u32, 如果attr->max_entries=0xffffffff,那么attr->max_entries+1时就会发生整数溢出使得size=0。后续在bpf_map_area_alloc函数中会申请一块大小为queue_size的堆内存,queue_size的大小由以下表达式计算:
sizeof(*qs) + (u64) value_size * size; // value_size 和 size我们都可控
可以看出,由于size在之前的计算过程中发生整数溢出变成了0,分配的大小只有sizeof(*qs)。
堆溢出
之后,可以在另一个bpf系统调用update这块map过程中,向这块过小的queue stack区域拷入数据,导致内核堆溢出。调用链如下:
__x64_sys_bpf()
->map_update_elem()
->queue_stack_map_push_elem()//堆溢出
其中发生溢出的是queue_stack_map_hash_elem()函数中的memcpy调用。由源码可知,memcpy的dst就是上面申请的queue stack区域,src是由用户态拷入的大小为qs->map.value_size的buffer, 拷贝长度由创建queue_stack时用户提供的attr.value_size决定,因此拷贝长度用户可控。
queue_stack_map_push_elem()函数如下:
static int queue_stack_map_push_elem(struct bpf_map *map, void *value,
u64 flags)
{
struct bpf_queue_stack *qs = bpf_queue_stack(map);
unsigned long irq_flags;
int err = 0;
void *dst;
bool replace = (flags & BPF_EXIST);
if (flags & BPF_NOEXIST || flags > BPF_EXIST)
return -EINVAL;
近日,我发现Linux内核BPF模块中一个质量较高linux内核堆溢出漏洞(已向linux社区提交漏洞补丁)。
我们可以100%稳定触发这个漏洞,并且可以利用它来进行本地提权获得root权限。这篇文章主要分析漏洞的成因以及基本漏洞利用方法。
漏洞模块
BPF(Berkeley Packet Filter)模块[1]是用于支持用户态自定义包过滤方法的内核模块,目前各大Linux发行版都默认开启了bpf支持,关于bpf官方文档的介绍如下:
Linux Socket Filtering (LSF) is derived from the Berkeley Packet Filter.Though there are some distinct differences between the BSD and LinuxKernel filtering, but when we speak of BPF or LSF in Linux context, wemean the very same mechanism of filtering in the Linux kernel.
BPF allows a user-space program to attach a filter onto any socket andallow or disallow certain types of data to come through the socket. LSFfollows exactly the same filter code structure as BSD’s BPF, so referringto the BSD bpf.4 manpage is very helpful in creating filters.
无奈人生安全网
漏洞分析
引入时间
该漏洞位于kernel/bpf/queue_stack_maps.c文件中,于2018-10-19在commit
f1a2e44a3aeccb3ff18d3ccc0b0203e70b95bd92中为bpf模块引入了名为queue/stack map的新功能。
Queue/stack maps implement a FIFO/LIFO data storage for ebpf programs.These maps support peek, pop and push operations that are exposed to eBPFprograms through the new bpf_map[peek/pop/push] helpers. Those operationsare exposed to userspace applications through the already existingsyscalls in the following way:
BPF_MAP_LOOKUP_ELEM -> peek
BPF_MAP_LOOKUP_AND_DELETE_ELEM -> pop
BPF_MAP_UPDATE_ELEM -> push
影响范围
该漏洞影响Linux Kernel 4.20rc1-4.20rc4,主要Linux发行版并不受其影响。
整数溢出
这枚漏洞的根本成因是在创建queue_stack_map时发生的整数溢出导致申请出的对象偏小,函数调用链如下:
__x64_sys_bpf()
->map_create()
->find_and_alloc_map()
->queue_stack_map_alloc()
漏洞函数queue_stack_map_alloc如下:
www.wnhack.com
static struct bpf_map *queue_stack_map_alloc(union bpf_attr *attr)
{
int ret, numa_node = bpf_map_attr_numa_node(attr);
struct bpf_queue_stack *qs;
u32 size, value_size;
u64 queue_size, cost;
size = attr->max_entries + 1; //整数溢出漏洞点
value_size = attr->value_size;
queue_size = sizeof(*qs) + (u64) value_size * size;
cost = queue_size;
if (cost >= U32_MAX - PAGE_SIZE)
return ERR_PTR(-E2BIG);
cost = round_up(cost, PAGE_SIZE) >> PAGE_SHIFT;
ret = bpf_map_precharge_memlock(cost);
if (ret 0)
return ERR_PTR(ret);
qs = bpf_map_area_alloc(queue_size, numa_node); //申请过小的堆块
if (!qs) www.wnhack.com
return ERR_PTR(-ENOMEM);
memset(qs, 0, sizeof(*qs));
bpf_map_init_from_attr(&qs->map, attr);
qs->map.pages = cost;
qs->size = size;
raw_spin_lock_init(&qs->lock);
return &qs->map;
}
上述函数在计算size时,使用了用户传入的参数attr->max_entries。由于size的类型是u32, 如果attr->max_entries=0xffffffff,那么attr->max_entries+1时就会发生整数溢出使得size=0。后续在bpf_map_area_alloc函数中会申请一块大小为queue_size的堆内存,queue_size的大小由以下表达式计算:
sizeof(*qs) + (u64) value_size * size; // value_size 和 size我们都可控
可以看出,由于size在之前的计算过程中发生整数溢出变成了0,分配的大小只有sizeof(*qs)。
堆溢出
之后,可以在另一个bpf系统调用update这块map过程中,向这块过小的queue stack区域拷入数据,导致内核堆溢出。调用链如下:
__x64_sys_bpf()
->map_update_elem() www.wnhack.com
->queue_stack_map_push_elem()//堆溢出
其中发生溢出的是queue_stack_map_hash_elem()函数中的memcpy调用。由源码可知,memcpy的dst就是上面申请的queue stack区域,src是由用户态拷入的大小为qs->map.value_size的buffer, 拷贝长度由创建queue_stack时用户提供的attr.value_size决定,因此拷贝长度用户可控。
queue_stack_map_push_elem()函数如下:
static int queue_stack_map_push_elem(struct bpf_map *map, void *value,
u64 flags)
{
struct bpf_queue_stack *qs = bpf_queue_stack(map);
unsigned long irq_flags;
int err = 0;
void *dst;
bool replace = (flags & BPF_EXIST);
if (flags & BPF_NOEXIST || flags > BPF_EXIST)
return -EINVAL;
内容来自无奈安全网