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 /
.
Best Answer
If your glibc is reasonably current, and devpts is set up correctly, there should be no need to invoke the
pt_chown
helper at all.You might be running into a known/potential issue removing setuid-root from
pt_chown
.grantpt()
supporteddevfs
from glibc-2.7, changes were made in glibc-2.11 though so that rather than explicitly checking forDEVFS_SUPER_MAGIC
, it checks instead to see if it needs to do any work before attemptingchown()
or falling back to invokingpt_chown
.From
glibc-2.17/sysdeps/unix/grantpt.c
A similar stanza is used to check the gid and the permissions. The catch is that the uid, gid and mode must match expectations (you, tty, and exactly 620; confirm with
/usr/libexec/pt_chown --help
). If not,chown()
(which would require capabilities CAP_CHOWN, CAP_FOWNER of the calling binary/process) is attempted, and if that fails, thept_chown
external helper (which must be setuid-root) is attempted. In order forpt_chown
to be able to use capabilities it (and hence your glibc) must have been compiled withHAVE_LIBCAP
. However, it seems thatpt_chown
is (as of glibc-2.17, and as you noted though you haven't stated the version) hard-coded to wantgeteuid()==0
regardless ofHAVE_LIBCAP
, relevant code fromglibc-2.17/login/programs/pt_chown.c
:(Expecting
geteuid()==0
before attempting to use capabilities doesn't seem to be really in the spirit of capabilities, I'd go with logging a bug on this one.)A potential workaround might be to give CAP_CHOWN, CAP_FOWNER to the affected programs, but I really don't recommend that since you cannot restrict that to ptys of course.
If that doesn't help you solve it, patching
sshd
andscreen
is slightly less disagreeable than patching glibc. Since the problem lies within glibc though, a cleaner approach would be selective use of DLL injection to implement a dummygrantpt()
.