netblue30 / firejail

Linux namespaces and seccomp-bpf sandbox
https://firejail.wordpress.com
GNU General Public License v2.0
5.73k stars 561 forks source link

--noroot does not allow to start urxvt/rxvt-unicode #31

Closed blueyed closed 9 years ago

blueyed commented 9 years ago

I am using Vimperator in Firefox and have set editor=vim-in-term, which is a script that calls urxvt ... vim.

The noroot option in /etc/firejail/firefox.profile causes urxvt to fail:

urxvt: can't initialize pseudo-tty, aborting.

From reading the description of noroot this does not seem to be obvious:

  --noroot - install a user namespace with a single user - the current
    user. root user does not exist in the new namespace. This option
    is not supported for --chroot and --overlay configurations.

The code from rxvt-unicode is this, where pty appears to come from libptytty (CVS at :pserver:anonymous@cvs.schmorp.de/schmorpforge):

if (!pty->get ())
  rxvt_fatal ("can't initialize pseudo-tty, aborting.\n");

It can be reproduced using:

firejail --noroot /usr/bin/rxvt-unicode

Using firejail --noroot strace -f /usr/bin/rxvt-unicode shows this at the end (group 5 being tty):

chown("/dev/pts/10", 1000, 5)           = -1 EINVAL (Invalid argument)
clone(Process 7 attached
child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f5891234ad0) = 7
[pid     6] wait4(7,  <unfinished ...>
[pid     7] set_robust_list(0x7f5891234ae0, 24) = 0
[pid     7] setrlimit(RLIMIT_CORE, {rlim_cur=0, rlim_max=0}) = 0
[pid     7] dup2(7, 3)                  = 3
[pid     7] openat(AT_FDCWD, "/proc/self/fd", O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC) = 8
[pid     7] getdents(8, /* 11 entries */, 32768) = 264
[pid     7] close(0)                    = 0
[pid     7] close(1)                    = 0
[pid     7] close(2)                    = 0
[pid     7] close(4)                    = 0
[pid     7] close(5)                    = 0
[pid     7] close(6)                    = 0
[pid     7] close(7)                    = 0
[pid     7] getdents(8, /* 0 entries */, 32768) = 0
[pid     7] close(8)                    = 0
[pid     7] open("/dev/null", O_RDONLY) = 0
[pid     7] open("/dev/null", O_WRONLY) = 1
[pid     7] dup2(1, 2)                  = 2
[pid     7] execve("/usr/lib/pt_chown", ["pt_chown"], [/* 0 vars */]) = 0
[pid     7] brk(0)                      = 0x7efede80a000
[pid     7] access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory)
[pid     7] mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7efede28a000
[pid     7] access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)
[pid     7] open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 4
[pid     7] fstat(4, {st_mode=S_IFREG|0644, st_size=265876, ...}) = 0
[pid     7] mmap(NULL, 265876, PROT_READ, MAP_PRIVATE, 4, 0) = 0x7efede249000
[pid     7] close(4)                    = 0
[pid     7] access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory)
[pid     7] open("/lib/x86_64-linux-gnu/libcap.so.2", O_RDONLY|O_CLOEXEC) = 4
[pid     7] read(4, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0p\25\0\0\0\0\0\0"..., 832) = 832
[pid     7] fstat(4, {st_mode=S_IFREG|0644, st_size=18952, ...}) = 0
[pid     7] mmap(NULL, 2114160, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 4, 0) = 0x7efeddc60000
[pid     7] mprotect(0x7efeddc64000, 2093056, PROT_NONE) = 0
[pid     7] mmap(0x7efedde63000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 4, 0x3000) = 0x7efedde63000
[pid     7] close(4)                    = 0
[pid     7] access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory)
[pid     7] open("/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 4
[pid     7] read(4, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0`\v\2\0\0\0\0\0"..., 832) = 832
[pid     7] fstat(4, {st_mode=S_IFREG|0755, st_size=1869392, ...}) = 0
[pid     7] mmap(NULL, 3972864, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 4, 0) = 0x7efedd896000
[pid     7] mprotect(0x7efedda56000, 2097152, PROT_NONE) = 0
[pid     7] mmap(0x7efeddc56000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 4, 0x1c0000) = 0x7efeddc56000
[pid     7] mmap(0x7efeddc5c000, 16128, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7efeddc5c000
[pid     7] close(4)                    = 0
[pid     7] mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7efede248000
[pid     7] mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7efede247000
[pid     7] mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7efede246000
[pid     7] arch_prctl(ARCH_SET_FS, 0x7efede247700) = 0
[pid     7] mprotect(0x7efeddc56000, 16384, PROT_READ) = 0
[pid     7] mprotect(0x7efedde63000, 4096, PROT_READ) = 0
[pid     7] mprotect(0x7efede28c000, 4096, PROT_READ) = 0
[pid     7] mprotect(0x7efede088000, 4096, PROT_READ) = 0
[pid     7] munmap(0x7efede249000, 265876) = 0
[pid     7] geteuid()                   = 1000
[pid     7] getuid()                    = 1000
[pid     7] setuid(1000)                = 0
[pid     7] brk(0)                      = 0x7efede80a000
[pid     7] brk(0x7efede82b000)         = 0x7efede82b000
[pid     7] write(2, "pt_chown: ", 10)  = 10
[pid     7] write(2, "needs to be installed setuid `ro"..., 35) = 35
[pid     7] write(2, "\n", 1)           = 1
[pid     7] exit_group(4)               = ?
[pid     7] +++ exited with 4 +++
<... wait4 resumed> [{WIFEXITED(s) && WEXITSTATUS(s) == 4}], 0, NULL) = 7
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=7, si_status=4, si_utime=0, si_stime=0} ---
rt_sigreturn()                          = 7
close(7)                                = 0
write(1, "urxvt: ", 7urxvt: )                  = 7
write(1, "can't initialize pseudo-tty, abo"..., 39can't initialize pseudo-tty, aborting.
) = 39
futex(0x7f588f3a9650, FUTEX_WAKE_PRIVATE, 2147483647) = 0
munmap(0x7f588b49c000, 475136)          = 0
poll([{fd=6, events=POLLIN|POLLOUT}], 1, 4294967295) = 1 ([{fd=6, revents=POLLOUT}])
writev(6, [{"_\24\2\0\2\0\0\5<\0\2\0\0\0\0\5.\0\2\0\3\0\0\5+\0\1\0", 28}, {NULL, 0}, {"", 0}], 3) = 28
poll([{fd=6, events=POLLIN}], 1, 4294967295) = 1 ([{fd=6, revents=POLLIN}])
recvmsg(6, {msg_name(0)=NULL, msg_iov(1)=[{"\1\2\235\0\0\0\0\0\t\0\300\4\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0", 4096}], msg_controllen=0, msg_flags=0}, 0) = 32
recvmsg(6, 0x7ffc5207d360, 0)           = -1 EAGAIN (Resource temporarily unavailable)
recvmsg(6, 0x7ffc5207d360, 0)           = -1 EAGAIN (Resource temporarily unavailable)
shutdown(6, SHUT_RDWR)                  = 0
close(6)                                = 0
rt_sigaction(SIGINT, {SIG_DFL, [INT], SA_RESTORER|SA_RESTART, 0x7f588f3df2f0}, {0x4381a0, ~[KILL STOP RTMIN RT_1], SA_RESTORER|SA_RESTART, 0x7f588f784d10}, 8) = 0
rt_sigaction(SIGTERM, {SIG_DFL, [TERM], SA_RESTORER|SA_RESTART, 0x7f588f3df2f0}, {0x4381a0, ~[KILL STOP RTMIN RT_1], SA_RESTORER|SA_RESTART, 0x7f588f784d10}, 8) = 0
exit_group(1)                           = ?
+++ exited with 1 +++

parent is shutting down, bye...

Is this a issue with rxvt-unicode, or is there anything firejail can do to allow this still?

ghost commented 9 years ago

From your error messages, it seems that urxvt is trying to start the pt_chown program to set the permissions on the new pty. This will fail under --noroot as pt_chown needs to be SUID root to run. With the --noroot option, there is no root user to SUID to!

The manual page for pt_chown says: "If you are using a 2.1 or newer Linux kernel with the 'devptsfs' or 'devfs' filesystems providing pty slaves, you don't need this program".

Therefore it might be possible to compile urxvt without pt_chown support. I'm not an expert in urxvt so you'd have to do some research.

blueyed commented 9 years ago

The call to pt_chown seems to happen through grantpt in get_pty (in libptytty/src/ptytty.C) (I have added the printf):

#if defined(UNIX98_PTY)

  static int
  get_pty (int *fd_tty, char **ttydev)
  {
    int pfd;

# if defined(HAVE_GETPT)
    pfd = getpt ();
# elif defined(HAVE_POSIX_OPENPT)
    pfd = posix_openpt (O_RDWR | O_NOCTTY);
# else
#  ifdef _AIX
    pfd = open ("/dev/ptc", O_RDWR | O_NOCTTY, 0);
#  else
    pfd = open ("/dev/ptmx", O_RDWR | O_NOCTTY, 0);
#  endif
# endif

    if (pfd >= 0)
      {
        printf("pfd: %d, %d\n", grantpt(pfd), errno);
        if (grantpt (pfd) == 0  /* change slave permissions */
            && unlockpt (pfd) == 0)
          {
            /* slave now unlocked */
            *ttydev = strdup (ptsname (pfd));   /* get slave's name */
            return pfd;
          }

        close (pfd);
      }

    return -1;
  }

#elif defined(HAVE_OPENPTY)

errno is EAGAIN 11 Resource temporarily unavailable, and while it is not in my manpage for grantpt a search appears to indicate that it means [EAGAIN] The system has no available pseudo-terminal devices.

btw: with firejail --noroot groups I am only in my user's group and nogroup. Is this expected?

It seems like the name --noroot is a bit misleading, because it apparently does much more?!

ghost commented 9 years ago

Personally, I think the --noroot option isn't misleading - it removes the root user. The pt_chown program is attempting to change to the root user (as it's installed SUID root), but firejail has removed the root user! Therefore the pt_chown program can't run, and the program can't allocate a pty.

This is basically exactly what firejail --noroot is meant to do! You've told firejail to remove the root user and block anything it does, for security. All modern Linuxes can work perfectly fine without a SUID program managing pseudo-terminals, whereas your Linux is running a SUID binary in the background without your knowledge. This is a potential security hole and is exactly the kind of thing that firejail was written for.

TL;DR you told firejail to block root, it succeeded.

blueyed commented 9 years ago

Well, grantpt comes from glibc (so it should be pretty common?!).

And it uses pt_chown as a (fallback) helper based on HAVE_PT_CHOWN.

   The  grantpt() function changes the mode and owner of the slave pseudoter‐
   minal device corresponding to the master pseudoterminal referred to by fd.
   The  user  ID  of the slave is set to the real UID of the calling process.
   The group ID is set to an unspecified value (e.g., tty).  The mode of  the
   slave is set to 0620 (crw--w----).

It looks to me like the pt_chown call gets only done, because the regular chown fails, which is because with --noroot the tty group does not exist anymore:

% touch foo
% sudo chgrp tty foo
% chgrp tty foo
% firejail --noroot chgrp tty foo
Parent pid 31667, child pid 31668
Child process initialized
chgrp: changing group of ‘foo’: Invalid argument

parent is shutting down, bye...

% firejail chgrp tty foo
Parent pid 31801, child pid 31802
Child process initialized

parent is shutting down, bye...

chgrp (and chmod) succeed if the group is already set as expected.

netblue30 commented 9 years ago

Not all programs run in "firejail --noroot". In fact, lots of them crash. SUID programs will fail trying to execute root-only operations, because there is no root user in the namespace.

rxvt example on a Debian system:

$ firejail --noroot rxvt 
Parent pid 5793, child pid 5794
Child process initialized
rxvt: can't open pseudo-tty
rxvt: aborting

parent is shutting down, bye...

rxvt is a SUID binary. Just don't use --noroot with SUID binaries.

blueyed commented 9 years ago

Neither rxvt-unicode nor gnome-terminal is SUID on my system (I have not checked rxvt - /etc/alternatives/rxvt points at /usr/bin/urxvt here, but I use /usr/local/bin/urxvt usually).

gnome-terminal fails with grantpt failed: Exec format error when used in the vim-in-term script (which gets called from Firefox via Vimperator). That appears to indicate the same issue.

The workaround seems to be using noroot from the Firefox profile, but I've hoped that there was a better fix for this.

I still think that if the tty group (id) was provided with --noroot then it might work.

blueyed commented 9 years ago

For what it's worth, my hack / workaround for this is currently this:

Index: src/ptytty.C
===================================================================
RCS file: /schmorpforge/libptytty/src/ptytty.C,v
retrieving revision 1.56
diff -u -r1.56 ptytty.C
--- src/ptytty.C    1 May 2015 13:12:17 -0000   1.56
+++ src/ptytty.C    19 Aug 2015 08:49:47 -0000
@@ -87,7 +87,7 @@

     if (pfd >= 0)
       {
-        if (grantpt (pfd) == 0 /* change slave permissions */
+        if ((grantpt (pfd) == 0 || errno == ENOEXEC || errno == EAGAIN)    /* change slave permissions */
             && unlockpt (pfd) == 0)
           {
             /* slave now unlocked */
ghost commented 9 years ago

A simple solution would be to use xterm instead of rxvt, as xterm works fine in --noroot mode. Is there any reason you can't use xterm instead? It would seem to be more secure than rxvt.

netblue30 commented 9 years ago

The issue with --noroot is like this:

I instruct the kernel to create a user namespace, with no root user in the namespace, just the current user and the group associated with the current user. I don't add any supplementary group to the namespace. The kernel code decides what is permitted or not, and it will crash the process according to its own rules. The easy fix is not to use --noroot with some programs. The real fix would be to argue with the kernel people and convince them to change user namespace code - good luck with that!

tty group: users in this group have permission to open /dev/tty. If I add tty group to noroot namespace, everybody will have permission to access /dev/tty directly. I don't think is a good idea . gnome-terminal has lots of problems related with the way they handle the terminal. Like Firefox, they also run a single instance of the program. I usually stay with xterm and lxterminal (from LXDE).

blueyed commented 9 years ago

Thanks for your explanation and suggestion to use xterm, which works. However, my config is tailored for rxvt-unicode and its features in general, and therefore will keep using my above patch instead for now.