$RodHat_
Syscall reference · Sandboxing

unveil(2)

Hide the whole filesystem except the paths you name, with per-path r/w/x/c permissions.

Since
OpenBSD 6.4check your kernel →
System
OpenBSD
Man page
unveil(2)

Synopsis

#include <unistd.h>

int unveil(const char *path, const char *permissions);

Rod's take

pledge decides which syscall families you get; unveil decides which parts of the filesystem exist at all. Name the paths with r, w, x or c, then lock it with unveil(NULL, NULL). Anything you didn't name comes back ENOENT, so a popped process can't even confirm /etc/shadow is there; a named path used the wrong way gets EACCES. You can tighten later, never loosen. Stack it with pledge and a daemon is boxed on both axes in an afternoon. Unveil directories rather than single files; programs misbehave badly when their files quietly vanish.

Flags and arguments that matter

NameWhat it does
rPath available for read operations; corresponds to the pledge "rpath" promise.
wPath available for writes (pledge chown, fattr, wpath); also allows connect(2) to AF_UNIX sockets.
xPath available for execute; corresponds to the pledge "exec" promise.
cPath may be created and removed; corresponds to pledge cpath, dpath and unix.
unveil(NULL, NULL)Lock: no further unveil() calls allowed. A pledge without the "unveil" promise also locks it.

Errors you will actually hit

errnoMeaning
EPERMAttempt to increase permissions, path not accessible, or unveil() called after locking.
E2BIGAdding path would exceed the per-process limit on unveiled paths.
ENOENTA directory in path did not exist.
EINVALInvalid permissions string.

Example

/* privilege-separated worker: docroot only, then lock */
void worker_setup(const char *docroot) {
    if (unveil(docroot, "r") == -1)
        err(1, "unveil docroot");
    if (unveil(NULL, NULL) == -1)
        err(1, "unveil lock");
    if (pledge("stdio rpath inet sendfd", NULL) == -1)
        err(1, "pledge");
}

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: Landlock (landlock_create_ruleset(2)) is the closest path-based restriction, though denied paths aren't made invisible. FreeBSD: Capsicum cap_enter(2) with pre-opened directory fds.

The long version, from the console

Sources

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

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