Linux 内核 __ptrace_may_access() 函数的逻辑漏洞 (CVE-2026-46333)

Lobsters Hottest 新闻

摘要

Qualys 披露了 Linux 内核 __ptrace_may_access() 函数中的一个逻辑漏洞 (CVE-2026-46333),可导致本地权限提升和信息泄露。该漏洞自 2016 年起存在,影响多个发行版,Qualys 已开发出四种概念验证利用代码。

<p><a href="https://lobste.rs/s/nwdn3w/logic_bug_linux_kernel_s_ptrace_may_access">评论</a></p>
查看原文
查看缓存全文

缓存时间: 2026/05/20 20:31

# Qualys 安全公告:Linux 内核 `__ptrace_may_access()` 函数中的逻辑错误 (CVE-2026-46333) ## 目录 - [摘要](#summary) - [分析](#analysis) - [案例研究:chage](#case-study-chage) - [案例研究:ssh-keysign](#case-study-ssh-keysign) - [案例研究:pkexec](#case-study-pkexec) - [案例研究:accounts-daemon](#case-study-accounts-daemon) - [致谢](#acknowledgments) - [时间线](#timeline) ## 摘要 我们发现 Linux 内核的 `__ptrace_may_access()` 函数中存在一个逻辑错误(授权绕过)。此漏洞可在本地被利用,实现信息泄露并以 root 身份执行任意命令。据我们所知,该漏洞于 2016 年 11 月(v4.10-rc1)由提交 bfedb58("mm: Add a user_ns owner to mm_struct and fix ptrace permission checks")引入。 我们为此漏洞开发了四种不同的利用方法(所有方法都依赖于 pidfd_getfd() 系统调用,该调用于 2020 年 1 月(v5.6-rc1)引入,但可能存在其他利用方式): - 针对 **chage**(一个设置了 set-uid root 或 set-gid shadow 权限的二进制文件)的漏洞,允许本地攻击者泄露 /etc/shadow 文件的内容(系统密码哈希)。我们在 Debian 13、Ubuntu 24.04 和 26.04、Fedora 43 和 44 的默认安装上成功测试了此漏洞;其他发行版也可能受到影响。 - 针对 **ssh-keysign**(一个设置了 set-uid root 权限的二进制文件)的漏洞,允许本地攻击者泄露主机的私钥(/etc/ssh/*_key)。我们在 Debian 13、Ubuntu 24.04 和 26.04 的默认安装上成功测试了此漏洞;其他发行版也可能受到影响。 - 针对 **pkexec**(一个设置了 set-uid root 权限的二进制文件)的漏洞,允许本地攻击者在真实用户实际坐在计算机前时以 root 身份执行任意命令(攻击者可以远程登录到计算机,例如通过 sshd)。我们在 Debian 13、Ubuntu Desktop 24.04 和 26.04、Fedora Workstation 43 和 44 的默认安装上成功测试了此漏洞;其他发行版也可能受到影响。 - 针对 **accounts-daemon**(一个 root 守护进程)的漏洞,允许本地攻击者以 root 身份执行任意命令。我们在 Debian 13、Fedora Workstation 43 和 44 的默认安装上成功测试了此漏洞;其他发行版也可能受到影响,但 Ubuntu 明显不受影响,因为它默认启用了 Yama ptrace 保护(将 kernel.yama.ptrace_scope 设置为 1)。 请注意,我们没有穷举搜索可利用的用户态程序(set-uid、set-gid、set-capabilities 二进制文件以及 root 守护进程);我们只是从过去的研究项目中记起了这四个,可能存在其他更好利用的程序。 **最后一刻说明**:2026 年 5 月 15 日星期五,我们在 https://www.openwall.com/lists/oss-security/2026/05/15/2 和 https://www.openwall.com/lists/oss-security/2026/05/15/8 上预发布了相关信息。 ## 分析 一个非特权用户如果想成功地对一个进程调用 `ptrace()`、`process_vm_readv()`、`process_vm_writev()`、`pidfd_getfd()`,或者访问该进程在 /proc/pid 中的敏感文件,必须首先通过 `__ptrace_may_access()` 中的两项安全检查: ```c 276 static int __ptrace_may_access(struct task_struct *task, unsigned int mode) 277 { ... 316 tcred = __task_cred(task); 317 if (uid_eq(caller_uid, tcred->euid) && 318 uid_eq(caller_uid, tcred->suid) && 319 uid_eq(caller_uid, tcred->uid) && 320 gid_eq(caller_gid, tcred->egid) && 321 gid_eq(caller_gid, tcred->sgid) && 322 gid_eq(caller_gid, tcred->gid)) 323 goto ok; ... 328 ok: ... 340 mm = task->mm; 341 if (mm && 342 (get_dumpable(mm) != SUID_DUMP_USER) && 343 !ptrace_has_cap(mm->user_ns, mode)) 344 return -EPERM; 345 346 return security_ptrace_access_check(task, mode); 347 } ``` 1. 在第 317-322 行,进程的有效、保存、真实 uid 和 gid 必须等于非特权用户的 uid 和 gid; 2. 在第 341-342 行,进程的 dumpable 标志必须等于 `SUID_DUMP_USER`(1)。默认情况下,如果进程更改了其 uid 或 gid,内核会自动将其 dumpable 标志设置为 `SUID_DUMP_DISABLE`(0),以防止非特权用户从该进程提取敏感信息或资源。例如,sshd-session 将其 root uid 和 gid 更改为已验证用户的 uid 和 gid,但其内存中仍可能包含秘密信息(私钥和密码哈希)。 不幸的是,第 342 行的 dumpable 标志检查可以被完全绕过:如果进程的 `mm` 指针在第 341 行为 NULL,那么非特权用户(其 uid 和 gid 与进程的 uid 和 gid 相同)可以诱使 `__ptrace_may_access()` 在第 346 行成功返回,即使该进程的 dumpable 标志实际上不等于 `SUID_DUMP_USER`(即,即使该进程曾经是特权进程)。 内核在 `do_exit()` 的第 964 行将进程的 mm 指针设置为 NULL,此时该进程正在终止: ```c 896 void __noreturn do_exit(long code) 897 { ... 964 exit_mm(); ... 971 exit_files(tsk); ... 1019 do_task_dead(); 1020 } ``` 那么,问题就变成了:在一个进程完全放弃其权限(变为攻击者的 uid 和 gid)之后,在其 mm 指针于第 964 行设置为 NULL 之后,但在它完全死亡(第 1019 行)之前,攻击者可以从该进程中窃取哪些敏感资源?最终,我们在 `pidfd_getfd()` 系统调用中找到了这个问题的答案: ```c 947 SYSCALL_DEFINE3(pidfd_getfd, int, pidfd, int, fd, 948 unsigned int, flags) 949 { ... 964 return pidfd_getfd(pid, fd); 910 static int pidfd_getfd(struct pid *pid, int fd) 911 { ... 920 file = __pidfd_fget(task, fd); 872 static struct file *__pidfd_fget(struct task_struct *task, int fd) 873 { ... 881 if (ptrace_may_access(task, PTRACE_MODE_ATTACH_REALCREDS)) 882 file = fget_task(task, fd); 1123 struct file *fget_task(struct task_struct *task, unsigned int fd) 1124 { ... 1128 if (task->files) 1129 file = __fget_files(task->files, fd, 0); ``` - 如果我们(攻击者)在一个进程刚刚将其权限降为我们自己的 uid 和 gid 后立即向其发送 SIGKILL; - 并且我们在该进程的 mm 指针被设置为 NULL(在 do_exit() 第 964 行)之后、但 files 指针被设置为 NULL(在 do_exit() 第 971 行)之前调用 pidfd_getfd(); - 那么第 881 行对 ptrace_may_access() 的调用会成功(因为进程的 uid 和 gid 在第 317-322 行与我们自己的非特权 uid 和 gid 相等,并且因为其 mm 指针在第 341 行是 NULL,因此绕过了第 342 行的 dumpable 标志检查); - 我们就可以在第 1129 行窃取该进程的任何打开的文件描述符,并像使用自己的文件描述符一样使用它。 ## 案例研究:chage chage 是 shadow-utils 中的一个设置了 set-uid root 或 set-gid shadow 权限的二进制文件,默认安装在大多数 Linux 发行版上。如果我们使用 `-l` 选项执行它,那么在第 776 行它以 O_RDONLY 模式打开 /etc/shadow,在第 778-779 行它将权限降为我们自己的 uid 和 gid: ```c 726 int main (int argc, char **argv) 727 { ... 753 ruid = getuid (); 754 rgid = getgid (); ... 776 open_files (lflg, &flags); 777 /* Drop privileges */ 778 if (lflg && ( (setregid (rgid, rgid) != 0) 779 || (setreuid (ruid, ruid) != 0))) { ``` 因此,如果我们直接在 778-779 行之后向 chage 进程发送 SIGKILL,并在一个紧密循环中调用 pidfd_getfd(),那么最终我们会赢得 do_exit() 中的竞态条件(在第 964 行和第 971 行之间),绕过 ptrace_may_access() 中的 dumpable 标志检查,并可以窃取 chage 的 /etc/shadow 文件描述符并读取其内容: ``` $ cat /etc/os-release PRETTY_NAME="Ubuntu 26.04 LTS" $ id uid=1001(jane) gid=1001(jane) groups=1001(jane) $ stat /usr/bin/chage Access: (2755/-rwxr-sr-x) Uid: ( 0/ root) Gid: ( 42/ shadow) $ ./exploit-chage root:*:20563:0:99999:7::: ... john:$6$zejBXeN4uVNvydnA$hwbwcoT24evWSI4SqM1p8YIInVMtqY2CCE.vfudaG1/mIKayCFraqWIbY0tSIiLFl.8ZrBm86owPU.Xa8HauQ0:20585:0:99999:7::: sshd:!*:20585:::::: jane:$y$j9T$r575buH7G8C84ZHsJRiee/$yyVfFeh/EMowm9GhXXC6TdgUGftwYpB8Uffa/k7VNE9:20585:0:99999:7::: ``` ## 案例研究:ssh-keysign ssh-keysign 是 OpenSSH 中的一个设置了 set-uid root 权限的二进制文件,默认安装在大多数 Linux 发行版上。尽管 EnableSSHKeysign 在 /etc/ssh/ssh_config 中默认禁用,但它在第 203-205 行打开了主机的私钥文件(/etc/ssh/*_key),并在第 211 行放弃了权限: ```c 176 main(int argc, char **argv) 177 { ... 203 key_fd[i++] = open(_PATH_HOST_ECDSA_KEY_FILE, O_RDONLY); 204 key_fd[i++] = open(_PATH_HOST_ED25519_KEY_FILE, O_RDONLY); 205 key_fd[i++] = open(_PATH_HOST_RSA_KEY_FILE, O_RDONLY); 206 207 if ((pw = getpwuid(getuid())) == NULL) 208 fatal("getpwuid failed"); 209 pw = pwcopy(pw); 210 211 permanently_set_uid(pw); ... 224 if (options.enable_ssh_keysign != 1) 225 fatal("ssh-keysign not enabled in %s", 226 _PATH_HOST_CONFIG_FILE); ``` 因此,如果我们直接在 211 行之后向 ssh-keysign 发送 SIGKILL,并在一个循环中调用 pidfd_getfd(),那么我们可以窃取 ssh-keysign 的任何 /etc/ssh/*_key 文件描述符并读取其内容: ``` $ cat /etc/os-release PRETTY_NAME="Ubuntu 26.04 LTS" $ id uid=1001(jane) gid=1001(jane) groups=1001(jane) $ stat /usr/lib/openssh/ssh-keysign Access: (4755/-rwsr-xr-x) Uid: ( 0/ root) Gid: ( 0/ root) $ ./exploit-ssh-keysign 3 -----BEGIN OPENSSH PRIVATE KEY----- b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAaAAAABNlY2RzYS ... $ ./exploit-ssh-keysign 4 -----BEGIN OPENSSH PRIVATE KEY----- b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW ... $ ./exploit-ssh-keysign 5 -----BEGIN OPENSSH PRIVATE KEY----- b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABlwAAAAdzc2gtcn ... ``` ## 案例研究:pkexec pkexec 是 polkit 包中的一个设置了 set-uid root 权限的二进制文件,默认安装在大多数 Linux 桌面发行版上。例如在 Debian 上,一个 "allow_active" 用户(实际坐在计算机前的用户)可以通过 pkexec --user 以 root 或其他任何用户身份执行 /usr/libexec/gsd-backlight-helper: ``` $ cat /etc/os-release PRETTY_NAME="Debian GNU/Linux 13 (trixie)" $ cat /usr/share/polkit-1/actions/org.gnome.settings-daemon.plugins.power.policy ... <allow_active>yes</allow_active> ... /usr/libexec/gsd-backlight-helper ... ``` ```c 469 main (int argc, char *argv[]) 470 { ... 585 opt_user = g_strdup (argv[n]); ... 641 rc = getpwnam_r (opt_user, &pwstruct, pwbuf, sizeof pwbuf, &pw); ... 1024 if (!fdwalk_close_on_exec (3)) ... 1086 (void) setregid (pw->pw_gid, pw->pw_gid); 1087 (void) setreuid (pw->pw_uid, pw->pw_uid); ... 1109 if (execv (path, exec_argv) != 0) ``` - 在第 641 行和第 1024 行之间,pkexec 连接到系统 dbus,并以 root 身份验证此连接(使用其 SCM_CREDENTIALS); - 在第 1024 行,pkexec 将所有大于等于 3 的打开文件描述符(包括连接到系统 dbus 的文件描述符)设置关闭-on-exec 标志(即,它将在第 1109 行被关闭); - 在第 1086-1087 行,pkexec 完全放弃其权限(降为 --user 选项在第 585 行指定的用户)。 因此: - 如果我们(攻击者)使用我们自己的用户作为 --user 选项执行 pkexec,并在它放弃权限到我们 uid 和 gid 之后(第 1086-1087 行)立即发送 SIGKILL; - 然后,如果我们在一个紧密循环中调用 pidfd_getfd(),我们可以窃取 pkexec 到系统 dbus 的连接,该连接已经以 root 身份验证; - 并通过此连接向 systemd(PID 1)发送请求以启动一个瞬时单元(StartTransientUnit),并以完全 root 权限执行任意命令(例如 ExecStart=/bin/sh -c 'id>>/tmp/pwned')。 乍一看,这似乎

相似文章

Linux 0-day漏洞:非特权用户可访问root拥有的文件

Lobsters Hottest

Linux内核__ptrace_may_access()存在0-day漏洞,非特权用户可读取root拥有的文件,例如SSH主机密钥和/etc/shadow。该漏洞影响众多发行版和内核版本,已有针对ssh-keysign和chage的利用代码可用。

CVE-2026-31431: Copy Fail

Lobsters Hottest

CVE-2026-31431(Copy Fail)是Linux内核中的一个本地提权漏洞,影响自2017年以来的所有主流发行版,允许非特权用户通过AF_ALG加密子系统对任何可读文件的页缓存进行确定性的4字节写入,从而获得root shell访问权限。

Linux内核中因单个错误字符导致的高危漏洞

Ars Technica

Linux内核中一个错误的字符引入了一个use-after-free漏洞(CVE-2026-53111),允许非特权用户在Debian和Ubuntu系统上将权限提升至root;该漏洞已修复并移植回旧版本。