我管理一个 Gentoo Hardened 盒子,它使用文件功能来消除对 setuid-root 二进制文件的大部分需求(例如/bin/ping
具有 CAP_NET_RAW 等)。
事实上,我剩下的唯一二进制文件就是这个:
abraxas ~ # find / -xdev -type f -perm -u=s
/usr/lib64/misc/glibc/pt_chown
abraxas ~ #
如果我删除 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;
}
删除上述程序上的 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
关于如何解决这个问题,我只有一些想法:
1) 将程序替换为仅返回 0 的骨架。
2) 在我的 libc 中修补 grantpt() 以使其不执行任何操作。
我可以将这两种方法自动化,但是有人对其中一种方法有建议,或者建议如何解决这个问题吗?
一旦解决了这个问题,我终于可以了mount -o remount,nosuid /
。
答案1
如果你的glibc是相当当前的,并且开发者如果设置正确,则根本不需要调用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;
}
...
类似的节用于检查 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
.然而,似乎pt_chown
是(截至glibc-2.17,正如您所指出的,尽管您没有说明版本)硬编码为想要geteuid()==0
不管的HAVE_LIBCAP
,相关代码来自glibc-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'"));
(geteuid()==0
在尝试使用功能之前进行预期似乎并不真正符合功能的精神,我会在这个问题上记录一个错误。)
一个潜在的解决方法可能是为受影响的程序提供 CAP_CHOWN、CAP_FOWNER,但我真的不推荐因为你当然不能将其限制为 ptys。
如果这不能帮助你解决问题,那么修补sshd
它screen
比修补 glibc 稍微不那么令人讨厌。由于问题出在 glibc 内部,所以更干净的方法将是有选择的使用DLL注入实施一个虚拟grantpt()
。