But first, let us discuss …
POSIX documents setrlimit(2). Disabling the ability to open new files, sockets, etc, or create new processes is actually pretty powerful.
Thanks to the privsep dhcpcd now has to support both Capsicum and Pledge, this
turned out to be pretty easy to implement.
The only issue with this is the
interface which dhcpcd makes great use of.
Implementations found on Linux, OpenBSD and Solaris return
EINVAL when the
nfds argument is greater than
RLIMIT_NOFILE where-as the other OS’s dhcpcd
This means that on Linux, OpenBSD and Solaris an attacker could close
an exiting file descriptor and try to open a new one.
On OpenBSD this is not that much of a big deal because pledge should stop
that from happening.
On Linux and Solaris, they can’t open a file thanks to the chrooted empty
directory but they can create a new one.
But thanks to
RLIMIT_FSIZE they can’t actually write to it.
At most they could create a network socket and send arbitary data over it.
For Linux, we could look into using seccomp to stop this.
For implementations such as NetBSD and FreeBSD, setting
RLIMIT_NOFILE to zero
with poll(2) still working means they cannot create any few file descriptor.
This means that if an attacker breaches a resource limited process it
can only work with the resources it has because it cannot fork another
process, nor open any files, sockets, etc.
It’s also running as an unpriviledged user locked in an empty directory,
so there is nothing to see or do there.
All the resources it currently has are:
- PF_INET, PF_LINK, etc sockets that can only query for data
- network proxy process (receives only, does not send)
- BPF processes (send and receive, the read and write filters are also locked)
- privileged actioneer (filters ioctls, validates paths and outbound traffic)
So the only way to do anything outside of what dhcpcd normally does is to use a facility that does not need to create a file or socket, or fork a process. The only remaining avenue of attack is to break the privileged actioneer process- that cannot be protected by any sandboxing as it needs to do a lot of stuff. Both OpenBSD’s and FreeBSD’s dhclient have such a privileged process as well.
The priviledged actioneer process itself doesn’t do a great deal. For example to add a route on BSD you create a RTM_ADD message. The master process will do this and instead of writing to the PF_ROUTE socket itself it will pass the message to the privilged actioneer process which in turn writes to it’s PF_ROUTE socket. Every ioctl, path accessed or network bound packet is validated by the privileged actioneer. Even though it’s generic, it’s also locked down.
Both bring system call filtering. For example, sysctl(2) does not need to create a new resource. Information available to the ordinal user such as uname may not be desirable to leak to these sandboxed processes. But then the question to ask is what can they do with it?
Pledge overcomes the
RLIMIT_NOFILE limitation for poll(2) on OpenBSD.
Capsicum goes a bit futher by limiting rights of each resource you have in terms of what you can do with them.
What they both bring to the table though is making sandboxing easier. Here, Pledge is the outright winner. As I pointed out in my initial blog post, Pledge is really easy. But make it too easy and it’s not as secure as it could be. Capsicum is harder and the resource limited sandbox is harder still.
The take-away point from this is while Capsicum and Pledge are nice, they don’t beat a good design.