Breaking down OpenBSD's signature security architecture: syscall sandboxing with pledge, filesystem restrictions with unveil, their application in the base system, and the supporting layers of W^X, KARL, security sysctls, and the WITNESS and MALLOC checks.

In episode 14 you secured the system's entry point with SSH hardening. Now we enter the realm that makes OpenBSD truly unique: securing processes from within. If firewalls and SSH protect from the outside, then pledge and unveil protect from the inside — even if a process is successfully exploited.
Imagine a web application that's been exploited. On other systems, the attacker gains full access like that process: able to read all files, open all connections. On OpenBSD, the application "promised" from the start it would only use certain capabilities — and if it tries for more, the system kills it immediately.
pledge(2) is a syscall that lets a process set promises — the list of allowed syscall capabilities. Once pledged, any syscall outside the promise quickly terminates the process. This is sandboxing different from other mechanisms: it works from "who owns the process" and "which syscalls are allowed".
The most basic example:
#include <unistd.h>
pledge("stdio rpath", NULL);After this line, the program may only use standard I/O (stdio) and read files (rpath). Opening a network connection, writing files, or running other processes — all denied.
| Promise | Meaning |
|---|---|
stdio | Standard I/O and fd inheritance |
rpath | Read files (open read-only) |
wpath | Write and create files |
cpath | Create, change, delete files/directories |
inet | IPv4/IPv6 sockets |
dns | Name resolution (domain sockets) |
proc | Fork and execute processes |
exec | Execute binaries |
In practice, a process sets broad promises at the start, then narrows them after initialization completes. For example: httpd opens its keys, reads its configuration, then sets narrower promises before serving requests.
unveil(2) is pledge's companion: restricting per-process filesystem access. With unveil(path, perms), a process can only see the unveiled paths, with specific permissions:
#include <unistd.h>
unveil("/var/www/htdocs", "r");
unveil("/etc/ssl", "r");
unveil(NULL, NULL);The unveil(NULL, NULL) call locks things down — after that, no new paths can be unveiled. The process can only read /var/www/htdocs and /etc/ssl; access to /etc/passwd, home directories, or any other path is denied by the kernel.
The pledge + unveil combination produces least privilege enforced by the kernel, not just a programmer's good intentions.
The strength of this approach is visible in the fact that almost every program in the base system is already pledged and unveiled. A few examples:
/etc/ssl.cat and ls use them.Verify with ktrace or by inspecting the source. A practical example: run a program that should be able to read many files, but because of unveil, its access fails:
doas httpd -n
tail -f /var/log/httpd.error.logAccess failures caused by unveil usually appear as EACCES or a similar message — a sign that the sandboxing is working.
Info
When developing your own applications on OpenBSD, make pledge and unveil part of the program from the start. Start with broad promises at startup, then narrow them. The kernel will tell you which syscall/path is missing via a stopping signal — a valuable debugging lesson.
pledge and unveil live in the middle of a broader ecosystem of mitigations:
Memory can never be writable and executable at the same time. This blocks many classic exploit techniques: writing shellcode to memory then executing it. Check with:
sysctl kern.wxabort
readelf -l /bin/ls | grep GNU_STACKIf GNU_STACK contains RW, the binary doesn't use W^X — something to be avoided.
Since episode 2, you've known that KARL relinks the kernel randomly on every boot. See that the kernel changes:
sysctl kern.osrelease
ls -la /bsdEvery reboot produces a /bsd with a different address layout — making kernel bug exploitation far harder.
Several security parameters can be reviewed via sysctl:
sysctl kern.randompid
sysctl net.inet.ip.redirect
sysctl net.inet.tcp.syncookieskern.randompid adds random PIDs to processes, syncookies protects against SYN floods. OpenBSD's defaults are already safe; your job is to not weaken them.
The OpenBSD kernel can be built with additional checks to find bugs:
These features are active in development kernels, not production — reinforcing the "test hard, release clean" philosophy.
It's important to be honest: pledge and unveil are only as strong as the applications that use them. Third-party programs that aren't pledged keep running with their normal privileges. That's why on OpenBSD:
In episode 15 you broke down OpenBSD's signature security architecture: syscall sandboxing with pledge which restricts syscalls, unveil which restricts the filesystem, their application in base tools, and the supporting layers of W^X, KARL, security sysctls, and the WITNESS and MALLOC checks.
Key takeaways:
pledge("stdio rpath", NULL) restricts syscalls; unveil(path, "r") restricts the filesystem.unveil(NULL, NULL) locks the access list — nothing more after that.In the next episode, episode 16, we'll build relayd & load balancing — the layer 4/7 relay and load balancer, arranging /etc/relayd.conf, doing health checks, TLS termination, and building a reverse proxy for web services.