Pledge
Seccomp was added in the Linux in March 2005, thanks to Andrea Arcangeli, and landed in 2.6.12. In 2012, coupling BPF for seccomp was suggested, and it’s now the main way of using seccomp.
But Theo de Raadt wasn’t convinced about the BPF:
Some BPF-style approaches have showed up. So you need to write a program to observe your program, to keep things secure? That is insane.
So in July 2015, he suggested a new
syscall, tame
, to
provide features in the spirit of seccomp
: restricting what a program can do.
This syscall was
renamed
to pledge
one month later.
pledge(2) is like
seccomp, but much easier to use thanks
to the named subsets: dns
, stdio
, recvfd
, exec
, …
The loss in granularity is a cheap price to pay in exchange for simplicity of
usage and deployment, for example
seccomp
can’t even be used to sandbox file
,
while it’s trivial to do so
with pledge
.
Its main drawback is that long-running programs using pledge
must be carefully split into
different components: like
Chrome
(2016), or
openntpd with a process for ntp communications, one to set the time and one to
resolve dns. This is doable for software written
and maintained by the OpenBSD people, but won’t scale for big external ones where
maintaining massive patches in sync won’t fly, or the sandboxes
rules
will be close to useless.
For example, while xz or bzip2 have a pretty cool sandbox, Firefox (sandboxed last year) or Suricata are allowing pretty much everything.
In 2022, Landry Breuil gave a talk at the EuroBSDcon 2022 in Vienna on the difficulties encountered when sandboxing firefox, and with the following conclusion:
pledge()
enabled by default since firefox 60 in may 2018unveil()
enabled by default since firefox 71 in december 2019- all upstreamed !
- just works, but somewhat wide promises ?
- codebase not written with sandboxing in mind from the start makes it very hard
- things being slowly moved out to other processes (socket) ?
- a bit ‘raw’, eg either killed or UB/crashes - hard to debug ?
ktrace
is your sole friendMOZ_LOG=OpenBSDSandbox:5
in the env prints unveil/pledge calls at process startup
But for short-running processes that are only doing one task, pledge allows to restrict capacities more and more at runtime, making it possible, for example, to apply restrictions according to flags, like in bzip2.
An interesting data-point is to compare the number of programs using seccomp vs. pledge, as well as their complexity. Seccomp is used by Android, systemd, QEMU, Chrome, vsftpd, openssh, lxd, tor, Firefox, and maybe a couple of others.
Pledge is used in Chromium, OpenSSH, go, cvs, spamd, dhclient, mount, ping, route, openssl, rsync, su, tmux, acme-client, arp, dig, radiusd, … basically, almost all binaries shipped with OpenBSD!
Unfortunately, as of December 2019, some of the rules are still pretty sparse. For example, binaries talking to the network and having either file writing or creating capabilities, or capable of calling setuid-like functions and executing binaries:
$ git grep '(pledge(' | grep inet | grep -E '[wc]path' | grep -v unveil
gnu/usr.bin/cvs/src/main.c:435: if (pledge("stdio rpath wpath cpath fattr getpw proc exec inet dns tty", NULL) == -1)
gnu/usr.bin/cvs/src/main.c:927: if (pledge("stdio rpath wpath cpath fattr getpw inet dns tty", NULL) == -1)
gnu/usr.bin/cvs/src/main.c:930: if (pledge("stdio rpath wpath cpath fattr getpw proc exec inet dns", NULL) == -1)
libexec/login_radius/login_radius.c:94: if (pledge("stdio rpath wpath inet dns tty", NULL) == -1) {
libexec/spamd/grey.c:1093: if (pledge("stdio rpath wpath inet flock proc exec", NULL) == -1) {
libexec/spamlogd/spamlogd.c:468: if (pledge("stdio rpath wpath inet flock", NULL) == -1)
libexec/talkd/talkd.c:84: if (pledge("stdio rpath wpath cpath inet dns", NULL) == -1) {
usr.bin/aucat/aucat.c:1387: if (pledge("stdio rpath wpath cpath inet unix dns audio", NULL) == -1)
usr.bin/ftp/fetch.c:667: if (pledge("stdio rpath wpath cpath inet dns tty", NULL) == -1)
usr.bin/ftp/main.c:499: if (pledge("stdio rpath wpath cpath dns tty inet proc exec fattr",
usr.bin/ftp/main.c:503: if (pledge("stdio rpath wpath cpath dns tty inet fattr",
usr.bin/openssl/ocsp.c:150: if (pledge("stdio cpath wpath rpath inet dns tty", NULL) == -1) {
usr.bin/openssl/openssl.c:402: if (pledge("stdio cpath wpath rpath inet dns proc flock tty", NULL) == -1) {
usr.bin/openssl/s_client.c:337: if (pledge("stdio cpath wpath rpath inet dns tty", NULL) == -1) {
usr.bin/ssh/clientloop.c:1246: if (pledge("stdio rpath wpath cpath unix inet dns recvfd sendfd proc exec id tty",
usr.bin/ssh/clientloop.c:1252: if (pledge("stdio rpath wpath cpath unix inet dns proc exec tty",
usr.bin/ssh/clientloop.c:1258: if (pledge("stdio rpath wpath cpath unix inet dns proc tty",
usr.bin/ssh/clientloop.c:1265: if (pledge("stdio cpath unix inet dns proc tty", NULL) == -1)
usr.bin/telnet/main.c:170: if (pledge("stdio rpath wpath getpw dns inet tty", NULL) == -1) {
usr.bin/tftp/main.c:174: if (pledge("stdio rpath wpath cpath dns inet", NULL) == -1)
usr.sbin/bgpctl/bgpctl.c:131: if (pledge("stdio rpath wpath cpath unix inet dns", NULL) == -1)
usr.sbin/httpd/httpd.c:227: if (pledge("stdio rpath wpath cpath inet dns sendfd", NULL) == -1)
usr.sbin/inetd/inetd.c:347: if (pledge("stdio rpath cpath getpw dns inet unix proc exec id", NULL) == -1)
usr.sbin/nsd/nsd.c:1114: if (pledge("stdio rpath wpath cpath dns inet proc", NULL) == -1)
usr.sbin/rmt/rmt.c:86: if (pledge("stdio rpath wpath cpath inet", NULL) == -1)
usr.sbin/switchctl/switchctl.c:131: if (pledge("stdio rpath wpath inet unix dns", NULL) == -1)
usr.sbin/switchd/switchd.c:207: if (pledge("stdio rpath wpath inet dns sendfd", NULL) == -1)
usr.sbin/tftpd/tftpd.c:393: if (pledge("stdio rpath wpath cpath fattr dns inet", NULL) == -1)
usr.sbin/unbound/smallapp/unbound-anchor.c:2394: if (pledge("stdio rpath wpath cpath inet dns", NULL) == -1)
$ git grep '(pledge(' | grep id | grep exec -c
27
$
But can can only improve over time :)
In 2022, sigsys has been
working
on porting pledge
and unveil
on FreeBSD.
Pledge is a really effective mitigation based on attack surface reduction, useable and used, that doesn’t add complexity nor hinder inspectability. It is what seccomp should have been.