app: run in native sandbox
Some checks failed
Test / Create distribution (push) Successful in 26s
Test / Fortify (push) Successful in 2m32s
Test / Data race detector (push) Successful in 4m9s
Test / Fpkg (push) Failing after 17m10s
Test / Flake checks (push) Has been skipped

Signed-off-by: Ophestra <cat@gensokyo.uk>
This commit is contained in:
Ophestra 2025-03-25 01:52:49 +09:00
parent e732dca762
commit 4e8f72022d
Signed by: cat
SSH Key Fingerprint: SHA256:gQ67O0enBZ7UdZypgtspB2FDM1g3GVw8nX0XSdcFw8Q
33 changed files with 934 additions and 1221 deletions

View File

@ -4,12 +4,15 @@ import (
"encoding/json" "encoding/json"
"log" "log"
"os" "os"
"path"
"git.gensokyo.uk/security/fortify/dbus" "git.gensokyo.uk/security/fortify/dbus"
"git.gensokyo.uk/security/fortify/fst"
"git.gensokyo.uk/security/fortify/sandbox/seccomp"
"git.gensokyo.uk/security/fortify/system" "git.gensokyo.uk/security/fortify/system"
) )
type bundleInfo struct { type appInfo struct {
Name string `json:"name"` Name string `json:"name"`
Version string `json:"version"` Version string `json:"version"`
@ -20,13 +23,15 @@ type bundleInfo struct {
// passed through to [fst.Config] // passed through to [fst.Config]
Groups []string `json:"groups,omitempty"` Groups []string `json:"groups,omitempty"`
// passed through to [fst.Config] // passed through to [fst.Config]
UserNS bool `json:"userns,omitempty"` Devel bool `json:"devel,omitempty"`
// passed through to [fst.Config]
Userns bool `json:"userns,omitempty"`
// passed through to [fst.Config] // passed through to [fst.Config]
Net bool `json:"net,omitempty"` Net bool `json:"net,omitempty"`
// passed through to [fst.Config] // passed through to [fst.Config]
Dev bool `json:"dev,omitempty"` Dev bool `json:"dev,omitempty"`
// passed through to [fst.Config] // passed through to [fst.Config]
NoNewSession bool `json:"no_new_session,omitempty"` Tty bool `json:"tty,omitempty"`
// passed through to [fst.Config] // passed through to [fst.Config]
MapRealUID bool `json:"map_real_uid,omitempty"` MapRealUID bool `json:"map_real_uid,omitempty"`
// passed through to [fst.Config] // passed through to [fst.Config]
@ -38,11 +43,9 @@ type bundleInfo struct {
// passed through to [fst.Config] // passed through to [fst.Config]
Enablements system.Enablements `json:"enablements"` Enablements system.Enablements `json:"enablements"`
// passed through inverted to [bwrap.SyscallPolicy] // passed through to [fst.Config]
Devel bool `json:"devel,omitempty"`
// passed through to [bwrap.SyscallPolicy]
Multiarch bool `json:"multiarch,omitempty"` Multiarch bool `json:"multiarch,omitempty"`
// passed through to [bwrap.SyscallPolicy] // passed through to [fst.Config]
Bluetooth bool `json:"bluetooth,omitempty"` Bluetooth bool `json:"bluetooth,omitempty"`
// allow gpu access within sandbox // allow gpu access within sandbox
@ -59,8 +62,63 @@ type bundleInfo struct {
ActivationPackage string `json:"activation_package"` ActivationPackage string `json:"activation_package"`
} }
func loadBundleInfo(name string, beforeFail func()) *bundleInfo { func (app *appInfo) toFst(pathSet *appPathSet, argv []string, flagDropShell bool) *fst.Config {
bundle := new(bundleInfo) config := &fst.Config{
ID: app.ID,
Args: argv,
Confinement: fst.ConfinementConfig{
AppID: app.AppID,
Groups: app.Groups,
Username: "fortify",
Inner: path.Join("/data/data", app.ID),
Outer: pathSet.homeDir,
Sandbox: &fst.SandboxConfig{
Hostname: formatHostname(app.Name),
Devel: app.Devel,
Userns: app.Userns,
Net: app.Net,
Dev: app.Dev,
Tty: app.Tty || flagDropShell,
MapRealUID: app.MapRealUID,
DirectWayland: app.DirectWayland,
Filesystem: []*fst.FilesystemConfig{
{Src: path.Join(pathSet.nixPath, "store"), Dst: "/nix/store", Must: true},
{Src: pathSet.metaPath, Dst: path.Join(fst.Tmp, "app"), Must: true},
{Src: "/etc/resolv.conf"},
{Src: "/sys/block"},
{Src: "/sys/bus"},
{Src: "/sys/class"},
{Src: "/sys/dev"},
{Src: "/sys/devices"},
},
Link: [][2]string{
{app.CurrentSystem, "/run/current-system"},
{"/run/current-system/sw/bin", "/bin"},
{"/run/current-system/sw/bin", "/usr/bin"},
},
Etc: path.Join(pathSet.cacheDir, "etc"),
AutoEtc: true,
},
ExtraPerms: []*fst.ExtraPermConfig{
{Path: dataHome, Execute: true},
{Ensure: true, Path: pathSet.baseDir, Read: true, Write: true, Execute: true},
},
SystemBus: app.SystemBus,
SessionBus: app.SessionBus,
Enablements: app.Enablements,
},
}
if app.Multiarch {
config.Confinement.Sandbox.Seccomp |= seccomp.FlagMultiarch
}
if app.Bluetooth {
config.Confinement.Sandbox.Seccomp |= seccomp.FlagBluetooth
}
return config
}
func loadAppInfo(name string, beforeFail func()) *appInfo {
bundle := new(appInfo)
if f, err := os.Open(name); err != nil { if f, err := os.Open(name); err != nil {
beforeFail() beforeFail()
log.Fatalf("cannot open bundle: %v", err) log.Fatalf("cannot open bundle: %v", err)

View File

@ -12,9 +12,7 @@ import (
"git.gensokyo.uk/security/fortify/command" "git.gensokyo.uk/security/fortify/command"
"git.gensokyo.uk/security/fortify/fst" "git.gensokyo.uk/security/fortify/fst"
"git.gensokyo.uk/security/fortify/helper/bwrap"
"git.gensokyo.uk/security/fortify/internal" "git.gensokyo.uk/security/fortify/internal"
"git.gensokyo.uk/security/fortify/internal/app/init0"
"git.gensokyo.uk/security/fortify/internal/app/shim" "git.gensokyo.uk/security/fortify/internal/app/shim"
"git.gensokyo.uk/security/fortify/internal/fmsg" "git.gensokyo.uk/security/fortify/internal/fmsg"
"git.gensokyo.uk/security/fortify/internal/sys" "git.gensokyo.uk/security/fortify/internal/sys"
@ -39,7 +37,6 @@ func init() {
func main() { func main() {
// early init path, skips root check and duplicate PR_SET_DUMPABLE // early init path, skips root check and duplicate PR_SET_DUMPABLE
sandbox.TryArgv0(fmsg.Output{}, fmsg.Prepare, internal.InstallFmsg) sandbox.TryArgv0(fmsg.Output{}, fmsg.Prepare, internal.InstallFmsg)
init0.TryArgv0()
if err := sandbox.SetDumpable(sandbox.SUID_DUMP_DISABLE); err != nil { if err := sandbox.SetDumpable(sandbox.SUID_DUMP_DISABLE); err != nil {
log.Printf("cannot set SUID_DUMP_DISABLE: %s", err) log.Printf("cannot set SUID_DUMP_DISABLE: %s", err)
@ -65,9 +62,7 @@ func main() {
Flag(&flagVerbose, "v", command.BoolFlag(false), "Print debug messages to the console"). Flag(&flagVerbose, "v", command.BoolFlag(false), "Print debug messages to the console").
Flag(&flagDropShell, "s", command.BoolFlag(false), "Drop to a shell in place of next fortify action") Flag(&flagDropShell, "s", command.BoolFlag(false), "Drop to a shell in place of next fortify action")
// internal commands
c.Command("shim", command.UsageInternal, func([]string) error { shim.Main(); return errSuccess }) c.Command("shim", command.UsageInternal, func([]string) error { shim.Main(); return errSuccess })
c.Command("init", command.UsageInternal, func([]string) error { init0.Main(); return errSuccess })
{ {
var ( var (
@ -124,7 +119,7 @@ func main() {
Parse bundle and app metadata, do pre-install checks. Parse bundle and app metadata, do pre-install checks.
*/ */
bundle := loadBundleInfo(path.Join(workDir, "bundle.json"), cleanup) bundle := loadAppInfo(path.Join(workDir, "bundle.json"), cleanup)
pathSet := pathSetByApp(bundle.ID) pathSet := pathSetByApp(bundle.ID)
app := bundle app := bundle
@ -140,7 +135,7 @@ func main() {
log.Printf("metadata path %q is not a file", pathSet.metaPath) log.Printf("metadata path %q is not a file", pathSet.metaPath)
return syscall.EBADMSG return syscall.EBADMSG
} else { } else {
app = loadBundleInfo(pathSet.metaPath, cleanup) app = loadAppInfo(pathSet.metaPath, cleanup)
if app.ID != bundle.ID { if app.ID != bundle.ID {
cleanup() cleanup()
log.Printf("app %q claims to have identifier %q", log.Printf("app %q claims to have identifier %q",
@ -273,7 +268,7 @@ func main() {
id := args[0] id := args[0]
pathSet := pathSetByApp(id) pathSet := pathSetByApp(id)
app := loadBundleInfo(pathSet.metaPath, func() {}) app := loadAppInfo(pathSet.metaPath, func() {})
if app.ID != id { if app.ID != id {
log.Printf("app %q claims to have identifier %q", id, app.ID) log.Printf("app %q claims to have identifier %q", id, app.ID)
return syscall.EBADE return syscall.EBADE
@ -322,51 +317,7 @@ func main() {
} }
argv = append(argv, args[1:]...) argv = append(argv, args[1:]...)
config := &fst.Config{ config := app.toFst(pathSet, argv, flagDropShell)
ID: app.ID,
Command: argv,
Confinement: fst.ConfinementConfig{
AppID: app.AppID,
Groups: app.Groups,
Username: "fortify",
Inner: path.Join("/data/data", app.ID),
Outer: pathSet.homeDir,
Sandbox: &fst.SandboxConfig{
Hostname: formatHostname(app.Name),
UserNS: app.UserNS,
Net: app.Net,
Dev: app.Dev,
Syscall: &bwrap.SyscallPolicy{DenyDevel: !app.Devel, Multiarch: app.Multiarch, Bluetooth: app.Bluetooth},
NoNewSession: app.NoNewSession || flagDropShell,
MapRealUID: app.MapRealUID,
DirectWayland: app.DirectWayland,
Filesystem: []*fst.FilesystemConfig{
{Src: path.Join(pathSet.nixPath, "store"), Dst: "/nix/store", Must: true},
{Src: pathSet.metaPath, Dst: path.Join(fst.Tmp, "app"), Must: true},
{Src: "/etc/resolv.conf"},
{Src: "/sys/block"},
{Src: "/sys/bus"},
{Src: "/sys/class"},
{Src: "/sys/dev"},
{Src: "/sys/devices"},
},
Link: [][2]string{
{app.CurrentSystem, "/run/current-system"},
{"/run/current-system/sw/bin", "/bin"},
{"/run/current-system/sw/bin", "/usr/bin"},
},
Etc: path.Join(pathSet.cacheDir, "etc"),
AutoEtc: true,
},
ExtraPerms: []*fst.ExtraPermConfig{
{Path: dataHome, Execute: true},
{Ensure: true, Path: pathSet.baseDir, Read: true, Write: true, Execute: true},
},
SystemBus: app.SystemBus,
SessionBus: app.SessionBus,
Enablements: app.Enablements,
},
}
/* /*
Expose GPU devices. Expose GPU devices.

View File

@ -11,14 +11,14 @@ import (
func mustRunApp(ctx context.Context, config *fst.Config, beforeFail func()) { func mustRunApp(ctx context.Context, config *fst.Config, beforeFail func()) {
rs := new(fst.RunState) rs := new(fst.RunState)
a := app.MustNew(std) a := app.MustNew(ctx, std)
if sa, err := a.Seal(config); err != nil { if sa, err := a.Seal(config); err != nil {
fmsg.PrintBaseError(err, "cannot seal app:") fmsg.PrintBaseError(err, "cannot seal app:")
rs.ExitCode = 1 rs.ExitCode = 1
} else { } else {
// this updates ExitCode // this updates ExitCode
app.PrintRunStateErr(rs, sa.Run(ctx, rs)) app.PrintRunStateErr(rs, sa.Run(rs))
} }
if rs.ExitCode != 0 { if rs.ExitCode != 0 {

View File

@ -6,18 +6,18 @@ import (
"strings" "strings"
"git.gensokyo.uk/security/fortify/fst" "git.gensokyo.uk/security/fortify/fst"
"git.gensokyo.uk/security/fortify/helper/bwrap"
"git.gensokyo.uk/security/fortify/internal" "git.gensokyo.uk/security/fortify/internal"
"git.gensokyo.uk/security/fortify/sandbox/seccomp"
) )
func withNixDaemon( func withNixDaemon(
ctx context.Context, ctx context.Context,
action string, command []string, net bool, updateConfig func(config *fst.Config) *fst.Config, action string, command []string, net bool, updateConfig func(config *fst.Config) *fst.Config,
app *bundleInfo, pathSet *appPathSet, dropShell bool, beforeFail func(), app *appInfo, pathSet *appPathSet, dropShell bool, beforeFail func(),
) { ) {
mustRunAppDropShell(ctx, updateConfig(&fst.Config{ mustRunAppDropShell(ctx, updateConfig(&fst.Config{
ID: app.ID, ID: app.ID,
Command: []string{shellPath, "-lc", "rm -f /nix/var/nix/daemon-socket/socket && " + Args: []string{shellPath, "-lc", "rm -f /nix/var/nix/daemon-socket/socket && " +
// start nix-daemon // start nix-daemon
"nix-daemon --store / & " + "nix-daemon --store / & " +
// wait for socket to appear // wait for socket to appear
@ -34,11 +34,11 @@ func withNixDaemon(
Inner: path.Join("/data/data", app.ID), Inner: path.Join("/data/data", app.ID),
Outer: pathSet.homeDir, Outer: pathSet.homeDir,
Sandbox: &fst.SandboxConfig{ Sandbox: &fst.SandboxConfig{
Hostname: formatHostname(app.Name) + "-" + action, Hostname: formatHostname(app.Name) + "-" + action,
UserNS: true, // nix sandbox requires userns Userns: true, // nix sandbox requires userns
Net: net, Net: net,
Syscall: &bwrap.SyscallPolicy{Multiarch: true}, Seccomp: seccomp.FlagMultiarch,
NoNewSession: dropShell, Tty: dropShell,
Filesystem: []*fst.FilesystemConfig{ Filesystem: []*fst.FilesystemConfig{
{Src: pathSet.nixPath, Dst: "/nix", Write: true, Must: true}, {Src: pathSet.nixPath, Dst: "/nix", Write: true, Must: true},
}, },
@ -61,19 +61,19 @@ func withNixDaemon(
func withCacheDir( func withCacheDir(
ctx context.Context, ctx context.Context,
action string, command []string, workDir string, action string, command []string, workDir string,
app *bundleInfo, pathSet *appPathSet, dropShell bool, beforeFail func()) { app *appInfo, pathSet *appPathSet, dropShell bool, beforeFail func()) {
mustRunAppDropShell(ctx, &fst.Config{ mustRunAppDropShell(ctx, &fst.Config{
ID: app.ID, ID: app.ID,
Command: []string{shellPath, "-lc", strings.Join(command, " && ")}, Args: []string{shellPath, "-lc", strings.Join(command, " && ")},
Confinement: fst.ConfinementConfig{ Confinement: fst.ConfinementConfig{
AppID: app.AppID, AppID: app.AppID,
Username: "nixos", Username: "nixos",
Inner: path.Join("/data/data", app.ID, "cache"), Inner: path.Join("/data/data", app.ID, "cache"),
Outer: pathSet.cacheDir, // this also ensures cacheDir via shim Outer: pathSet.cacheDir, // this also ensures cacheDir via shim
Sandbox: &fst.SandboxConfig{ Sandbox: &fst.SandboxConfig{
Hostname: formatHostname(app.Name) + "-" + action, Hostname: formatHostname(app.Name) + "-" + action,
Syscall: &bwrap.SyscallPolicy{Multiarch: true}, Seccomp: seccomp.FlagMultiarch,
NoNewSession: dropShell, Tty: dropShell,
Filesystem: []*fst.FilesystemConfig{ Filesystem: []*fst.FilesystemConfig{
{Src: path.Join(workDir, "nix"), Dst: "/nix", Must: true}, {Src: path.Join(workDir, "nix"), Dst: "/nix", Must: true},
{Src: workDir, Dst: path.Join(fst.Tmp, "bundle"), Must: true}, {Src: workDir, Dst: path.Join(fst.Tmp, "bundle"), Must: true},
@ -97,7 +97,7 @@ func withCacheDir(
func mustRunAppDropShell(ctx context.Context, config *fst.Config, dropShell bool, beforeFail func()) { func mustRunAppDropShell(ctx context.Context, config *fst.Config, dropShell bool, beforeFail func()) {
if dropShell { if dropShell {
config.Command = []string{shellPath, "-l"} config.Args = []string{shellPath, "-l"}
mustRunApp(ctx, config, beforeFail) mustRunApp(ctx, config, beforeFail)
beforeFail() beforeFail()
internal.Exit(0) internal.Exit(0)

View File

@ -2,7 +2,6 @@
package fst package fst
import ( import (
"context"
"time" "time"
) )
@ -19,7 +18,7 @@ type App interface {
type SealedApp interface { type SealedApp interface {
// Run commits sealed system setup and starts the app process. // Run commits sealed system setup and starts the app process.
Run(ctx context.Context, rs *RunState) error Run(rs *RunState) error
} }
// RunState stores the outcome of a call to [SealedApp.Run]. // RunState stores the outcome of a call to [SealedApp.Run].

View File

@ -2,7 +2,7 @@ package fst
import ( import (
"git.gensokyo.uk/security/fortify/dbus" "git.gensokyo.uk/security/fortify/dbus"
"git.gensokyo.uk/security/fortify/helper/bwrap" "git.gensokyo.uk/security/fortify/sandbox/seccomp"
"git.gensokyo.uk/security/fortify/system" "git.gensokyo.uk/security/fortify/system"
) )
@ -14,8 +14,11 @@ type Config struct {
// passed to wayland security-context-v1 as application ID // passed to wayland security-context-v1 as application ID
// and used as part of defaults in dbus session proxy // and used as part of defaults in dbus session proxy
ID string `json:"id"` ID string `json:"id"`
// final argv, passed to init
Command []string `json:"command"` // absolute path to executable file
Path string `json:"path,omitempty"`
// final args passed to container init
Args []string `json:"args"`
Confinement ConfinementConfig `json:"confinement"` Confinement ConfinementConfig `json:"confinement"`
} }
@ -26,13 +29,13 @@ type ConfinementConfig struct {
AppID int `json:"app_id"` AppID int `json:"app_id"`
// list of supplementary groups to inherit // list of supplementary groups to inherit
Groups []string `json:"groups"` Groups []string `json:"groups"`
// passwd username in the sandbox, defaults to passwd name of target uid or chronos // passwd username in container, defaults to passwd name of target uid or chronos
Username string `json:"username,omitempty"` Username string `json:"username,omitempty"`
// home directory in sandbox, empty for outer // home directory in container, empty for outer
Inner string `json:"home_inner"` Inner string `json:"home_inner"`
// home directory in init namespace // home directory in init namespace
Outer string `json:"home"` Outer string `json:"home"`
// bwrap sandbox confinement configuration // abstract sandbox configuration
Sandbox *SandboxConfig `json:"sandbox"` Sandbox *SandboxConfig `json:"sandbox"`
// extra acl ops, runs after everything else // extra acl ops, runs after everything else
ExtraPerms []*ExtraPermConfig `json:"extra_perms,omitempty"` ExtraPerms []*ExtraPermConfig `json:"extra_perms,omitempty"`
@ -44,7 +47,7 @@ type ConfinementConfig struct {
// nil value makes session bus proxy assume built-in defaults // nil value makes session bus proxy assume built-in defaults
SessionBus *dbus.Config `json:"session_bus,omitempty"` SessionBus *dbus.Config `json:"session_bus,omitempty"`
// system resources to expose to the sandbox // system resources to expose to the container
Enablements system.Enablements `json:"enablements"` Enablements system.Enablements `json:"enablements"`
} }
@ -76,24 +79,12 @@ func (e *ExtraPermConfig) String() string {
return string(buf) return string(buf)
} }
type FilesystemConfig struct {
// mount point in sandbox, same as src if empty
Dst string `json:"dst,omitempty"`
// host filesystem path to make available to sandbox
Src string `json:"src"`
// write access
Write bool `json:"write,omitempty"`
// device access
Device bool `json:"dev,omitempty"`
// fail if mount fails
Must bool `json:"require,omitempty"`
}
// Template returns a fully populated instance of Config. // Template returns a fully populated instance of Config.
func Template() *Config { func Template() *Config {
return &Config{ return &Config{
ID: "org.chromium.Chromium", ID: "org.chromium.Chromium",
Command: []string{ Path: "/run/current-system/sw/bin/chromium",
Args: []string{
"chromium", "chromium",
"--ignore-gpu-blocklist", "--ignore-gpu-blocklist",
"--disable-smooth-scrolling", "--disable-smooth-scrolling",
@ -108,11 +99,13 @@ func Template() *Config {
Inner: "/var/lib/fortify", Inner: "/var/lib/fortify",
Sandbox: &SandboxConfig{ Sandbox: &SandboxConfig{
Hostname: "localhost", Hostname: "localhost",
UserNS: true, Devel: true,
Userns: true,
Net: true, Net: true,
Dev: true, Dev: true,
Syscall: &bwrap.SyscallPolicy{DenyDevel: true, Multiarch: true}, Seccomp: seccomp.FlagMultiarch,
NoNewSession: true, Tty: true,
Multiarch: true,
MapRealUID: true, MapRealUID: true,
DirectWayland: false, DirectWayland: false,
// example API credentials pulled from Google Chrome // example API credentials pulled from Google Chrome
@ -131,10 +124,10 @@ func Template() *Config {
Dst: "/data/data/org.chromium.Chromium", Write: true, Must: true}, Dst: "/data/data/org.chromium.Chromium", Write: true, Must: true},
{Src: "/dev/dri", Device: true}, {Src: "/dev/dri", Device: true},
}, },
Link: [][2]string{{"/run/user/65534", "/run/user/150"}}, Link: [][2]string{{"/run/user/65534", "/run/user/150"}},
Etc: "/etc", Etc: "/etc",
AutoEtc: true, AutoEtc: true,
Override: []string{"/var/run/nscd"}, Cover: []string{"/var/run/nscd"},
}, },
ExtraPerms: []*ExtraPermConfig{ ExtraPerms: []*ExtraPermConfig{
{Path: "/var/lib/fortify/u0", Ensure: true, Execute: true}, {Path: "/var/lib/fortify/u0", Ensure: true, Execute: true},

View File

@ -4,125 +4,149 @@ import (
"errors" "errors"
"fmt" "fmt"
"io/fs" "io/fs"
"maps"
"path" "path"
"slices"
"syscall"
"git.gensokyo.uk/security/fortify/dbus" "git.gensokyo.uk/security/fortify/dbus"
"git.gensokyo.uk/security/fortify/helper/bwrap" "git.gensokyo.uk/security/fortify/sandbox"
"git.gensokyo.uk/security/fortify/sandbox/seccomp"
) )
// SandboxConfig describes resources made available to the sandbox. // SandboxConfig describes resources made available to the sandbox.
type SandboxConfig struct { type (
// unix hostname within sandbox SandboxConfig struct {
Hostname string `json:"hostname,omitempty"` // container hostname
// allow userns within sandbox Hostname string `json:"hostname,omitempty"`
UserNS bool `json:"userns,omitempty"`
// share net namespace
Net bool `json:"net,omitempty"`
// share all devices
Dev bool `json:"dev,omitempty"`
// seccomp syscall filter policy
Syscall *bwrap.SyscallPolicy `json:"syscall"`
// do not run in new session
NoNewSession bool `json:"no_new_session,omitempty"`
// map target user uid to privileged user uid in the user namespace
MapRealUID bool `json:"map_real_uid"`
// direct access to wayland socket; when this gets set no attempt is made to attach security-context-v1
// and the bare socket is mounted to the sandbox
DirectWayland bool `json:"direct_wayland,omitempty"`
// final environment variables // extra seccomp flags
Env map[string]string `json:"env"` Seccomp seccomp.SyscallOpts `json:"seccomp"`
// sandbox host filesystem access // allow ptrace and friends
Filesystem []*FilesystemConfig `json:"filesystem"` Devel bool `json:"devel,omitempty"`
// symlinks created inside the sandbox // allow userns creation in container
Link [][2]string `json:"symlink"` Userns bool `json:"userns,omitempty"`
// read-only /etc directory // share host net namespace
Etc string `json:"etc,omitempty"` Net bool `json:"net,omitempty"`
// automatically set up /etc symlinks // expose main process tty
AutoEtc bool `json:"auto_etc"` Tty bool `json:"tty,omitempty"`
// mount tmpfs over these paths, // allow multiarch
// runs right before [ConfinementConfig.ExtraPerms] Multiarch bool `json:"multiarch,omitempty"`
Override []string `json:"override"`
}
// SandboxSys encapsulates system functions used during the creation of [bwrap.Config]. // initial process environment variables
type SandboxSys interface { Env map[string]string `json:"env"`
Getuid() int // map target user uid to privileged user uid in the user namespace
Paths() Paths MapRealUID bool `json:"map_real_uid"`
ReadDir(name string) ([]fs.DirEntry, error)
EvalSymlinks(path string) (string, error)
Println(v ...any) // expose all devices
Printf(format string, v ...any) Dev bool `json:"dev,omitempty"`
} // container host filesystem bind mounts
Filesystem []*FilesystemConfig `json:"filesystem"`
// create symlinks inside container filesystem
Link [][2]string `json:"symlink"`
// Bwrap returns the address of the corresponding bwrap.Config to s. // direct access to wayland socket; when this gets set no attempt is made to attach security-context-v1
// Note that remaining tmpfs entries must be queued by the caller prior to launch. // and the bare socket is mounted to the sandbox
func (s *SandboxConfig) Bwrap(sys SandboxSys, uid *int) (*bwrap.Config, error) { DirectWayland bool `json:"direct_wayland,omitempty"`
// read-only /etc directory
Etc string `json:"etc,omitempty"`
// automatically set up /etc symlinks
AutoEtc bool `json:"auto_etc"`
// cover these paths or create them if they do not already exist
Cover []string `json:"cover"`
}
// SandboxSys encapsulates system functions used during [sandbox.Container] initialisation.
SandboxSys interface {
Getuid() int
Getgid() int
Paths() Paths
ReadDir(name string) ([]fs.DirEntry, error)
EvalSymlinks(path string) (string, error)
Println(v ...any)
Printf(format string, v ...any)
}
// FilesystemConfig is a representation of [sandbox.BindMount].
FilesystemConfig struct {
// mount point in container, same as src if empty
Dst string `json:"dst,omitempty"`
// host filesystem path to make available to the container
Src string `json:"src"`
// do not mount filesystem read-only
Write bool `json:"write,omitempty"`
// do not disable device files
Device bool `json:"dev,omitempty"`
// fail if the bind mount cannot be established for any reason
Must bool `json:"require,omitempty"`
}
)
// ToContainer initialises [sandbox.Params] via [SandboxConfig].
// Note that remaining container setup must be queued by the [App] implementation.
func (s *SandboxConfig) ToContainer(sys SandboxSys, uid, gid *int) (*sandbox.Params, map[string]string, error) {
if s == nil { if s == nil {
return nil, errors.New("nil sandbox config") return nil, nil, syscall.EBADE
} }
if s.Syscall == nil { container := &sandbox.Params{
sys.Println("syscall filter not configured, PROCEED WITH CAUTION")
}
if !s.MapRealUID {
// mapped uid defaults to 65534 to work around file ownership checks due to a bwrap limitation
*uid = 65534
} else {
// some programs fail to connect to dbus session running as a different uid, so a separate workaround
// is introduced to map priv-side caller uid in namespace
*uid = sys.Getuid()
}
conf := (&bwrap.Config{
Net: s.Net,
UserNS: s.UserNS,
UID: uid,
GID: uid,
Hostname: s.Hostname, Hostname: s.Hostname,
Clearenv: true, Ops: new(sandbox.Ops),
SetEnv: s.Env, Seccomp: s.Seccomp,
}
/* this is only 4 KiB of memory on a 64-bit system, /* this is only 4 KiB of memory on a 64-bit system,
permissive defaults on NixOS results in around 100 entries permissive defaults on NixOS results in around 100 entries
so this capacity should eliminate copies for most setups */ so this capacity should eliminate copies for most setups */
Filesystem: make([]bwrap.FSBuilder, 0, 256), *container.Ops = slices.Grow(*container.Ops, 1<<8)
Syscall: s.Syscall, if s.Devel {
NewSession: !s.NoNewSession, container.Flags |= sandbox.FAllowDevel
DieWithParent: true, }
AsInit: true, if s.Userns {
container.Flags |= sandbox.FAllowUserns
}
if s.Net {
container.Flags |= sandbox.FAllowNet
}
if s.Tty {
container.Flags |= sandbox.FAllowTTY
}
// initialise unconditionally as Once cannot be justified if s.MapRealUID {
// for saving such a miniscule amount of memory /* some programs fail to connect to dbus session running as a different uid
Chmod: make(bwrap.ChmodConfig), so this workaround is introduced to map priv-side caller uid in container */
}). container.Uid = sys.Getuid()
Procfs("/proc"). *uid = container.Uid
Tmpfs(Tmp, 4*1024) container.Gid = sys.Getgid()
*gid = container.Gid
} else {
*uid = sandbox.OverflowUid()
*gid = sandbox.OverflowGid()
}
container.
Proc("/proc").
Tmpfs(Tmp, 1<<12, 0755)
if !s.Dev { if !s.Dev {
conf.DevTmpfs("/dev").Mqueue("/dev/mqueue") container.Dev("/dev").Mqueue("/dev/mqueue")
} else { } else {
conf.Bind("/dev", "/dev", false, true, true) container.Bind("/dev", "/dev", sandbox.BindDevice)
} }
if !s.AutoEtc { /* retrieve paths and hide them if they're made available in the sandbox;
if s.Etc == "" { this feature tries to improve user experience of permissive defaults, and
conf.Dir("/etc") to warn about issues in custom configuration; it is NOT a security feature
} else { and should not be treated as such, ALWAYS be careful with what you bind */
conf.Bind(s.Etc, "/etc")
}
}
// retrieve paths and hide them if they're made available in the sandbox
var hidePaths []string var hidePaths []string
sc := sys.Paths() sc := sys.Paths()
hidePaths = append(hidePaths, sc.RuntimePath, sc.SharePath) hidePaths = append(hidePaths, sc.RuntimePath, sc.SharePath)
_, systemBusAddr := dbus.Address() _, systemBusAddr := dbus.Address()
if entries, err := dbus.Parse([]byte(systemBusAddr)); err != nil { if entries, err := dbus.Parse([]byte(systemBusAddr)); err != nil {
return nil, err return nil, nil, err
} else { } else {
// there is usually only one, do not preallocate // there is usually only one, do not preallocate
for _, entry := range entries { for _, entry := range entries {
@ -148,7 +172,7 @@ func (s *SandboxConfig) Bwrap(sys SandboxSys, uid *int) (*bwrap.Config, error) {
hidePathMatch := make([]bool, len(hidePaths)) hidePathMatch := make([]bool, len(hidePaths))
for i := range hidePaths { for i := range hidePaths {
if err := evalSymlinks(sys, &hidePaths[i]); err != nil { if err := evalSymlinks(sys, &hidePaths[i]); err != nil {
return nil, err return nil, nil, err
} }
} }
@ -158,19 +182,19 @@ func (s *SandboxConfig) Bwrap(sys SandboxSys, uid *int) (*bwrap.Config, error) {
} }
if !path.IsAbs(c.Src) { if !path.IsAbs(c.Src) {
return nil, fmt.Errorf("src path %q is not absolute", c.Src) return nil, nil, fmt.Errorf("src path %q is not absolute", c.Src)
} }
dest := c.Dst dest := c.Dst
if c.Dst == "" { if c.Dst == "" {
dest = c.Src dest = c.Src
} else if !path.IsAbs(dest) { } else if !path.IsAbs(dest) {
return nil, fmt.Errorf("dst path %q is not absolute", dest) return nil, nil, fmt.Errorf("dst path %q is not absolute", dest)
} }
srcH := c.Src srcH := c.Src
if err := evalSymlinks(sys, &srcH); err != nil { if err := evalSymlinks(sys, &srcH); err != nil {
return nil, err return nil, nil, err
} }
for i := range hidePaths { for i := range hidePaths {
@ -180,54 +204,71 @@ func (s *SandboxConfig) Bwrap(sys SandboxSys, uid *int) (*bwrap.Config, error) {
} }
if ok, err := deepContainsH(srcH, hidePaths[i]); err != nil { if ok, err := deepContainsH(srcH, hidePaths[i]); err != nil {
return nil, err return nil, nil, err
} else if ok { } else if ok {
hidePathMatch[i] = true hidePathMatch[i] = true
sys.Printf("hiding paths from %q", c.Src) sys.Printf("hiding paths from %q", c.Src)
} }
} }
conf.Bind(c.Src, dest, !c.Must, c.Write, c.Device) var flags int
if c.Write {
flags |= sandbox.BindWritable
}
if c.Device {
flags |= sandbox.BindDevice | sandbox.BindWritable
}
if !c.Must {
flags |= sandbox.BindOptional
}
container.Bind(c.Src, dest, flags)
} }
// hide marked paths before setting up shares // cover matched paths
for i, ok := range hidePathMatch { for i, ok := range hidePathMatch {
if ok { if ok {
conf.Tmpfs(hidePaths[i], 8192) container.Tmpfs(hidePaths[i], 1<<13, 0755)
} }
} }
for _, l := range s.Link { for _, l := range s.Link {
conf.Symlink(l[0], l[1]) container.Link(l[0], l[1])
} }
if s.AutoEtc { // perf: this might work better if implemented as a setup op in container init
etc := s.Etc if !s.AutoEtc {
if etc == "" { if s.Etc != "" {
etc = "/etc" container.Bind(s.Etc, "/etc", 0)
} }
conf.Bind(etc, Tmp+"/etc") } else {
etcPath := s.Etc
if etcPath == "" {
etcPath = "/etc"
}
container.
Bind(etcPath, Tmp+"/etc", 0).
Mkdir("/etc", 0700)
// link host /etc contents to prevent passwd/group from being overwritten // link host /etc contents to prevent dropping passwd/group bind mounts
if d, err := sys.ReadDir(etc); err != nil { if d, err := sys.ReadDir(etcPath); err != nil {
return nil, err return nil, nil, err
} else { } else {
for _, ent := range d { for _, ent := range d {
name := ent.Name() n := ent.Name()
switch name { switch n {
case "passwd": case "passwd":
case "group": case "group":
case "mtab": case "mtab":
conf.Symlink("/proc/mounts", "/etc/"+name) container.Link("/proc/mounts", "/etc/"+n)
default: default:
conf.Symlink(Tmp+"/etc/"+name, "/etc/"+name) container.Link(Tmp+"/etc/"+n, "/etc/"+n)
} }
} }
} }
} }
return conf, nil return container, maps.Clone(s.Env), nil
} }
func evalSymlinks(sys SandboxSys, v *string) error { func evalSymlinks(sys SandboxSys, v *string) error {

View File

@ -1,6 +1,7 @@
package app package app
import ( import (
"context"
"fmt" "fmt"
"log" "log"
"sync" "sync"
@ -10,9 +11,10 @@ import (
"git.gensokyo.uk/security/fortify/internal/sys" "git.gensokyo.uk/security/fortify/internal/sys"
) )
func New(os sys.State) (fst.App, error) { func New(ctx context.Context, os sys.State) (fst.App, error) {
a := new(app) a := new(app)
a.sys = os a.sys = os
a.ctx = ctx
id := new(fst.ID) id := new(fst.ID)
err := fst.NewAppID(id) err := fst.NewAppID(id)
@ -21,8 +23,8 @@ func New(os sys.State) (fst.App, error) {
return a, err return a, err
} }
func MustNew(os sys.State) fst.App { func MustNew(ctx context.Context, os sys.State) fst.App {
a, err := New(os) a, err := New(ctx, os)
if err != nil { if err != nil {
log.Fatalf("cannot create app: %v", err) log.Fatalf("cannot create app: %v", err)
} }
@ -32,6 +34,7 @@ func MustNew(os sys.State) fst.App {
type app struct { type app struct {
id *stringPair[fst.ID] id *stringPair[fst.ID]
sys sys.State sys sys.State
ctx context.Context
*outcome *outcome
mu sync.RWMutex mu sync.RWMutex
@ -71,7 +74,7 @@ func (a *app) Seal(config *fst.Config) (fst.SealedApp, error) {
seal := new(outcome) seal := new(outcome)
seal.id = a.id seal.id = a.id
err := seal.finalise(a.sys, config) err := seal.finalise(a.ctx, a.sys, config)
if err == nil { if err == nil {
a.outcome = seal a.outcome = seal
} }

View File

@ -4,7 +4,7 @@ import (
"git.gensokyo.uk/security/fortify/acl" "git.gensokyo.uk/security/fortify/acl"
"git.gensokyo.uk/security/fortify/dbus" "git.gensokyo.uk/security/fortify/dbus"
"git.gensokyo.uk/security/fortify/fst" "git.gensokyo.uk/security/fortify/fst"
"git.gensokyo.uk/security/fortify/helper/bwrap" "git.gensokyo.uk/security/fortify/sandbox"
"git.gensokyo.uk/security/fortify/system" "git.gensokyo.uk/security/fortify/system"
) )
@ -12,20 +12,20 @@ var testCasesNixos = []sealTestCase{
{ {
"nixos chromium direct wayland", new(stubNixOS), "nixos chromium direct wayland", new(stubNixOS),
&fst.Config{ &fst.Config{
ID: "org.chromium.Chromium", ID: "org.chromium.Chromium",
Command: []string{"/nix/store/yqivzpzzn7z5x0lq9hmbzygh45d8rhqd-chromium-start"}, Path: "/nix/store/yqivzpzzn7z5x0lq9hmbzygh45d8rhqd-chromium-start",
Confinement: fst.ConfinementConfig{ Confinement: fst.ConfinementConfig{
AppID: 1, Groups: []string{}, Username: "u0_a1", AppID: 1, Groups: []string{}, Username: "u0_a1",
Outer: "/var/lib/persist/module/fortify/0/1", Outer: "/var/lib/persist/module/fortify/0/1",
Sandbox: &fst.SandboxConfig{ Sandbox: &fst.SandboxConfig{
UserNS: true, Net: true, MapRealUID: true, DirectWayland: true, Env: nil, AutoEtc: true, Userns: true, Net: true, MapRealUID: true, DirectWayland: true, Env: nil, AutoEtc: true,
Filesystem: []*fst.FilesystemConfig{ Filesystem: []*fst.FilesystemConfig{
{Src: "/bin", Must: true}, {Src: "/usr/bin", Must: true}, {Src: "/bin", Must: true}, {Src: "/usr/bin", Must: true},
{Src: "/nix/store", Must: true}, {Src: "/run/current-system", Must: true}, {Src: "/nix/store", Must: true}, {Src: "/run/current-system", Must: true},
{Src: "/sys/block"}, {Src: "/sys/bus"}, {Src: "/sys/class"}, {Src: "/sys/dev"}, {Src: "/sys/devices"}, {Src: "/sys/block"}, {Src: "/sys/bus"}, {Src: "/sys/class"}, {Src: "/sys/dev"}, {Src: "/sys/devices"},
{Src: "/run/opengl-driver", Must: true}, {Src: "/dev/dri", Device: true}, {Src: "/run/opengl-driver", Must: true}, {Src: "/dev/dri", Device: true},
}, },
Override: []string{"/var/run/nscd"}, Cover: []string{"/var/run/nscd"},
}, },
SystemBus: &dbus.Config{ SystemBus: &dbus.Config{
Talk: []string{"org.bluez", "org.freedesktop.Avahi", "org.freedesktop.UPower"}, Talk: []string{"org.bluez", "org.freedesktop.Avahi", "org.freedesktop.UPower"},
@ -88,136 +88,133 @@ var testCasesNixos = []sealTestCase{
}). }).
UpdatePerm("/tmp/fortify.1971/8e2c76b066dabe574cf073bdb46eb5c1/bus", acl.Read, acl.Write). UpdatePerm("/tmp/fortify.1971/8e2c76b066dabe574cf073bdb46eb5c1/bus", acl.Read, acl.Write).
UpdatePerm("/tmp/fortify.1971/8e2c76b066dabe574cf073bdb46eb5c1/system_bus_socket", acl.Read, acl.Write), UpdatePerm("/tmp/fortify.1971/8e2c76b066dabe574cf073bdb46eb5c1/system_bus_socket", acl.Read, acl.Write),
(&bwrap.Config{ &sandbox.Params{
Net: true, Uid: 1971,
UserNS: true, Gid: 100,
Chdir: "/var/lib/persist/module/fortify/0/1", Flags: sandbox.FAllowNet | sandbox.FAllowUserns,
Clearenv: true, Dir: "/var/lib/persist/module/fortify/0/1",
SetEnv: map[string]string{ Path: "/nix/store/yqivzpzzn7z5x0lq9hmbzygh45d8rhqd-chromium-start",
"DBUS_SESSION_BUS_ADDRESS": "unix:path=/run/user/1971/bus", Args: []string{"/nix/store/yqivzpzzn7z5x0lq9hmbzygh45d8rhqd-chromium-start"},
"DBUS_SYSTEM_BUS_ADDRESS": "unix:path=/run/dbus/system_bus_socket", Env: []string{
"HOME": "/var/lib/persist/module/fortify/0/1", "DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1971/bus",
"PULSE_COOKIE": fst.Tmp + "/pulse-cookie", "DBUS_SYSTEM_BUS_ADDRESS=unix:path=/run/dbus/system_bus_socket",
"PULSE_SERVER": "unix:/run/user/1971/pulse/native", "HOME=/var/lib/persist/module/fortify/0/1",
"SHELL": "/run/current-system/sw/bin/zsh", "PULSE_COOKIE=" + fst.Tmp + "/pulse-cookie",
"TERM": "xterm-256color", "PULSE_SERVER=unix:/run/user/1971/pulse/native",
"USER": "u0_a1", "TERM=xterm-256color",
"WAYLAND_DISPLAY": "wayland-0", "USER=u0_a1",
"XDG_RUNTIME_DIR": "/run/user/1971", "WAYLAND_DISPLAY=wayland-0",
"XDG_SESSION_CLASS": "user", "XDG_RUNTIME_DIR=/run/user/1971",
"XDG_SESSION_TYPE": "tty", "XDG_SESSION_CLASS=user",
"XDG_SESSION_TYPE=tty",
}, },
Chmod: make(bwrap.ChmodConfig), Ops: new(sandbox.Ops).
NewSession: true, Proc("/proc").
DieWithParent: true, Tmpfs(fst.Tmp, 4096, 0755).
AsInit: true, Dev("/dev").Mqueue("/dev/mqueue").
}).SetUID(1971).SetGID(1971). Bind("/bin", "/bin", 0).
Procfs("/proc"). Bind("/usr/bin", "/usr/bin", 0).
Tmpfs(fst.Tmp, 4096). Bind("/nix/store", "/nix/store", 0).
DevTmpfs("/dev").Mqueue("/dev/mqueue"). Bind("/run/current-system", "/run/current-system", 0).
Bind("/bin", "/bin"). Bind("/sys/block", "/sys/block", sandbox.BindOptional).
Bind("/usr/bin", "/usr/bin"). Bind("/sys/bus", "/sys/bus", sandbox.BindOptional).
Bind("/nix/store", "/nix/store"). Bind("/sys/class", "/sys/class", sandbox.BindOptional).
Bind("/run/current-system", "/run/current-system"). Bind("/sys/dev", "/sys/dev", sandbox.BindOptional).
Bind("/sys/block", "/sys/block", true). Bind("/sys/devices", "/sys/devices", sandbox.BindOptional).
Bind("/sys/bus", "/sys/bus", true). Bind("/run/opengl-driver", "/run/opengl-driver", 0).
Bind("/sys/class", "/sys/class", true). Bind("/dev/dri", "/dev/dri", sandbox.BindDevice|sandbox.BindWritable|sandbox.BindOptional).
Bind("/sys/dev", "/sys/dev", true). Bind("/etc", fst.Tmp+"/etc", 0).
Bind("/sys/devices", "/sys/devices", true). Mkdir("/etc", 0700).
Bind("/run/opengl-driver", "/run/opengl-driver"). Link(fst.Tmp+"/etc/alsa", "/etc/alsa").
Bind("/dev/dri", "/dev/dri", true, true, true). Link(fst.Tmp+"/etc/bashrc", "/etc/bashrc").
Bind("/etc", fst.Tmp+"/etc"). Link(fst.Tmp+"/etc/binfmt.d", "/etc/binfmt.d").
Symlink(fst.Tmp+"/etc/alsa", "/etc/alsa"). Link(fst.Tmp+"/etc/dbus-1", "/etc/dbus-1").
Symlink(fst.Tmp+"/etc/bashrc", "/etc/bashrc"). Link(fst.Tmp+"/etc/default", "/etc/default").
Symlink(fst.Tmp+"/etc/binfmt.d", "/etc/binfmt.d"). Link(fst.Tmp+"/etc/ethertypes", "/etc/ethertypes").
Symlink(fst.Tmp+"/etc/dbus-1", "/etc/dbus-1"). Link(fst.Tmp+"/etc/fonts", "/etc/fonts").
Symlink(fst.Tmp+"/etc/default", "/etc/default"). Link(fst.Tmp+"/etc/fstab", "/etc/fstab").
Symlink(fst.Tmp+"/etc/ethertypes", "/etc/ethertypes"). Link(fst.Tmp+"/etc/fuse.conf", "/etc/fuse.conf").
Symlink(fst.Tmp+"/etc/fonts", "/etc/fonts"). Link(fst.Tmp+"/etc/host.conf", "/etc/host.conf").
Symlink(fst.Tmp+"/etc/fstab", "/etc/fstab"). Link(fst.Tmp+"/etc/hostid", "/etc/hostid").
Symlink(fst.Tmp+"/etc/fuse.conf", "/etc/fuse.conf"). Link(fst.Tmp+"/etc/hostname", "/etc/hostname").
Symlink(fst.Tmp+"/etc/host.conf", "/etc/host.conf"). Link(fst.Tmp+"/etc/hostname.CHECKSUM", "/etc/hostname.CHECKSUM").
Symlink(fst.Tmp+"/etc/hostid", "/etc/hostid"). Link(fst.Tmp+"/etc/hosts", "/etc/hosts").
Symlink(fst.Tmp+"/etc/hostname", "/etc/hostname"). Link(fst.Tmp+"/etc/inputrc", "/etc/inputrc").
Symlink(fst.Tmp+"/etc/hostname.CHECKSUM", "/etc/hostname.CHECKSUM"). Link(fst.Tmp+"/etc/ipsec.d", "/etc/ipsec.d").
Symlink(fst.Tmp+"/etc/hosts", "/etc/hosts"). Link(fst.Tmp+"/etc/issue", "/etc/issue").
Symlink(fst.Tmp+"/etc/inputrc", "/etc/inputrc"). Link(fst.Tmp+"/etc/kbd", "/etc/kbd").
Symlink(fst.Tmp+"/etc/ipsec.d", "/etc/ipsec.d"). Link(fst.Tmp+"/etc/libblockdev", "/etc/libblockdev").
Symlink(fst.Tmp+"/etc/issue", "/etc/issue"). Link(fst.Tmp+"/etc/locale.conf", "/etc/locale.conf").
Symlink(fst.Tmp+"/etc/kbd", "/etc/kbd"). Link(fst.Tmp+"/etc/localtime", "/etc/localtime").
Symlink(fst.Tmp+"/etc/libblockdev", "/etc/libblockdev"). Link(fst.Tmp+"/etc/login.defs", "/etc/login.defs").
Symlink(fst.Tmp+"/etc/locale.conf", "/etc/locale.conf"). Link(fst.Tmp+"/etc/lsb-release", "/etc/lsb-release").
Symlink(fst.Tmp+"/etc/localtime", "/etc/localtime"). Link(fst.Tmp+"/etc/lvm", "/etc/lvm").
Symlink(fst.Tmp+"/etc/login.defs", "/etc/login.defs"). Link(fst.Tmp+"/etc/machine-id", "/etc/machine-id").
Symlink(fst.Tmp+"/etc/lsb-release", "/etc/lsb-release"). Link(fst.Tmp+"/etc/man_db.conf", "/etc/man_db.conf").
Symlink(fst.Tmp+"/etc/lvm", "/etc/lvm"). Link(fst.Tmp+"/etc/modprobe.d", "/etc/modprobe.d").
Symlink(fst.Tmp+"/etc/machine-id", "/etc/machine-id"). Link(fst.Tmp+"/etc/modules-load.d", "/etc/modules-load.d").
Symlink(fst.Tmp+"/etc/man_db.conf", "/etc/man_db.conf"). Link("/proc/mounts", "/etc/mtab").
Symlink(fst.Tmp+"/etc/modprobe.d", "/etc/modprobe.d"). Link(fst.Tmp+"/etc/nanorc", "/etc/nanorc").
Symlink(fst.Tmp+"/etc/modules-load.d", "/etc/modules-load.d"). Link(fst.Tmp+"/etc/netgroup", "/etc/netgroup").
Symlink("/proc/mounts", "/etc/mtab"). Link(fst.Tmp+"/etc/NetworkManager", "/etc/NetworkManager").
Symlink(fst.Tmp+"/etc/nanorc", "/etc/nanorc"). Link(fst.Tmp+"/etc/nix", "/etc/nix").
Symlink(fst.Tmp+"/etc/netgroup", "/etc/netgroup"). Link(fst.Tmp+"/etc/nixos", "/etc/nixos").
Symlink(fst.Tmp+"/etc/NetworkManager", "/etc/NetworkManager"). Link(fst.Tmp+"/etc/NIXOS", "/etc/NIXOS").
Symlink(fst.Tmp+"/etc/nix", "/etc/nix"). Link(fst.Tmp+"/etc/nscd.conf", "/etc/nscd.conf").
Symlink(fst.Tmp+"/etc/nixos", "/etc/nixos"). Link(fst.Tmp+"/etc/nsswitch.conf", "/etc/nsswitch.conf").
Symlink(fst.Tmp+"/etc/NIXOS", "/etc/NIXOS"). Link(fst.Tmp+"/etc/opensnitchd", "/etc/opensnitchd").
Symlink(fst.Tmp+"/etc/nscd.conf", "/etc/nscd.conf"). Link(fst.Tmp+"/etc/os-release", "/etc/os-release").
Symlink(fst.Tmp+"/etc/nsswitch.conf", "/etc/nsswitch.conf"). Link(fst.Tmp+"/etc/pam", "/etc/pam").
Symlink(fst.Tmp+"/etc/opensnitchd", "/etc/opensnitchd"). Link(fst.Tmp+"/etc/pam.d", "/etc/pam.d").
Symlink(fst.Tmp+"/etc/os-release", "/etc/os-release"). Link(fst.Tmp+"/etc/pipewire", "/etc/pipewire").
Symlink(fst.Tmp+"/etc/pam", "/etc/pam"). Link(fst.Tmp+"/etc/pki", "/etc/pki").
Symlink(fst.Tmp+"/etc/pam.d", "/etc/pam.d"). Link(fst.Tmp+"/etc/polkit-1", "/etc/polkit-1").
Symlink(fst.Tmp+"/etc/pipewire", "/etc/pipewire"). Link(fst.Tmp+"/etc/profile", "/etc/profile").
Symlink(fst.Tmp+"/etc/pki", "/etc/pki"). Link(fst.Tmp+"/etc/protocols", "/etc/protocols").
Symlink(fst.Tmp+"/etc/polkit-1", "/etc/polkit-1"). Link(fst.Tmp+"/etc/qemu", "/etc/qemu").
Symlink(fst.Tmp+"/etc/profile", "/etc/profile"). Link(fst.Tmp+"/etc/resolv.conf", "/etc/resolv.conf").
Symlink(fst.Tmp+"/etc/protocols", "/etc/protocols"). Link(fst.Tmp+"/etc/resolvconf.conf", "/etc/resolvconf.conf").
Symlink(fst.Tmp+"/etc/qemu", "/etc/qemu"). Link(fst.Tmp+"/etc/rpc", "/etc/rpc").
Symlink(fst.Tmp+"/etc/resolv.conf", "/etc/resolv.conf"). Link(fst.Tmp+"/etc/samba", "/etc/samba").
Symlink(fst.Tmp+"/etc/resolvconf.conf", "/etc/resolvconf.conf"). Link(fst.Tmp+"/etc/sddm.conf", "/etc/sddm.conf").
Symlink(fst.Tmp+"/etc/rpc", "/etc/rpc"). Link(fst.Tmp+"/etc/secureboot", "/etc/secureboot").
Symlink(fst.Tmp+"/etc/samba", "/etc/samba"). Link(fst.Tmp+"/etc/services", "/etc/services").
Symlink(fst.Tmp+"/etc/sddm.conf", "/etc/sddm.conf"). Link(fst.Tmp+"/etc/set-environment", "/etc/set-environment").
Symlink(fst.Tmp+"/etc/secureboot", "/etc/secureboot"). Link(fst.Tmp+"/etc/shadow", "/etc/shadow").
Symlink(fst.Tmp+"/etc/services", "/etc/services"). Link(fst.Tmp+"/etc/shells", "/etc/shells").
Symlink(fst.Tmp+"/etc/set-environment", "/etc/set-environment"). Link(fst.Tmp+"/etc/ssh", "/etc/ssh").
Symlink(fst.Tmp+"/etc/shadow", "/etc/shadow"). Link(fst.Tmp+"/etc/ssl", "/etc/ssl").
Symlink(fst.Tmp+"/etc/shells", "/etc/shells"). Link(fst.Tmp+"/etc/static", "/etc/static").
Symlink(fst.Tmp+"/etc/ssh", "/etc/ssh"). Link(fst.Tmp+"/etc/subgid", "/etc/subgid").
Symlink(fst.Tmp+"/etc/ssl", "/etc/ssl"). Link(fst.Tmp+"/etc/subuid", "/etc/subuid").
Symlink(fst.Tmp+"/etc/static", "/etc/static"). Link(fst.Tmp+"/etc/sudoers", "/etc/sudoers").
Symlink(fst.Tmp+"/etc/subgid", "/etc/subgid"). Link(fst.Tmp+"/etc/sysctl.d", "/etc/sysctl.d").
Symlink(fst.Tmp+"/etc/subuid", "/etc/subuid"). Link(fst.Tmp+"/etc/systemd", "/etc/systemd").
Symlink(fst.Tmp+"/etc/sudoers", "/etc/sudoers"). Link(fst.Tmp+"/etc/terminfo", "/etc/terminfo").
Symlink(fst.Tmp+"/etc/sysctl.d", "/etc/sysctl.d"). Link(fst.Tmp+"/etc/tmpfiles.d", "/etc/tmpfiles.d").
Symlink(fst.Tmp+"/etc/systemd", "/etc/systemd"). Link(fst.Tmp+"/etc/udev", "/etc/udev").
Symlink(fst.Tmp+"/etc/terminfo", "/etc/terminfo"). Link(fst.Tmp+"/etc/udisks2", "/etc/udisks2").
Symlink(fst.Tmp+"/etc/tmpfiles.d", "/etc/tmpfiles.d"). Link(fst.Tmp+"/etc/UPower", "/etc/UPower").
Symlink(fst.Tmp+"/etc/udev", "/etc/udev"). Link(fst.Tmp+"/etc/vconsole.conf", "/etc/vconsole.conf").
Symlink(fst.Tmp+"/etc/udisks2", "/etc/udisks2"). Link(fst.Tmp+"/etc/X11", "/etc/X11").
Symlink(fst.Tmp+"/etc/UPower", "/etc/UPower"). Link(fst.Tmp+"/etc/zfs", "/etc/zfs").
Symlink(fst.Tmp+"/etc/vconsole.conf", "/etc/vconsole.conf"). Link(fst.Tmp+"/etc/zinputrc", "/etc/zinputrc").
Symlink(fst.Tmp+"/etc/X11", "/etc/X11"). Link(fst.Tmp+"/etc/zoneinfo", "/etc/zoneinfo").
Symlink(fst.Tmp+"/etc/zfs", "/etc/zfs"). Link(fst.Tmp+"/etc/zprofile", "/etc/zprofile").
Symlink(fst.Tmp+"/etc/zinputrc", "/etc/zinputrc"). Link(fst.Tmp+"/etc/zshenv", "/etc/zshenv").
Symlink(fst.Tmp+"/etc/zoneinfo", "/etc/zoneinfo"). Link(fst.Tmp+"/etc/zshrc", "/etc/zshrc").
Symlink(fst.Tmp+"/etc/zprofile", "/etc/zprofile"). Tmpfs("/run/user", 4096, 0755).
Symlink(fst.Tmp+"/etc/zshenv", "/etc/zshenv"). Tmpfs("/run/user/1971", 8388608, 0755).
Symlink(fst.Tmp+"/etc/zshrc", "/etc/zshrc"). Bind("/tmp/fortify.1971/tmpdir/1", "/tmp", sandbox.BindWritable).
Tmpfs("/run/user", 1048576). Bind("/var/lib/persist/module/fortify/0/1", "/var/lib/persist/module/fortify/0/1", sandbox.BindWritable).
Tmpfs("/run/user/1971", 8388608). Place("/etc/passwd", []byte("u0_a1:x:1971:100:Fortify:/var/lib/persist/module/fortify/0/1:/run/current-system/sw/bin/zsh\n")).
Bind("/tmp/fortify.1971/tmpdir/1", "/tmp", false, true). Place("/etc/group", []byte("fortify:x:100:\n")).
Bind("/var/lib/persist/module/fortify/0/1", "/var/lib/persist/module/fortify/0/1", false, true). Bind("/run/user/1971/wayland-0", "/run/user/1971/wayland-0", 0).
CopyBind("/etc/passwd", []byte("u0_a1:x:1971:1971:Fortify:/var/lib/persist/module/fortify/0/1:/run/current-system/sw/bin/zsh\n")). Bind("/run/user/1971/fortify/8e2c76b066dabe574cf073bdb46eb5c1/pulse", "/run/user/1971/pulse/native", 0).
CopyBind("/etc/group", []byte("fortify:x:1971:\n")). Place(fst.Tmp+"/pulse-cookie", nil).
Bind("/run/user/1971/wayland-0", "/run/user/1971/wayland-0"). Bind("/tmp/fortify.1971/8e2c76b066dabe574cf073bdb46eb5c1/bus", "/run/user/1971/bus", 0).
Bind("/run/user/1971/fortify/8e2c76b066dabe574cf073bdb46eb5c1/pulse", "/run/user/1971/pulse/native"). Bind("/tmp/fortify.1971/8e2c76b066dabe574cf073bdb46eb5c1/system_bus_socket", "/run/dbus/system_bus_socket", 0).
CopyBind(fst.Tmp+"/pulse-cookie", nil). Tmpfs("/var/run/nscd", 8192, 0755),
Bind("/tmp/fortify.1971/8e2c76b066dabe574cf073bdb46eb5c1/bus", "/run/user/1971/bus"). },
Bind("/tmp/fortify.1971/8e2c76b066dabe574cf073bdb46eb5c1/system_bus_socket", "/run/dbus/system_bus_socket").
Tmpfs("/var/run/nscd", 8192).
Bind("/run/wrappers/bin/fortify", "/.fortify/sbin/fortify").
Symlink("fortify", "/.fortify/sbin/init0"),
}, },
} }

View File

@ -6,7 +6,7 @@ import (
"git.gensokyo.uk/security/fortify/acl" "git.gensokyo.uk/security/fortify/acl"
"git.gensokyo.uk/security/fortify/dbus" "git.gensokyo.uk/security/fortify/dbus"
"git.gensokyo.uk/security/fortify/fst" "git.gensokyo.uk/security/fortify/fst"
"git.gensokyo.uk/security/fortify/helper/bwrap" "git.gensokyo.uk/security/fortify/sandbox"
"git.gensokyo.uk/security/fortify/system" "git.gensokyo.uk/security/fortify/system"
) )
@ -14,7 +14,6 @@ var testCasesPd = []sealTestCase{
{ {
"nixos permissive defaults no enablements", new(stubNixOS), "nixos permissive defaults no enablements", new(stubNixOS),
&fst.Config{ &fst.Config{
Command: make([]string, 0),
Confinement: fst.ConfinementConfig{ Confinement: fst.ConfinementConfig{
AppID: 0, AppID: 0,
Username: "chronos", Username: "chronos",
@ -35,136 +34,132 @@ var testCasesPd = []sealTestCase{
Ephemeral(system.Process, "/run/user/1971/fortify/4a450b6596d7bc15bd01780eb9a607ac", 0700).UpdatePermType(system.Process, "/run/user/1971/fortify/4a450b6596d7bc15bd01780eb9a607ac", acl.Execute). Ephemeral(system.Process, "/run/user/1971/fortify/4a450b6596d7bc15bd01780eb9a607ac", 0700).UpdatePermType(system.Process, "/run/user/1971/fortify/4a450b6596d7bc15bd01780eb9a607ac", acl.Execute).
Ensure("/tmp/fortify.1971/tmpdir", 0700).UpdatePermType(system.User, "/tmp/fortify.1971/tmpdir", acl.Execute). Ensure("/tmp/fortify.1971/tmpdir", 0700).UpdatePermType(system.User, "/tmp/fortify.1971/tmpdir", acl.Execute).
Ensure("/tmp/fortify.1971/tmpdir/0", 01700).UpdatePermType(system.User, "/tmp/fortify.1971/tmpdir/0", acl.Read, acl.Write, acl.Execute), Ensure("/tmp/fortify.1971/tmpdir/0", 01700).UpdatePermType(system.User, "/tmp/fortify.1971/tmpdir/0", acl.Read, acl.Write, acl.Execute),
(&bwrap.Config{ &sandbox.Params{
Net: true, Flags: sandbox.FAllowNet | sandbox.FAllowUserns | sandbox.FAllowTTY,
UserNS: true, Dir: "/home/chronos",
Clearenv: true, Path: "/run/current-system/sw/bin/zsh",
Syscall: new(bwrap.SyscallPolicy), Args: []string{"/run/current-system/sw/bin/zsh"},
Chdir: "/home/chronos", Env: []string{
SetEnv: map[string]string{ "HOME=/home/chronos",
"HOME": "/home/chronos", "TERM=xterm-256color",
"SHELL": "/run/current-system/sw/bin/zsh", "USER=chronos",
"TERM": "xterm-256color", "XDG_RUNTIME_DIR=/run/user/65534",
"USER": "chronos", "XDG_SESSION_CLASS=user",
"XDG_RUNTIME_DIR": "/run/user/65534", "XDG_SESSION_TYPE=tty",
"XDG_SESSION_CLASS": "user", },
"XDG_SESSION_TYPE": "tty"}, Ops: new(sandbox.Ops).
Chmod: make(bwrap.ChmodConfig), Proc("/proc").
DieWithParent: true, Tmpfs(fst.Tmp, 4096, 0755).
AsInit: true, Dev("/dev").Mqueue("/dev/mqueue").
}).SetUID(65534).SetGID(65534). Bind("/bin", "/bin", sandbox.BindWritable).
Procfs("/proc"). Bind("/boot", "/boot", sandbox.BindWritable).
Tmpfs(fst.Tmp, 4096). Bind("/home", "/home", sandbox.BindWritable).
DevTmpfs("/dev").Mqueue("/dev/mqueue"). Bind("/lib", "/lib", sandbox.BindWritable).
Bind("/bin", "/bin", false, true). Bind("/lib64", "/lib64", sandbox.BindWritable).
Bind("/boot", "/boot", false, true). Bind("/nix", "/nix", sandbox.BindWritable).
Bind("/home", "/home", false, true). Bind("/root", "/root", sandbox.BindWritable).
Bind("/lib", "/lib", false, true). Bind("/run", "/run", sandbox.BindWritable).
Bind("/lib64", "/lib64", false, true). Bind("/srv", "/srv", sandbox.BindWritable).
Bind("/nix", "/nix", false, true). Bind("/sys", "/sys", sandbox.BindWritable).
Bind("/root", "/root", false, true). Bind("/usr", "/usr", sandbox.BindWritable).
Bind("/run", "/run", false, true). Bind("/var", "/var", sandbox.BindWritable).
Bind("/srv", "/srv", false, true). Bind("/dev/kvm", "/dev/kvm", sandbox.BindWritable|sandbox.BindDevice|sandbox.BindOptional).
Bind("/sys", "/sys", false, true). Tmpfs("/run/user/1971", 8192, 0755).
Bind("/usr", "/usr", false, true). Tmpfs("/run/dbus", 8192, 0755).
Bind("/var", "/var", false, true). Bind("/etc", fst.Tmp+"/etc", 0).
Bind("/dev/kvm", "/dev/kvm", true, true, true). Mkdir("/etc", 0700).
Tmpfs("/run/user/1971", 8192). Link(fst.Tmp+"/etc/alsa", "/etc/alsa").
Tmpfs("/run/dbus", 8192). Link(fst.Tmp+"/etc/bashrc", "/etc/bashrc").
Bind("/etc", fst.Tmp+"/etc"). Link(fst.Tmp+"/etc/binfmt.d", "/etc/binfmt.d").
Symlink(fst.Tmp+"/etc/alsa", "/etc/alsa"). Link(fst.Tmp+"/etc/dbus-1", "/etc/dbus-1").
Symlink(fst.Tmp+"/etc/bashrc", "/etc/bashrc"). Link(fst.Tmp+"/etc/default", "/etc/default").
Symlink(fst.Tmp+"/etc/binfmt.d", "/etc/binfmt.d"). Link(fst.Tmp+"/etc/ethertypes", "/etc/ethertypes").
Symlink(fst.Tmp+"/etc/dbus-1", "/etc/dbus-1"). Link(fst.Tmp+"/etc/fonts", "/etc/fonts").
Symlink(fst.Tmp+"/etc/default", "/etc/default"). Link(fst.Tmp+"/etc/fstab", "/etc/fstab").
Symlink(fst.Tmp+"/etc/ethertypes", "/etc/ethertypes"). Link(fst.Tmp+"/etc/fuse.conf", "/etc/fuse.conf").
Symlink(fst.Tmp+"/etc/fonts", "/etc/fonts"). Link(fst.Tmp+"/etc/host.conf", "/etc/host.conf").
Symlink(fst.Tmp+"/etc/fstab", "/etc/fstab"). Link(fst.Tmp+"/etc/hostid", "/etc/hostid").
Symlink(fst.Tmp+"/etc/fuse.conf", "/etc/fuse.conf"). Link(fst.Tmp+"/etc/hostname", "/etc/hostname").
Symlink(fst.Tmp+"/etc/host.conf", "/etc/host.conf"). Link(fst.Tmp+"/etc/hostname.CHECKSUM", "/etc/hostname.CHECKSUM").
Symlink(fst.Tmp+"/etc/hostid", "/etc/hostid"). Link(fst.Tmp+"/etc/hosts", "/etc/hosts").
Symlink(fst.Tmp+"/etc/hostname", "/etc/hostname"). Link(fst.Tmp+"/etc/inputrc", "/etc/inputrc").
Symlink(fst.Tmp+"/etc/hostname.CHECKSUM", "/etc/hostname.CHECKSUM"). Link(fst.Tmp+"/etc/ipsec.d", "/etc/ipsec.d").
Symlink(fst.Tmp+"/etc/hosts", "/etc/hosts"). Link(fst.Tmp+"/etc/issue", "/etc/issue").
Symlink(fst.Tmp+"/etc/inputrc", "/etc/inputrc"). Link(fst.Tmp+"/etc/kbd", "/etc/kbd").
Symlink(fst.Tmp+"/etc/ipsec.d", "/etc/ipsec.d"). Link(fst.Tmp+"/etc/libblockdev", "/etc/libblockdev").
Symlink(fst.Tmp+"/etc/issue", "/etc/issue"). Link(fst.Tmp+"/etc/locale.conf", "/etc/locale.conf").
Symlink(fst.Tmp+"/etc/kbd", "/etc/kbd"). Link(fst.Tmp+"/etc/localtime", "/etc/localtime").
Symlink(fst.Tmp+"/etc/libblockdev", "/etc/libblockdev"). Link(fst.Tmp+"/etc/login.defs", "/etc/login.defs").
Symlink(fst.Tmp+"/etc/locale.conf", "/etc/locale.conf"). Link(fst.Tmp+"/etc/lsb-release", "/etc/lsb-release").
Symlink(fst.Tmp+"/etc/localtime", "/etc/localtime"). Link(fst.Tmp+"/etc/lvm", "/etc/lvm").
Symlink(fst.Tmp+"/etc/login.defs", "/etc/login.defs"). Link(fst.Tmp+"/etc/machine-id", "/etc/machine-id").
Symlink(fst.Tmp+"/etc/lsb-release", "/etc/lsb-release"). Link(fst.Tmp+"/etc/man_db.conf", "/etc/man_db.conf").
Symlink(fst.Tmp+"/etc/lvm", "/etc/lvm"). Link(fst.Tmp+"/etc/modprobe.d", "/etc/modprobe.d").
Symlink(fst.Tmp+"/etc/machine-id", "/etc/machine-id"). Link(fst.Tmp+"/etc/modules-load.d", "/etc/modules-load.d").
Symlink(fst.Tmp+"/etc/man_db.conf", "/etc/man_db.conf"). Link("/proc/mounts", "/etc/mtab").
Symlink(fst.Tmp+"/etc/modprobe.d", "/etc/modprobe.d"). Link(fst.Tmp+"/etc/nanorc", "/etc/nanorc").
Symlink(fst.Tmp+"/etc/modules-load.d", "/etc/modules-load.d"). Link(fst.Tmp+"/etc/netgroup", "/etc/netgroup").
Symlink("/proc/mounts", "/etc/mtab"). Link(fst.Tmp+"/etc/NetworkManager", "/etc/NetworkManager").
Symlink(fst.Tmp+"/etc/nanorc", "/etc/nanorc"). Link(fst.Tmp+"/etc/nix", "/etc/nix").
Symlink(fst.Tmp+"/etc/netgroup", "/etc/netgroup"). Link(fst.Tmp+"/etc/nixos", "/etc/nixos").
Symlink(fst.Tmp+"/etc/NetworkManager", "/etc/NetworkManager"). Link(fst.Tmp+"/etc/NIXOS", "/etc/NIXOS").
Symlink(fst.Tmp+"/etc/nix", "/etc/nix"). Link(fst.Tmp+"/etc/nscd.conf", "/etc/nscd.conf").
Symlink(fst.Tmp+"/etc/nixos", "/etc/nixos"). Link(fst.Tmp+"/etc/nsswitch.conf", "/etc/nsswitch.conf").
Symlink(fst.Tmp+"/etc/NIXOS", "/etc/NIXOS"). Link(fst.Tmp+"/etc/opensnitchd", "/etc/opensnitchd").
Symlink(fst.Tmp+"/etc/nscd.conf", "/etc/nscd.conf"). Link(fst.Tmp+"/etc/os-release", "/etc/os-release").
Symlink(fst.Tmp+"/etc/nsswitch.conf", "/etc/nsswitch.conf"). Link(fst.Tmp+"/etc/pam", "/etc/pam").
Symlink(fst.Tmp+"/etc/opensnitchd", "/etc/opensnitchd"). Link(fst.Tmp+"/etc/pam.d", "/etc/pam.d").
Symlink(fst.Tmp+"/etc/os-release", "/etc/os-release"). Link(fst.Tmp+"/etc/pipewire", "/etc/pipewire").
Symlink(fst.Tmp+"/etc/pam", "/etc/pam"). Link(fst.Tmp+"/etc/pki", "/etc/pki").
Symlink(fst.Tmp+"/etc/pam.d", "/etc/pam.d"). Link(fst.Tmp+"/etc/polkit-1", "/etc/polkit-1").
Symlink(fst.Tmp+"/etc/pipewire", "/etc/pipewire"). Link(fst.Tmp+"/etc/profile", "/etc/profile").
Symlink(fst.Tmp+"/etc/pki", "/etc/pki"). Link(fst.Tmp+"/etc/protocols", "/etc/protocols").
Symlink(fst.Tmp+"/etc/polkit-1", "/etc/polkit-1"). Link(fst.Tmp+"/etc/qemu", "/etc/qemu").
Symlink(fst.Tmp+"/etc/profile", "/etc/profile"). Link(fst.Tmp+"/etc/resolv.conf", "/etc/resolv.conf").
Symlink(fst.Tmp+"/etc/protocols", "/etc/protocols"). Link(fst.Tmp+"/etc/resolvconf.conf", "/etc/resolvconf.conf").
Symlink(fst.Tmp+"/etc/qemu", "/etc/qemu"). Link(fst.Tmp+"/etc/rpc", "/etc/rpc").
Symlink(fst.Tmp+"/etc/resolv.conf", "/etc/resolv.conf"). Link(fst.Tmp+"/etc/samba", "/etc/samba").
Symlink(fst.Tmp+"/etc/resolvconf.conf", "/etc/resolvconf.conf"). Link(fst.Tmp+"/etc/sddm.conf", "/etc/sddm.conf").
Symlink(fst.Tmp+"/etc/rpc", "/etc/rpc"). Link(fst.Tmp+"/etc/secureboot", "/etc/secureboot").
Symlink(fst.Tmp+"/etc/samba", "/etc/samba"). Link(fst.Tmp+"/etc/services", "/etc/services").
Symlink(fst.Tmp+"/etc/sddm.conf", "/etc/sddm.conf"). Link(fst.Tmp+"/etc/set-environment", "/etc/set-environment").
Symlink(fst.Tmp+"/etc/secureboot", "/etc/secureboot"). Link(fst.Tmp+"/etc/shadow", "/etc/shadow").
Symlink(fst.Tmp+"/etc/services", "/etc/services"). Link(fst.Tmp+"/etc/shells", "/etc/shells").
Symlink(fst.Tmp+"/etc/set-environment", "/etc/set-environment"). Link(fst.Tmp+"/etc/ssh", "/etc/ssh").
Symlink(fst.Tmp+"/etc/shadow", "/etc/shadow"). Link(fst.Tmp+"/etc/ssl", "/etc/ssl").
Symlink(fst.Tmp+"/etc/shells", "/etc/shells"). Link(fst.Tmp+"/etc/static", "/etc/static").
Symlink(fst.Tmp+"/etc/ssh", "/etc/ssh"). Link(fst.Tmp+"/etc/subgid", "/etc/subgid").
Symlink(fst.Tmp+"/etc/ssl", "/etc/ssl"). Link(fst.Tmp+"/etc/subuid", "/etc/subuid").
Symlink(fst.Tmp+"/etc/static", "/etc/static"). Link(fst.Tmp+"/etc/sudoers", "/etc/sudoers").
Symlink(fst.Tmp+"/etc/subgid", "/etc/subgid"). Link(fst.Tmp+"/etc/sysctl.d", "/etc/sysctl.d").
Symlink(fst.Tmp+"/etc/subuid", "/etc/subuid"). Link(fst.Tmp+"/etc/systemd", "/etc/systemd").
Symlink(fst.Tmp+"/etc/sudoers", "/etc/sudoers"). Link(fst.Tmp+"/etc/terminfo", "/etc/terminfo").
Symlink(fst.Tmp+"/etc/sysctl.d", "/etc/sysctl.d"). Link(fst.Tmp+"/etc/tmpfiles.d", "/etc/tmpfiles.d").
Symlink(fst.Tmp+"/etc/systemd", "/etc/systemd"). Link(fst.Tmp+"/etc/udev", "/etc/udev").
Symlink(fst.Tmp+"/etc/terminfo", "/etc/terminfo"). Link(fst.Tmp+"/etc/udisks2", "/etc/udisks2").
Symlink(fst.Tmp+"/etc/tmpfiles.d", "/etc/tmpfiles.d"). Link(fst.Tmp+"/etc/UPower", "/etc/UPower").
Symlink(fst.Tmp+"/etc/udev", "/etc/udev"). Link(fst.Tmp+"/etc/vconsole.conf", "/etc/vconsole.conf").
Symlink(fst.Tmp+"/etc/udisks2", "/etc/udisks2"). Link(fst.Tmp+"/etc/X11", "/etc/X11").
Symlink(fst.Tmp+"/etc/UPower", "/etc/UPower"). Link(fst.Tmp+"/etc/zfs", "/etc/zfs").
Symlink(fst.Tmp+"/etc/vconsole.conf", "/etc/vconsole.conf"). Link(fst.Tmp+"/etc/zinputrc", "/etc/zinputrc").
Symlink(fst.Tmp+"/etc/X11", "/etc/X11"). Link(fst.Tmp+"/etc/zoneinfo", "/etc/zoneinfo").
Symlink(fst.Tmp+"/etc/zfs", "/etc/zfs"). Link(fst.Tmp+"/etc/zprofile", "/etc/zprofile").
Symlink(fst.Tmp+"/etc/zinputrc", "/etc/zinputrc"). Link(fst.Tmp+"/etc/zshenv", "/etc/zshenv").
Symlink(fst.Tmp+"/etc/zoneinfo", "/etc/zoneinfo"). Link(fst.Tmp+"/etc/zshrc", "/etc/zshrc").
Symlink(fst.Tmp+"/etc/zprofile", "/etc/zprofile"). Tmpfs("/run/user", 4096, 0755).
Symlink(fst.Tmp+"/etc/zshenv", "/etc/zshenv"). Tmpfs("/run/user/65534", 8388608, 0755).
Symlink(fst.Tmp+"/etc/zshrc", "/etc/zshrc"). Bind("/tmp/fortify.1971/tmpdir/0", "/tmp", sandbox.BindWritable).
Tmpfs("/run/user", 1048576). Bind("/home/chronos", "/home/chronos", sandbox.BindWritable).
Tmpfs("/run/user/65534", 8388608). Place("/etc/passwd", []byte("chronos:x:65534:65534:Fortify:/home/chronos:/run/current-system/sw/bin/zsh\n")).
Bind("/tmp/fortify.1971/tmpdir/0", "/tmp", false, true). Place("/etc/group", []byte("fortify:x:65534:\n")).
Bind("/home/chronos", "/home/chronos", false, true). Tmpfs("/var/run/nscd", 8192, 0755),
CopyBind("/etc/passwd", []byte("chronos:x:65534:65534:Fortify:/home/chronos:/run/current-system/sw/bin/zsh\n")). },
CopyBind("/etc/group", []byte("fortify:x:65534:\n")).
Tmpfs("/var/run/nscd", 8192).
Bind("/run/wrappers/bin/fortify", "/.fortify/sbin/fortify").
Symlink("fortify", "/.fortify/sbin/init0"),
}, },
{ {
"nixos permissive defaults chromium", new(stubNixOS), "nixos permissive defaults chromium", new(stubNixOS),
&fst.Config{ &fst.Config{
ID: "org.chromium.Chromium", ID: "org.chromium.Chromium",
Command: []string{"/run/current-system/sw/bin/zsh", "-c", "exec chromium "}, Args: []string{"zsh", "-c", "exec chromium "},
Confinement: fst.ConfinementConfig{ Confinement: fst.ConfinementConfig{
AppID: 9, AppID: 9,
Groups: []string{"video"}, Groups: []string{"video"},
@ -254,141 +249,136 @@ var testCasesPd = []sealTestCase{
}). }).
UpdatePerm("/tmp/fortify.1971/ebf083d1b175911782d413369b64ce7c/bus", acl.Read, acl.Write). UpdatePerm("/tmp/fortify.1971/ebf083d1b175911782d413369b64ce7c/bus", acl.Read, acl.Write).
UpdatePerm("/tmp/fortify.1971/ebf083d1b175911782d413369b64ce7c/system_bus_socket", acl.Read, acl.Write), UpdatePerm("/tmp/fortify.1971/ebf083d1b175911782d413369b64ce7c/system_bus_socket", acl.Read, acl.Write),
(&bwrap.Config{ &sandbox.Params{
Net: true, Flags: sandbox.FAllowNet | sandbox.FAllowUserns | sandbox.FAllowTTY,
UserNS: true, Dir: "/home/chronos",
Chdir: "/home/chronos", Path: "/run/current-system/sw/bin/zsh",
Clearenv: true, Args: []string{"zsh", "-c", "exec chromium "},
Syscall: new(bwrap.SyscallPolicy), Env: []string{
SetEnv: map[string]string{ "DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/65534/bus",
"DBUS_SESSION_BUS_ADDRESS": "unix:path=/run/user/65534/bus", "DBUS_SYSTEM_BUS_ADDRESS=unix:path=/run/dbus/system_bus_socket",
"DBUS_SYSTEM_BUS_ADDRESS": "unix:path=/run/dbus/system_bus_socket", "HOME=/home/chronos",
"HOME": "/home/chronos", "PULSE_COOKIE=" + fst.Tmp + "/pulse-cookie",
"PULSE_COOKIE": fst.Tmp + "/pulse-cookie", "PULSE_SERVER=unix:/run/user/65534/pulse/native",
"PULSE_SERVER": "unix:/run/user/65534/pulse/native", "TERM=xterm-256color",
"SHELL": "/run/current-system/sw/bin/zsh", "USER=chronos",
"TERM": "xterm-256color", "WAYLAND_DISPLAY=wayland-0",
"USER": "chronos", "XDG_RUNTIME_DIR=/run/user/65534",
"WAYLAND_DISPLAY": "wayland-0", "XDG_SESSION_CLASS=user",
"XDG_RUNTIME_DIR": "/run/user/65534", "XDG_SESSION_TYPE=tty",
"XDG_SESSION_CLASS": "user",
"XDG_SESSION_TYPE": "tty",
}, },
Chmod: make(bwrap.ChmodConfig), Ops: new(sandbox.Ops).
DieWithParent: true, Proc("/proc").
AsInit: true, Tmpfs(fst.Tmp, 4096, 0755).
}).SetUID(65534).SetGID(65534). Dev("/dev").Mqueue("/dev/mqueue").
Procfs("/proc"). Bind("/bin", "/bin", sandbox.BindWritable).
Tmpfs(fst.Tmp, 4096). Bind("/boot", "/boot", sandbox.BindWritable).
DevTmpfs("/dev").Mqueue("/dev/mqueue"). Bind("/home", "/home", sandbox.BindWritable).
Bind("/bin", "/bin", false, true). Bind("/lib", "/lib", sandbox.BindWritable).
Bind("/boot", "/boot", false, true). Bind("/lib64", "/lib64", sandbox.BindWritable).
Bind("/home", "/home", false, true). Bind("/nix", "/nix", sandbox.BindWritable).
Bind("/lib", "/lib", false, true). Bind("/root", "/root", sandbox.BindWritable).
Bind("/lib64", "/lib64", false, true). Bind("/run", "/run", sandbox.BindWritable).
Bind("/nix", "/nix", false, true). Bind("/srv", "/srv", sandbox.BindWritable).
Bind("/root", "/root", false, true). Bind("/sys", "/sys", sandbox.BindWritable).
Bind("/run", "/run", false, true). Bind("/usr", "/usr", sandbox.BindWritable).
Bind("/srv", "/srv", false, true). Bind("/var", "/var", sandbox.BindWritable).
Bind("/sys", "/sys", false, true). Bind("/dev/dri", "/dev/dri", sandbox.BindWritable|sandbox.BindDevice|sandbox.BindOptional).
Bind("/usr", "/usr", false, true). Bind("/dev/kvm", "/dev/kvm", sandbox.BindWritable|sandbox.BindDevice|sandbox.BindOptional).
Bind("/var", "/var", false, true). Tmpfs("/run/user/1971", 8192, 0755).
Bind("/dev/dri", "/dev/dri", true, true, true). Tmpfs("/run/dbus", 8192, 0755).
Bind("/dev/kvm", "/dev/kvm", true, true, true). Bind("/etc", fst.Tmp+"/etc", 0).
Tmpfs("/run/user/1971", 8192). Mkdir("/etc", 0700).
Tmpfs("/run/dbus", 8192). Link(fst.Tmp+"/etc/alsa", "/etc/alsa").
Bind("/etc", fst.Tmp+"/etc"). Link(fst.Tmp+"/etc/bashrc", "/etc/bashrc").
Symlink(fst.Tmp+"/etc/alsa", "/etc/alsa"). Link(fst.Tmp+"/etc/binfmt.d", "/etc/binfmt.d").
Symlink(fst.Tmp+"/etc/bashrc", "/etc/bashrc"). Link(fst.Tmp+"/etc/dbus-1", "/etc/dbus-1").
Symlink(fst.Tmp+"/etc/binfmt.d", "/etc/binfmt.d"). Link(fst.Tmp+"/etc/default", "/etc/default").
Symlink(fst.Tmp+"/etc/dbus-1", "/etc/dbus-1"). Link(fst.Tmp+"/etc/ethertypes", "/etc/ethertypes").
Symlink(fst.Tmp+"/etc/default", "/etc/default"). Link(fst.Tmp+"/etc/fonts", "/etc/fonts").
Symlink(fst.Tmp+"/etc/ethertypes", "/etc/ethertypes"). Link(fst.Tmp+"/etc/fstab", "/etc/fstab").
Symlink(fst.Tmp+"/etc/fonts", "/etc/fonts"). Link(fst.Tmp+"/etc/fuse.conf", "/etc/fuse.conf").
Symlink(fst.Tmp+"/etc/fstab", "/etc/fstab"). Link(fst.Tmp+"/etc/host.conf", "/etc/host.conf").
Symlink(fst.Tmp+"/etc/fuse.conf", "/etc/fuse.conf"). Link(fst.Tmp+"/etc/hostid", "/etc/hostid").
Symlink(fst.Tmp+"/etc/host.conf", "/etc/host.conf"). Link(fst.Tmp+"/etc/hostname", "/etc/hostname").
Symlink(fst.Tmp+"/etc/hostid", "/etc/hostid"). Link(fst.Tmp+"/etc/hostname.CHECKSUM", "/etc/hostname.CHECKSUM").
Symlink(fst.Tmp+"/etc/hostname", "/etc/hostname"). Link(fst.Tmp+"/etc/hosts", "/etc/hosts").
Symlink(fst.Tmp+"/etc/hostname.CHECKSUM", "/etc/hostname.CHECKSUM"). Link(fst.Tmp+"/etc/inputrc", "/etc/inputrc").
Symlink(fst.Tmp+"/etc/hosts", "/etc/hosts"). Link(fst.Tmp+"/etc/ipsec.d", "/etc/ipsec.d").
Symlink(fst.Tmp+"/etc/inputrc", "/etc/inputrc"). Link(fst.Tmp+"/etc/issue", "/etc/issue").
Symlink(fst.Tmp+"/etc/ipsec.d", "/etc/ipsec.d"). Link(fst.Tmp+"/etc/kbd", "/etc/kbd").
Symlink(fst.Tmp+"/etc/issue", "/etc/issue"). Link(fst.Tmp+"/etc/libblockdev", "/etc/libblockdev").
Symlink(fst.Tmp+"/etc/kbd", "/etc/kbd"). Link(fst.Tmp+"/etc/locale.conf", "/etc/locale.conf").
Symlink(fst.Tmp+"/etc/libblockdev", "/etc/libblockdev"). Link(fst.Tmp+"/etc/localtime", "/etc/localtime").
Symlink(fst.Tmp+"/etc/locale.conf", "/etc/locale.conf"). Link(fst.Tmp+"/etc/login.defs", "/etc/login.defs").
Symlink(fst.Tmp+"/etc/localtime", "/etc/localtime"). Link(fst.Tmp+"/etc/lsb-release", "/etc/lsb-release").
Symlink(fst.Tmp+"/etc/login.defs", "/etc/login.defs"). Link(fst.Tmp+"/etc/lvm", "/etc/lvm").
Symlink(fst.Tmp+"/etc/lsb-release", "/etc/lsb-release"). Link(fst.Tmp+"/etc/machine-id", "/etc/machine-id").
Symlink(fst.Tmp+"/etc/lvm", "/etc/lvm"). Link(fst.Tmp+"/etc/man_db.conf", "/etc/man_db.conf").
Symlink(fst.Tmp+"/etc/machine-id", "/etc/machine-id"). Link(fst.Tmp+"/etc/modprobe.d", "/etc/modprobe.d").
Symlink(fst.Tmp+"/etc/man_db.conf", "/etc/man_db.conf"). Link(fst.Tmp+"/etc/modules-load.d", "/etc/modules-load.d").
Symlink(fst.Tmp+"/etc/modprobe.d", "/etc/modprobe.d"). Link("/proc/mounts", "/etc/mtab").
Symlink(fst.Tmp+"/etc/modules-load.d", "/etc/modules-load.d"). Link(fst.Tmp+"/etc/nanorc", "/etc/nanorc").
Symlink("/proc/mounts", "/etc/mtab"). Link(fst.Tmp+"/etc/netgroup", "/etc/netgroup").
Symlink(fst.Tmp+"/etc/nanorc", "/etc/nanorc"). Link(fst.Tmp+"/etc/NetworkManager", "/etc/NetworkManager").
Symlink(fst.Tmp+"/etc/netgroup", "/etc/netgroup"). Link(fst.Tmp+"/etc/nix", "/etc/nix").
Symlink(fst.Tmp+"/etc/NetworkManager", "/etc/NetworkManager"). Link(fst.Tmp+"/etc/nixos", "/etc/nixos").
Symlink(fst.Tmp+"/etc/nix", "/etc/nix"). Link(fst.Tmp+"/etc/NIXOS", "/etc/NIXOS").
Symlink(fst.Tmp+"/etc/nixos", "/etc/nixos"). Link(fst.Tmp+"/etc/nscd.conf", "/etc/nscd.conf").
Symlink(fst.Tmp+"/etc/NIXOS", "/etc/NIXOS"). Link(fst.Tmp+"/etc/nsswitch.conf", "/etc/nsswitch.conf").
Symlink(fst.Tmp+"/etc/nscd.conf", "/etc/nscd.conf"). Link(fst.Tmp+"/etc/opensnitchd", "/etc/opensnitchd").
Symlink(fst.Tmp+"/etc/nsswitch.conf", "/etc/nsswitch.conf"). Link(fst.Tmp+"/etc/os-release", "/etc/os-release").
Symlink(fst.Tmp+"/etc/opensnitchd", "/etc/opensnitchd"). Link(fst.Tmp+"/etc/pam", "/etc/pam").
Symlink(fst.Tmp+"/etc/os-release", "/etc/os-release"). Link(fst.Tmp+"/etc/pam.d", "/etc/pam.d").
Symlink(fst.Tmp+"/etc/pam", "/etc/pam"). Link(fst.Tmp+"/etc/pipewire", "/etc/pipewire").
Symlink(fst.Tmp+"/etc/pam.d", "/etc/pam.d"). Link(fst.Tmp+"/etc/pki", "/etc/pki").
Symlink(fst.Tmp+"/etc/pipewire", "/etc/pipewire"). Link(fst.Tmp+"/etc/polkit-1", "/etc/polkit-1").
Symlink(fst.Tmp+"/etc/pki", "/etc/pki"). Link(fst.Tmp+"/etc/profile", "/etc/profile").
Symlink(fst.Tmp+"/etc/polkit-1", "/etc/polkit-1"). Link(fst.Tmp+"/etc/protocols", "/etc/protocols").
Symlink(fst.Tmp+"/etc/profile", "/etc/profile"). Link(fst.Tmp+"/etc/qemu", "/etc/qemu").
Symlink(fst.Tmp+"/etc/protocols", "/etc/protocols"). Link(fst.Tmp+"/etc/resolv.conf", "/etc/resolv.conf").
Symlink(fst.Tmp+"/etc/qemu", "/etc/qemu"). Link(fst.Tmp+"/etc/resolvconf.conf", "/etc/resolvconf.conf").
Symlink(fst.Tmp+"/etc/resolv.conf", "/etc/resolv.conf"). Link(fst.Tmp+"/etc/rpc", "/etc/rpc").
Symlink(fst.Tmp+"/etc/resolvconf.conf", "/etc/resolvconf.conf"). Link(fst.Tmp+"/etc/samba", "/etc/samba").
Symlink(fst.Tmp+"/etc/rpc", "/etc/rpc"). Link(fst.Tmp+"/etc/sddm.conf", "/etc/sddm.conf").
Symlink(fst.Tmp+"/etc/samba", "/etc/samba"). Link(fst.Tmp+"/etc/secureboot", "/etc/secureboot").
Symlink(fst.Tmp+"/etc/sddm.conf", "/etc/sddm.conf"). Link(fst.Tmp+"/etc/services", "/etc/services").
Symlink(fst.Tmp+"/etc/secureboot", "/etc/secureboot"). Link(fst.Tmp+"/etc/set-environment", "/etc/set-environment").
Symlink(fst.Tmp+"/etc/services", "/etc/services"). Link(fst.Tmp+"/etc/shadow", "/etc/shadow").
Symlink(fst.Tmp+"/etc/set-environment", "/etc/set-environment"). Link(fst.Tmp+"/etc/shells", "/etc/shells").
Symlink(fst.Tmp+"/etc/shadow", "/etc/shadow"). Link(fst.Tmp+"/etc/ssh", "/etc/ssh").
Symlink(fst.Tmp+"/etc/shells", "/etc/shells"). Link(fst.Tmp+"/etc/ssl", "/etc/ssl").
Symlink(fst.Tmp+"/etc/ssh", "/etc/ssh"). Link(fst.Tmp+"/etc/static", "/etc/static").
Symlink(fst.Tmp+"/etc/ssl", "/etc/ssl"). Link(fst.Tmp+"/etc/subgid", "/etc/subgid").
Symlink(fst.Tmp+"/etc/static", "/etc/static"). Link(fst.Tmp+"/etc/subuid", "/etc/subuid").
Symlink(fst.Tmp+"/etc/subgid", "/etc/subgid"). Link(fst.Tmp+"/etc/sudoers", "/etc/sudoers").
Symlink(fst.Tmp+"/etc/subuid", "/etc/subuid"). Link(fst.Tmp+"/etc/sysctl.d", "/etc/sysctl.d").
Symlink(fst.Tmp+"/etc/sudoers", "/etc/sudoers"). Link(fst.Tmp+"/etc/systemd", "/etc/systemd").
Symlink(fst.Tmp+"/etc/sysctl.d", "/etc/sysctl.d"). Link(fst.Tmp+"/etc/terminfo", "/etc/terminfo").
Symlink(fst.Tmp+"/etc/systemd", "/etc/systemd"). Link(fst.Tmp+"/etc/tmpfiles.d", "/etc/tmpfiles.d").
Symlink(fst.Tmp+"/etc/terminfo", "/etc/terminfo"). Link(fst.Tmp+"/etc/udev", "/etc/udev").
Symlink(fst.Tmp+"/etc/tmpfiles.d", "/etc/tmpfiles.d"). Link(fst.Tmp+"/etc/udisks2", "/etc/udisks2").
Symlink(fst.Tmp+"/etc/udev", "/etc/udev"). Link(fst.Tmp+"/etc/UPower", "/etc/UPower").
Symlink(fst.Tmp+"/etc/udisks2", "/etc/udisks2"). Link(fst.Tmp+"/etc/vconsole.conf", "/etc/vconsole.conf").
Symlink(fst.Tmp+"/etc/UPower", "/etc/UPower"). Link(fst.Tmp+"/etc/X11", "/etc/X11").
Symlink(fst.Tmp+"/etc/vconsole.conf", "/etc/vconsole.conf"). Link(fst.Tmp+"/etc/zfs", "/etc/zfs").
Symlink(fst.Tmp+"/etc/X11", "/etc/X11"). Link(fst.Tmp+"/etc/zinputrc", "/etc/zinputrc").
Symlink(fst.Tmp+"/etc/zfs", "/etc/zfs"). Link(fst.Tmp+"/etc/zoneinfo", "/etc/zoneinfo").
Symlink(fst.Tmp+"/etc/zinputrc", "/etc/zinputrc"). Link(fst.Tmp+"/etc/zprofile", "/etc/zprofile").
Symlink(fst.Tmp+"/etc/zoneinfo", "/etc/zoneinfo"). Link(fst.Tmp+"/etc/zshenv", "/etc/zshenv").
Symlink(fst.Tmp+"/etc/zprofile", "/etc/zprofile"). Link(fst.Tmp+"/etc/zshrc", "/etc/zshrc").
Symlink(fst.Tmp+"/etc/zshenv", "/etc/zshenv"). Tmpfs("/run/user", 4096, 0755).
Symlink(fst.Tmp+"/etc/zshrc", "/etc/zshrc"). Tmpfs("/run/user/65534", 8388608, 0755).
Tmpfs("/run/user", 1048576). Bind("/tmp/fortify.1971/tmpdir/9", "/tmp", sandbox.BindWritable).
Tmpfs("/run/user/65534", 8388608). Bind("/home/chronos", "/home/chronos", sandbox.BindWritable).
Bind("/tmp/fortify.1971/tmpdir/9", "/tmp", false, true). Place("/etc/passwd", []byte("chronos:x:65534:65534:Fortify:/home/chronos:/run/current-system/sw/bin/zsh\n")).
Bind("/home/chronos", "/home/chronos", false, true). Place("/etc/group", []byte("fortify:x:65534:\n")).
CopyBind("/etc/passwd", []byte("chronos:x:65534:65534:Fortify:/home/chronos:/run/current-system/sw/bin/zsh\n")). Bind("/tmp/fortify.1971/wayland/ebf083d1b175911782d413369b64ce7c", "/run/user/65534/wayland-0", 0).
CopyBind("/etc/group", []byte("fortify:x:65534:\n")). Bind("/run/user/1971/fortify/ebf083d1b175911782d413369b64ce7c/pulse", "/run/user/65534/pulse/native", 0).
Bind("/tmp/fortify.1971/wayland/ebf083d1b175911782d413369b64ce7c", "/run/user/65534/wayland-0"). Place(fst.Tmp+"/pulse-cookie", nil).
Bind("/run/user/1971/fortify/ebf083d1b175911782d413369b64ce7c/pulse", "/run/user/65534/pulse/native"). Bind("/tmp/fortify.1971/ebf083d1b175911782d413369b64ce7c/bus", "/run/user/65534/bus", 0).
CopyBind(fst.Tmp+"/pulse-cookie", nil). Bind("/tmp/fortify.1971/ebf083d1b175911782d413369b64ce7c/system_bus_socket", "/run/dbus/system_bus_socket", 0).
Bind("/tmp/fortify.1971/ebf083d1b175911782d413369b64ce7c/bus", "/run/user/65534/bus"). Tmpfs("/var/run/nscd", 8192, 0755),
Bind("/tmp/fortify.1971/ebf083d1b175911782d413369b64ce7c/system_bus_socket", "/run/dbus/system_bus_socket"). },
Tmpfs("/var/run/nscd", 8192).
Bind("/run/wrappers/bin/fortify", "/.fortify/sbin/fortify").
Symlink("fortify", "/.fortify/sbin/init0"),
}, },
} }

View File

@ -55,10 +55,8 @@ func (s *stubNixOS) LookPath(file string) (string, error) {
} }
switch file { switch file {
case "sudo": case "zsh":
return "/run/wrappers/bin/sudo", nil return "/run/current-system/sw/bin/zsh", nil
case "machinectl":
return "/home/ophestra/.nix-profile/bin/machinectl", nil
default: default:
panic(fmt.Sprintf("attempted to look up unexpected executable %q", file)) panic(fmt.Sprintf("attempted to look up unexpected executable %q", file))
} }

View File

@ -8,19 +8,19 @@ import (
"time" "time"
"git.gensokyo.uk/security/fortify/fst" "git.gensokyo.uk/security/fortify/fst"
"git.gensokyo.uk/security/fortify/helper/bwrap"
"git.gensokyo.uk/security/fortify/internal/app" "git.gensokyo.uk/security/fortify/internal/app"
"git.gensokyo.uk/security/fortify/internal/sys" "git.gensokyo.uk/security/fortify/internal/sys"
"git.gensokyo.uk/security/fortify/sandbox"
"git.gensokyo.uk/security/fortify/system" "git.gensokyo.uk/security/fortify/system"
) )
type sealTestCase struct { type sealTestCase struct {
name string name string
os sys.State os sys.State
config *fst.Config config *fst.Config
id fst.ID id fst.ID
wantSys *system.I wantSys *system.I
wantBwrap *bwrap.Config wantContainer *sandbox.Params
} }
func TestApp(t *testing.T) { func TestApp(t *testing.T) {
@ -30,15 +30,15 @@ func TestApp(t *testing.T) {
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
a := app.NewWithID(tc.id, tc.os) a := app.NewWithID(tc.id, tc.os)
var ( var (
gotSys *system.I gotSys *system.I
gotBwrap *bwrap.Config gotContainer *sandbox.Params
) )
if !t.Run("seal", func(t *testing.T) { if !t.Run("seal", func(t *testing.T) {
if sa, err := a.Seal(tc.config); err != nil { if sa, err := a.Seal(tc.config); err != nil {
t.Errorf("Seal: error = %v", err) t.Errorf("Seal: error = %v", err)
return return
} else { } else {
gotSys, gotBwrap = app.AppSystemBwrap(a, sa) gotSys, gotContainer = app.AppIParams(a, sa)
} }
}) { }) {
return return
@ -51,10 +51,10 @@ func TestApp(t *testing.T) {
} }
}) })
t.Run("compare bwrap", func(t *testing.T) { t.Run("compare params", func(t *testing.T) {
if !reflect.DeepEqual(gotBwrap, tc.wantBwrap) { if !reflect.DeepEqual(gotContainer, tc.wantContainer) {
t.Errorf("seal: bwrap =\n%s\n, want\n%s", t.Errorf("seal: params =\n%s\n, want\n%s",
mustMarshal(gotBwrap), mustMarshal(tc.wantBwrap)) mustMarshal(gotContainer), mustMarshal(tc.wantContainer))
} }
}) })
}) })

View File

@ -2,8 +2,8 @@ package app
import ( import (
"git.gensokyo.uk/security/fortify/fst" "git.gensokyo.uk/security/fortify/fst"
"git.gensokyo.uk/security/fortify/helper/bwrap"
"git.gensokyo.uk/security/fortify/internal/sys" "git.gensokyo.uk/security/fortify/internal/sys"
"git.gensokyo.uk/security/fortify/sandbox"
"git.gensokyo.uk/security/fortify/system" "git.gensokyo.uk/security/fortify/system"
) )
@ -14,7 +14,7 @@ func NewWithID(id fst.ID, os sys.State) fst.App {
return a return a
} }
func AppSystemBwrap(a fst.App, sa fst.SealedApp) (*system.I, *bwrap.Config) { func AppIParams(a fst.App, sa fst.SealedApp) (*system.I, *sandbox.Params) {
v := a.(*app) v := a.(*app)
seal := sa.(*outcome) seal := sa.(*outcome)
if v.outcome != seal || v.id != seal.id { if v.outcome != seal || v.id != seal.id {

View File

@ -1,18 +0,0 @@
package init0
import (
"os"
"path"
"git.gensokyo.uk/security/fortify/internal"
)
// used by the parent process
// TryArgv0 calls [Main] if the last element of argv0 is "init0".
func TryArgv0() {
if len(os.Args) > 0 && path.Base(os.Args[0]) == "init0" {
Main()
internal.Exit(0)
}
}

View File

@ -1,165 +0,0 @@
package init0
import (
"errors"
"log"
"os"
"os/exec"
"os/signal"
"syscall"
"time"
"git.gensokyo.uk/security/fortify/internal"
"git.gensokyo.uk/security/fortify/internal/fmsg"
"git.gensokyo.uk/security/fortify/sandbox"
)
const (
// time to wait for linger processes after death of initial process
residualProcessTimeout = 5 * time.Second
)
// everything beyond this point runs within pid namespace
// proceed with caution!
func Main() {
// sharing stdout with shim
// USE WITH CAUTION
fmsg.Prepare("init0")
// setting this prevents ptrace
if err := sandbox.SetDumpable(sandbox.SUID_DUMP_DISABLE); err != nil {
log.Fatalf("cannot set SUID_DUMP_DISABLE: %s", err)
}
if os.Getpid() != 1 {
log.Fatal("this process must run as pid 1")
}
// receive setup payload
var (
payload Payload
closeSetup func() error
)
if f, err := sandbox.Receive(Env, &payload, nil); err != nil {
if errors.Is(err, sandbox.ErrInvalid) {
log.Fatal("invalid config descriptor")
}
if errors.Is(err, sandbox.ErrNotSet) {
log.Fatal("FORTIFY_INIT not set")
}
log.Fatalf("cannot decode init setup payload: %v", err)
} else {
fmsg.Store(payload.Verbose)
closeSetup = f
// child does not need to see this
if err = os.Unsetenv(Env); err != nil {
log.Printf("cannot unset %s: %v", Env, err)
// not fatal
} else {
fmsg.Verbose("received configuration")
}
}
// die with parent
if err := sandbox.SetPdeathsig(syscall.SIGKILL); err != nil {
log.Fatalf("prctl(PR_SET_PDEATHSIG, SIGKILL): %v", err)
}
cmd := exec.Command(payload.Argv0)
cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr
cmd.Args = payload.Argv
cmd.Env = os.Environ()
if err := cmd.Start(); err != nil {
log.Fatalf("cannot start %q: %v", payload.Argv0, err)
}
fmsg.Suspend()
// close setup pipe as setup is now complete
if err := closeSetup(); err != nil {
log.Println("cannot close setup pipe:", err)
// not fatal
}
sig := make(chan os.Signal, 2)
signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM)
type winfo struct {
wpid int
wstatus syscall.WaitStatus
}
info := make(chan winfo, 1)
done := make(chan struct{})
go func() {
var (
err error
wpid = -2
wstatus syscall.WaitStatus
)
// keep going until no child process is left
for wpid != -1 {
if err != nil {
break
}
if wpid != -2 {
info <- winfo{wpid, wstatus}
}
err = syscall.EINTR
for errors.Is(err, syscall.EINTR) {
wpid, err = syscall.Wait4(-1, &wstatus, 0, nil)
}
}
if !errors.Is(err, syscall.ECHILD) {
log.Println("unexpected wait4 response:", err)
}
close(done)
}()
// closed after residualProcessTimeout has elapsed after initial process death
timeout := make(chan struct{})
r := 2
for {
select {
case s := <-sig:
if fmsg.Resume() {
fmsg.Verbosef("terminating on %s after process start", s.String())
} else {
fmsg.Verbosef("terminating on %s", s.String())
}
internal.Exit(0)
case w := <-info:
if w.wpid == cmd.Process.Pid {
// initial process exited, output is most likely available again
fmsg.Resume()
switch {
case w.wstatus.Exited():
r = w.wstatus.ExitStatus()
case w.wstatus.Signaled():
r = 128 + int(w.wstatus.Signal())
default:
r = 255
}
go func() {
time.Sleep(residualProcessTimeout)
close(timeout)
}()
}
case <-done:
internal.Exit(r)
case <-timeout:
log.Println("timeout exceeded waiting for lingering processes")
internal.Exit(r)
}
}
}

View File

@ -1,13 +0,0 @@
package init0
const Env = "FORTIFY_INIT"
type Payload struct {
// target full exec path
Argv0 string
// child full argv
Argv []string
// verbosity pass through
Verbose bool
}

View File

@ -3,15 +3,12 @@ package app
import ( import (
"context" "context"
"errors" "errors"
"fmt"
"log" "log"
"os/exec" "os/exec"
"path/filepath"
"strings" "strings"
"time" "time"
"git.gensokyo.uk/security/fortify/fst" "git.gensokyo.uk/security/fortify/fst"
"git.gensokyo.uk/security/fortify/helper"
"git.gensokyo.uk/security/fortify/internal" "git.gensokyo.uk/security/fortify/internal"
"git.gensokyo.uk/security/fortify/internal/app/shim" "git.gensokyo.uk/security/fortify/internal/app/shim"
"git.gensokyo.uk/security/fortify/internal/fmsg" "git.gensokyo.uk/security/fortify/internal/fmsg"
@ -21,7 +18,7 @@ import (
const shimSetupTimeout = 5 * time.Second const shimSetupTimeout = 5 * time.Second
func (seal *outcome) Run(ctx context.Context, rs *fst.RunState) error { func (seal *outcome) Run(rs *fst.RunState) error {
if !seal.f.CompareAndSwap(false, true) { if !seal.f.CompareAndSwap(false, true) {
// run does much more than just starting a process; calling it twice, even if the first call fails, will result // run does much more than just starting a process; calling it twice, even if the first call fails, will result
// in inconsistent state that is impossible to clean up; return here to limit damage and hopefully give the // in inconsistent state that is impossible to clean up; return here to limit damage and hopefully give the
@ -37,33 +34,11 @@ func (seal *outcome) Run(ctx context.Context, rs *fst.RunState) error {
fmsg.Verbosef("version %s", internal.Version()) fmsg.Verbosef("version %s", internal.Version())
fmsg.Verbosef("setuid helper at %s", internal.MustFsuPath()) fmsg.Verbosef("setuid helper at %s", internal.MustFsuPath())
/*
resolve exec paths
*/
shimExec := [2]string{helper.BubblewrapName}
if len(seal.command) > 0 {
shimExec[1] = seal.command[0]
}
for i, n := range shimExec {
if len(n) == 0 {
continue
}
if filepath.Base(n) == n {
if s, err := exec.LookPath(n); err == nil {
shimExec[i] = s
} else {
return fmsg.WrapError(err,
fmt.Sprintf("executable file %q not found in $PATH", n))
}
}
}
/* /*
prepare/revert os state prepare/revert os state
*/ */
if err := seal.sys.Commit(ctx); err != nil { if err := seal.sys.Commit(seal.ctx); err != nil {
return err return err
} }
store := state.NewMulti(seal.runDirPath) store := state.NewMulti(seal.runDirPath)
@ -137,7 +112,6 @@ func (seal *outcome) Run(ctx context.Context, rs *fst.RunState) error {
if startTime, err := cmd.Start( if startTime, err := cmd.Start(
seal.user.aid.String(), seal.user.aid.String(),
seal.user.supp, seal.user.supp,
seal.bwrapSync,
); err != nil { ); err != nil {
return err return err
} else { } else {
@ -145,7 +119,7 @@ func (seal *outcome) Run(ctx context.Context, rs *fst.RunState) error {
rs.Time = startTime rs.Time = startTime
} }
c, cancel := context.WithTimeout(ctx, shimSetupTimeout) ctx, cancel := context.WithTimeout(seal.ctx, shimSetupTimeout)
defer cancel() defer cancel()
go func() { go func() {
@ -154,11 +128,9 @@ func (seal *outcome) Run(ctx context.Context, rs *fst.RunState) error {
cancel() cancel()
}() }()
if err := cmd.Serve(c, &shim.Payload{ if err := cmd.Serve(ctx, &shim.Params{
Argv: seal.command, Container: seal.container,
Exec: shimExec, Home: seal.user.data,
Bwrap: seal.container,
Home: seal.user.data,
Verbose: fmsg.Load(), Verbose: fmsg.Load(),
}); err != nil { }); err != nil {
@ -199,18 +171,22 @@ func (seal *outcome) Run(ctx context.Context, rs *fst.RunState) error {
// this is reached when a fault makes an already running shim impossible to continue execution // this is reached when a fault makes an already running shim impossible to continue execution
// however a kill signal could not be delivered (should actually always happen like that since fsu) // however a kill signal could not be delivered (should actually always happen like that since fsu)
// the effects of this is similar to the alternative exit path and ensures shim death // the effects of this is similar to the alternative exit path and ensures shim death
case err := <-cmd.WaitFallback(): case err := <-cmd.Fallback():
rs.ExitCode = 255 rs.ExitCode = 255
log.Printf("cannot terminate shim on faulted setup: %v", err) log.Printf("cannot terminate shim on faulted setup: %v", err)
// alternative exit path relying on shim behaviour on monitor process exit // alternative exit path relying on shim behaviour on monitor process exit
case <-ctx.Done(): case <-seal.ctx.Done():
fmsg.Verbose("alternative exit path selected") fmsg.Verbose("alternative exit path selected")
} }
fmsg.Resume() fmsg.Resume()
if seal.sync != nil {
if err := seal.sync.Close(); err != nil {
log.Printf("cannot close wayland security context: %v", err)
}
}
if seal.dbusMsg != nil { if seal.dbusMsg != nil {
// dump dbus message buffer
seal.dbusMsg() seal.dbusMsg()
} }

View File

@ -2,24 +2,28 @@ package app
import ( import (
"bytes" "bytes"
"context"
"encoding/gob" "encoding/gob"
"errors" "errors"
"fmt" "fmt"
"io" "io"
"io/fs" "io/fs"
"maps"
"os" "os"
"path" "path"
"regexp" "regexp"
"slices"
"strings" "strings"
"sync/atomic" "sync/atomic"
"syscall"
"git.gensokyo.uk/security/fortify/acl" "git.gensokyo.uk/security/fortify/acl"
"git.gensokyo.uk/security/fortify/dbus" "git.gensokyo.uk/security/fortify/dbus"
"git.gensokyo.uk/security/fortify/fst" "git.gensokyo.uk/security/fortify/fst"
"git.gensokyo.uk/security/fortify/helper/bwrap"
"git.gensokyo.uk/security/fortify/internal" "git.gensokyo.uk/security/fortify/internal"
"git.gensokyo.uk/security/fortify/internal/fmsg" "git.gensokyo.uk/security/fortify/internal/fmsg"
"git.gensokyo.uk/security/fortify/internal/sys" "git.gensokyo.uk/security/fortify/internal/sys"
"git.gensokyo.uk/security/fortify/sandbox"
"git.gensokyo.uk/security/fortify/system" "git.gensokyo.uk/security/fortify/system"
"git.gensokyo.uk/security/fortify/wl" "git.gensokyo.uk/security/fortify/wl"
) )
@ -65,19 +69,19 @@ type outcome struct {
// copied from [sys.State] response // copied from [sys.State] response
runDirPath string runDirPath string
// passed through from [fst.Config]
command []string
// initial [fst.Config] gob stream for state data; // initial [fst.Config] gob stream for state data;
// this is prepared ahead of time as config is mutated during seal creation // this is prepared ahead of time as config is clobbered during seal creation
ct io.WriterTo ct io.WriterTo
// dump dbus proxy message buffer // dump dbus proxy message buffer
dbusMsg func() dbusMsg func()
user fsuUser user fsuUser
sys *system.I sys *system.I
container *bwrap.Config ctx context.Context
bwrapSync *os.File
container *sandbox.Params
env map[string]string
sync *os.File
f atomic.Bool f atomic.Bool
} }
@ -100,7 +104,17 @@ type fsuUser struct {
username string username string
} }
func (seal *outcome) finalise(sys sys.State, config *fst.Config) error { func (seal *outcome) finalise(ctx context.Context, sys sys.State, config *fst.Config) error {
if seal.ctx != nil {
panic("finalise called twice")
}
seal.ctx = ctx
shellPath := "/bin/sh"
if s, ok := sys.LookupEnv(shell); ok && path.IsAbs(s) {
shellPath = s
}
{ {
// encode initial configuration for state tracking // encode initial configuration for state tracking
ct := new(bytes.Buffer) ct := new(bytes.Buffer)
@ -111,9 +125,6 @@ func (seal *outcome) finalise(sys sys.State, config *fst.Config) error {
seal.ct = ct seal.ct = ct
} }
// pass through command slice; this value is never touched in the main process
seal.command = config.Command
// allowed aid range 0 to 9999, this is checked again in fsu // allowed aid range 0 to 9999, this is checked again in fsu
if config.Confinement.AppID < 0 || config.Confinement.AppID > 9999 { if config.Confinement.AppID < 0 || config.Confinement.AppID > 9999 {
return fmsg.WrapError(ErrUser, return fmsg.WrapError(ErrUser,
@ -167,12 +178,24 @@ func (seal *outcome) finalise(sys sys.State, config *fst.Config) error {
if config.Confinement.Sandbox == nil { if config.Confinement.Sandbox == nil {
fmsg.Verbose("sandbox configuration not supplied, PROCEED WITH CAUTION") fmsg.Verbose("sandbox configuration not supplied, PROCEED WITH CAUTION")
// fsu clears the environment so resolve paths early
if !path.IsAbs(config.Path) {
if len(config.Args) > 0 {
if p, err := sys.LookPath(config.Args[0]); err != nil {
return fmsg.WrapError(err, err.Error())
} else {
config.Path = p
}
} else {
config.Path = shellPath
}
}
conf := &fst.SandboxConfig{ conf := &fst.SandboxConfig{
UserNS: true, Userns: true,
Net: true, Net: true,
Syscall: new(bwrap.SyscallPolicy), Tty: true,
NoNewSession: true, AutoEtc: true,
AutoEtc: true,
} }
// bind entries in / // bind entries in /
if d, err := sys.ReadDir("/"); err != nil { if d, err := sys.ReadDir("/"); err != nil {
@ -198,7 +221,7 @@ func (seal *outcome) finalise(sys sys.State, config *fst.Config) error {
// hide nscd from sandbox if present // hide nscd from sandbox if present
nscd := "/var/run/nscd" nscd := "/var/run/nscd"
if _, err := sys.Stat(nscd); !errors.Is(err, fs.ErrNotExist) { if _, err := sys.Stat(nscd); !errors.Is(err, fs.ErrNotExist) {
conf.Override = append(conf.Override, nscd) conf.Cover = append(conf.Cover, nscd)
} }
// bind GPU stuff // bind GPU stuff
if config.Confinement.Enablements.Has(system.EX11) || config.Confinement.Enablements.Has(system.EWayland) { if config.Confinement.Enablements.Has(system.EX11) || config.Confinement.Enablements.Has(system.EWayland) {
@ -210,17 +233,29 @@ func (seal *outcome) finalise(sys sys.State, config *fst.Config) error {
config.Confinement.Sandbox = conf config.Confinement.Sandbox = conf
} }
var mapuid *stringPair[int] var mapuid, mapgid *stringPair[int]
{ {
var uid int var uid, gid int
var err error var err error
seal.container, err = config.Confinement.Sandbox.Bwrap(sys, &uid) seal.container, seal.env, err = config.Confinement.Sandbox.ToContainer(sys, &uid, &gid)
if err != nil { if err != nil {
return err return fmsg.WrapErrorSuffix(err,
"cannot initialise container configuration:")
} }
if !path.IsAbs(config.Path) {
return fmsg.WrapError(syscall.EINVAL,
"invalid program path")
}
if len(config.Args) == 0 {
config.Args = []string{config.Path}
}
seal.container.Path = config.Path
seal.container.Args = config.Args
mapuid = newInt(uid) mapuid = newInt(uid)
if seal.container.SetEnv == nil { mapgid = newInt(gid)
seal.container.SetEnv = make(map[string]string) if seal.env == nil {
seal.env = make(map[string]string)
} }
} }
@ -255,35 +290,27 @@ func (seal *outcome) finalise(sys sys.State, config *fst.Config) error {
// inner XDG_RUNTIME_DIR default formatting of `/run/user/%d` as post-fsu user // inner XDG_RUNTIME_DIR default formatting of `/run/user/%d` as post-fsu user
innerRuntimeDir := path.Join("/run/user", mapuid.String()) innerRuntimeDir := path.Join("/run/user", mapuid.String())
seal.container.Tmpfs("/run/user", 1*1024*1024) seal.container.Tmpfs("/run/user", 1<<12, 0755)
seal.container.Tmpfs(innerRuntimeDir, 8*1024*1024) seal.container.Tmpfs(innerRuntimeDir, 1<<23, 0755)
seal.container.SetEnv[xdgRuntimeDir] = innerRuntimeDir seal.env[xdgRuntimeDir] = innerRuntimeDir
seal.container.SetEnv[xdgSessionClass] = "user" seal.env[xdgSessionClass] = "user"
seal.container.SetEnv[xdgSessionType] = "tty" seal.env[xdgSessionType] = "tty"
// outer path for inner /tmp // outer path for inner /tmp
{ {
tmpdir := path.Join(sc.SharePath, "tmpdir") tmpdir := path.Join(sc.SharePath, "tmpdir")
seal.sys.Ensure(tmpdir, 0700) seal.sys.Ensure(tmpdir, 0700)
seal.sys.UpdatePermType(system.User, tmpdir, acl.Execute) seal.sys.UpdatePermType(system.User, tmpdir, acl.Execute)
tmpdirProc := path.Join(tmpdir, seal.user.aid.String()) tmpdirInst := path.Join(tmpdir, seal.user.aid.String())
seal.sys.Ensure(tmpdirProc, 01700) seal.sys.Ensure(tmpdirInst, 01700)
seal.sys.UpdatePermType(system.User, tmpdirProc, acl.Read, acl.Write, acl.Execute) seal.sys.UpdatePermType(system.User, tmpdirInst, acl.Read, acl.Write, acl.Execute)
seal.container.Bind(tmpdirProc, "/tmp", false, true) seal.container.Bind(tmpdirInst, "/tmp", sandbox.BindWritable)
} }
/* /*
Passwd database Passwd database
*/ */
// look up shell
sh := "/bin/sh"
if s, ok := sys.LookupEnv(shell); ok {
seal.container.SetEnv[shell] = s
sh = s
}
// bind home directory
homeDir := "/var/empty" homeDir := "/var/empty"
if seal.user.home != "" { if seal.user.home != "" {
homeDir = seal.user.home homeDir = seal.user.home
@ -292,27 +319,25 @@ func (seal *outcome) finalise(sys sys.State, config *fst.Config) error {
if seal.user.username != "" { if seal.user.username != "" {
username = seal.user.username username = seal.user.username
} }
seal.container.Bind(seal.user.data, homeDir, false, true) seal.container.Bind(seal.user.data, homeDir, sandbox.BindWritable)
seal.container.Chdir = homeDir seal.container.Dir = homeDir
seal.container.SetEnv["HOME"] = homeDir seal.env["HOME"] = homeDir
seal.container.SetEnv["USER"] = username seal.env["USER"] = username
// generate /etc/passwd and /etc/group seal.container.Place("/etc/passwd",
seal.container.CopyBind("/etc/passwd", []byte(username+":x:"+mapuid.String()+":"+mapgid.String()+":Fortify:"+homeDir+":"+shellPath+"\n"))
[]byte(username+":x:"+mapuid.String()+":"+mapuid.String()+":Fortify:"+homeDir+":"+sh+"\n")) seal.container.Place("/etc/group",
seal.container.CopyBind("/etc/group", []byte("fortify:x:"+mapgid.String()+":\n"))
[]byte("fortify:x:"+mapuid.String()+":\n"))
/* /*
Display servers Display servers
*/ */
// pass $TERM to launcher // pass $TERM for proper terminal I/O in shell
if t, ok := sys.LookupEnv(term); ok { if t, ok := sys.LookupEnv(term); ok {
seal.container.SetEnv[term] = t seal.env[term] = t
} }
// set up wayland
if config.Confinement.Enablements.Has(system.EWayland) { if config.Confinement.Enablements.Has(system.EWayland) {
// outer wayland socket (usually `/run/user/%d/wayland-%d`) // outer wayland socket (usually `/run/user/%d/wayland-%d`)
var socketPath string var socketPath string
@ -326,7 +351,7 @@ func (seal *outcome) finalise(sys sys.State, config *fst.Config) error {
} }
innerPath := path.Join(innerRuntimeDir, wl.FallbackName) innerPath := path.Join(innerRuntimeDir, wl.FallbackName)
seal.container.SetEnv[wl.WaylandDisplay] = wl.FallbackName seal.env[wl.WaylandDisplay] = wl.FallbackName
if !config.Confinement.Sandbox.DirectWayland { // set up security-context-v1 if !config.Confinement.Sandbox.DirectWayland { // set up security-context-v1
socketDir := path.Join(sc.SharePath, "wayland") socketDir := path.Join(sc.SharePath, "wayland")
@ -337,25 +362,23 @@ func (seal *outcome) finalise(sys sys.State, config *fst.Config) error {
// use instance ID in case app id is not set // use instance ID in case app id is not set
appID = "uk.gensokyo.fortify." + seal.id.String() appID = "uk.gensokyo.fortify." + seal.id.String()
} }
seal.sys.Wayland(&seal.bwrapSync, outerPath, socketPath, appID, seal.id.String()) seal.sys.Wayland(&seal.sync, outerPath, socketPath, appID, seal.id.String())
seal.container.Bind(outerPath, innerPath) seal.container.Bind(outerPath, innerPath, 0)
} else { // bind mount wayland socket (insecure) } else { // bind mount wayland socket (insecure)
fmsg.Verbose("direct wayland access, PROCEED WITH CAUTION") fmsg.Verbose("direct wayland access, PROCEED WITH CAUTION")
seal.container.Bind(socketPath, innerPath) seal.container.Bind(socketPath, innerPath, 0)
seal.sys.UpdatePermType(system.EWayland, socketPath, acl.Read, acl.Write, acl.Execute) seal.sys.UpdatePermType(system.EWayland, socketPath, acl.Read, acl.Write, acl.Execute)
} }
} }
// set up X11
if config.Confinement.Enablements.Has(system.EX11) { if config.Confinement.Enablements.Has(system.EX11) {
// discover X11 and grant user permission via the `ChangeHosts` command
if d, ok := sys.LookupEnv(display); !ok { if d, ok := sys.LookupEnv(display); !ok {
return fmsg.WrapError(ErrXDisplay, return fmsg.WrapError(ErrXDisplay,
"DISPLAY is not set") "DISPLAY is not set")
} else { } else {
seal.sys.ChangeHosts("#" + seal.user.uid.String()) seal.sys.ChangeHosts("#" + seal.user.uid.String())
seal.container.SetEnv[display] = d seal.env[display] = d
seal.container.Bind("/tmp/.X11-unix", "/tmp/.X11-unix") seal.container.Bind("/tmp/.X11-unix", "/tmp/.X11-unix", 0)
} }
} }
@ -396,8 +419,8 @@ func (seal *outcome) finalise(sys sys.State, config *fst.Config) error {
innerPulseRuntimeDir := path.Join(sharePathLocal, "pulse") innerPulseRuntimeDir := path.Join(sharePathLocal, "pulse")
innerPulseSocket := path.Join(innerRuntimeDir, "pulse", "native") innerPulseSocket := path.Join(innerRuntimeDir, "pulse", "native")
seal.sys.Link(pulseSocket, innerPulseRuntimeDir) seal.sys.Link(pulseSocket, innerPulseRuntimeDir)
seal.container.Bind(innerPulseRuntimeDir, innerPulseSocket) seal.container.Bind(innerPulseRuntimeDir, innerPulseSocket, 0)
seal.container.SetEnv[pulseServer] = "unix:" + innerPulseSocket seal.env[pulseServer] = "unix:" + innerPulseSocket
// publish current user's pulse cookie for target user // publish current user's pulse cookie for target user
if src, err := discoverPulseCookie(sys); err != nil { if src, err := discoverPulseCookie(sys); err != nil {
@ -405,9 +428,9 @@ func (seal *outcome) finalise(sys sys.State, config *fst.Config) error {
fmsg.Verbose(strings.TrimSpace(err.(*fmsg.BaseError).Message())) fmsg.Verbose(strings.TrimSpace(err.(*fmsg.BaseError).Message()))
} else { } else {
innerDst := fst.Tmp + "/pulse-cookie" innerDst := fst.Tmp + "/pulse-cookie"
seal.container.SetEnv[pulseCookie] = innerDst seal.env[pulseCookie] = innerDst
payload := new([]byte) var payload *[]byte
seal.container.CopyBindRef(innerDst, &payload) seal.container.PlaceP(innerDst, &payload)
seal.sys.CopyFile(payload, src, 256, 256) seal.sys.CopyFile(payload, src, 256, 256)
} }
} }
@ -437,13 +460,13 @@ func (seal *outcome) finalise(sys sys.State, config *fst.Config) error {
// share proxy sockets // share proxy sockets
sessionInner := path.Join(innerRuntimeDir, "bus") sessionInner := path.Join(innerRuntimeDir, "bus")
seal.container.SetEnv[dbusSessionBusAddress] = "unix:path=" + sessionInner seal.env[dbusSessionBusAddress] = "unix:path=" + sessionInner
seal.container.Bind(sessionPath, sessionInner) seal.container.Bind(sessionPath, sessionInner, 0)
seal.sys.UpdatePerm(sessionPath, acl.Read, acl.Write) seal.sys.UpdatePerm(sessionPath, acl.Read, acl.Write)
if config.Confinement.SystemBus != nil { if config.Confinement.SystemBus != nil {
systemInner := "/run/dbus/system_bus_socket" systemInner := "/run/dbus/system_bus_socket"
seal.container.SetEnv[dbusSystemBusAddress] = "unix:path=" + systemInner seal.env[dbusSystemBusAddress] = "unix:path=" + systemInner
seal.container.Bind(systemPath, systemInner) seal.container.Bind(systemPath, systemInner, 0)
seal.sys.UpdatePerm(systemPath, acl.Read, acl.Write) seal.sys.UpdatePerm(systemPath, acl.Read, acl.Write)
} }
} }
@ -452,9 +475,8 @@ func (seal *outcome) finalise(sys sys.State, config *fst.Config) error {
Miscellaneous Miscellaneous
*/ */
// queue overriding tmpfs at the end of seal.container.Filesystem for _, dest := range config.Confinement.Sandbox.Cover {
for _, dest := range config.Confinement.Sandbox.Override { seal.container.Tmpfs(dest, 1<<13, 0755)
seal.container.Tmpfs(dest, 8*1024)
} }
// append ExtraPerms last // append ExtraPerms last
@ -480,12 +502,13 @@ func (seal *outcome) finalise(sys sys.State, config *fst.Config) error {
seal.sys.UpdatePermType(system.User, p.Path, perms...) seal.sys.UpdatePermType(system.User, p.Path, perms...)
} }
// mount fortify in sandbox for init // flatten and sort env for deterministic behaviour
seal.container.Bind(sys.MustExecutable(), path.Join(fst.Tmp, "sbin/fortify")) seal.container.Env = make([]string, 0, len(seal.env))
seal.container.Symlink("fortify", path.Join(fst.Tmp, "sbin/init0")) maps.All(seal.env)(func(k string, v string) bool { seal.container.Env = append(seal.container.Env, k+"="+v); return true })
slices.Sort(seal.container.Env)
fmsg.Verbosef("created application seal for uid %s (%s) groups: %v, command: %s", fmsg.Verbosef("created application seal for uid %s (%s) groups: %v, argv: %s",
seal.user.uid, seal.user.username, config.Confinement.Groups, config.Command) seal.user.uid, seal.user.username, config.Confinement.Groups, seal.container.Args)
return nil return nil
} }

View File

@ -7,18 +7,26 @@ import (
"os" "os"
"os/exec" "os/exec"
"os/signal" "os/signal"
"path"
"strconv"
"syscall" "syscall"
"time"
"git.gensokyo.uk/security/fortify/fst"
"git.gensokyo.uk/security/fortify/helper"
"git.gensokyo.uk/security/fortify/internal" "git.gensokyo.uk/security/fortify/internal"
"git.gensokyo.uk/security/fortify/internal/app/init0"
"git.gensokyo.uk/security/fortify/internal/fmsg" "git.gensokyo.uk/security/fortify/internal/fmsg"
"git.gensokyo.uk/security/fortify/sandbox" "git.gensokyo.uk/security/fortify/sandbox"
) )
const Env = "FORTIFY_SHIM"
type Params struct {
// finalised container params
Container *sandbox.Params
// path to outer home directory
Home string
// verbosity pass through
Verbose bool
}
// everything beyond this point runs as unconstrained target user // everything beyond this point runs as unconstrained target user
// proceed with caution! // proceed with caution!
@ -27,17 +35,15 @@ func Main() {
// USE WITH CAUTION // USE WITH CAUTION
fmsg.Prepare("shim") fmsg.Prepare("shim")
// setting this prevents ptrace
if err := sandbox.SetDumpable(sandbox.SUID_DUMP_DISABLE); err != nil { if err := sandbox.SetDumpable(sandbox.SUID_DUMP_DISABLE); err != nil {
log.Fatalf("cannot set SUID_DUMP_DISABLE: %s", err) log.Fatalf("cannot set SUID_DUMP_DISABLE: %s", err)
} }
// receive setup payload
var ( var (
payload Payload params Params
closeSetup func() error closeSetup func() error
) )
if f, err := sandbox.Receive(Env, &payload, nil); err != nil { if f, err := sandbox.Receive(Env, &params, nil); err != nil {
if errors.Is(err, sandbox.ErrInvalid) { if errors.Is(err, sandbox.ErrInvalid) {
log.Fatal("invalid config descriptor") log.Fatal("invalid config descriptor")
} }
@ -45,32 +51,26 @@ func Main() {
log.Fatal("FORTIFY_SHIM not set") log.Fatal("FORTIFY_SHIM not set")
} }
log.Fatalf("cannot decode shim setup payload: %v", err) log.Fatalf("cannot receive shim setup params: %v", err)
} else { } else {
internal.InstallFmsg(payload.Verbose) internal.InstallFmsg(params.Verbose)
closeSetup = f closeSetup = f
} }
if payload.Bwrap == nil { if params.Container == nil || params.Container.Ops == nil {
log.Fatal("bwrap config not supplied") log.Fatal("invalid container params")
}
// restore bwrap sync fd
var syncFd *os.File
if payload.Sync != nil {
syncFd = os.NewFile(*payload.Sync, "sync")
} }
// close setup socket // close setup socket
if err := closeSetup(); err != nil { if err := closeSetup(); err != nil {
log.Println("cannot close setup pipe:", err) log.Printf("cannot close setup pipe: %v", err)
// not fatal // not fatal
} }
// ensure home directory as target user // ensure home directory as target user
if s, err := os.Stat(payload.Home); err != nil { if s, err := os.Stat(params.Home); err != nil {
if os.IsNotExist(err) { if os.IsNotExist(err) {
if err = os.Mkdir(payload.Home, 0700); err != nil { if err = os.Mkdir(params.Home, 0700); err != nil {
log.Fatalf("cannot create home directory: %v", err) log.Fatalf("cannot create home directory: %v", err)
} }
} else { } else {
@ -79,72 +79,37 @@ func Main() {
// home directory is created, proceed // home directory is created, proceed
} else if !s.IsDir() { } else if !s.IsDir() {
log.Fatalf("data path %q is not a directory", payload.Home) log.Fatalf("path %q is not a directory", params.Home)
} }
var ic init0.Payload var name string
if len(params.Container.Args) > 0 {
// resolve argv0 name = params.Container.Args[0]
ic.Argv = payload.Argv
if len(ic.Argv) > 0 {
// looked up from $PATH by parent
ic.Argv0 = payload.Exec[1]
} else {
// no argv, look up shell instead
var ok bool
if payload.Bwrap.SetEnv == nil {
log.Fatal("no command was specified and environment is unset")
}
if ic.Argv0, ok = payload.Bwrap.SetEnv["SHELL"]; !ok {
log.Fatal("no command was specified and $SHELL was unset")
}
ic.Argv = []string{ic.Argv0}
} }
conf := payload.Bwrap
var extraFiles []*os.File
// serve setup payload
if fd, encoder, err := sandbox.Setup(&extraFiles); err != nil {
log.Fatalf("cannot pipe: %v", err)
} else {
conf.SetEnv[init0.Env] = strconv.Itoa(fd)
go func() {
fmsg.Verbose("transmitting config to init")
if err = encoder.Encode(&ic); err != nil {
log.Fatalf("cannot transmit init config: %v", err)
}
}()
}
helper.BubblewrapName = payload.Exec[0] // resolved bwrap path by parent
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM) ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer stop() // unreachable defer stop() // unreachable
if b, err := helper.NewBwrap( container := sandbox.New(ctx, name)
ctx, path.Join(fst.Tmp, "sbin/init0"), container.Params = *params.Container
nil, false, container.Stdin, container.Stdout, container.Stderr = os.Stdin, os.Stdout, os.Stderr
func(int, int) []string { return make([]string, 0) }, container.Cancel = func(cmd *exec.Cmd) error { return cmd.Process.Signal(os.Interrupt) }
func(cmd *exec.Cmd) { cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr }, container.WaitDelay = 2 * time.Second
extraFiles,
conf, syncFd, if err := container.Start(); err != nil {
); err != nil { fmsg.PrintBaseError(err, "cannot start container:")
log.Fatalf("malformed sandbox config: %v", err) os.Exit(1)
} else { }
// run and pass through exit code if err := container.Serve(); err != nil {
if err = b.Start(); err != nil { fmsg.PrintBaseError(err, "cannot configure container:")
log.Fatalf("cannot start target process: %v", err) }
} else if err = b.Wait(); err != nil { if err := container.Wait(); err != nil {
var exitError *exec.ExitError var exitError *exec.ExitError
if !errors.As(err, &exitError) { if !errors.As(err, &exitError) {
log.Printf("wait: %v", err) if errors.Is(err, context.Canceled) {
internal.Exit(127) os.Exit(2)
panic("unreachable")
} }
internal.Exit(exitError.ExitCode()) log.Printf("wait: %v", err)
panic("unreachable") os.Exit(127)
} }
os.Exit(exitError.ExitCode())
} }
} }

View File

@ -1,23 +0,0 @@
package shim
import (
"git.gensokyo.uk/security/fortify/helper/bwrap"
)
const Env = "FORTIFY_SHIM"
type Payload struct {
// child full argv
Argv []string
// bwrap, target full exec path
Exec [2]string
// bwrap config
Bwrap *bwrap.Config
// path to outer home directory
Home string
// sync fd
Sync *uintptr
// verbosity pass through
Verbose bool
}

View File

@ -8,9 +8,9 @@ import (
"os/exec" "os/exec"
"strconv" "strconv"
"strings" "strings"
"syscall"
"time" "time"
"git.gensokyo.uk/security/fortify/helper/proc"
"git.gensokyo.uk/security/fortify/internal" "git.gensokyo.uk/security/fortify/internal"
"git.gensokyo.uk/security/fortify/internal/fmsg" "git.gensokyo.uk/security/fortify/internal/fmsg"
"git.gensokyo.uk/security/fortify/sandbox" "git.gensokyo.uk/security/fortify/sandbox"
@ -25,10 +25,11 @@ type Shim struct {
killFallback chan error killFallback chan error
// monitor to shim encoder // monitor to shim encoder
encoder *gob.Encoder encoder *gob.Encoder
// bwrap --sync-fd value
sync *uintptr
} }
func (s *Shim) Unwrap() *exec.Cmd { return s.cmd }
func (s *Shim) Fallback() chan error { return s.killFallback }
func (s *Shim) String() string { func (s *Shim) String() string {
if s.cmd == nil { if s.cmd == nil {
return "(unused shim manager)" return "(unused shim manager)"
@ -36,21 +37,9 @@ func (s *Shim) String() string {
return s.cmd.String() return s.cmd.String()
} }
func (s *Shim) Unwrap() *exec.Cmd {
return s.cmd
}
func (s *Shim) WaitFallback() chan error {
return s.killFallback
}
func (s *Shim) Start( func (s *Shim) Start(
// string representation of application id
aid string, aid string,
// string representation of supplementary group ids
supp []string, supp []string,
// bwrap --sync-fd
syncFd *os.File,
) (*time.Time, error) { ) (*time.Time, error) {
// prepare user switcher invocation // prepare user switcher invocation
fsuPath := internal.MustFsuPath() fsuPath := internal.MustFsuPath()
@ -76,12 +65,6 @@ func (s *Shim) Start(
s.cmd.Stdin, s.cmd.Stdout, s.cmd.Stderr = os.Stdin, os.Stdout, os.Stderr s.cmd.Stdin, s.cmd.Stdout, s.cmd.Stderr = os.Stdin, os.Stdout, os.Stderr
s.cmd.Dir = "/" s.cmd.Dir = "/"
// pass sync fd if set
if syncFd != nil {
fd := proc.ExtraFile(s.cmd, syncFd)
s.sync = &fd
}
fmsg.Verbose("starting shim via fsu:", s.cmd) fmsg.Verbose("starting shim via fsu:", s.cmd)
// withhold messages to stderr // withhold messages to stderr
fmsg.Suspend() fmsg.Suspend()
@ -90,10 +73,11 @@ func (s *Shim) Start(
"cannot start fsu:") "cannot start fsu:")
} }
startTime := time.Now().UTC() startTime := time.Now().UTC()
return &startTime, nil return &startTime, nil
} }
func (s *Shim) Serve(ctx context.Context, payload *Payload) error { func (s *Shim) Serve(ctx context.Context, params *Params) error {
// kill shim if something goes wrong and an error is returned // kill shim if something goes wrong and an error is returned
s.killFallback = make(chan error, 1) s.killFallback = make(chan error, 1)
killShim := func() { killShim := func() {
@ -103,9 +87,8 @@ func (s *Shim) Serve(ctx context.Context, payload *Payload) error {
} }
defer func() { killShim() }() defer func() { killShim() }()
payload.Sync = s.sync
encodeErr := make(chan error) encodeErr := make(chan error)
go func() { encodeErr <- s.encoder.Encode(payload) }() go func() { encodeErr <- s.encoder.Encode(params) }()
select { select {
// encode return indicates setup completion // encode return indicates setup completion
@ -121,11 +104,11 @@ func (s *Shim) Serve(ctx context.Context, payload *Payload) error {
case <-ctx.Done(): case <-ctx.Done():
err := ctx.Err() err := ctx.Err()
if errors.Is(err, context.Canceled) { if errors.Is(err, context.Canceled) {
return fmsg.WrapError(errors.New("shim setup canceled"), return fmsg.WrapError(syscall.ECANCELED,
"shim setup canceled") "shim setup canceled")
} }
if errors.Is(err, context.DeadlineExceeded) { if errors.Is(err, context.DeadlineExceeded) {
return fmsg.WrapError(errors.New("deadline exceeded waiting for shim"), return fmsg.WrapError(syscall.ETIMEDOUT,
"deadline exceeded waiting for shim") "deadline exceeded waiting for shim")
} }
// unreachable // unreachable

View File

@ -96,7 +96,7 @@ func testStore(t *testing.T, s state.Store) {
} else { } else {
slices.Sort(aids) slices.Sort(aids)
want := []int{0, 1} want := []int{0, 1}
if slices.Compare(aids, want) != 0 { if !slices.Equal(aids, want) {
t.Fatalf("List() = %#v, want %#v", aids, want) t.Fatalf("List() = %#v, want %#v", aids, want)
} }
} }

20
main.go
View File

@ -20,7 +20,6 @@ import (
"git.gensokyo.uk/security/fortify/fst" "git.gensokyo.uk/security/fortify/fst"
"git.gensokyo.uk/security/fortify/internal" "git.gensokyo.uk/security/fortify/internal"
"git.gensokyo.uk/security/fortify/internal/app" "git.gensokyo.uk/security/fortify/internal/app"
"git.gensokyo.uk/security/fortify/internal/app/init0"
"git.gensokyo.uk/security/fortify/internal/app/shim" "git.gensokyo.uk/security/fortify/internal/app/shim"
"git.gensokyo.uk/security/fortify/internal/fmsg" "git.gensokyo.uk/security/fortify/internal/fmsg"
"git.gensokyo.uk/security/fortify/internal/state" "git.gensokyo.uk/security/fortify/internal/state"
@ -43,7 +42,6 @@ var std sys.State = new(sys.Std)
func main() { func main() {
// early init path, skips root check and duplicate PR_SET_DUMPABLE // early init path, skips root check and duplicate PR_SET_DUMPABLE
sandbox.TryArgv0(fmsg.Output{}, fmsg.Prepare, internal.InstallFmsg) sandbox.TryArgv0(fmsg.Output{}, fmsg.Prepare, internal.InstallFmsg)
init0.TryArgv0()
if err := sandbox.SetDumpable(sandbox.SUID_DUMP_DISABLE); err != nil { if err := sandbox.SetDumpable(sandbox.SUID_DUMP_DISABLE); err != nil {
log.Printf("cannot set SUID_DUMP_DISABLE: %s", err) log.Printf("cannot set SUID_DUMP_DISABLE: %s", err)
@ -76,9 +74,7 @@ func buildCommand(out io.Writer) command.Command {
Flag(&flagVerbose, "v", command.BoolFlag(false), "Print debug messages to the console"). Flag(&flagVerbose, "v", command.BoolFlag(false), "Print debug messages to the console").
Flag(&flagJSON, "json", command.BoolFlag(false), "Serialise output as JSON when applicable") Flag(&flagJSON, "json", command.BoolFlag(false), "Serialise output as JSON when applicable")
// internal commands
c.Command("shim", command.UsageInternal, func([]string) error { shim.Main(); return errSuccess }) c.Command("shim", command.UsageInternal, func([]string) error { shim.Main(); return errSuccess })
c.Command("init", command.UsageInternal, func([]string) error { init0.Main(); return errSuccess })
c.Command("app", "Launch app defined by the specified config file", func(args []string) error { c.Command("app", "Launch app defined by the specified config file", func(args []string) error {
if len(args) < 1 { if len(args) < 1 {
@ -87,10 +83,9 @@ func buildCommand(out io.Writer) command.Command {
// config extraArgs... // config extraArgs...
config := tryPath(args[0]) config := tryPath(args[0])
config.Command = append(config.Command, args[1:]...) config.Args = append(config.Args, args[1:]...)
// invoke app runApp(config)
runApp(app.MustNew(std), config)
panic("unreachable") panic("unreachable")
}) })
@ -112,8 +107,8 @@ func buildCommand(out io.Writer) command.Command {
c.NewCommand("run", "Configure and start a permissive default sandbox", func(args []string) error { c.NewCommand("run", "Configure and start a permissive default sandbox", func(args []string) error {
// initialise config from flags // initialise config from flags
config := &fst.Config{ config := &fst.Config{
ID: fid, ID: fid,
Command: args, Args: args,
} }
if aid < 0 || aid > 9999 { if aid < 0 || aid > 9999 {
@ -199,7 +194,7 @@ func buildCommand(out io.Writer) command.Command {
} }
// invoke app // invoke app
runApp(app.MustNew(std), config) runApp(config)
panic("unreachable") panic("unreachable")
}). }).
Flag(&dbusConfigSession, "dbus-config", command.StringFlag("builtin"), Flag(&dbusConfigSession, "dbus-config", command.StringFlag("builtin"),
@ -279,10 +274,11 @@ func buildCommand(out io.Writer) command.Command {
return c return c
} }
func runApp(a fst.App, config *fst.Config) { func runApp(config *fst.Config) {
ctx, stop := signal.NotifyContext(context.Background(), ctx, stop := signal.NotifyContext(context.Background(),
syscall.SIGINT, syscall.SIGTERM) syscall.SIGINT, syscall.SIGTERM)
defer stop() // unreachable defer stop() // unreachable
a := app.MustNew(ctx, std)
rs := new(fst.RunState) rs := new(fst.RunState)
if sa, err := a.Seal(config); err != nil { if sa, err := a.Seal(config); err != nil {
@ -290,7 +286,7 @@ func runApp(a fst.App, config *fst.Config) {
rs.ExitCode = 1 rs.ExitCode = 1
} else { } else {
// this updates ExitCode // this updates ExitCode
app.PrintRunStateErr(rs, sa.Run(ctx, rs)) app.PrintRunStateErr(rs, sa.Run(rs))
} }
internal.Exit(rs.ExitCode) internal.Exit(rs.ExitCode)
} }

View File

@ -86,12 +86,11 @@ in
enablements = with app.capability; (if wayland then 1 else 0) + (if x11 then 2 else 0) + (if dbus then 4 else 0) + (if pulse then 8 else 0); enablements = with app.capability; (if wayland then 1 else 0) + (if x11 then 2 else 0) + (if dbus then 4 else 0) + (if pulse then 8 else 0);
conf = { conf = {
inherit (app) id; inherit (app) id;
command = [ path = pkgs.writeScript "${app.name}-start" ''
(pkgs.writeScript "${app.name}-start" '' #!${pkgs.zsh}${pkgs.zsh.shellPath}
#!${pkgs.zsh}${pkgs.zsh.shellPath} ${script}
${script} '';
'') args = [ "${app.name}-start" ];
];
confinement = { confinement = {
app_id = aid; app_id = aid;
inherit (app) groups; inherit (app) groups;
@ -99,17 +98,15 @@ in
home = getsubhome fid aid; home = getsubhome fid aid;
sandbox = { sandbox = {
inherit (app) inherit (app)
devel
userns userns
net net
dev dev
tty
multiarch
env env
; ;
syscall = {
inherit (app) compat multiarch bluetooth;
deny_devel = !app.devel;
};
map_real_uid = app.mapRealUid; map_real_uid = app.mapRealUid;
no_new_session = app.tty;
direct_wayland = app.insecureWayland; direct_wayland = app.insecureWayland;
filesystem = filesystem =
let let
@ -149,7 +146,7 @@ in
] ]
++ app.extraPaths; ++ app.extraPaths;
auto_etc = true; auto_etc = true;
override = [ "/var/run/nscd" ]; cover = [ "/var/run/nscd" ];
}; };
inherit enablements; inherit enablements;
inherit (dbusConfig) session_bus system_bus; inherit (dbusConfig) session_bus system_bus;

View File

@ -148,21 +148,19 @@ in
''; '';
}; };
nix = mkEnableOption "nix daemon"; devel = mkEnableOption "debugging-related kernel interfaces";
userns = mkEnableOption "user namespace"; userns = mkEnableOption "user namespace creation";
mapRealUid = mkEnableOption "mapping to priv-user uid";
dev = mkEnableOption "access to all devices";
tty = mkEnableOption "access to the controlling terminal"; tty = mkEnableOption "access to the controlling terminal";
insecureWayland = mkEnableOption "direct access to the Wayland socket"; multiarch = mkEnableOption "multiarch kernel-level support";
net = mkEnableOption "network access" // { net = mkEnableOption "network access" // {
default = true; default = true;
}; };
compat = mkEnableOption "disable syscall filter extensions"; nix = mkEnableOption "nix daemon access";
devel = mkEnableOption "development kernel APIs"; mapRealUid = mkEnableOption "mapping to priv-user uid";
multiarch = mkEnableOption "multiarch kernel support"; dev = mkEnableOption "access to all devices";
bluetooth = mkEnableOption "AF_BLUETOOTH socket operations"; insecureWayland = mkEnableOption "direct access to the Wayland socket";
gpu = mkOption { gpu = mkOption {
type = nullOr bool; type = nullOr bool;

View File

@ -89,10 +89,10 @@ func printShowInstance(
flags = append(flags, name) flags = append(flags, name)
} }
} }
writeFlag("userns", sandbox.UserNS) writeFlag("userns", sandbox.Userns)
writeFlag("net", sandbox.Net) writeFlag("net", sandbox.Net)
writeFlag("dev", sandbox.Dev) writeFlag("dev", sandbox.Dev)
writeFlag("tty", sandbox.NoNewSession) writeFlag("tty", sandbox.Tty)
writeFlag("mapuid", sandbox.MapRealUID) writeFlag("mapuid", sandbox.MapRealUID)
writeFlag("directwl", sandbox.DirectWayland) writeFlag("directwl", sandbox.DirectWayland)
writeFlag("autoetc", sandbox.AutoEtc) writeFlag("autoetc", sandbox.AutoEtc)
@ -107,14 +107,14 @@ func printShowInstance(
} }
t.Printf(" Etc:\t%s\n", etc) t.Printf(" Etc:\t%s\n", etc)
if len(sandbox.Override) > 0 { if len(sandbox.Cover) > 0 {
t.Printf(" Overrides:\t%s\n", strings.Join(sandbox.Override, " ")) t.Printf(" Cover:\t%s\n", strings.Join(sandbox.Cover, " "))
} }
// Env map[string]string `json:"env"` // Env map[string]string `json:"env"`
// Link [][2]string `json:"symlink"` // Link [][2]string `json:"symlink"`
} }
t.Printf(" Command:\t%s\n", strings.Join(config.Command, " ")) t.Printf(" Command:\t%s\n", strings.Join(config.Args, " "))
t.Printf("\n") t.Printf("\n")
if !short { if !short {
@ -256,7 +256,7 @@ func printPs(output io.Writer, now time.Time, s state.Store, short, flagJSON boo
) )
if e.Config != nil { if e.Config != nil {
es = e.Config.Confinement.Enablements.String() es = e.Config.Confinement.Enablements.String()
cs = fmt.Sprintf("%q", e.Config.Command) cs = fmt.Sprintf("%q", e.Config.Args)
as = strconv.Itoa(e.Config.Confinement.AppID) as = strconv.Itoa(e.Config.Confinement.AppID)
} }
t.Printf("\t%s\t%d\t%s\t%s\t%s\t%s\n", t.Printf("\t%s\t%d\t%s\t%s\t%s\t%s\n",

View File

@ -43,7 +43,7 @@ func Test_printShowInstance(t *testing.T) {
Hostname: "localhost" Hostname: "localhost"
Flags: userns net dev tty mapuid autoetc Flags: userns net dev tty mapuid autoetc
Etc: /etc Etc: /etc
Overrides: /var/run/nscd Cover: /var/run/nscd
Command: chromium --ignore-gpu-blocklist --disable-smooth-scrolling --enable-features=UseOzonePlatform --ozone-platform=wayland Command: chromium --ignore-gpu-blocklist --disable-smooth-scrolling --enable-features=UseOzonePlatform --ozone-platform=wayland
Filesystem Filesystem
@ -127,7 +127,7 @@ App
Hostname: "localhost" Hostname: "localhost"
Flags: userns net dev tty mapuid autoetc Flags: userns net dev tty mapuid autoetc
Etc: /etc Etc: /etc
Overrides: /var/run/nscd Cover: /var/run/nscd
Command: chromium --ignore-gpu-blocklist --disable-smooth-scrolling --enable-features=UseOzonePlatform --ozone-platform=wayland Command: chromium --ignore-gpu-blocklist --disable-smooth-scrolling --enable-features=UseOzonePlatform --ozone-platform=wayland
Filesystem Filesystem
@ -192,7 +192,8 @@ App
"pid": 3735928559, "pid": 3735928559,
"config": { "config": {
"id": "org.chromium.Chromium", "id": "org.chromium.Chromium",
"command": [ "path": "/run/current-system/sw/bin/chromium",
"args": [
"chromium", "chromium",
"--ignore-gpu-blocklist", "--ignore-gpu-blocklist",
"--disable-smooth-scrolling", "--disable-smooth-scrolling",
@ -209,24 +210,19 @@ App
"home": "/var/lib/persist/home/org.chromium.Chromium", "home": "/var/lib/persist/home/org.chromium.Chromium",
"sandbox": { "sandbox": {
"hostname": "localhost", "hostname": "localhost",
"seccomp": 32,
"devel": true,
"userns": true, "userns": true,
"net": true, "net": true,
"dev": true, "tty": true,
"syscall": { "multiarch": true,
"compat": false,
"deny_devel": true,
"multiarch": true,
"linux32": false,
"can": false,
"bluetooth": false
},
"no_new_session": true,
"map_real_uid": true,
"env": { "env": {
"GOOGLE_API_KEY": "AIzaSyBHDrl33hwRp4rMQY0ziRbj8K9LPA6vUCY", "GOOGLE_API_KEY": "AIzaSyBHDrl33hwRp4rMQY0ziRbj8K9LPA6vUCY",
"GOOGLE_DEFAULT_CLIENT_ID": "77185425430.apps.googleusercontent.com", "GOOGLE_DEFAULT_CLIENT_ID": "77185425430.apps.googleusercontent.com",
"GOOGLE_DEFAULT_CLIENT_SECRET": "OTJgUOQcT7lO7GsGZq2G4IlT" "GOOGLE_DEFAULT_CLIENT_SECRET": "OTJgUOQcT7lO7GsGZq2G4IlT"
}, },
"map_real_uid": true,
"dev": true,
"filesystem": [ "filesystem": [
{ {
"src": "/nix/store" "src": "/nix/store"
@ -259,7 +255,7 @@ App
], ],
"etc": "/etc", "etc": "/etc",
"auto_etc": true, "auto_etc": true,
"override": [ "cover": [
"/var/run/nscd" "/var/run/nscd"
] ]
}, },
@ -320,7 +316,8 @@ App
`}, `},
{"json config", nil, fst.Template(), false, true, `{ {"json config", nil, fst.Template(), false, true, `{
"id": "org.chromium.Chromium", "id": "org.chromium.Chromium",
"command": [ "path": "/run/current-system/sw/bin/chromium",
"args": [
"chromium", "chromium",
"--ignore-gpu-blocklist", "--ignore-gpu-blocklist",
"--disable-smooth-scrolling", "--disable-smooth-scrolling",
@ -337,24 +334,19 @@ App
"home": "/var/lib/persist/home/org.chromium.Chromium", "home": "/var/lib/persist/home/org.chromium.Chromium",
"sandbox": { "sandbox": {
"hostname": "localhost", "hostname": "localhost",
"seccomp": 32,
"devel": true,
"userns": true, "userns": true,
"net": true, "net": true,
"dev": true, "tty": true,
"syscall": { "multiarch": true,
"compat": false,
"deny_devel": true,
"multiarch": true,
"linux32": false,
"can": false,
"bluetooth": false
},
"no_new_session": true,
"map_real_uid": true,
"env": { "env": {
"GOOGLE_API_KEY": "AIzaSyBHDrl33hwRp4rMQY0ziRbj8K9LPA6vUCY", "GOOGLE_API_KEY": "AIzaSyBHDrl33hwRp4rMQY0ziRbj8K9LPA6vUCY",
"GOOGLE_DEFAULT_CLIENT_ID": "77185425430.apps.googleusercontent.com", "GOOGLE_DEFAULT_CLIENT_ID": "77185425430.apps.googleusercontent.com",
"GOOGLE_DEFAULT_CLIENT_SECRET": "OTJgUOQcT7lO7GsGZq2G4IlT" "GOOGLE_DEFAULT_CLIENT_SECRET": "OTJgUOQcT7lO7GsGZq2G4IlT"
}, },
"map_real_uid": true,
"dev": true,
"filesystem": [ "filesystem": [
{ {
"src": "/nix/store" "src": "/nix/store"
@ -387,7 +379,7 @@ App
], ],
"etc": "/etc", "etc": "/etc",
"auto_etc": true, "auto_etc": true,
"override": [ "cover": [
"/var/run/nscd" "/var/run/nscd"
] ]
}, },
@ -506,7 +498,8 @@ func Test_printPs(t *testing.T) {
"pid": 3735928559, "pid": 3735928559,
"config": { "config": {
"id": "org.chromium.Chromium", "id": "org.chromium.Chromium",
"command": [ "path": "/run/current-system/sw/bin/chromium",
"args": [
"chromium", "chromium",
"--ignore-gpu-blocklist", "--ignore-gpu-blocklist",
"--disable-smooth-scrolling", "--disable-smooth-scrolling",
@ -523,24 +516,19 @@ func Test_printPs(t *testing.T) {
"home": "/var/lib/persist/home/org.chromium.Chromium", "home": "/var/lib/persist/home/org.chromium.Chromium",
"sandbox": { "sandbox": {
"hostname": "localhost", "hostname": "localhost",
"seccomp": 32,
"devel": true,
"userns": true, "userns": true,
"net": true, "net": true,
"dev": true, "tty": true,
"syscall": { "multiarch": true,
"compat": false,
"deny_devel": true,
"multiarch": true,
"linux32": false,
"can": false,
"bluetooth": false
},
"no_new_session": true,
"map_real_uid": true,
"env": { "env": {
"GOOGLE_API_KEY": "AIzaSyBHDrl33hwRp4rMQY0ziRbj8K9LPA6vUCY", "GOOGLE_API_KEY": "AIzaSyBHDrl33hwRp4rMQY0ziRbj8K9LPA6vUCY",
"GOOGLE_DEFAULT_CLIENT_ID": "77185425430.apps.googleusercontent.com", "GOOGLE_DEFAULT_CLIENT_ID": "77185425430.apps.googleusercontent.com",
"GOOGLE_DEFAULT_CLIENT_SECRET": "OTJgUOQcT7lO7GsGZq2G4IlT" "GOOGLE_DEFAULT_CLIENT_SECRET": "OTJgUOQcT7lO7GsGZq2G4IlT"
}, },
"map_real_uid": true,
"dev": true,
"filesystem": [ "filesystem": [
{ {
"src": "/nix/store" "src": "/nix/store"
@ -573,7 +561,7 @@ func Test_printPs(t *testing.T) {
], ],
"etc": "/etc", "etc": "/etc",
"auto_etc": true, "auto_etc": true,
"override": [ "cover": [
"/var/run/nscd" "/var/run/nscd"
] ]
}, },

View File

@ -93,7 +93,7 @@ func TestExport(t *testing.T) {
t.Errorf("Close: error = %v", err) t.Errorf("Close: error = %v", err)
return return
} }
if got := digest.Sum(nil); slices.Compare(got, tc.want) != 0 { if got := digest.Sum(nil); !slices.Equal(got, tc.want) {
t.Fatalf("Export() hash = %x, want %x", t.Fatalf("Export() hash = %x, want %x",
got, tc.want) got, tc.want)
return return

View File

@ -22,14 +22,6 @@ func SetDumpable(dumpable uintptr) error {
return nil return nil
} }
func SetPdeathsig(sig syscall.Signal) error {
if _, _, errno := syscall.RawSyscall(syscall.SYS_PRCTL, syscall.PR_SET_PDEATHSIG, uintptr(sig), 0); errno != 0 {
return errno
}
return nil
}
// IgnoringEINTR makes a function call and repeats it if it returns an // IgnoringEINTR makes a function call and repeats it if it returns an
// EINTR error. This appears to be required even though we install all // EINTR error. This appears to be required even though we install all
// signal handlers with SA_RESTART: see #22838, #38033, #38836, #40846. // signal handlers with SA_RESTART: see #22838, #38033, #38836, #40846.

View File

@ -12,14 +12,9 @@
fs = fs "dead" { fs = fs "dead" {
".fortify" = fs "800001ed" { ".fortify" = fs "800001ed" {
etc = fs "800001ed" null null; etc = fs "800001ed" null null;
sbin = fs "800001c0" {
fortify = fs "16d" null null;
init0 = fs "80001ff" null null;
} null;
} null; } null;
bin = fs "800001ed" { sh = fs "80001ff" null null; } null; bin = fs "800001ed" { sh = fs "80001ff" null null; } null;
dev = fs "800001ed" { dev = fs "800001ed" {
console = fs "4200190" null null;
core = fs "80001ff" null null; core = fs "80001ff" null null;
dri = fs "800001ed" { dri = fs "800001ed" {
by-path = fs "800001ed" { by-path = fs "800001ed" {
@ -59,7 +54,7 @@
"fstab" = fs "80001ff" null null; "fstab" = fs "80001ff" null null;
"fsurc" = fs "80001ff" null null; "fsurc" = fs "80001ff" null null;
"fuse.conf" = fs "80001ff" null null; "fuse.conf" = fs "80001ff" null null;
"group" = fs "180" null "fortify:x:1000:\n"; "group" = fs "180" null "fortify:x:100:\n";
"host.conf" = fs "80001ff" null null; "host.conf" = fs "80001ff" null null;
"hostname" = fs "80001ff" null null; "hostname" = fs "80001ff" null null;
"hosts" = fs "80001ff" null null; "hosts" = fs "80001ff" null null;
@ -84,7 +79,7 @@
"os-release" = fs "80001ff" null null; "os-release" = fs "80001ff" null null;
"pam" = fs "80001ff" null null; "pam" = fs "80001ff" null null;
"pam.d" = fs "80001ff" null null; "pam.d" = fs "80001ff" null null;
"passwd" = fs "180" null "u0_a3:x:1000:1000:Fortify:/var/lib/fortify/u0/a3:/run/current-system/sw/bin/bash\n"; "passwd" = fs "180" null "u0_a3:x:1000:100:Fortify:/var/lib/fortify/u0/a3:/run/current-system/sw/bin/bash\n";
"pipewire" = fs "80001ff" null null; "pipewire" = fs "80001ff" null null;
"pki" = fs "80001ff" null null; "pki" = fs "80001ff" null null;
"polkit-1" = fs "80001ff" null null; "polkit-1" = fs "80001ff" null null;
@ -185,10 +180,10 @@
} null; } null;
mount = [ mount = [
(ent "/newroot" "/" "rw,nosuid,nodev,relatime" "tmpfs" "tmpfs" "rw,uid=1000003,gid=1000003") (ent "/sysroot" "/" "rw,nosuid,nodev,relatime" "tmpfs" "rootfs" "rw,uid=1000003,gid=1000003")
(ent "/" "/proc" "rw,nosuid,nodev,noexec,relatime" "proc" "proc" "rw") (ent "/" "/proc" "rw,nosuid,nodev,noexec,relatime" "proc" "proc" "rw")
(ent "/" "/.fortify" "rw,nosuid,nodev,relatime" "tmpfs" "tmpfs" "rw,size=4k,mode=755,uid=1000003,gid=1000003") (ent "/" "/.fortify" "rw,nosuid,nodev,relatime" "tmpfs" "tmpfs" "rw,size=4k,mode=755,uid=1000003,gid=1000003")
(ent "/" "/dev" "rw,nosuid,nodev,relatime" "tmpfs" "tmpfs" "rw,mode=755,uid=1000003,gid=1000003") (ent "/" "/dev" "rw,nosuid,nodev,relatime" "tmpfs" "devtmpfs" "rw,mode=755,uid=1000003,gid=1000003")
(ent "/null" "/dev/null" "rw,nosuid" "devtmpfs" "devtmpfs" ignore) (ent "/null" "/dev/null" "rw,nosuid" "devtmpfs" "devtmpfs" ignore)
(ent "/zero" "/dev/zero" "rw,nosuid" "devtmpfs" "devtmpfs" ignore) (ent "/zero" "/dev/zero" "rw,nosuid" "devtmpfs" "devtmpfs" ignore)
(ent "/full" "/dev/full" "rw,nosuid" "devtmpfs" "devtmpfs" ignore) (ent "/full" "/dev/full" "rw,nosuid" "devtmpfs" "devtmpfs" ignore)
@ -196,8 +191,7 @@
(ent "/urandom" "/dev/urandom" "rw,nosuid" "devtmpfs" "devtmpfs" ignore) (ent "/urandom" "/dev/urandom" "rw,nosuid" "devtmpfs" "devtmpfs" ignore)
(ent "/tty" "/dev/tty" "rw,nosuid" "devtmpfs" "devtmpfs" ignore) (ent "/tty" "/dev/tty" "rw,nosuid" "devtmpfs" "devtmpfs" ignore)
(ent "/" "/dev/pts" "rw,nosuid,noexec,relatime" "devpts" "devpts" "rw,mode=620,ptmxmode=666") (ent "/" "/dev/pts" "rw,nosuid,noexec,relatime" "devpts" "devpts" "rw,mode=620,ptmxmode=666")
(ent ignore "/dev/console" "rw,nosuid,noexec,relatime" "devpts" "devpts" "rw,gid=3,mode=620,ptmxmode=666") (ent "/" "/dev/mqueue" "rw,nosuid,nodev,noexec,relatime" "mqueue" "mqueue" "rw")
(ent "/" "/dev/mqueue" "rw,relatime" "mqueue" "mqueue" "rw")
(ent "/bin" "/bin" "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw") (ent "/bin" "/bin" "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
(ent "/usr/bin" "/usr/bin" "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw") (ent "/usr/bin" "/usr/bin" "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
(ent "/" "/nix/store" "ro,nosuid,nodev,relatime" "overlay" "overlay" "rw,lowerdir=/mnt-root/nix/.ro-store,upperdir=/mnt-root/nix/.rw-store/upper,workdir=/mnt-root/nix/.rw-store/work,uuid=on") (ent "/" "/nix/store" "ro,nosuid,nodev,relatime" "overlay" "overlay" "rw,lowerdir=/mnt-root/nix/.ro-store,upperdir=/mnt-root/nix/.rw-store/upper,workdir=/mnt-root/nix/.rw-store/work,uuid=on")
@ -210,17 +204,16 @@
(ent ignore "/run/opengl-driver" "ro,nosuid,nodev,relatime" "overlay" "overlay" "rw,lowerdir=/mnt-root/nix/.ro-store,upperdir=/mnt-root/nix/.rw-store/upper,workdir=/mnt-root/nix/.rw-store/work,uuid=on") (ent ignore "/run/opengl-driver" "ro,nosuid,nodev,relatime" "overlay" "overlay" "rw,lowerdir=/mnt-root/nix/.ro-store,upperdir=/mnt-root/nix/.rw-store/upper,workdir=/mnt-root/nix/.rw-store/work,uuid=on")
(ent "/dri" "/dev/dri" "rw,nosuid" "devtmpfs" "devtmpfs" ignore) (ent "/dri" "/dev/dri" "rw,nosuid" "devtmpfs" "devtmpfs" ignore)
(ent "/etc" "/.fortify/etc" "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw") (ent "/etc" "/.fortify/etc" "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
(ent "/" "/run/user" "rw,nosuid,nodev,relatime" "tmpfs" "tmpfs" "rw,size=1024k,mode=755,uid=1000003,gid=1000003") (ent "/" "/run/user" "rw,nosuid,nodev,relatime" "tmpfs" "tmpfs" "rw,size=4k,mode=755,uid=1000003,gid=1000003")
(ent "/" "/run/user/1000" "rw,nosuid,nodev,relatime" "tmpfs" "tmpfs" "rw,size=8192k,mode=755,uid=1000003,gid=1000003") (ent "/" "/run/user/1000" "rw,nosuid,nodev,relatime" "tmpfs" "tmpfs" "rw,size=8192k,mode=755,uid=1000003,gid=1000003")
(ent "/tmp/fortify.1000/tmpdir/3" "/tmp" "rw,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw") (ent "/tmp/fortify.1000/tmpdir/3" "/tmp" "rw,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
(ent "/var/lib/fortify/u0/a3" "/var/lib/fortify/u0/a3" "rw,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw") (ent "/var/lib/fortify/u0/a3" "/var/lib/fortify/u0/a3" "rw,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
(ent ignore "/etc/passwd" "ro,nosuid,nodev,relatime" "tmpfs" "tmpfs" "rw,uid=1000003,gid=1000003") (ent ignore "/etc/passwd" "ro,nosuid,nodev,relatime" "tmpfs" "rootfs" "rw,uid=1000003,gid=1000003")
(ent ignore "/etc/group" "ro,nosuid,nodev,relatime" "tmpfs" "tmpfs" "rw,uid=1000003,gid=1000003") (ent ignore "/etc/group" "ro,nosuid,nodev,relatime" "tmpfs" "rootfs" "rw,uid=1000003,gid=1000003")
(ent ignore "/run/user/1000/wayland-0" "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw") (ent ignore "/run/user/1000/wayland-0" "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
(ent ignore "/run/user/1000/pulse/native" "ro,nosuid,nodev,relatime" "tmpfs" "tmpfs" ignore) (ent ignore "/run/user/1000/pulse/native" "ro,nosuid,nodev,relatime" "tmpfs" "tmpfs" ignore)
(ent ignore "/run/user/1000/bus" "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw") (ent ignore "/run/user/1000/bus" "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
(ent "/" "/var/run/nscd" "rw,nosuid,nodev,relatime" "tmpfs" "tmpfs" "rw,size=8k,mode=755,uid=1000003,gid=1000003") (ent "/" "/var/run/nscd" "rw,nosuid,nodev,relatime" "tmpfs" "tmpfs" "rw,size=8k,mode=755,uid=1000003,gid=1000003")
(ent ignore "/.fortify/sbin/fortify" "ro,nosuid,nodev,relatime" "overlay" "overlay" "rw,lowerdir=/mnt-root/nix/.ro-store,upperdir=/mnt-root/nix/.rw-store/upper,workdir=/mnt-root/nix/.rw-store/work,uuid=on")
]; ];
seccomp = true; seccomp = true;

View File

@ -12,14 +12,9 @@
fs = fs "dead" { fs = fs "dead" {
".fortify" = fs "800001ed" { ".fortify" = fs "800001ed" {
etc = fs "800001ed" null null; etc = fs "800001ed" null null;
sbin = fs "800001c0" {
fortify = fs "16d" null null;
init0 = fs "80001ff" null null;
} null;
} null; } null;
bin = fs "800001ed" { sh = fs "80001ff" null null; } null; bin = fs "800001ed" { sh = fs "80001ff" null null; } null;
dev = fs "800001ed" { dev = fs "800001ed" {
console = fs "4200190" null null;
core = fs "80001ff" null null; core = fs "80001ff" null null;
dri = fs "800001ed" { dri = fs "800001ed" {
by-path = fs "800001ed" { by-path = fs "800001ed" {
@ -185,10 +180,10 @@
} null; } null;
mount = [ mount = [
(ent "/newroot" "/" "rw,nosuid,nodev,relatime" "tmpfs" "tmpfs" "rw,uid=1000001,gid=1000001") (ent "/sysroot" "/" "rw,nosuid,nodev,relatime" "tmpfs" "rootfs" "rw,uid=1000001,gid=1000001")
(ent "/" "/proc" "rw,nosuid,nodev,noexec,relatime" "proc" "proc" "rw") (ent "/" "/proc" "rw,nosuid,nodev,noexec,relatime" "proc" "proc" "rw")
(ent "/" "/.fortify" "rw,nosuid,nodev,relatime" "tmpfs" "tmpfs" "rw,size=4k,mode=755,uid=1000001,gid=1000001") (ent "/" "/.fortify" "rw,nosuid,nodev,relatime" "tmpfs" "tmpfs" "rw,size=4k,mode=755,uid=1000001,gid=1000001")
(ent "/" "/dev" "rw,nosuid,nodev,relatime" "tmpfs" "tmpfs" "rw,mode=755,uid=1000001,gid=1000001") (ent "/" "/dev" "rw,nosuid,nodev,relatime" "tmpfs" "devtmpfs" "rw,mode=755,uid=1000001,gid=1000001")
(ent "/null" "/dev/null" "rw,nosuid" "devtmpfs" "devtmpfs" ignore) (ent "/null" "/dev/null" "rw,nosuid" "devtmpfs" "devtmpfs" ignore)
(ent "/zero" "/dev/zero" "rw,nosuid" "devtmpfs" "devtmpfs" ignore) (ent "/zero" "/dev/zero" "rw,nosuid" "devtmpfs" "devtmpfs" ignore)
(ent "/full" "/dev/full" "rw,nosuid" "devtmpfs" "devtmpfs" ignore) (ent "/full" "/dev/full" "rw,nosuid" "devtmpfs" "devtmpfs" ignore)
@ -196,8 +191,7 @@
(ent "/urandom" "/dev/urandom" "rw,nosuid" "devtmpfs" "devtmpfs" ignore) (ent "/urandom" "/dev/urandom" "rw,nosuid" "devtmpfs" "devtmpfs" ignore)
(ent "/tty" "/dev/tty" "rw,nosuid" "devtmpfs" "devtmpfs" ignore) (ent "/tty" "/dev/tty" "rw,nosuid" "devtmpfs" "devtmpfs" ignore)
(ent "/" "/dev/pts" "rw,nosuid,noexec,relatime" "devpts" "devpts" "rw,mode=620,ptmxmode=666") (ent "/" "/dev/pts" "rw,nosuid,noexec,relatime" "devpts" "devpts" "rw,mode=620,ptmxmode=666")
(ent ignore "/dev/console" "rw,nosuid,noexec,relatime" "devpts" "devpts" "rw,gid=3,mode=620,ptmxmode=666") (ent "/" "/dev/mqueue" "rw,nosuid,nodev,noexec,relatime" "mqueue" "mqueue" "rw")
(ent "/" "/dev/mqueue" "rw,relatime" "mqueue" "mqueue" "rw")
(ent "/bin" "/bin" "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw") (ent "/bin" "/bin" "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
(ent "/usr/bin" "/usr/bin" "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw") (ent "/usr/bin" "/usr/bin" "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
(ent "/" "/nix/store" "ro,nosuid,nodev,relatime" "overlay" "overlay" "rw,lowerdir=/mnt-root/nix/.ro-store,upperdir=/mnt-root/nix/.rw-store/upper,workdir=/mnt-root/nix/.rw-store/work,uuid=on") (ent "/" "/nix/store" "ro,nosuid,nodev,relatime" "overlay" "overlay" "rw,lowerdir=/mnt-root/nix/.ro-store,upperdir=/mnt-root/nix/.rw-store/upper,workdir=/mnt-root/nix/.rw-store/work,uuid=on")
@ -210,17 +204,16 @@
(ent ignore "/run/opengl-driver" "ro,nosuid,nodev,relatime" "overlay" "overlay" "rw,lowerdir=/mnt-root/nix/.ro-store,upperdir=/mnt-root/nix/.rw-store/upper,workdir=/mnt-root/nix/.rw-store/work,uuid=on") (ent ignore "/run/opengl-driver" "ro,nosuid,nodev,relatime" "overlay" "overlay" "rw,lowerdir=/mnt-root/nix/.ro-store,upperdir=/mnt-root/nix/.rw-store/upper,workdir=/mnt-root/nix/.rw-store/work,uuid=on")
(ent "/dri" "/dev/dri" "rw,nosuid" "devtmpfs" "devtmpfs" ignore) (ent "/dri" "/dev/dri" "rw,nosuid" "devtmpfs" "devtmpfs" ignore)
(ent "/etc" "/.fortify/etc" "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw") (ent "/etc" "/.fortify/etc" "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
(ent "/" "/run/user" "rw,nosuid,nodev,relatime" "tmpfs" "tmpfs" "rw,size=1024k,mode=755,uid=1000001,gid=1000001") (ent "/" "/run/user" "rw,nosuid,nodev,relatime" "tmpfs" "tmpfs" "rw,size=4k,mode=755,uid=1000001,gid=1000001")
(ent "/" "/run/user/65534" "rw,nosuid,nodev,relatime" "tmpfs" "tmpfs" "rw,size=8192k,mode=755,uid=1000001,gid=1000001") (ent "/" "/run/user/65534" "rw,nosuid,nodev,relatime" "tmpfs" "tmpfs" "rw,size=8192k,mode=755,uid=1000001,gid=1000001")
(ent "/tmp/fortify.1000/tmpdir/1" "/tmp" "rw,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw") (ent "/tmp/fortify.1000/tmpdir/1" "/tmp" "rw,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
(ent "/var/lib/fortify/u0/a1" "/var/lib/fortify/u0/a1" "rw,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw") (ent "/var/lib/fortify/u0/a1" "/var/lib/fortify/u0/a1" "rw,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
(ent ignore "/etc/passwd" "ro,nosuid,nodev,relatime" "tmpfs" "tmpfs" "rw,uid=1000001,gid=1000001") (ent ignore "/etc/passwd" "ro,nosuid,nodev,relatime" "tmpfs" "rootfs" "rw,uid=1000001,gid=1000001")
(ent ignore "/etc/group" "ro,nosuid,nodev,relatime" "tmpfs" "tmpfs" "rw,uid=1000001,gid=1000001") (ent ignore "/etc/group" "ro,nosuid,nodev,relatime" "tmpfs" "rootfs" "rw,uid=1000001,gid=1000001")
(ent ignore "/run/user/65534/wayland-0" "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw") (ent ignore "/run/user/65534/wayland-0" "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
(ent ignore "/run/user/65534/pulse/native" "ro,nosuid,nodev,relatime" "tmpfs" "tmpfs" ignore) (ent ignore "/run/user/65534/pulse/native" "ro,nosuid,nodev,relatime" "tmpfs" "tmpfs" ignore)
(ent ignore "/run/user/65534/bus" "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw") (ent ignore "/run/user/65534/bus" "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
(ent "/" "/var/run/nscd" "rw,nosuid,nodev,relatime" "tmpfs" "tmpfs" "rw,size=8k,mode=755,uid=1000001,gid=1000001") (ent "/" "/var/run/nscd" "rw,nosuid,nodev,relatime" "tmpfs" "tmpfs" "rw,size=8k,mode=755,uid=1000001,gid=1000001")
(ent ignore "/.fortify/sbin/fortify" "ro,nosuid,nodev,relatime" "overlay" "overlay" "rw,lowerdir=/mnt-root/nix/.ro-store,upperdir=/mnt-root/nix/.rw-store/upper,workdir=/mnt-root/nix/.rw-store/work,uuid=on")
]; ];
seccomp = true; seccomp = true;

View File

@ -12,10 +12,6 @@
fs = fs "dead" { fs = fs "dead" {
".fortify" = fs "800001ed" { ".fortify" = fs "800001ed" {
etc = fs "800001ed" null null; etc = fs "800001ed" null null;
sbin = fs "800001c0" {
fortify = fs "16d" null null;
init0 = fs "80001ff" null null;
} null;
} null; } null;
bin = fs "800001ed" { sh = fs "80001ff" null null; } null; bin = fs "800001ed" { sh = fs "80001ff" null null; } null;
dev = fs "800001ed" { dev = fs "800001ed" {
@ -185,10 +181,10 @@
} null; } null;
mount = [ mount = [
(ent "/newroot" "/" "rw,nosuid,nodev,relatime" "tmpfs" "tmpfs" "rw,uid=1000002,gid=1000002") (ent "/sysroot" "/" "rw,nosuid,nodev,relatime" "tmpfs" "rootfs" "rw,uid=1000002,gid=1000002")
(ent "/" "/proc" "rw,nosuid,nodev,noexec,relatime" "proc" "proc" "rw") (ent "/" "/proc" "rw,nosuid,nodev,noexec,relatime" "proc" "proc" "rw")
(ent "/" "/.fortify" "rw,nosuid,nodev,relatime" "tmpfs" "tmpfs" "rw,size=4k,mode=755,uid=1000002,gid=1000002") (ent "/" "/.fortify" "rw,nosuid,nodev,relatime" "tmpfs" "tmpfs" "rw,size=4k,mode=755,uid=1000002,gid=1000002")
(ent "/" "/dev" "rw,nosuid,nodev,relatime" "tmpfs" "tmpfs" "rw,mode=755,uid=1000002,gid=1000002") (ent "/" "/dev" "rw,nosuid,nodev,relatime" "tmpfs" "devtmpfs" "rw,mode=755,uid=1000002,gid=1000002")
(ent "/null" "/dev/null" "rw,nosuid" "devtmpfs" "devtmpfs" ignore) (ent "/null" "/dev/null" "rw,nosuid" "devtmpfs" "devtmpfs" ignore)
(ent "/zero" "/dev/zero" "rw,nosuid" "devtmpfs" "devtmpfs" ignore) (ent "/zero" "/dev/zero" "rw,nosuid" "devtmpfs" "devtmpfs" ignore)
(ent "/full" "/dev/full" "rw,nosuid" "devtmpfs" "devtmpfs" ignore) (ent "/full" "/dev/full" "rw,nosuid" "devtmpfs" "devtmpfs" ignore)
@ -197,7 +193,7 @@
(ent "/tty" "/dev/tty" "rw,nosuid" "devtmpfs" "devtmpfs" ignore) (ent "/tty" "/dev/tty" "rw,nosuid" "devtmpfs" "devtmpfs" ignore)
(ent "/" "/dev/pts" "rw,nosuid,noexec,relatime" "devpts" "devpts" "rw,mode=620,ptmxmode=666") (ent "/" "/dev/pts" "rw,nosuid,noexec,relatime" "devpts" "devpts" "rw,mode=620,ptmxmode=666")
(ent ignore "/dev/console" "rw,nosuid,noexec,relatime" "devpts" "devpts" "rw,gid=3,mode=620,ptmxmode=666") (ent ignore "/dev/console" "rw,nosuid,noexec,relatime" "devpts" "devpts" "rw,gid=3,mode=620,ptmxmode=666")
(ent "/" "/dev/mqueue" "rw,relatime" "mqueue" "mqueue" "rw") (ent "/" "/dev/mqueue" "rw,nosuid,nodev,noexec,relatime" "mqueue" "mqueue" "rw")
(ent "/bin" "/bin" "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw") (ent "/bin" "/bin" "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
(ent "/usr/bin" "/usr/bin" "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw") (ent "/usr/bin" "/usr/bin" "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
(ent "/" "/nix/store" "ro,nosuid,nodev,relatime" "overlay" "overlay" "rw,lowerdir=/mnt-root/nix/.ro-store,upperdir=/mnt-root/nix/.rw-store/upper,workdir=/mnt-root/nix/.rw-store/work,uuid=on") (ent "/" "/nix/store" "ro,nosuid,nodev,relatime" "overlay" "overlay" "rw,lowerdir=/mnt-root/nix/.ro-store,upperdir=/mnt-root/nix/.rw-store/upper,workdir=/mnt-root/nix/.rw-store/work,uuid=on")
@ -210,17 +206,16 @@
(ent ignore "/run/opengl-driver" "ro,nosuid,nodev,relatime" "overlay" "overlay" "rw,lowerdir=/mnt-root/nix/.ro-store,upperdir=/mnt-root/nix/.rw-store/upper,workdir=/mnt-root/nix/.rw-store/work,uuid=on") (ent ignore "/run/opengl-driver" "ro,nosuid,nodev,relatime" "overlay" "overlay" "rw,lowerdir=/mnt-root/nix/.ro-store,upperdir=/mnt-root/nix/.rw-store/upper,workdir=/mnt-root/nix/.rw-store/work,uuid=on")
(ent "/dri" "/dev/dri" "rw,nosuid" "devtmpfs" "devtmpfs" ignore) (ent "/dri" "/dev/dri" "rw,nosuid" "devtmpfs" "devtmpfs" ignore)
(ent "/etc" "/.fortify/etc" "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw") (ent "/etc" "/.fortify/etc" "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
(ent "/" "/run/user" "rw,nosuid,nodev,relatime" "tmpfs" "tmpfs" "rw,size=1024k,mode=755,uid=1000002,gid=1000002") (ent "/" "/run/user" "rw,nosuid,nodev,relatime" "tmpfs" "tmpfs" "rw,size=4k,mode=755,uid=1000002,gid=1000002")
(ent "/" "/run/user/65534" "rw,nosuid,nodev,relatime" "tmpfs" "tmpfs" "rw,size=8192k,mode=755,uid=1000002,gid=1000002") (ent "/" "/run/user/65534" "rw,nosuid,nodev,relatime" "tmpfs" "tmpfs" "rw,size=8192k,mode=755,uid=1000002,gid=1000002")
(ent "/tmp/fortify.1000/tmpdir/2" "/tmp" "rw,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw") (ent "/tmp/fortify.1000/tmpdir/2" "/tmp" "rw,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
(ent "/var/lib/fortify/u0/a2" "/var/lib/fortify/u0/a2" "rw,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw") (ent "/var/lib/fortify/u0/a2" "/var/lib/fortify/u0/a2" "rw,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
(ent ignore "/etc/passwd" "ro,nosuid,nodev,relatime" "tmpfs" "tmpfs" "rw,uid=1000002,gid=1000002") (ent ignore "/etc/passwd" "ro,nosuid,nodev,relatime" "tmpfs" "rootfs" "rw,uid=1000002,gid=1000002")
(ent ignore "/etc/group" "ro,nosuid,nodev,relatime" "tmpfs" "tmpfs" "rw,uid=1000002,gid=1000002") (ent ignore "/etc/group" "ro,nosuid,nodev,relatime" "tmpfs" "rootfs" "rw,uid=1000002,gid=1000002")
(ent ignore "/run/user/65534/wayland-0" "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw") (ent ignore "/run/user/65534/wayland-0" "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
(ent ignore "/run/user/65534/pulse/native" "ro,nosuid,nodev,relatime" "tmpfs" "tmpfs" ignore) (ent ignore "/run/user/65534/pulse/native" "ro,nosuid,nodev,relatime" "tmpfs" "tmpfs" ignore)
(ent ignore "/run/user/65534/bus" "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw") (ent ignore "/run/user/65534/bus" "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
(ent "/" "/var/run/nscd" "rw,nosuid,nodev,relatime" "tmpfs" "tmpfs" "rw,size=8k,mode=755,uid=1000002,gid=1000002") (ent "/" "/var/run/nscd" "rw,nosuid,nodev,relatime" "tmpfs" "tmpfs" "rw,size=8k,mode=755,uid=1000002,gid=1000002")
(ent ignore "/.fortify/sbin/fortify" "ro,nosuid,nodev,relatime" "overlay" "overlay" "rw,lowerdir=/mnt-root/nix/.ro-store,upperdir=/mnt-root/nix/.rw-store/upper,workdir=/mnt-root/nix/.rw-store/work,uuid=on")
]; ];
seccomp = true; seccomp = true;

View File

@ -62,9 +62,12 @@ def check_state(name, enablements):
config = instance['config'] config = instance['config']
if len(config['command']) != 1 or not (config['command'][0].startswith("/nix/store/")) or not ( command = f"{name}-start"
config['command'][0].endswith(f"{name}-start")): if not (config['path'].startswith("/nix/store/")) or not (config['path'].endswith(command)):
raise Exception(f"unexpected command {instance['config']['command']}") raise Exception(f"unexpected path {config['path']}")
if len(config['args']) != 1 or config['args'][0] != command:
raise Exception(f"unexpected args {config['args']}")
if config['confinement']['enablements'] != enablements: if config['confinement']['enablements'] != enablements:
raise Exception(f"unexpected enablements {instance['config']['confinement']['enablements']}") raise Exception(f"unexpected enablements {instance['config']['confinement']['enablements']}")