[漏洞分析]CVE-2022-0185linux内核提权(逃逸)CVE-2022-0185 linux 内核提权(逃逸)
⽂章⽬录
github地址:
漏洞简介
漏洞编号: CVE-2022-0185
漏洞产品: linux kernel - fsconfig syscall
影响版本: linux kernel 5.1-rc1 ~
漏洞危害: 在cap_sys_admin 权限基础上进⾏提权或容器逃逸
源码获取: apt source linux-image-unsigned-5.11.0-44-generic
环境搭建
调试环境
5.X 内核编译环境docker :
漏洞分析docker:
准备了两个内核⼀个发⾏版,⼀个⾃⼰编译版
⼀个下载的发⾏版内核5.11.0-44 ⽤于验证调试分析exp(发⾏版内核不会崩溃)
⼀个编译的带符号的5.13 ⽤于有符号调试poc
安装qemu、gdb、gdb-peda 等
漏洞相关在 /root/cve-2022-0185
boot_exp.sh ⽤于启动exp 验证调试环境,发⾏版5.11.0-44⽆符号内核
boot_poc.sh ⽤于启动poc 验证环境,可以跑崩内核,但⽆法跑exp,⾃编译5.13 有符号内核
exp ⽬录, 源码(作者: BitsByWill),直接编译exploit_fuse即可。
ubuntu20.04 验证环境
ubuntu 20.04虚拟机exp 运⾏环境,
准备ubuntu20.04 虚拟机,然后更换内核:
apt-get install linux-image-5.11.0-44-generic
grep menuentry /boot/grub/grub.cfg
vim /etc/default/grub
#修改 GRUB_DEFAULT 选项为上⾯结果中想要启动内核的下标
update-grub
#如果不⽣效的话则直接进⼊/boot ⽬录将之前的内核相关⽂件(带之前内核编号的⽂件)全部删掉,然后启动时候报不到内核,然后⼿动选择内核启动也可以#编译exp
make fuse
./exploit
提权效果
漏洞原理
漏洞发⽣的系统调⽤是fsconfig 中的 FSCONFIG_SET_STRING 操作选项,该系统调⽤⽤于对已经打开的⽂件系统上下⽂进⾏⼀些配置,需要的前提条件是具备CAP_SYS_ADMIN cap权限:
fsopen的主要⽬的就是创建⼀个⽂件系统上下⽂,然后把它和⼀个⽂件描述符挂钩,返回⽂件描述符。fsopen后⾯就是fsconfig,从字⾯意思应该可以猜到,我们上⾯通过fsopen创建了⼀个⽂件系统上下⽂,下⾯的fsconfig可能就是⽤来配置⽂件系统上下⽂⾥的内容的。事实上fsconfig确实主要是做这个配置⼯作的,除了⽂件系统上下⽂,同时它还⽀持其它的⼯作。
漏洞发⽣点
⾸先漏洞出现在 legacy_parse_param 函数中:
linux-5.11\fs\fs_context.c : 502 : legacy_parse_param
static int legacy_parse_param(struct fs_context *fc,struct fs_parameter *param)
{
struct legacy_fs_context *ctx = fc->fs_private;
unsigned int size = ctx->data_size;
size_t len =0;
··· ···
··· ···
switch(param->type){
case fs_value_is_string:
len =1+ param->size;
fallthrough;
··· ···
}
if(len > PAGE_SIZE -2- size)//此处边界检查有问题
return invalf(fc,"VFS: Legacy: Cumulative options too large");
if(strchr(param->key,',')||
(param->type == fs_value_is_string &&
memchr(param->string,',', param->size)))
return invalf(fc,"VFS: Legacy: Option '%s' contained comma",
param->key);
if(!ctx->legacy_data){
ctx->legacy_data =kmalloc(PAGE_SIZE, GFP_KERNEL);//在第⼀次时会分配⼀页⼤⼩
if(!ctx->legacy_data)
return-ENOMEM;
}
ctx->legacy_data[size++]=',';
len =strlen(param->key);
memcpy(ctx->legacy_data + size, param->key, len);
size += len;
if(param->type == fs_value_is_string){
ctx->legacy_data[size++]='=';
memcpy(ctx->legacy_data + size, param->string, param->size);//拷贝,可能越界
size += param->size;
}
ctx->legacy_data[size]='\0';
ctx->data_size = size;
ctx->param_type = LEGACY_FS_INDIVIDUAL_PARAMS;
return0;
}
关键在于后⾯的memcpy,会将我们传⼊的param->string 拷贝到ctx->legacy_data 之中。⽽判断是否拷贝越界就在前⾯的(len > PAGE_SIZE -2 - size) 判断,这⾥判断是有问题的,判断类型是size_t 也就是unsigned int,如果size > PAGE_SIZE - 2 则会发⽣整数溢出反转,造成len < PAGE_SIZE - 2 - size ,进⽽判断通过,后⾯拷贝的时候size 是⼤于 PAGE_SIZE - 2的,造成拷贝越界。
⽤到的⼀些数据结构:
struct fs_context {
const struct fs_context_operations *ops;
struct mutex uapi_mutex;/* Userspace access mutex */
struct file_system_type *fs_type;
void*fs_private;/* The filesystem's context */
void*sget_key;
struct dentry *root;/* The root and superblock */
struct user_namespace *user_ns;/* The user namespace for this mount */ struct net *net_ns;/* The network namespace for this mount */
const struct cred *cred;/* The mounter's credentials */
struct p_log log;/* Logging buffer */
const char*source;/* The source name (eg. dev path) */
void*security;/* Linux S&M options */
void*s_fs_info;/* Proposed s_fs_info */
unsigned int sb_flags;/* Proposed superblock flags (SB_*) */
unsigned int sb_flags_mask;/* Superblock flags that were changed */ unsigned int s_iflags;/* OR'd with sb->s_iflags */cve漏洞库
unsigned int lsm_flags;/* Information flags from the fs to the LSM */
enum fs_context_purpose purpose:8;
enum fs_context_phase phase:8;/* The phase the context is in */
bool need_free:1;/* Need to call ops->free() */
bool global:1;/* Goes into &init_user_ns */
bool oldapi:1;/* Coming from mount(2) */
};
struct legacy_fs_context {
char*legacy_data;/* Data page for legacy filesystems */
size_t data_size;
enum legacy_fs_param param_type;
};
struct fs_parameter {
const char*key;/* Parameter name */
enum fs_value_type type:8;/* The type of value here */
union{
char*string;
void*blob;
struct filename *name;
struct file *file;
};
size_t size;
int dirfd;
};
调⽤路径
下⾯分析⼀下函数调⽤栈,⾸先⼊⼝肯定是 fsconfig 系统调⽤:linux-5.11\fs\fsopen.c : 314 : SYSCALL_DEFINE5(fsconfig,…
SYSCALL_DEFINE5(fsconfig,
int, fd,
unsigned int, cmd,
const char __user *, _key,
const void __user *, _value,
int, aux)
{
struct fs_context *fc;
struct fd f;
int ret;
int lookup_flags =0;
struct fs_parameter param ={
.type = fs_value_is_undefined,
};
··· ···
f =fdget(fd);
if(!f.file)
return-EBADF;
ret =-EINVAL;
if(f.file->f_op !=&fscontext_fops)
goto out_f;
fc = f.file->private_data;//设置fc
··· ···
switch(cmd){
··· ···
case FSCONFIG_SET_STRING:
//初始化结构体中的联合体中的string成员为⽤户传⼊的字符串
param.string =strndup_user(_value,256);
if(IS_ERR(param.string)){
ret =PTR_ERR(param.string);
goto out_key;
}
param.size =strlen(param.string);//设置size
break;
··· ···
··· ···
}
ret =mutex_lock_interruptible(&fc->uapi_mutex);
if(ret ==0){
ret =vfs_fsconfig_locked(fc, cmd,¶m);
mutex_unlock(&fc->uapi_mutex);
}
··· ···
··· ···
}
在fsconfig 系统调⽤的⼊⼝中,先根据⽂件描述符fd 初始化⽂件系统上下⽂结构体fc,然后根据⽤户传⼊的参数设置param 结构体,该结构体变量就是后⾯在漏洞发⽣函数legacy_parse_param 中使⽤的param。接下来进⼊vfs_fsconfig_locked函数:
linux-5.11\fs\fsopen.c : 216 : vfs_fsconfig_locked
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论