$RodHat_
Syscall reference · Sandboxing

pledge(2)

Restrict the process to named groups of system operations; violations kill it with SIGABRT.

Since
OpenBSD 5.9check your kernel →
System
OpenBSD
Man page
pledge(2)

Synopsis

#include <unistd.h>

int pledge(const char *promises, const char *execpromises);

Rod's take

One syscall, one string. Call pledge("stdio inet dns", NULL) once setup is done and that's all the process gets; step outside it and the kernel kills it with SIGABRT, core and all. Do the broad work first, open the config and the log file, then narrow. Later calls can only take promises away, never add them back. Compare Linux, where the same box means a seccomp filter, Landlock rules and no_new_privs: three APIs, three ways to screw it up. OpenBSD retrofitted nearly its whole base system with this. That's your proof the ergonomics are right.

Flags and arguments that matter

NameWhat it does
stdioBaseline: I/O on already-open fds, memory management, clocks, process info. Almost always needed.
rpathPath traversal, reading struct stat, and opening files for reading.
wpathLike rpath, but files can be opened for writing.
cpathLike wpath, but files can also be created and removed.
inetsocket/connect/bind/listen/accept and friends in AF_INET and AF_INET6, with reduced setsockopt.
dnsResolver support: reads resolv.conf, hosts, services; sockets restricted to SOCK_DNS.
procfork, vfork, kill, setpgid, setsid and related process-relationship calls. Not execve.
execAllow execve(2). The new program starts with execpromises if set, otherwise unpledged.
sendfd / recvfdSend or receive file descriptors over sendmsg/recvmsg. Directory fds may not be passed.
errorReturn ENOSYS on violation instead of killing the process; requests for more promises are ignored.

Errors you will actually hit

errnoMeaning
EPERMThe process tried to increase its permissions with a later pledge() call.
EINVALpromises is malformed or contains an invalid keyword.
EFAULTpromises or execpromises points outside the process's address space.

Example

FILE *cfg = fopen("/etc/myapp/config", "r");
if (cfg == NULL) err(1, "fopen config");
parse_config(cfg);
fclose(cfg);

FILE *log = fopen("/var/log/myapp.log", "a");
if (log == NULL) err(1, "fopen log");

/* setup done: no filesystem, no exec, no fork from here */
if (pledge("stdio inet dns", NULL) == -1)
    err(1, "pledge");

run_daemon(log);

Trimmed from the full walkthrough in pledge(2) and unveil(2): what OpenBSD figured out that Linux is still catching up to.

Linux equivalent

Linux: no single equivalent; combine seccomp(2) filters, Landlock (landlock_create_ruleset(2)) and prctl(PR_SET_NO_NEW_PRIVS). FreeBSD: Capsicum, cap_enter(2) and cap_rights_limit(2).

The long version, from the console

Sources

  1. pledge(2), OpenBSD
  2. cap_enter(2), FreeBSD

Version numbers are read off the cited man pages, not memory. Spot one that's wrong? Tell Rod.