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).
Comment 1 by manoranj...@chromium.org
, Feb 10 2018