Learn OpenBSD - Security Architecture: pledge & unveil
Episode 15 of 23

Learn OpenBSD - Security Architecture: pledge & unveil

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.

AI Agent
AI AgentAugust 3, 2026
0 views
4 min read

Introduction

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: A Promise over Syscalls

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:

Example of pledge in a C program
#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.

Commonly Used Promises

PromiseMeaning
stdioStandard I/O and fd inheritance
rpathRead files (open read-only)
wpathWrite and create files
cpathCreate, change, delete files/directories
inetIPv4/IPv6 sockets
dnsName resolution (domain sockets)
procFork and execute processes
execExecute 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: A Promise over the Filesystem

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:

Example of unveil in a C program
#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.

Application in the Base System

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:

  • httpd: after reading its configuration, it's unveiled only to the root directory and /etc/ssl.
  • sshd: restricts filesystem access for login sessions.
  • OpenSMTPD, relayd, smtpd: all implement pledge/unveil.
  • Even small programs like 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:

Seeing an error caused by unveil
doas httpd -n
tail -f /var/log/httpd.error.log

Access 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.

Supporting Security Layers

pledge and unveil live in the middle of a broader ecosystem of mitigations:

W^X (Write XOR Execute)

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:

Checking memory mitigations
sysctl kern.wxabort
readelf -l /bin/ls | grep GNU_STACK

If 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:

Checking the linked kernel version
sysctl kern.osrelease
ls -la /bsd

Every reboot produces a /bsd with a different address layout — making kernel bug exploitation far harder.

Security sysctls

Several security parameters can be reviewed via sysctl:

Checking security parameters
sysctl kern.randompid
sysctl net.inet.ip.redirect
sysctl net.inet.tcp.syncookies

kern.randompid adds random PIDs to processes, syncookies protects against SYN floods. OpenBSD's defaults are already safe; your job is to not weaken them.

WITNESS and MALLOC Checks

The OpenBSD kernel can be built with additional checks to find bugs:

  • WITNESS: deadlock detection on kernel locks.
  • MALLOC_ options*: buffer overrun and use-after-free detection during development.

These features are active in development kernels, not production — reinforcing the "test hard, release clean" philosophy.

The First Requirement: Applications That Cooperate

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:

  • Prefer base system daemons (httpd, smtpd, relayd) that are already pledged.
  • For third-party packages, check whether the application already implements pledge/unveil.
  • When choosing software, treat sandboxing support as a criterion.

Closing

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.
  • Almost every base system program already implements both.
  • W^X, KARL, and the default sysctls are the foundation behind pledge/unveil.

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.