漏洞分析
函数调用链
__x64_sys_fsconfig()->vfs_fsconfig_locked()->vfs_parse_fs_param()->legacy_parse_param()
__x64_sys_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,
};
if (fd < 0)
return -EINVAL;
switch (cmd) {
...
case FSCONFIG_SET_STRING:
if (!_key || !_value || aux)
return -EINVAL;
break;
...
}
...
fc = f.file->private_data;
...
if (_key) {
param.key = strndup_user(_key, 256);
if (IS_ERR(param.key)) {
ret = PTR_ERR(param.key);
goto out_f;
}
}
switch (cmd) {
...
case FSCONFIG_SET_STRING:
param.type = fs_value_is_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);
break;
...
}
ret = mutex_lock_interruptible(&fc->uapi_mutex);
if (ret == 0) {
ret = vfs_fsconfig_locked(fc, cmd, ¶m);
mutex_unlock(&fc->uapi_mutex);
}
switch (cmd) {
case FSCONFIG_SET_STRING:
kfree(param.string);
break;
...
}
...
}
//https://elixir.bootlin.com/linux/v5.14.21/source/fs/fsopen.c#L314
将位于用户态的key字段以及value字段拷贝至内核并将指针写入该文件系统的结构体中
但是长度限制为小于0x100字节
调用下一步的vfs_fsconfig_locked函数
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;
int ret;
...
switch (param->type) {
case fs_value_is_string:
len = 1 + param->size;
fallthrough;
...
}
if (len > PAGE_SIZE - 2 - size) [1]
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); [2]
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) { [3]
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;
return 0;
}//https://elixir.bootlin.com/linux/v5.14.21/source/fs/fs_context.c#L525
该函数在[2]处为ctx->legacy_data字段初始化赋值一个kmalloc-4K大小的slab
而该字段对应唯一的文件描述符,即多次对同一文件描述符调用该函数即可对该字段对应的slab进行多次写入
在[3]处将拷贝至内核的key字符串以及val字符串memcpy至ctx->legacy_data上并添加,=字符
len为用户态所传输的字符串长度+1,size为当前的ctx->legacy_data所对应的字符串长度,因此二者皆为可控变量
而注意到在[1]处存在负溢出
当size>=0xFFF时,PAGE_SIZE-2-size会造成负溢出并通过len>PAGE_SIZE-2-size的检测
然后依然执行拷贝用户态数据至ctx->legacy_data指针对应slab的操作
然而此时的size已经超过了4K的slab的大小,因此存在kmalloc-4K的chunk_overlap
且溢出长度不限,并可以分多次溢出
但由于前面将用户态内容拷贝至内核时strndup的存在,存在\x00截断
漏洞利用
利用msg_msg进行任意地址读写
msg_msg结构体
struct msg_msg {
struct list_head m_list;
long m_type;
size_t m_ts; /* message text size */
struct msg_msgseg *next;
void *security;
/* the actual message follows immediately */
};
//https://elixir.bootlin.com/linux/v5.14.21/source/include/linux/msg.h#L9
msgsnd()函数
函数调用链:
msgsnd()->ksys_msgsnd()->do_msgsnd()-> load_msg()->alloc_msg()
load_msg()->copy_from_user()
使用方法:
int make_queue(key_t key,int msgflg)
{
int result;
if ((result=msgget(key, msgflg))==-1)
{
perror("[X] Msgget Failure");
exit(0);
}
return result;
}
void send_msg(int msqid,void *msgp,size_t msgsz,int msgflg)
{
if (msgsnd(msqid, msgp, msgsz, msgflg)==-1)
{
perror("[X] Msgsend Failure");
exit(0);
}
return;
}
qid=make_queue(IPC_PRIVATE,0666|IPC_CREAT);
send_msg(qid,message,size-0x30,0);
msgsrv()函数
函数调用链:
msgrcv()->ksys_msgrcv()->do_msgrcv()->find_msg()
do_msg_fill()->store_msg()
free_msg()
使用方法:
ssize_t get_msg(int msqid,void *msgp,size_t msgsz,long msgtyp,int msgflg)
{
ssize_t ret;
result=msgrcv(msqid,msgp,msgsz,msgtyp,msgflg);
if (result<0)
{
perror("[X] Msgrcv Failure");
exit(0);
}
return result;
}
任意地址读
通过chunk_overlap或者UAF更改struct msg_msgseg *next字段,将其指向待泄漏内存区域即可完成泄漏
(该指针所指向的地址其需要为NULL,否则unlink时可能会出现段错误)
通过chunk_overlap泄漏kernel_base
发送长度为0x1018的msg_msg
其前0xFB0字节的内容会被分配在kmalloc-4K的空间
剩下的0x28字节与0x8字节的next指针则被分配在kmalloc-32的空间
通过大量堆喷射seq_operations在kmalloc-32空间,而此时第一个struct msg_msg结构体的next指针正好指向kmalloc-32空间
此时通过UAF或者chunk_overlap更改m_ts字段,伪造信息长度,即可将位于kmalloc-32空间的信息后面的内容泄漏
其中可能包含seq_operations结构体中所包含的指向某个全局结构体的指针,而该指针即使在开启了FG-KASLR的情况下其偏移依然是固定的,因此非常适合进行kernel_base地址泄漏
任意地址写
Poc:
参考文章:
[1] https://www.willsroot.io/2022/01/cve-2022-0185.html
[2] https://blog.csdn.net/panhewu9919/article/details/120820617