Download
76 lines 2.6 KiBGoModified 2026-10-27 raw
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576
//go:build openbsd

package sandbox

import (
	"errors"
	"os"
	"slices"

	"golang.org/x/sys/unix"
)

// system lets the renderer run ssh, and git from OpenBSD's package (the
// renderer adds the git it runs itself): x on them, and /dev/null,
// which os/exec opens for every child's unset stdin. It also names files
// those programs read (the dynamic loader and its libraries, ssh's
// configuration and known_hosts, the resolver), but the programs do not
// need them here: execve(2) starts a program with no unveils (see
// unveil(2)), so git and ssh see the whole filesystem. This list confines
// the renderer itself, not what it runs.
var system = []Path{
	{Path: "/dev/null", Perms: "rw"},
	{Path: "/usr/local/libexec/git", Perms: "rx"},
	{Path: "/usr/local/share/git-core", Perms: "r"},
	{Path: "/bin/sh", Perms: "x"},
	{Path: "/usr/bin/ssh", Perms: "x"},
	{Path: "/usr/libexec/ld.so", Perms: "r"},
	{Path: "/var/run/ld.so.hints", Perms: "r"},
	{Path: "/usr/lib", Perms: "r"},
	{Path: "/usr/local/lib", Perms: "r"},
	{Path: "/etc/ssl", Perms: "r"},
	{Path: "/etc/ssh", Perms: "r"},
	{Path: "/etc/resolv.conf", Perms: "r"},
	{Path: "/etc/hosts", Perms: "r"},
	{Path: "/etc/pwd.db", Perms: "r"},
	{Path: "/etc/localtime", Perms: "r"},
}

// Restrict limits the renderer's filesystem visibility to the given
// paths, plus system, then locks the unveil list. Each entry is a path and
// permission string ("r", "rwc", "x").
//
// An entry whose parent directory does not exist (a home directory of
// /nonexistent, say) is skipped: leaving it out only grants less, and
// whatever needed it fails later with an error that names the real path.
func Restrict(paths []Path) error {
	for _, p := range slices.Concat(system, paths) {
		if err := unix.Unveil(p.Path, p.Perms); err != nil && !errors.Is(err, unix.ENOENT) {
			return err
		}
	}
	return unix.UnveilBlock()
}

// Unveil adds exactly the given paths, without the renderer's system
// list and without locking, so more can follow. A path whose directory
// does not exist is an error here: a caller that names it means to use
// it.
func Unveil(paths []Path) error {
	for _, p := range paths {
		if err := unix.Unveil(p.Path, p.Perms); err != nil {
			return &os.PathError{Op: "unveil " + p.Perms, Path: p.Path, Err: err}
		}
	}
	return nil
}

// Lock forbids further unveil calls.
func Lock() error { return unix.UnveilBlock() }

// Pledge restricts the process to promises. Programs it executes start
// unpledged, as execpromises is left alone.
func Pledge(promises string) error { return unix.PledgePromises(promises) }

// Supported reports whether Unveil and Pledge do anything here.
const Supported = true