NAME
Fugu::Sandbox - pledge and unveil as a platform abstraction
SYNOPSIS
use Fugu::Sandbox;
Fugu::Sandbox->unveil(
paths => [
['/var/db/mydaemon', 'rwc'],
['/etc/mydaemon.conf', 'r', { optional => 1 }],
Fugu::Sandbox->system_paths,
],
on_skip => sub ($path) { ... },
);
Fugu::Sandbox->unveil_lock;
Fugu::Sandbox->pledge(promises => 'stdio rpath inet');
DESCRIPTION
Fugu::Sandbox wraps pledge(2) and unveil(2) so that callers do not test the operating system themselves. On OpenBSD, the restrictions are real. On all other platforms, each method is a no-op that returns success. is_supported() tells a caller or a test which of the two it gets.
All methods are class methods. There is no object because the two syscalls that the module wraps are process-global. A method that fails dies with the promise string or the path that caused the failure, and with the errno. There is no force flag and no warn-and-continue mode. The module never logs. The caller decides what to report.
Two methods, perl_lib_dirs() and system_paths(), assemble path lists and call no syscall. They never touch the filesystem view. Thus a unit test can prove the inventory of a daemon on any platform.
On OpenBSD, Fugu::Sandbox loads the base-system modules OpenBSD::Pledge and OpenBSD::Unveil when it loads. If these modules do not load, the process dies immediately. On that platform, the absence of these modules shows a broken perl and not an unsupported system.
is_supported
is_supported() returns true only where the restrictions are real, that is, on OpenBSD.
pledge
pledge(promises => $string) limits the process to the space-separated set of promises, as pledge(2) defines it. The method returns 1. On failure, it dies and gives the promises and the error. The first call defines the sandbox. Later calls can only make it smaller.
unveil
unveil(paths => arrayref of pairs, on_skip => sub) limits the filesystem view to the listed paths with the listed unveil(2) permission strings. The argument is an ordered list of pairs, not a hash. unveil(2) replaces the permissions of a path and does not merge them. Thus the order of parent before child is necessary, and the key order of a hash makes that order random.
Each entry can have { optional => 1 } as a third element. If a necessary path is absent, the method dies and gives the path. If the method accepts a path with a typo and gives no report, unveil becomes useless. If an optional path is absent, the method skips it and reports it through the on_skip callback. The optional flag is for paths that can be absent on a system that operates correctly. Examples are a configuration file and the socket of a daemon that does not run. The method returns 1.
unveil_lock
unveil_lock() prevents more unveil() calls and locks the view for the life of the process. The method returns 1. It dies on failure.
perl_lib_dirs
perl_lib_dirs() returns the library directories of the perl that runs. The values are privlibexp, archlibexp, sitelibexp and sitearchexp from Config.
These are stable facts about the interpreter build. They are not the live @INC. A program adds directories to @INC at run time, and a module can add one later still. A daemon that unveils the library tree for a late require must name the stable set, and never a set that depends on the moment of the call.
system_paths
system_paths() returns the read-only unveil() inventory that every daemon repeats: /dev/urandom, /etc/resolv.conf, /etc/hosts, /etc/services, /etc/protocols and /etc/localtime. The entries are ready for unveil(). All of them carry the optional flag except /dev/urandom, which every system has. A resolver file is absent on a host with no name service, and that host still runs the daemon.
RETURN VALUES
perl_lib_dirs() returns a list of directory names.
system_paths() returns a list of unveil() entries.
SEE ALSO
pledge(2), unveil(2), Config, OpenBSD::Pledge, OpenBSD::Unveil
AUTHORS
Dick Olsson <hi@senzilla.io>