解决这个 glibc 问题的最佳方法是什么?

解决这个 glibc 问题的最佳方法是什么?

我管理一个 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。

如果这不能帮助你解决问题,那么修补sshdscreen比修补 glibc 稍微不那么令人讨厌。由于问题出在 glibc 内部,所以更干净的方法将是有选择的使用DLL注入实施一个虚拟grantpt()

相关内容