D. J. Bernstein
UNIX

Disabling the network

UNIX kernel designers should add a function
     #include <unistd.h>

     void disablenetwork(void);
that stops this process from talking to the network, except through previously connected sockets. This means that any subsequent bind(), connect(), sendto(), or socket() will return EACCES. Exception: AF_UNIX socket activities involve the filesystem, rather than the network, so they are permitted.

This is a security mechanism, so it must be irrevocable, inherited through fork(), and inherited through exec(). It must also be applied to any kernel extensions that provide similar features to connect() et al.

The basic problem

Situation: You want to decompress a stream of data from the network. You don't trust the decompression program. You run the decompression program under a dynamically created uid, so that an attacker who seizes control of the program can't use kill(), ptrace(), etc. You run it in a chroot jail, so that an attacker who seizes control of the program can't write anything to the filesystem. You run it with appropriate data/vmem/nproc resource limits, so that an attacker who seizes control of the program can't chew up much memory.

You'd also like to run it without network access, so that an attacker who seizes control of the program can't use it to launder his network connections. The rest of this web page discusses several mechanisms to disable network access.

Of course, there's nothing special about decompression. Almost everything that the computer does should be partitioned in this way, so that most bugs aren't vulnerabilities. Decompression is simply one example of the importance of partitioning: except on systems with particularly paranoid free() implementations, attackers could seize control of a huge number of programs that used any version of the zlib decompression library from 19980217 zlib 1.0.9 to 20020311 zlib 1.1.4.

Mechanism 1: disablenetwork()

You simply call disablenetwork() before exec()ing the decompression program.

Problem: disablenetwork() is currently unportable. On the other hand, because it's so simple, there's hope of seeing it broadly implemented.

Mechanism 2: jail()

FreeBSD's jail() function combines chroot(), some irrelevant restrictions on uid 0, and an IP-address restriction. The IP address can be set to a bogus address that's configured to have no access to anything else. (It's safe for separate processes to share this address.)

Problem: jail() is unportable, and is not as easy for kernels to support as disablenetwork().

Problem: Address configuration is unportable.

Mechanism 3: ptrace()

By tracing a process, a monitor can catch each call to connect() et al., and can force the calls to fail.

Problem: Tracing is unportable. Every system has a tracing mechanism, but the tracing mechanisms aren't compatible, and no specific mechanism is easy for other kernels to support.

Problem: Tracing is much more painful to use than a simple pre-exec system call.

Problem: On many systems, tracing isn't actually powerful enough to disable specific system calls.

Problem: Tracing is often slow.

Mechanism 4: systrace

Niels Provos's systrace, which is included in OpenBSD and NetBSD, allows all sorts of control over system calls.

Problem: systrace is unportable, and is much more difficult for kernels to support than disablenetwork().

Problem: Although systrace can deny specified system calls with a simple pre-exec system call, it can't enforce more complicated rules (such as allowing AF_UNIX) without ptrace-style user-level monitoring.

Mechanism 5: rsbac

RSBAC, a set of Linux patches, allows all sorts of control over system calls.

Problem: RSBAC is unportable, and is much more difficult for kernels to support than disablenetwork().

Mechanism 6: grsecurity

grsecurity, a set of Linux patches, allows all sorts of control over system calls.

Problem: grsecurity is unportable, and is much more difficult for kernels to support than disablenetwork().

Mechanism 7: selinux

I'm told that SELinux has enough power to simulate disablenetwork(). I'm not sure how painful this is to use; I haven't seen the details.

Problem: The SELinux mechanisms are, of course, unportable.

Mechanism 8: pf

The OpenBSD packet filter, pf, understands the rule
     block quick user blah
to deny network access to user blah.

Problem: pf is unportable, and is much more difficult for kernels to support than disablenetwork().

Problem: A large pool of network-disabled uids appears to require a large (and slow) list of rules.

Mechanism 9: iptables

I'm told that, starting in Linux 2.4.18,
     iptables -I OUTPUT -j DROP -m owner --gid-owner nonet
blocks sockets created by the nonet group ID.

Problem: iptables is unportable, and is much more difficult for kernels to support than disablenetwork().

Mechanism 10: RLIMIT_NOFILE

Setting a hard resource limit of 0 for RLIMIT_NOFILE will prevent the process from creating any new file descriptors, including sockets.

Problem: On most systems, every dynamically linked program needs new file descriptors. Static linking, with one of the most common C libraries, chews up half a megabyte for every program. This is not an insurmountable obstacle: see, e.g., dietlibc.

Problem: Every program with a configuration file needs new file descriptors. This isn't an issue for pure filters such as gunzip, but it requires a redesign of more complicated programs such as PDF-to-bitmap converters.