New issue
Advanced search Search tips

Issue 810880 link

Starred by 2 users

Issue metadata

Status: Available
Owner: ----
Cc:
Components:
EstimatedDays: ----
NextAction: ----
OS: Linux , Android
Pri: 3
Type: Bug



Sign in to add a comment

seccomp baseline policy permits sendmsg() to any accessible, bound UNIX datagram socket

Project Member Reported by jannh@google.com, Feb 9 2018

Issue description

Chrome Version: 64.0.3282.140
OS: Linux


The Linux seccomp sandbox permits sendmsg() and sendto() on arbitrary targets. In the source:

https://cs.chromium.org/chromium/src/sandbox/linux/seccomp-bpf-helpers/syscall_sets.cc?type=cs&q=__NR_sendto&l=561

In the disassembled BPF of a renderer:

0070     if nr < 0x00000031: [true +98, false +25] -> ret ALLOW (syscalls: sendto, recvfrom, sendmsg, recvmsg, shutdown)


The Linux seccomp policy also permits the creation of UNIX domain sockets of arbitrary type. In the source:

https://cs.chromium.org/chromium/src/sandbox/linux/seccomp-bpf-helpers/baseline_policy.cc?type=cs&q=__NR_socketpair&l=207

In the disassembled BPF of a renderer:

006d     if nr >= 0x00000033: [true +0, false +2]
006e       if nr < 0x00000035: [true +91, false +0] -> ret TRAP
01a7       if args[0].high == 0x00000000: [true +3, false +0]
01ac         if args[0].low == 0x00000001: [true +39, false +40] -> ret ALLOW (syscalls: socketpair)
01d5         ret TRAP
01a8       if args[0].high != 0xffffffff: [true +39, false +0] -> ret TRAP
01aa       if args[0].low NO-COMMON-BITS 0x80000000: [true +37, false +0] -> ret TRAP
01ac       if args[0].low == 0x00000001: [true +39, false +40] -> ret ALLOW (syscalls: socketpair)
01d5       ret TRAP


The send(2) manpage recommends, without describing what happens when this guidance is violated:

       The msg_name field is used on an unconnected socket to specify the target address for  a  data‐
       gram.  It points to a buffer containing the address; the msg_namelen field should be set to the
       size of the address.  For a connected socket, these fields should be specified as NULL  and  0,
       respectively.

The actual behavior seems to be (determined by reading kernel sources, verified with a small demo program) that unix_dgram_sendmsg() prefers the target address provided by the caller, ignoring whether the socket is already connected to another address.



Demo program:

$ cat receiver.c
#include <err.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>

int main(void) {
  int s = socket(AF_UNIX, SOCK_DGRAM, 0);
  if (s == -1)
    err(1, "socket");
  struct sockaddr_un addr = {
    .sun_family = AF_UNIX,
    .sun_path = "\0asdf"
  };
  if (bind(s, (struct sockaddr *)&addr, sizeof(sa_family_t)+5))
    err(1, "bind");
  while (1) {
    char msg[1000];
    int len = read(s, msg, sizeof(msg));
    if (len <= 0)
      err(1, "read");
    write(1, msg, len);
  }
}
$ gcc -o receiver receiver.c && ./receiver &
[1] 113707
$ cat sender.c
#define _GNU_SOURCE
#include <err.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
#include <stdio.h>
#include <sched.h>

int main(void) {
  int socks[2];
  // allowed: https://cs.chromium.org/chromium/src/sandbox/linux/seccomp-bpf-helpers/baseline_policy.cc?q=__NR_socketpair&l=207&dr=C
  if (socketpair(AF_UNIX, SOCK_DGRAM, 0, socks))
    err(1, "socketpair");
  struct sockaddr_un addr = {
    .sun_family = AF_UNIX,
    .sun_path = "\0asdf"
  };
  #define MSG "hello\n"
  // allowed, with comment warning about specifying destination being possible:
  // https://cs.chromium.org/chromium/src/sandbox/linux/seccomp-bpf-helpers/syscall_sets.cc?type=cs&q=__NR_sendto&l=561
  int res = sendto(socks[0], MSG, sizeof(MSG), 0, (struct sockaddr *)&addr, sizeof(sa_family_t)+5);
  printf("res = %d\n", res);
}
$ gcc -o sender sender.c
$ ./sender
hello
res = 7
$ 


Beyond permitting sending datagrams to bound unix datagram sockets (in the filesystem and in the abstract namespace), this also makes it possible to test whether given paths exist in the filesystem by checking error codes:

$ cat send_bad_test.c 
#define _GNU_SOURCE
#include <err.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
#include <stdio.h>

int main(int argc, char **argv) {
  int socks[2];
  if (socketpair(AF_UNIX, SOCK_DGRAM, 0, socks))
    err(1, "socketpair");
  struct sockaddr_un addr = {
    .sun_family = AF_UNIX
  };
  strcpy(addr.sun_path, argv[1]);
  #define MSG "hello\n"
  int res = sendto(socks[0], MSG, sizeof(MSG), 0, (struct sockaddr *)&addr, sizeof(addr));
  if (res == -1)
    err(1, "sendto");
}
$ gcc -o send_bad_test send_bad_test.c
$ ./send_bad_test /etc/passwd
send_bad_test: sendto: Permission denied
$ ./send_bad_test /etc/nosuchfile
send_bad_test: sendto: No such file or directory
$ 


As far as I can tell, it would be hard to restrict sendmsg() because all the interesting parameters are stored in memory. However, I think that it should be possible to lock down the second argument of socketpair() to only permit SOCK_STREAM and SOCK_SEQPACKET, which seem to be the types with which Chrome normally invokes socketpair(), and thereby prevent the sandboxed code from ever getting a SOCK_DGRAM socket on which sendto()/sendmsg() could be called.


I am not marking this as a security bug because, apart from the already limited impact, this seems to be fully mitigated by the combination of chroot() (for filesystem stuff) and network namespace (for the abstract namespace) on Linux, and on Android, the policy seems to deliberately permit even connect() to UNIX domain sockets from inside the sandbox (https://cs.chromium.org/chromium/src/sandbox/linux/seccomp-bpf-helpers/baseline_policy_android.cc?q=__NR_connect&l=138&dr=C).
 
Cc: thomasanderson@chromium.org

Comment 2 by rsesek@chromium.org, Feb 28 2018

Cc: jorgelo@chromium.org
Components: Internals>Sandbox
Labels: OS-Android
Status: Available (was: Untriaged)
Cc: mpdenton@chromium.org

Sign in to add a comment