Aar*_*nes 26 security setuid glibc linux-kernel
我管理一个 Gentoo Hardened box,它使用文件功能来消除对 setuid-root 二进制文件的大部分需求(例如,/bin/ping有 CAP_NET_RAW 等)。
事实上,我剩下的唯一二进制文件就是这个:
abraxas ~ # find / -xdev -type f -perm -u=s
/usr/lib64/misc/glibc/pt_chown
abraxas ~ #
Run Code Online (Sandbox Code Playgroud)
如果我删除了 setuid 位,或者重新挂载我的根文件系统nosuid,sshd 和 GNU Screen 将停止工作,因为它们调用grantpt(3)了它们的主伪终端,而 glibc 显然会执行这个程序来 chown 和 chmod 下的从伪终端/dev/pts/,而 GNU Screen 关心这个函数的时间失败。
问题是,手册页grantpt(3)明确指出,在 Linux 下,devpts挂载文件系统后,不需要此类帮助程序二进制文件;内核会自动将slave的UID & GID设置为打开的进程的真实UID & GID /dev/ptmx(通过调用getpt(3))。
我编写了一个小示例程序来演示这一点:
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
int main(void)
{
int master;
char slave[16];
struct stat slavestat;
if ((master = getpt()) < 0) {
fprintf(stderr, "getpt: %m\n");
return 1;
}
printf("Opened a UNIX98 master terminal, fd = %d\n", master);
/* I am not going to call grantpt() because I am trying to
* demonstrate that it is not necessary with devpts mounted,
* the owners and mode will be set automatically by the kernel.
*/
if (unlockpt(master) < 0) {
fprintf(stderr, "unlockpt: %m\n");
return 2;
}
memset(slave, 0, sizeof(slave));
if (ptsname_r(master, slave, sizeof(slave)) < 0) {
fprintf(stderr, "ptsname: %m\n");
return 2;
}
printf("Device name of slave pseudoterminal: %s\n", slave);
if (stat(slave, &slavestat) < 0) {
fprintf(stderr, "stat: %m\n");
return 3;
}
printf("Information for device %s:\n", slave);
printf(" Owner UID: %d\n", slavestat.st_uid);
printf(" Owner GID: %d\n", slavestat.st_gid);
printf(" Octal mode: %04o\n", slavestat.st_mode & 00007777);
return 0;
}
Run Code Online (Sandbox Code Playgroud)
在删除上述程序中的 setuid 位后观察它的运行情况:
aaron@abraxas ~ $ id
uid=1000(aaron) gid=100(users) groups=100(users)
aaron@abraxas ~ $ ./ptytest
Opened a UNIX98 master terminal, fd = 3
Device name of slave pseudoterminal: /dev/pts/17
Information for device /dev/pts/17:
Owner UID: 1000
Owner GID: 100
Octal mode: 0620
Run Code Online (Sandbox Code Playgroud)
关于如何解决这个问题,我只有几个想法:
1) 用只返回 0 的骨架替换程序。
2) 在我的 libc 中修补 grantpt() 以不执行任何操作。
我可以自动执行这两个操作,但是有没有人对其中一个提出建议,或者建议如何解决这个问题?
一旦解决了这个问题,我终于可以了mount -o remount,nosuid /。
如果您的glibc相当最新,并且devpts设置正确,则根本不需要调用pt_chown帮助程序。
您可能会遇到从 中删除 setuid-root 的已知/潜在问题pt_chown。
grantpt()devfs从glibc-2.7开始受支持,但在glibc-2.11中进行了更改,因此它不是显式检查,而是在尝试或回退到调用DEVFS_SUPER_MAGIC之前检查是否需要执行任何工作。chown()pt_chown
从glibc-2.17/sysdeps/unix/grantpt.c
...
uid_t uid = __getuid ();
if (st.st_uid != uid)
{
if (__chown (buf, uid, st.st_gid) < 0)
goto helper;
}
...
Run Code Online (Sandbox Code Playgroud)
类似的节用于检查 gid 和权限。问题是 uid、gid 和模式必须符合预期(you、tty 和620;用 确认)/usr/libexec/pt_chown --help。如果没有,则chown()尝试(这需要调用二进制文件/进程的 CAP_CHOWN、CAP_FOWNER 功能),如果失败,则pt_chown尝试外部帮助程序(必须是 setuid-root)。为了pt_chown能够使用功能,它(以及你的 glibc)必须使用HAVE_LIBCAP. 然而,似乎(从glibc-2.17pt_chown开始,正如您所指出的,尽管您没有说明版本)硬编码为想要无论相关代码如何:geteuid()==0 HAVE_LIBCAPglibc-2.17/login/programs/pt_chown.c
...
if (argc == 1 && euid == 0)
{
#ifdef HAVE_LIBCAP
/* Drop privileges. */
if (uid != euid)
...
#endif
/* Normal invocation of this program is with no arguments and
with privileges. */
return do_pt_chown ();
}
...
/* Check if we are properly installed. */
if (euid != 0)
error (FAIL_EXEC, 0, gettext ("needs to be installed setuid `root'"));
Run Code Online (Sandbox Code Playgroud)
(geteuid()==0在尝试使用功能之前进行预期似乎并不真正符合功能的精神,我会在这个问题上记录一个错误。)
一个潜在的解决方法可能是为受影响的程序提供 CAP_CHOWN、CAP_FOWNER,但我真的不建议这样做,因为你当然不能将其限制为 ptys。
如果这不能帮助你解决问题,那么修补sshd它screen比修补 glibc 稍微不那么令人讨厌。由于问题出在 glibc 内部,更简洁的方法是选择性地使用 DLL 注入来实现虚拟grantpt().