26

I administer a Gentoo Hardened box that uses file capabilities to eliminate most of the need for setuid-root binaries (e.g. /bin/ping has CAP_NET_RAW, etc).

Infact, the only binary I have left is this one:

abraxas ~ # find / -xdev -type f -perm -u=s
/usr/lib64/misc/glibc/pt_chown
abraxas ~ # 

If I remove the setuid bit, or remount my root filesystem nosuid, sshd and GNU Screen stop working, because they call grantpt(3) on their master pesudoterminals and glibc apparently executes this program to chown and chmod the slave pseudoterminal under /dev/pts/, and GNU Screen cares about when this function fails.

The problem is, the manpage for grantpt(3) explicitly states that under Linux, with the devpts filesystem mounted, no such helper binary is required; the kernel will automatically set the UID & GID of the slave to the real UID & GID of the process that opened /dev/ptmx (by calling getpt(3)).

I have written a small example program to demonstrate this:

#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;
}

Observe it in action with the setuid bit on the aforementioned program removed:

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

I have only a few ideas as to how to work around this problem:

1) Replace the program with a skeleton that simply returns 0.

2) Patch grantpt() in my libc to do nothing.

I can automate both of these, but does anyone have a recommendation for one over the other, or recommendations for how else to solve this?

Once this is solved, I can finally mount -o remount,nosuid /.

  • While I await a response, I went with approach 1, and sshd and GNU Screen still work. – Aaron Jones Mar 16 '13 at 17:37
  • What exactly are the programs that fail? Perhaps they are broken and check not for the pty (as they should) but for the program? – vonbrand Mar 16 '13 at 22:03
  • Any program that does not have CAP_CHOWN and CAP_FOWNER, calls grantpt(), and the helper binary is not started with EUID == 0, will have a non-zero return code for grantpt(), and programs SHOULD abort PTS creation when this happens, as per ptmx(4). – Aaron Jones Mar 16 '13 at 22:21
  • I eventually patched the glibc source to immediately return 0 in grantpt() and immediately return 0 in pt_chown's main(). Everything works great without the need for a setuid-root binary now. – Aaron Jones Mar 16 '13 at 22:22
  • 3
    That "solution" gives me the willies... in the best case, it papers over a bug, it probably creates a new bug, in the worst case it creates a serious security vulnerability. Please take this up with the glibc developers. – vonbrand Mar 16 '13 at 22:25
  • The helper binary is simply not required for Linux when devpts is mounted, because the kernel will change the mode & owner UID/GID when the process calls getpt(). This is exactly what the helper binary also does, but it causes grantpt() to return a non-zero exit code if the chown() or chmod() fail. This is undesirable because when the root filesystem is mounted nosuid, people without capabilities to perform chown() or chmod() on arbitrary files cannot create pseudoterminals without an error code, and thus cannot use things like GNU Screen. This does not create a vulnerability or a bug. – Aaron Jones Mar 16 '13 at 22:26
  • You will notice that at line 270 of pty.c in version 4.0.3 of GNU Screen, it will abort if grantpt() returns a non-zero result. This is exactly what will happen if an unprivileged user attempts to use GNU Screen with a nosuid root filesystem, because grantpt() calls the helper binary regardless of whether devpts is mounted, and the helper binary will return a non-zero result if its EUID is not 0. – Aaron Jones Mar 16 '13 at 22:50
  • 3
    Then report this to the glibc people. – vonbrand Mar 16 '13 at 22:55
  • Any reason why you can't/don't want to give CAP_CHOWN to the helper app? – peterph Mar 26 '13 at 00:09
  • That would not make a difference, you would have to modify the glibc sources (where the helper app is contained) anyway, to remove the check for EUID == 0. – Aaron Jones Mar 26 '13 at 21:09
  • Patch grantpt() to return success if the permissions are already as they should be? If that's not what it's doing...
  • – frostschutz Apr 13 '13 at 01:17