diff --git a/cmd/fpkg/bundle.go b/cmd/fpkg/app.go similarity index 52% rename from cmd/fpkg/bundle.go rename to cmd/fpkg/app.go index d164116..a1e5de9 100644 --- a/cmd/fpkg/bundle.go +++ b/cmd/fpkg/app.go @@ -4,12 +4,15 @@ import ( "encoding/json" "log" "os" + "path" "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" ) -type bundleInfo struct { +type appInfo struct { Name string `json:"name"` Version string `json:"version"` @@ -20,13 +23,15 @@ type bundleInfo struct { // passed through to [fst.Config] Groups []string `json:"groups,omitempty"` // 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] Net bool `json:"net,omitempty"` // passed through to [fst.Config] Dev bool `json:"dev,omitempty"` // passed through to [fst.Config] - NoNewSession bool `json:"no_new_session,omitempty"` + Tty bool `json:"tty,omitempty"` // passed through to [fst.Config] MapRealUID bool `json:"map_real_uid,omitempty"` // passed through to [fst.Config] @@ -38,11 +43,9 @@ type bundleInfo struct { // passed through to [fst.Config] Enablements system.Enablements `json:"enablements"` - // passed through inverted to [bwrap.SyscallPolicy] - Devel bool `json:"devel,omitempty"` - // passed through to [bwrap.SyscallPolicy] + // passed through to [fst.Config] Multiarch bool `json:"multiarch,omitempty"` - // passed through to [bwrap.SyscallPolicy] + // passed through to [fst.Config] Bluetooth bool `json:"bluetooth,omitempty"` // allow gpu access within sandbox @@ -59,8 +62,64 @@ type bundleInfo struct { ActivationPackage string `json:"activation_package"` } -func loadBundleInfo(name string, beforeFail func()) *bundleInfo { - bundle := new(bundleInfo) +func (app *appInfo) toFst(pathSet *appPathSet, argv []string, flagDropShell bool) *fst.Config { + config := &fst.Config{ + ID: app.ID, + Path: argv[0], + 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 { beforeFail() log.Fatalf("cannot open bundle: %v", err) diff --git a/cmd/fpkg/main.go b/cmd/fpkg/main.go index e23ce50..268c2d4 100644 --- a/cmd/fpkg/main.go +++ b/cmd/fpkg/main.go @@ -12,9 +12,7 @@ import ( "git.gensokyo.uk/security/fortify/command" "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/app/init0" "git.gensokyo.uk/security/fortify/internal/app/shim" "git.gensokyo.uk/security/fortify/internal/fmsg" "git.gensokyo.uk/security/fortify/internal/sys" @@ -39,7 +37,6 @@ func init() { func main() { // early init path, skips root check and duplicate PR_SET_DUMPABLE sandbox.TryArgv0(fmsg.Output{}, fmsg.Prepare, internal.InstallFmsg) - init0.TryArgv0() if err := sandbox.SetDumpable(sandbox.SUID_DUMP_DISABLE); err != nil { 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(&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("init", command.UsageInternal, func([]string) error { init0.Main(); return errSuccess }) { var ( @@ -124,7 +119,7 @@ func main() { 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) app := bundle @@ -140,7 +135,7 @@ func main() { log.Printf("metadata path %q is not a file", pathSet.metaPath) return syscall.EBADMSG } else { - app = loadBundleInfo(pathSet.metaPath, cleanup) + app = loadAppInfo(pathSet.metaPath, cleanup) if app.ID != bundle.ID { cleanup() log.Printf("app %q claims to have identifier %q", @@ -273,7 +268,7 @@ func main() { id := args[0] pathSet := pathSetByApp(id) - app := loadBundleInfo(pathSet.metaPath, func() {}) + app := loadAppInfo(pathSet.metaPath, func() {}) if app.ID != id { log.Printf("app %q claims to have identifier %q", id, app.ID) return syscall.EBADE @@ -322,51 +317,7 @@ func main() { } argv = append(argv, args[1:]...) - config := &fst.Config{ - 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, - }, - } + config := app.toFst(pathSet, argv, flagDropShell) /* Expose GPU devices. diff --git a/cmd/fpkg/proc.go b/cmd/fpkg/proc.go index ea106ad..311ae4f 100644 --- a/cmd/fpkg/proc.go +++ b/cmd/fpkg/proc.go @@ -11,14 +11,14 @@ import ( func mustRunApp(ctx context.Context, config *fst.Config, beforeFail func()) { rs := new(fst.RunState) - a := app.MustNew(std) + a := app.MustNew(ctx, std) if sa, err := a.Seal(config); err != nil { fmsg.PrintBaseError(err, "cannot seal app:") rs.ExitCode = 1 } else { // this updates ExitCode - app.PrintRunStateErr(rs, sa.Run(ctx, rs)) + app.PrintRunStateErr(rs, sa.Run(rs)) } if rs.ExitCode != 0 { diff --git a/cmd/fpkg/test/test.py b/cmd/fpkg/test/test.py index eaf07fc..7d4e0d5 100644 --- a/cmd/fpkg/test/test.py +++ b/cmd/fpkg/test/test.py @@ -62,8 +62,8 @@ def check_state(name, enablements): config = instance['config'] - if len(config['command']) != 1 or not (config['command'][0].startswith("/nix/store/")) or f"fortify-{name}-" not in (config['command'][0]): - raise Exception(f"unexpected command {instance['config']['command']}") + if len(config['args']) != 1 or not (config['args'][0].startswith("/nix/store/")) or f"fortify-{name}-" not in (config['args'][0]): + raise Exception(f"unexpected args {instance['config']['args']}") if config['confinement']['enablements'] != enablements: raise Exception(f"unexpected enablements {instance['config']['confinement']['enablements']}") diff --git a/cmd/fpkg/with.go b/cmd/fpkg/with.go index d7b0e96..d321de3 100644 --- a/cmd/fpkg/with.go +++ b/cmd/fpkg/with.go @@ -6,18 +6,19 @@ import ( "strings" "git.gensokyo.uk/security/fortify/fst" - "git.gensokyo.uk/security/fortify/helper/bwrap" "git.gensokyo.uk/security/fortify/internal" + "git.gensokyo.uk/security/fortify/sandbox/seccomp" ) func withNixDaemon( ctx context.Context, 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{ - ID: app.ID, - Command: []string{shellPath, "-lc", "rm -f /nix/var/nix/daemon-socket/socket && " + + ID: app.ID, + Path: shellPath, + Args: []string{shellPath, "-lc", "rm -f /nix/var/nix/daemon-socket/socket && " + // start nix-daemon "nix-daemon --store / & " + // wait for socket to appear @@ -34,11 +35,11 @@ func withNixDaemon( Inner: path.Join("/data/data", app.ID), Outer: pathSet.homeDir, Sandbox: &fst.SandboxConfig{ - Hostname: formatHostname(app.Name) + "-" + action, - UserNS: true, // nix sandbox requires userns - Net: net, - Syscall: &bwrap.SyscallPolicy{Multiarch: true}, - NoNewSession: dropShell, + Hostname: formatHostname(app.Name) + "-" + action, + Userns: true, // nix sandbox requires userns + Net: net, + Seccomp: seccomp.FlagMultiarch, + Tty: dropShell, Filesystem: []*fst.FilesystemConfig{ {Src: pathSet.nixPath, Dst: "/nix", Write: true, Must: true}, }, @@ -61,19 +62,20 @@ func withNixDaemon( func withCacheDir( ctx context.Context, 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{ - ID: app.ID, - Command: []string{shellPath, "-lc", strings.Join(command, " && ")}, + ID: app.ID, + Path: shellPath, + Args: []string{shellPath, "-lc", strings.Join(command, " && ")}, Confinement: fst.ConfinementConfig{ AppID: app.AppID, Username: "nixos", Inner: path.Join("/data/data", app.ID, "cache"), Outer: pathSet.cacheDir, // this also ensures cacheDir via shim Sandbox: &fst.SandboxConfig{ - Hostname: formatHostname(app.Name) + "-" + action, - Syscall: &bwrap.SyscallPolicy{Multiarch: true}, - NoNewSession: dropShell, + Hostname: formatHostname(app.Name) + "-" + action, + Seccomp: seccomp.FlagMultiarch, + Tty: dropShell, Filesystem: []*fst.FilesystemConfig{ {Src: path.Join(workDir, "nix"), Dst: "/nix", Must: true}, {Src: workDir, Dst: path.Join(fst.Tmp, "bundle"), Must: true}, @@ -97,7 +99,7 @@ func withCacheDir( func mustRunAppDropShell(ctx context.Context, config *fst.Config, dropShell bool, beforeFail func()) { if dropShell { - config.Command = []string{shellPath, "-l"} + config.Args = []string{shellPath, "-l"} mustRunApp(ctx, config, beforeFail) beforeFail() internal.Exit(0) diff --git a/fst/app.go b/fst/app.go index 87a836c..3e02639 100644 --- a/fst/app.go +++ b/fst/app.go @@ -2,7 +2,6 @@ package fst import ( - "context" "time" ) @@ -19,7 +18,7 @@ type App interface { type SealedApp interface { // 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]. diff --git a/fst/config.go b/fst/config.go index d96934d..7fc3af6 100644 --- a/fst/config.go +++ b/fst/config.go @@ -2,7 +2,7 @@ package fst import ( "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" ) @@ -14,8 +14,11 @@ type Config struct { // passed to wayland security-context-v1 as application ID // and used as part of defaults in dbus session proxy 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"` } @@ -26,13 +29,13 @@ type ConfinementConfig struct { AppID int `json:"app_id"` // list of supplementary groups to inherit 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"` - // home directory in sandbox, empty for outer + // home directory in container, empty for outer Inner string `json:"home_inner"` // home directory in init namespace Outer string `json:"home"` - // bwrap sandbox confinement configuration + // abstract sandbox configuration Sandbox *SandboxConfig `json:"sandbox"` // extra acl ops, runs after everything else ExtraPerms []*ExtraPermConfig `json:"extra_perms,omitempty"` @@ -44,7 +47,7 @@ type ConfinementConfig struct { // nil value makes session bus proxy assume built-in defaults 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"` } @@ -76,24 +79,12 @@ func (e *ExtraPermConfig) String() string { 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. func Template() *Config { return &Config{ - ID: "org.chromium.Chromium", - Command: []string{ + ID: "org.chromium.Chromium", + Path: "/run/current-system/sw/bin/chromium", + Args: []string{ "chromium", "--ignore-gpu-blocklist", "--disable-smooth-scrolling", @@ -108,11 +99,13 @@ func Template() *Config { Inner: "/var/lib/fortify", Sandbox: &SandboxConfig{ Hostname: "localhost", - UserNS: true, + Devel: true, + Userns: true, Net: true, Dev: true, - Syscall: &bwrap.SyscallPolicy{DenyDevel: true, Multiarch: true}, - NoNewSession: true, + Seccomp: seccomp.FlagMultiarch, + Tty: true, + Multiarch: true, MapRealUID: true, DirectWayland: false, // example API credentials pulled from Google Chrome @@ -131,10 +124,10 @@ func Template() *Config { Dst: "/data/data/org.chromium.Chromium", Write: true, Must: true}, {Src: "/dev/dri", Device: true}, }, - Link: [][2]string{{"/run/user/65534", "/run/user/150"}}, - Etc: "/etc", - AutoEtc: true, - Override: []string{"/var/run/nscd"}, + Link: [][2]string{{"/run/user/65534", "/run/user/150"}}, + Etc: "/etc", + AutoEtc: true, + Cover: []string{"/var/run/nscd"}, }, ExtraPerms: []*ExtraPermConfig{ {Path: "/var/lib/fortify/u0", Ensure: true, Execute: true}, diff --git a/fst/sandbox.go b/fst/sandbox.go index fc12da4..b1240ec 100644 --- a/fst/sandbox.go +++ b/fst/sandbox.go @@ -4,125 +4,149 @@ import ( "errors" "fmt" "io/fs" + "maps" "path" + "slices" + "syscall" "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. -type SandboxConfig struct { - // unix hostname within sandbox - Hostname string `json:"hostname,omitempty"` - // allow userns within sandbox - 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"` +type ( + SandboxConfig struct { + // container hostname + Hostname string `json:"hostname,omitempty"` - // final environment variables - Env map[string]string `json:"env"` - // sandbox host filesystem access - Filesystem []*FilesystemConfig `json:"filesystem"` - // symlinks created inside the sandbox - Link [][2]string `json:"symlink"` - // read-only /etc directory - Etc string `json:"etc,omitempty"` - // automatically set up /etc symlinks - AutoEtc bool `json:"auto_etc"` - // mount tmpfs over these paths, - // runs right before [ConfinementConfig.ExtraPerms] - Override []string `json:"override"` -} + // extra seccomp flags + Seccomp seccomp.SyscallOpts `json:"seccomp"` + // allow ptrace and friends + Devel bool `json:"devel,omitempty"` + // allow userns creation in container + Userns bool `json:"userns,omitempty"` + // share host net namespace + Net bool `json:"net,omitempty"` + // expose main process tty + Tty bool `json:"tty,omitempty"` + // allow multiarch + Multiarch bool `json:"multiarch,omitempty"` -// SandboxSys encapsulates system functions used during the creation of [bwrap.Config]. -type SandboxSys interface { - Getuid() int - Paths() Paths - ReadDir(name string) ([]fs.DirEntry, error) - EvalSymlinks(path string) (string, error) + // initial process environment variables + Env map[string]string `json:"env"` + // map target user uid to privileged user uid in the user namespace + MapRealUID bool `json:"map_real_uid"` - Println(v ...any) - Printf(format string, v ...any) -} + // expose all devices + 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. -// Note that remaining tmpfs entries must be queued by the caller prior to launch. -func (s *SandboxConfig) Bwrap(sys SandboxSys, uid *int) (*bwrap.Config, error) { + // 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"` + + // 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 { - return nil, errors.New("nil sandbox config") + return nil, nil, syscall.EBADE } - if s.Syscall == nil { - 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, + container := &sandbox.Params{ Hostname: s.Hostname, - Clearenv: true, - SetEnv: s.Env, + Ops: new(sandbox.Ops), + Seccomp: s.Seccomp, + } - /* this is only 4 KiB of memory on a 64-bit system, - permissive defaults on NixOS results in around 100 entries - so this capacity should eliminate copies for most setups */ - Filesystem: make([]bwrap.FSBuilder, 0, 256), + /* this is only 4 KiB of memory on a 64-bit system, + permissive defaults on NixOS results in around 100 entries + so this capacity should eliminate copies for most setups */ + *container.Ops = slices.Grow(*container.Ops, 1<<8) - Syscall: s.Syscall, - NewSession: !s.NoNewSession, - DieWithParent: true, - AsInit: true, + if s.Devel { + container.Flags |= sandbox.FAllowDevel + } + 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 - // for saving such a miniscule amount of memory - Chmod: make(bwrap.ChmodConfig), - }). - Procfs("/proc"). - Tmpfs(Tmp, 4*1024) + if s.MapRealUID { + /* some programs fail to connect to dbus session running as a different uid + so this workaround is introduced to map priv-side caller uid in container */ + container.Uid = sys.Getuid() + *uid = container.Uid + 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 { - conf.DevTmpfs("/dev").Mqueue("/dev/mqueue") + container.Dev("/dev").Mqueue("/dev/mqueue") } else { - conf.Bind("/dev", "/dev", false, true, true) + container.Bind("/dev", "/dev", sandbox.BindDevice) } - if !s.AutoEtc { - if s.Etc == "" { - conf.Dir("/etc") - } else { - conf.Bind(s.Etc, "/etc") - } - } - - // retrieve paths and hide them if they're made available in the sandbox + /* retrieve paths and hide them if they're made available in the sandbox; + this feature tries to improve user experience of permissive defaults, and + to warn about issues in custom configuration; it is NOT a security feature + and should not be treated as such, ALWAYS be careful with what you bind */ var hidePaths []string sc := sys.Paths() hidePaths = append(hidePaths, sc.RuntimePath, sc.SharePath) _, systemBusAddr := dbus.Address() if entries, err := dbus.Parse([]byte(systemBusAddr)); err != nil { - return nil, err + return nil, nil, err } else { // there is usually only one, do not preallocate for _, entry := range entries { @@ -148,7 +172,7 @@ func (s *SandboxConfig) Bwrap(sys SandboxSys, uid *int) (*bwrap.Config, error) { hidePathMatch := make([]bool, len(hidePaths)) for i := range hidePaths { 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) { - 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 if c.Dst == "" { dest = c.Src } 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 if err := evalSymlinks(sys, &srcH); err != nil { - return nil, err + return nil, nil, err } 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 { - return nil, err + return nil, nil, err } else if ok { hidePathMatch[i] = true 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 { if ok { - conf.Tmpfs(hidePaths[i], 8192) + container.Tmpfs(hidePaths[i], 1<<13, 0755) } } for _, l := range s.Link { - conf.Symlink(l[0], l[1]) + container.Link(l[0], l[1]) } - if s.AutoEtc { - etc := s.Etc - if etc == "" { - etc = "/etc" + // perf: this might work better if implemented as a setup op in container init + if !s.AutoEtc { + if s.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 - if d, err := sys.ReadDir(etc); err != nil { - return nil, err + // link host /etc contents to prevent dropping passwd/group bind mounts + if d, err := sys.ReadDir(etcPath); err != nil { + return nil, nil, err } else { for _, ent := range d { - name := ent.Name() - switch name { + n := ent.Name() + switch n { case "passwd": case "group": case "mtab": - conf.Symlink("/proc/mounts", "/etc/"+name) + container.Link("/proc/mounts", "/etc/"+n) 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 { diff --git a/internal/app/app.go b/internal/app/app.go index c4668a0..42d67b9 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -1,6 +1,7 @@ package app import ( + "context" "fmt" "log" "sync" @@ -10,9 +11,10 @@ import ( "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.sys = os + a.ctx = ctx id := new(fst.ID) err := fst.NewAppID(id) @@ -21,8 +23,8 @@ func New(os sys.State) (fst.App, error) { return a, err } -func MustNew(os sys.State) fst.App { - a, err := New(os) +func MustNew(ctx context.Context, os sys.State) fst.App { + a, err := New(ctx, os) if err != nil { log.Fatalf("cannot create app: %v", err) } @@ -32,6 +34,7 @@ func MustNew(os sys.State) fst.App { type app struct { id *stringPair[fst.ID] sys sys.State + ctx context.Context *outcome mu sync.RWMutex @@ -71,7 +74,7 @@ func (a *app) Seal(config *fst.Config) (fst.SealedApp, error) { seal := new(outcome) seal.id = a.id - err := seal.finalise(a.sys, config) + err := seal.finalise(a.ctx, a.sys, config) if err == nil { a.outcome = seal } diff --git a/internal/app/app_nixos_test.go b/internal/app/app_nixos_test.go index 683b179..ca9fde0 100644 --- a/internal/app/app_nixos_test.go +++ b/internal/app/app_nixos_test.go @@ -4,7 +4,7 @@ import ( "git.gensokyo.uk/security/fortify/acl" "git.gensokyo.uk/security/fortify/dbus" "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" ) @@ -12,20 +12,20 @@ var testCasesNixos = []sealTestCase{ { "nixos chromium direct wayland", new(stubNixOS), &fst.Config{ - ID: "org.chromium.Chromium", - Command: []string{"/nix/store/yqivzpzzn7z5x0lq9hmbzygh45d8rhqd-chromium-start"}, + ID: "org.chromium.Chromium", + Path: "/nix/store/yqivzpzzn7z5x0lq9hmbzygh45d8rhqd-chromium-start", Confinement: fst.ConfinementConfig{ AppID: 1, Groups: []string{}, Username: "u0_a1", Outer: "/var/lib/persist/module/fortify/0/1", 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{ {Src: "/bin", Must: true}, {Src: "/usr/bin", 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: "/run/opengl-driver", Must: true}, {Src: "/dev/dri", Device: true}, }, - Override: []string{"/var/run/nscd"}, + Cover: []string{"/var/run/nscd"}, }, SystemBus: &dbus.Config{ 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/system_bus_socket", acl.Read, acl.Write), - (&bwrap.Config{ - Net: true, - UserNS: true, - Chdir: "/var/lib/persist/module/fortify/0/1", - Clearenv: true, - SetEnv: map[string]string{ - "DBUS_SESSION_BUS_ADDRESS": "unix:path=/run/user/1971/bus", - "DBUS_SYSTEM_BUS_ADDRESS": "unix:path=/run/dbus/system_bus_socket", - "HOME": "/var/lib/persist/module/fortify/0/1", - "PULSE_COOKIE": fst.Tmp + "/pulse-cookie", - "PULSE_SERVER": "unix:/run/user/1971/pulse/native", - "SHELL": "/run/current-system/sw/bin/zsh", - "TERM": "xterm-256color", - "USER": "u0_a1", - "WAYLAND_DISPLAY": "wayland-0", - "XDG_RUNTIME_DIR": "/run/user/1971", - "XDG_SESSION_CLASS": "user", - "XDG_SESSION_TYPE": "tty", + &sandbox.Params{ + Uid: 1971, + Gid: 100, + Flags: sandbox.FAllowNet | sandbox.FAllowUserns, + Dir: "/var/lib/persist/module/fortify/0/1", + Path: "/nix/store/yqivzpzzn7z5x0lq9hmbzygh45d8rhqd-chromium-start", + Args: []string{"/nix/store/yqivzpzzn7z5x0lq9hmbzygh45d8rhqd-chromium-start"}, + Env: []string{ + "DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1971/bus", + "DBUS_SYSTEM_BUS_ADDRESS=unix:path=/run/dbus/system_bus_socket", + "HOME=/var/lib/persist/module/fortify/0/1", + "PULSE_COOKIE=" + fst.Tmp + "/pulse-cookie", + "PULSE_SERVER=unix:/run/user/1971/pulse/native", + "TERM=xterm-256color", + "USER=u0_a1", + "WAYLAND_DISPLAY=wayland-0", + "XDG_RUNTIME_DIR=/run/user/1971", + "XDG_SESSION_CLASS=user", + "XDG_SESSION_TYPE=tty", }, - Chmod: make(bwrap.ChmodConfig), - NewSession: true, - DieWithParent: true, - AsInit: true, - }).SetUID(1971).SetGID(1971). - Procfs("/proc"). - Tmpfs(fst.Tmp, 4096). - DevTmpfs("/dev").Mqueue("/dev/mqueue"). - Bind("/bin", "/bin"). - Bind("/usr/bin", "/usr/bin"). - Bind("/nix/store", "/nix/store"). - Bind("/run/current-system", "/run/current-system"). - Bind("/sys/block", "/sys/block", true). - Bind("/sys/bus", "/sys/bus", true). - Bind("/sys/class", "/sys/class", true). - Bind("/sys/dev", "/sys/dev", true). - Bind("/sys/devices", "/sys/devices", true). - Bind("/run/opengl-driver", "/run/opengl-driver"). - Bind("/dev/dri", "/dev/dri", true, true, true). - Bind("/etc", fst.Tmp+"/etc"). - Symlink(fst.Tmp+"/etc/alsa", "/etc/alsa"). - Symlink(fst.Tmp+"/etc/bashrc", "/etc/bashrc"). - Symlink(fst.Tmp+"/etc/binfmt.d", "/etc/binfmt.d"). - Symlink(fst.Tmp+"/etc/dbus-1", "/etc/dbus-1"). - Symlink(fst.Tmp+"/etc/default", "/etc/default"). - Symlink(fst.Tmp+"/etc/ethertypes", "/etc/ethertypes"). - Symlink(fst.Tmp+"/etc/fonts", "/etc/fonts"). - Symlink(fst.Tmp+"/etc/fstab", "/etc/fstab"). - Symlink(fst.Tmp+"/etc/fuse.conf", "/etc/fuse.conf"). - Symlink(fst.Tmp+"/etc/host.conf", "/etc/host.conf"). - Symlink(fst.Tmp+"/etc/hostid", "/etc/hostid"). - Symlink(fst.Tmp+"/etc/hostname", "/etc/hostname"). - Symlink(fst.Tmp+"/etc/hostname.CHECKSUM", "/etc/hostname.CHECKSUM"). - Symlink(fst.Tmp+"/etc/hosts", "/etc/hosts"). - Symlink(fst.Tmp+"/etc/inputrc", "/etc/inputrc"). - Symlink(fst.Tmp+"/etc/ipsec.d", "/etc/ipsec.d"). - Symlink(fst.Tmp+"/etc/issue", "/etc/issue"). - Symlink(fst.Tmp+"/etc/kbd", "/etc/kbd"). - Symlink(fst.Tmp+"/etc/libblockdev", "/etc/libblockdev"). - Symlink(fst.Tmp+"/etc/locale.conf", "/etc/locale.conf"). - Symlink(fst.Tmp+"/etc/localtime", "/etc/localtime"). - Symlink(fst.Tmp+"/etc/login.defs", "/etc/login.defs"). - Symlink(fst.Tmp+"/etc/lsb-release", "/etc/lsb-release"). - Symlink(fst.Tmp+"/etc/lvm", "/etc/lvm"). - Symlink(fst.Tmp+"/etc/machine-id", "/etc/machine-id"). - Symlink(fst.Tmp+"/etc/man_db.conf", "/etc/man_db.conf"). - Symlink(fst.Tmp+"/etc/modprobe.d", "/etc/modprobe.d"). - Symlink(fst.Tmp+"/etc/modules-load.d", "/etc/modules-load.d"). - Symlink("/proc/mounts", "/etc/mtab"). - Symlink(fst.Tmp+"/etc/nanorc", "/etc/nanorc"). - Symlink(fst.Tmp+"/etc/netgroup", "/etc/netgroup"). - Symlink(fst.Tmp+"/etc/NetworkManager", "/etc/NetworkManager"). - Symlink(fst.Tmp+"/etc/nix", "/etc/nix"). - Symlink(fst.Tmp+"/etc/nixos", "/etc/nixos"). - Symlink(fst.Tmp+"/etc/NIXOS", "/etc/NIXOS"). - Symlink(fst.Tmp+"/etc/nscd.conf", "/etc/nscd.conf"). - Symlink(fst.Tmp+"/etc/nsswitch.conf", "/etc/nsswitch.conf"). - Symlink(fst.Tmp+"/etc/opensnitchd", "/etc/opensnitchd"). - Symlink(fst.Tmp+"/etc/os-release", "/etc/os-release"). - Symlink(fst.Tmp+"/etc/pam", "/etc/pam"). - Symlink(fst.Tmp+"/etc/pam.d", "/etc/pam.d"). - Symlink(fst.Tmp+"/etc/pipewire", "/etc/pipewire"). - Symlink(fst.Tmp+"/etc/pki", "/etc/pki"). - Symlink(fst.Tmp+"/etc/polkit-1", "/etc/polkit-1"). - Symlink(fst.Tmp+"/etc/profile", "/etc/profile"). - Symlink(fst.Tmp+"/etc/protocols", "/etc/protocols"). - Symlink(fst.Tmp+"/etc/qemu", "/etc/qemu"). - Symlink(fst.Tmp+"/etc/resolv.conf", "/etc/resolv.conf"). - Symlink(fst.Tmp+"/etc/resolvconf.conf", "/etc/resolvconf.conf"). - Symlink(fst.Tmp+"/etc/rpc", "/etc/rpc"). - Symlink(fst.Tmp+"/etc/samba", "/etc/samba"). - Symlink(fst.Tmp+"/etc/sddm.conf", "/etc/sddm.conf"). - Symlink(fst.Tmp+"/etc/secureboot", "/etc/secureboot"). - Symlink(fst.Tmp+"/etc/services", "/etc/services"). - Symlink(fst.Tmp+"/etc/set-environment", "/etc/set-environment"). - Symlink(fst.Tmp+"/etc/shadow", "/etc/shadow"). - Symlink(fst.Tmp+"/etc/shells", "/etc/shells"). - Symlink(fst.Tmp+"/etc/ssh", "/etc/ssh"). - Symlink(fst.Tmp+"/etc/ssl", "/etc/ssl"). - Symlink(fst.Tmp+"/etc/static", "/etc/static"). - Symlink(fst.Tmp+"/etc/subgid", "/etc/subgid"). - Symlink(fst.Tmp+"/etc/subuid", "/etc/subuid"). - Symlink(fst.Tmp+"/etc/sudoers", "/etc/sudoers"). - Symlink(fst.Tmp+"/etc/sysctl.d", "/etc/sysctl.d"). - Symlink(fst.Tmp+"/etc/systemd", "/etc/systemd"). - Symlink(fst.Tmp+"/etc/terminfo", "/etc/terminfo"). - Symlink(fst.Tmp+"/etc/tmpfiles.d", "/etc/tmpfiles.d"). - Symlink(fst.Tmp+"/etc/udev", "/etc/udev"). - Symlink(fst.Tmp+"/etc/udisks2", "/etc/udisks2"). - Symlink(fst.Tmp+"/etc/UPower", "/etc/UPower"). - Symlink(fst.Tmp+"/etc/vconsole.conf", "/etc/vconsole.conf"). - Symlink(fst.Tmp+"/etc/X11", "/etc/X11"). - Symlink(fst.Tmp+"/etc/zfs", "/etc/zfs"). - Symlink(fst.Tmp+"/etc/zinputrc", "/etc/zinputrc"). - Symlink(fst.Tmp+"/etc/zoneinfo", "/etc/zoneinfo"). - Symlink(fst.Tmp+"/etc/zprofile", "/etc/zprofile"). - Symlink(fst.Tmp+"/etc/zshenv", "/etc/zshenv"). - Symlink(fst.Tmp+"/etc/zshrc", "/etc/zshrc"). - Tmpfs("/run/user", 1048576). - Tmpfs("/run/user/1971", 8388608). - Bind("/tmp/fortify.1971/tmpdir/1", "/tmp", false, true). - Bind("/var/lib/persist/module/fortify/0/1", "/var/lib/persist/module/fortify/0/1", false, true). - CopyBind("/etc/passwd", []byte("u0_a1:x:1971:1971:Fortify:/var/lib/persist/module/fortify/0/1:/run/current-system/sw/bin/zsh\n")). - CopyBind("/etc/group", []byte("fortify:x:1971:\n")). - Bind("/run/user/1971/wayland-0", "/run/user/1971/wayland-0"). - Bind("/run/user/1971/fortify/8e2c76b066dabe574cf073bdb46eb5c1/pulse", "/run/user/1971/pulse/native"). - CopyBind(fst.Tmp+"/pulse-cookie", nil). - 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"), + Ops: new(sandbox.Ops). + Proc("/proc"). + Tmpfs(fst.Tmp, 4096, 0755). + Dev("/dev").Mqueue("/dev/mqueue"). + Bind("/bin", "/bin", 0). + Bind("/usr/bin", "/usr/bin", 0). + Bind("/nix/store", "/nix/store", 0). + Bind("/run/current-system", "/run/current-system", 0). + Bind("/sys/block", "/sys/block", sandbox.BindOptional). + Bind("/sys/bus", "/sys/bus", sandbox.BindOptional). + Bind("/sys/class", "/sys/class", sandbox.BindOptional). + Bind("/sys/dev", "/sys/dev", sandbox.BindOptional). + Bind("/sys/devices", "/sys/devices", sandbox.BindOptional). + Bind("/run/opengl-driver", "/run/opengl-driver", 0). + Bind("/dev/dri", "/dev/dri", sandbox.BindDevice|sandbox.BindWritable|sandbox.BindOptional). + Bind("/etc", fst.Tmp+"/etc", 0). + Mkdir("/etc", 0700). + Link(fst.Tmp+"/etc/alsa", "/etc/alsa"). + Link(fst.Tmp+"/etc/bashrc", "/etc/bashrc"). + Link(fst.Tmp+"/etc/binfmt.d", "/etc/binfmt.d"). + Link(fst.Tmp+"/etc/dbus-1", "/etc/dbus-1"). + Link(fst.Tmp+"/etc/default", "/etc/default"). + Link(fst.Tmp+"/etc/ethertypes", "/etc/ethertypes"). + Link(fst.Tmp+"/etc/fonts", "/etc/fonts"). + Link(fst.Tmp+"/etc/fstab", "/etc/fstab"). + Link(fst.Tmp+"/etc/fuse.conf", "/etc/fuse.conf"). + Link(fst.Tmp+"/etc/host.conf", "/etc/host.conf"). + Link(fst.Tmp+"/etc/hostid", "/etc/hostid"). + Link(fst.Tmp+"/etc/hostname", "/etc/hostname"). + Link(fst.Tmp+"/etc/hostname.CHECKSUM", "/etc/hostname.CHECKSUM"). + Link(fst.Tmp+"/etc/hosts", "/etc/hosts"). + Link(fst.Tmp+"/etc/inputrc", "/etc/inputrc"). + Link(fst.Tmp+"/etc/ipsec.d", "/etc/ipsec.d"). + Link(fst.Tmp+"/etc/issue", "/etc/issue"). + Link(fst.Tmp+"/etc/kbd", "/etc/kbd"). + Link(fst.Tmp+"/etc/libblockdev", "/etc/libblockdev"). + Link(fst.Tmp+"/etc/locale.conf", "/etc/locale.conf"). + Link(fst.Tmp+"/etc/localtime", "/etc/localtime"). + Link(fst.Tmp+"/etc/login.defs", "/etc/login.defs"). + Link(fst.Tmp+"/etc/lsb-release", "/etc/lsb-release"). + Link(fst.Tmp+"/etc/lvm", "/etc/lvm"). + Link(fst.Tmp+"/etc/machine-id", "/etc/machine-id"). + Link(fst.Tmp+"/etc/man_db.conf", "/etc/man_db.conf"). + Link(fst.Tmp+"/etc/modprobe.d", "/etc/modprobe.d"). + Link(fst.Tmp+"/etc/modules-load.d", "/etc/modules-load.d"). + Link("/proc/mounts", "/etc/mtab"). + Link(fst.Tmp+"/etc/nanorc", "/etc/nanorc"). + Link(fst.Tmp+"/etc/netgroup", "/etc/netgroup"). + Link(fst.Tmp+"/etc/NetworkManager", "/etc/NetworkManager"). + Link(fst.Tmp+"/etc/nix", "/etc/nix"). + Link(fst.Tmp+"/etc/nixos", "/etc/nixos"). + Link(fst.Tmp+"/etc/NIXOS", "/etc/NIXOS"). + Link(fst.Tmp+"/etc/nscd.conf", "/etc/nscd.conf"). + Link(fst.Tmp+"/etc/nsswitch.conf", "/etc/nsswitch.conf"). + Link(fst.Tmp+"/etc/opensnitchd", "/etc/opensnitchd"). + Link(fst.Tmp+"/etc/os-release", "/etc/os-release"). + Link(fst.Tmp+"/etc/pam", "/etc/pam"). + Link(fst.Tmp+"/etc/pam.d", "/etc/pam.d"). + Link(fst.Tmp+"/etc/pipewire", "/etc/pipewire"). + Link(fst.Tmp+"/etc/pki", "/etc/pki"). + Link(fst.Tmp+"/etc/polkit-1", "/etc/polkit-1"). + Link(fst.Tmp+"/etc/profile", "/etc/profile"). + Link(fst.Tmp+"/etc/protocols", "/etc/protocols"). + Link(fst.Tmp+"/etc/qemu", "/etc/qemu"). + Link(fst.Tmp+"/etc/resolv.conf", "/etc/resolv.conf"). + Link(fst.Tmp+"/etc/resolvconf.conf", "/etc/resolvconf.conf"). + Link(fst.Tmp+"/etc/rpc", "/etc/rpc"). + Link(fst.Tmp+"/etc/samba", "/etc/samba"). + Link(fst.Tmp+"/etc/sddm.conf", "/etc/sddm.conf"). + Link(fst.Tmp+"/etc/secureboot", "/etc/secureboot"). + Link(fst.Tmp+"/etc/services", "/etc/services"). + Link(fst.Tmp+"/etc/set-environment", "/etc/set-environment"). + Link(fst.Tmp+"/etc/shadow", "/etc/shadow"). + Link(fst.Tmp+"/etc/shells", "/etc/shells"). + Link(fst.Tmp+"/etc/ssh", "/etc/ssh"). + Link(fst.Tmp+"/etc/ssl", "/etc/ssl"). + Link(fst.Tmp+"/etc/static", "/etc/static"). + Link(fst.Tmp+"/etc/subgid", "/etc/subgid"). + Link(fst.Tmp+"/etc/subuid", "/etc/subuid"). + Link(fst.Tmp+"/etc/sudoers", "/etc/sudoers"). + Link(fst.Tmp+"/etc/sysctl.d", "/etc/sysctl.d"). + Link(fst.Tmp+"/etc/systemd", "/etc/systemd"). + Link(fst.Tmp+"/etc/terminfo", "/etc/terminfo"). + Link(fst.Tmp+"/etc/tmpfiles.d", "/etc/tmpfiles.d"). + Link(fst.Tmp+"/etc/udev", "/etc/udev"). + Link(fst.Tmp+"/etc/udisks2", "/etc/udisks2"). + Link(fst.Tmp+"/etc/UPower", "/etc/UPower"). + Link(fst.Tmp+"/etc/vconsole.conf", "/etc/vconsole.conf"). + Link(fst.Tmp+"/etc/X11", "/etc/X11"). + Link(fst.Tmp+"/etc/zfs", "/etc/zfs"). + Link(fst.Tmp+"/etc/zinputrc", "/etc/zinputrc"). + Link(fst.Tmp+"/etc/zoneinfo", "/etc/zoneinfo"). + Link(fst.Tmp+"/etc/zprofile", "/etc/zprofile"). + Link(fst.Tmp+"/etc/zshenv", "/etc/zshenv"). + Link(fst.Tmp+"/etc/zshrc", "/etc/zshrc"). + Tmpfs("/run/user", 4096, 0755). + Tmpfs("/run/user/1971", 8388608, 0755). + Bind("/tmp/fortify.1971/tmpdir/1", "/tmp", sandbox.BindWritable). + Bind("/var/lib/persist/module/fortify/0/1", "/var/lib/persist/module/fortify/0/1", sandbox.BindWritable). + Place("/etc/passwd", []byte("u0_a1:x:1971:100:Fortify:/var/lib/persist/module/fortify/0/1:/run/current-system/sw/bin/zsh\n")). + Place("/etc/group", []byte("fortify:x:100:\n")). + Bind("/run/user/1971/wayland-0", "/run/user/1971/wayland-0", 0). + Bind("/run/user/1971/fortify/8e2c76b066dabe574cf073bdb46eb5c1/pulse", "/run/user/1971/pulse/native", 0). + Place(fst.Tmp+"/pulse-cookie", nil). + Bind("/tmp/fortify.1971/8e2c76b066dabe574cf073bdb46eb5c1/bus", "/run/user/1971/bus", 0). + Bind("/tmp/fortify.1971/8e2c76b066dabe574cf073bdb46eb5c1/system_bus_socket", "/run/dbus/system_bus_socket", 0). + Tmpfs("/var/run/nscd", 8192, 0755), + }, }, } diff --git a/internal/app/app_pd_test.go b/internal/app/app_pd_test.go index 602965c..1535579 100644 --- a/internal/app/app_pd_test.go +++ b/internal/app/app_pd_test.go @@ -6,7 +6,7 @@ import ( "git.gensokyo.uk/security/fortify/acl" "git.gensokyo.uk/security/fortify/dbus" "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" ) @@ -14,7 +14,6 @@ var testCasesPd = []sealTestCase{ { "nixos permissive defaults no enablements", new(stubNixOS), &fst.Config{ - Command: make([]string, 0), Confinement: fst.ConfinementConfig{ AppID: 0, 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). 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), - (&bwrap.Config{ - Net: true, - UserNS: true, - Clearenv: true, - Syscall: new(bwrap.SyscallPolicy), - Chdir: "/home/chronos", - SetEnv: map[string]string{ - "HOME": "/home/chronos", - "SHELL": "/run/current-system/sw/bin/zsh", - "TERM": "xterm-256color", - "USER": "chronos", - "XDG_RUNTIME_DIR": "/run/user/65534", - "XDG_SESSION_CLASS": "user", - "XDG_SESSION_TYPE": "tty"}, - Chmod: make(bwrap.ChmodConfig), - DieWithParent: true, - AsInit: true, - }).SetUID(65534).SetGID(65534). - Procfs("/proc"). - Tmpfs(fst.Tmp, 4096). - DevTmpfs("/dev").Mqueue("/dev/mqueue"). - Bind("/bin", "/bin", false, true). - Bind("/boot", "/boot", false, true). - Bind("/home", "/home", false, true). - Bind("/lib", "/lib", false, true). - Bind("/lib64", "/lib64", false, true). - Bind("/nix", "/nix", false, true). - Bind("/root", "/root", false, true). - Bind("/run", "/run", false, true). - Bind("/srv", "/srv", false, true). - Bind("/sys", "/sys", false, true). - Bind("/usr", "/usr", false, true). - Bind("/var", "/var", false, true). - Bind("/dev/kvm", "/dev/kvm", true, true, true). - Tmpfs("/run/user/1971", 8192). - Tmpfs("/run/dbus", 8192). - Bind("/etc", fst.Tmp+"/etc"). - Symlink(fst.Tmp+"/etc/alsa", "/etc/alsa"). - Symlink(fst.Tmp+"/etc/bashrc", "/etc/bashrc"). - Symlink(fst.Tmp+"/etc/binfmt.d", "/etc/binfmt.d"). - Symlink(fst.Tmp+"/etc/dbus-1", "/etc/dbus-1"). - Symlink(fst.Tmp+"/etc/default", "/etc/default"). - Symlink(fst.Tmp+"/etc/ethertypes", "/etc/ethertypes"). - Symlink(fst.Tmp+"/etc/fonts", "/etc/fonts"). - Symlink(fst.Tmp+"/etc/fstab", "/etc/fstab"). - Symlink(fst.Tmp+"/etc/fuse.conf", "/etc/fuse.conf"). - Symlink(fst.Tmp+"/etc/host.conf", "/etc/host.conf"). - Symlink(fst.Tmp+"/etc/hostid", "/etc/hostid"). - Symlink(fst.Tmp+"/etc/hostname", "/etc/hostname"). - Symlink(fst.Tmp+"/etc/hostname.CHECKSUM", "/etc/hostname.CHECKSUM"). - Symlink(fst.Tmp+"/etc/hosts", "/etc/hosts"). - Symlink(fst.Tmp+"/etc/inputrc", "/etc/inputrc"). - Symlink(fst.Tmp+"/etc/ipsec.d", "/etc/ipsec.d"). - Symlink(fst.Tmp+"/etc/issue", "/etc/issue"). - Symlink(fst.Tmp+"/etc/kbd", "/etc/kbd"). - Symlink(fst.Tmp+"/etc/libblockdev", "/etc/libblockdev"). - Symlink(fst.Tmp+"/etc/locale.conf", "/etc/locale.conf"). - Symlink(fst.Tmp+"/etc/localtime", "/etc/localtime"). - Symlink(fst.Tmp+"/etc/login.defs", "/etc/login.defs"). - Symlink(fst.Tmp+"/etc/lsb-release", "/etc/lsb-release"). - Symlink(fst.Tmp+"/etc/lvm", "/etc/lvm"). - Symlink(fst.Tmp+"/etc/machine-id", "/etc/machine-id"). - Symlink(fst.Tmp+"/etc/man_db.conf", "/etc/man_db.conf"). - Symlink(fst.Tmp+"/etc/modprobe.d", "/etc/modprobe.d"). - Symlink(fst.Tmp+"/etc/modules-load.d", "/etc/modules-load.d"). - Symlink("/proc/mounts", "/etc/mtab"). - Symlink(fst.Tmp+"/etc/nanorc", "/etc/nanorc"). - Symlink(fst.Tmp+"/etc/netgroup", "/etc/netgroup"). - Symlink(fst.Tmp+"/etc/NetworkManager", "/etc/NetworkManager"). - Symlink(fst.Tmp+"/etc/nix", "/etc/nix"). - Symlink(fst.Tmp+"/etc/nixos", "/etc/nixos"). - Symlink(fst.Tmp+"/etc/NIXOS", "/etc/NIXOS"). - Symlink(fst.Tmp+"/etc/nscd.conf", "/etc/nscd.conf"). - Symlink(fst.Tmp+"/etc/nsswitch.conf", "/etc/nsswitch.conf"). - Symlink(fst.Tmp+"/etc/opensnitchd", "/etc/opensnitchd"). - Symlink(fst.Tmp+"/etc/os-release", "/etc/os-release"). - Symlink(fst.Tmp+"/etc/pam", "/etc/pam"). - Symlink(fst.Tmp+"/etc/pam.d", "/etc/pam.d"). - Symlink(fst.Tmp+"/etc/pipewire", "/etc/pipewire"). - Symlink(fst.Tmp+"/etc/pki", "/etc/pki"). - Symlink(fst.Tmp+"/etc/polkit-1", "/etc/polkit-1"). - Symlink(fst.Tmp+"/etc/profile", "/etc/profile"). - Symlink(fst.Tmp+"/etc/protocols", "/etc/protocols"). - Symlink(fst.Tmp+"/etc/qemu", "/etc/qemu"). - Symlink(fst.Tmp+"/etc/resolv.conf", "/etc/resolv.conf"). - Symlink(fst.Tmp+"/etc/resolvconf.conf", "/etc/resolvconf.conf"). - Symlink(fst.Tmp+"/etc/rpc", "/etc/rpc"). - Symlink(fst.Tmp+"/etc/samba", "/etc/samba"). - Symlink(fst.Tmp+"/etc/sddm.conf", "/etc/sddm.conf"). - Symlink(fst.Tmp+"/etc/secureboot", "/etc/secureboot"). - Symlink(fst.Tmp+"/etc/services", "/etc/services"). - Symlink(fst.Tmp+"/etc/set-environment", "/etc/set-environment"). - Symlink(fst.Tmp+"/etc/shadow", "/etc/shadow"). - Symlink(fst.Tmp+"/etc/shells", "/etc/shells"). - Symlink(fst.Tmp+"/etc/ssh", "/etc/ssh"). - Symlink(fst.Tmp+"/etc/ssl", "/etc/ssl"). - Symlink(fst.Tmp+"/etc/static", "/etc/static"). - Symlink(fst.Tmp+"/etc/subgid", "/etc/subgid"). - Symlink(fst.Tmp+"/etc/subuid", "/etc/subuid"). - Symlink(fst.Tmp+"/etc/sudoers", "/etc/sudoers"). - Symlink(fst.Tmp+"/etc/sysctl.d", "/etc/sysctl.d"). - Symlink(fst.Tmp+"/etc/systemd", "/etc/systemd"). - Symlink(fst.Tmp+"/etc/terminfo", "/etc/terminfo"). - Symlink(fst.Tmp+"/etc/tmpfiles.d", "/etc/tmpfiles.d"). - Symlink(fst.Tmp+"/etc/udev", "/etc/udev"). - Symlink(fst.Tmp+"/etc/udisks2", "/etc/udisks2"). - Symlink(fst.Tmp+"/etc/UPower", "/etc/UPower"). - Symlink(fst.Tmp+"/etc/vconsole.conf", "/etc/vconsole.conf"). - Symlink(fst.Tmp+"/etc/X11", "/etc/X11"). - Symlink(fst.Tmp+"/etc/zfs", "/etc/zfs"). - Symlink(fst.Tmp+"/etc/zinputrc", "/etc/zinputrc"). - Symlink(fst.Tmp+"/etc/zoneinfo", "/etc/zoneinfo"). - Symlink(fst.Tmp+"/etc/zprofile", "/etc/zprofile"). - Symlink(fst.Tmp+"/etc/zshenv", "/etc/zshenv"). - Symlink(fst.Tmp+"/etc/zshrc", "/etc/zshrc"). - Tmpfs("/run/user", 1048576). - Tmpfs("/run/user/65534", 8388608). - Bind("/tmp/fortify.1971/tmpdir/0", "/tmp", false, true). - Bind("/home/chronos", "/home/chronos", false, true). - 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"), + &sandbox.Params{ + Flags: sandbox.FAllowNet | sandbox.FAllowUserns | sandbox.FAllowTTY, + Dir: "/home/chronos", + Path: "/run/current-system/sw/bin/zsh", + Args: []string{"/run/current-system/sw/bin/zsh"}, + Env: []string{ + "HOME=/home/chronos", + "TERM=xterm-256color", + "USER=chronos", + "XDG_RUNTIME_DIR=/run/user/65534", + "XDG_SESSION_CLASS=user", + "XDG_SESSION_TYPE=tty", + }, + Ops: new(sandbox.Ops). + Proc("/proc"). + Tmpfs(fst.Tmp, 4096, 0755). + Dev("/dev").Mqueue("/dev/mqueue"). + Bind("/bin", "/bin", sandbox.BindWritable). + Bind("/boot", "/boot", sandbox.BindWritable). + Bind("/home", "/home", sandbox.BindWritable). + Bind("/lib", "/lib", sandbox.BindWritable). + Bind("/lib64", "/lib64", sandbox.BindWritable). + Bind("/nix", "/nix", sandbox.BindWritable). + Bind("/root", "/root", sandbox.BindWritable). + Bind("/run", "/run", sandbox.BindWritable). + Bind("/srv", "/srv", sandbox.BindWritable). + Bind("/sys", "/sys", sandbox.BindWritable). + Bind("/usr", "/usr", sandbox.BindWritable). + Bind("/var", "/var", sandbox.BindWritable). + Bind("/dev/kvm", "/dev/kvm", sandbox.BindWritable|sandbox.BindDevice|sandbox.BindOptional). + Tmpfs("/run/user/1971", 8192, 0755). + Tmpfs("/run/dbus", 8192, 0755). + Bind("/etc", fst.Tmp+"/etc", 0). + Mkdir("/etc", 0700). + Link(fst.Tmp+"/etc/alsa", "/etc/alsa"). + Link(fst.Tmp+"/etc/bashrc", "/etc/bashrc"). + Link(fst.Tmp+"/etc/binfmt.d", "/etc/binfmt.d"). + Link(fst.Tmp+"/etc/dbus-1", "/etc/dbus-1"). + Link(fst.Tmp+"/etc/default", "/etc/default"). + Link(fst.Tmp+"/etc/ethertypes", "/etc/ethertypes"). + Link(fst.Tmp+"/etc/fonts", "/etc/fonts"). + Link(fst.Tmp+"/etc/fstab", "/etc/fstab"). + Link(fst.Tmp+"/etc/fuse.conf", "/etc/fuse.conf"). + Link(fst.Tmp+"/etc/host.conf", "/etc/host.conf"). + Link(fst.Tmp+"/etc/hostid", "/etc/hostid"). + Link(fst.Tmp+"/etc/hostname", "/etc/hostname"). + Link(fst.Tmp+"/etc/hostname.CHECKSUM", "/etc/hostname.CHECKSUM"). + Link(fst.Tmp+"/etc/hosts", "/etc/hosts"). + Link(fst.Tmp+"/etc/inputrc", "/etc/inputrc"). + Link(fst.Tmp+"/etc/ipsec.d", "/etc/ipsec.d"). + Link(fst.Tmp+"/etc/issue", "/etc/issue"). + Link(fst.Tmp+"/etc/kbd", "/etc/kbd"). + Link(fst.Tmp+"/etc/libblockdev", "/etc/libblockdev"). + Link(fst.Tmp+"/etc/locale.conf", "/etc/locale.conf"). + Link(fst.Tmp+"/etc/localtime", "/etc/localtime"). + Link(fst.Tmp+"/etc/login.defs", "/etc/login.defs"). + Link(fst.Tmp+"/etc/lsb-release", "/etc/lsb-release"). + Link(fst.Tmp+"/etc/lvm", "/etc/lvm"). + Link(fst.Tmp+"/etc/machine-id", "/etc/machine-id"). + Link(fst.Tmp+"/etc/man_db.conf", "/etc/man_db.conf"). + Link(fst.Tmp+"/etc/modprobe.d", "/etc/modprobe.d"). + Link(fst.Tmp+"/etc/modules-load.d", "/etc/modules-load.d"). + Link("/proc/mounts", "/etc/mtab"). + Link(fst.Tmp+"/etc/nanorc", "/etc/nanorc"). + Link(fst.Tmp+"/etc/netgroup", "/etc/netgroup"). + Link(fst.Tmp+"/etc/NetworkManager", "/etc/NetworkManager"). + Link(fst.Tmp+"/etc/nix", "/etc/nix"). + Link(fst.Tmp+"/etc/nixos", "/etc/nixos"). + Link(fst.Tmp+"/etc/NIXOS", "/etc/NIXOS"). + Link(fst.Tmp+"/etc/nscd.conf", "/etc/nscd.conf"). + Link(fst.Tmp+"/etc/nsswitch.conf", "/etc/nsswitch.conf"). + Link(fst.Tmp+"/etc/opensnitchd", "/etc/opensnitchd"). + Link(fst.Tmp+"/etc/os-release", "/etc/os-release"). + Link(fst.Tmp+"/etc/pam", "/etc/pam"). + Link(fst.Tmp+"/etc/pam.d", "/etc/pam.d"). + Link(fst.Tmp+"/etc/pipewire", "/etc/pipewire"). + Link(fst.Tmp+"/etc/pki", "/etc/pki"). + Link(fst.Tmp+"/etc/polkit-1", "/etc/polkit-1"). + Link(fst.Tmp+"/etc/profile", "/etc/profile"). + Link(fst.Tmp+"/etc/protocols", "/etc/protocols"). + Link(fst.Tmp+"/etc/qemu", "/etc/qemu"). + Link(fst.Tmp+"/etc/resolv.conf", "/etc/resolv.conf"). + Link(fst.Tmp+"/etc/resolvconf.conf", "/etc/resolvconf.conf"). + Link(fst.Tmp+"/etc/rpc", "/etc/rpc"). + Link(fst.Tmp+"/etc/samba", "/etc/samba"). + Link(fst.Tmp+"/etc/sddm.conf", "/etc/sddm.conf"). + Link(fst.Tmp+"/etc/secureboot", "/etc/secureboot"). + Link(fst.Tmp+"/etc/services", "/etc/services"). + Link(fst.Tmp+"/etc/set-environment", "/etc/set-environment"). + Link(fst.Tmp+"/etc/shadow", "/etc/shadow"). + Link(fst.Tmp+"/etc/shells", "/etc/shells"). + Link(fst.Tmp+"/etc/ssh", "/etc/ssh"). + Link(fst.Tmp+"/etc/ssl", "/etc/ssl"). + Link(fst.Tmp+"/etc/static", "/etc/static"). + Link(fst.Tmp+"/etc/subgid", "/etc/subgid"). + Link(fst.Tmp+"/etc/subuid", "/etc/subuid"). + Link(fst.Tmp+"/etc/sudoers", "/etc/sudoers"). + Link(fst.Tmp+"/etc/sysctl.d", "/etc/sysctl.d"). + Link(fst.Tmp+"/etc/systemd", "/etc/systemd"). + Link(fst.Tmp+"/etc/terminfo", "/etc/terminfo"). + Link(fst.Tmp+"/etc/tmpfiles.d", "/etc/tmpfiles.d"). + Link(fst.Tmp+"/etc/udev", "/etc/udev"). + Link(fst.Tmp+"/etc/udisks2", "/etc/udisks2"). + Link(fst.Tmp+"/etc/UPower", "/etc/UPower"). + Link(fst.Tmp+"/etc/vconsole.conf", "/etc/vconsole.conf"). + Link(fst.Tmp+"/etc/X11", "/etc/X11"). + Link(fst.Tmp+"/etc/zfs", "/etc/zfs"). + Link(fst.Tmp+"/etc/zinputrc", "/etc/zinputrc"). + Link(fst.Tmp+"/etc/zoneinfo", "/etc/zoneinfo"). + Link(fst.Tmp+"/etc/zprofile", "/etc/zprofile"). + Link(fst.Tmp+"/etc/zshenv", "/etc/zshenv"). + Link(fst.Tmp+"/etc/zshrc", "/etc/zshrc"). + Tmpfs("/run/user", 4096, 0755). + Tmpfs("/run/user/65534", 8388608, 0755). + Bind("/tmp/fortify.1971/tmpdir/0", "/tmp", sandbox.BindWritable). + Bind("/home/chronos", "/home/chronos", sandbox.BindWritable). + Place("/etc/passwd", []byte("chronos:x:65534:65534:Fortify:/home/chronos:/run/current-system/sw/bin/zsh\n")). + Place("/etc/group", []byte("fortify:x:65534:\n")). + Tmpfs("/var/run/nscd", 8192, 0755), + }, }, { "nixos permissive defaults chromium", new(stubNixOS), &fst.Config{ - ID: "org.chromium.Chromium", - Command: []string{"/run/current-system/sw/bin/zsh", "-c", "exec chromium "}, + ID: "org.chromium.Chromium", + Args: []string{"zsh", "-c", "exec chromium "}, Confinement: fst.ConfinementConfig{ AppID: 9, 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/system_bus_socket", acl.Read, acl.Write), - (&bwrap.Config{ - Net: true, - UserNS: true, - Chdir: "/home/chronos", - Clearenv: true, - Syscall: new(bwrap.SyscallPolicy), - SetEnv: map[string]string{ - "DBUS_SESSION_BUS_ADDRESS": "unix:path=/run/user/65534/bus", - "DBUS_SYSTEM_BUS_ADDRESS": "unix:path=/run/dbus/system_bus_socket", - "HOME": "/home/chronos", - "PULSE_COOKIE": fst.Tmp + "/pulse-cookie", - "PULSE_SERVER": "unix:/run/user/65534/pulse/native", - "SHELL": "/run/current-system/sw/bin/zsh", - "TERM": "xterm-256color", - "USER": "chronos", - "WAYLAND_DISPLAY": "wayland-0", - "XDG_RUNTIME_DIR": "/run/user/65534", - "XDG_SESSION_CLASS": "user", - "XDG_SESSION_TYPE": "tty", + &sandbox.Params{ + Flags: sandbox.FAllowNet | sandbox.FAllowUserns | sandbox.FAllowTTY, + Dir: "/home/chronos", + Path: "/run/current-system/sw/bin/zsh", + Args: []string{"zsh", "-c", "exec chromium "}, + Env: []string{ + "DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/65534/bus", + "DBUS_SYSTEM_BUS_ADDRESS=unix:path=/run/dbus/system_bus_socket", + "HOME=/home/chronos", + "PULSE_COOKIE=" + fst.Tmp + "/pulse-cookie", + "PULSE_SERVER=unix:/run/user/65534/pulse/native", + "TERM=xterm-256color", + "USER=chronos", + "WAYLAND_DISPLAY=wayland-0", + "XDG_RUNTIME_DIR=/run/user/65534", + "XDG_SESSION_CLASS=user", + "XDG_SESSION_TYPE=tty", }, - Chmod: make(bwrap.ChmodConfig), - DieWithParent: true, - AsInit: true, - }).SetUID(65534).SetGID(65534). - Procfs("/proc"). - Tmpfs(fst.Tmp, 4096). - DevTmpfs("/dev").Mqueue("/dev/mqueue"). - Bind("/bin", "/bin", false, true). - Bind("/boot", "/boot", false, true). - Bind("/home", "/home", false, true). - Bind("/lib", "/lib", false, true). - Bind("/lib64", "/lib64", false, true). - Bind("/nix", "/nix", false, true). - Bind("/root", "/root", false, true). - Bind("/run", "/run", false, true). - Bind("/srv", "/srv", false, true). - Bind("/sys", "/sys", false, true). - Bind("/usr", "/usr", false, true). - Bind("/var", "/var", false, true). - Bind("/dev/dri", "/dev/dri", true, true, true). - Bind("/dev/kvm", "/dev/kvm", true, true, true). - Tmpfs("/run/user/1971", 8192). - Tmpfs("/run/dbus", 8192). - Bind("/etc", fst.Tmp+"/etc"). - Symlink(fst.Tmp+"/etc/alsa", "/etc/alsa"). - Symlink(fst.Tmp+"/etc/bashrc", "/etc/bashrc"). - Symlink(fst.Tmp+"/etc/binfmt.d", "/etc/binfmt.d"). - Symlink(fst.Tmp+"/etc/dbus-1", "/etc/dbus-1"). - Symlink(fst.Tmp+"/etc/default", "/etc/default"). - Symlink(fst.Tmp+"/etc/ethertypes", "/etc/ethertypes"). - Symlink(fst.Tmp+"/etc/fonts", "/etc/fonts"). - Symlink(fst.Tmp+"/etc/fstab", "/etc/fstab"). - Symlink(fst.Tmp+"/etc/fuse.conf", "/etc/fuse.conf"). - Symlink(fst.Tmp+"/etc/host.conf", "/etc/host.conf"). - Symlink(fst.Tmp+"/etc/hostid", "/etc/hostid"). - Symlink(fst.Tmp+"/etc/hostname", "/etc/hostname"). - Symlink(fst.Tmp+"/etc/hostname.CHECKSUM", "/etc/hostname.CHECKSUM"). - Symlink(fst.Tmp+"/etc/hosts", "/etc/hosts"). - Symlink(fst.Tmp+"/etc/inputrc", "/etc/inputrc"). - Symlink(fst.Tmp+"/etc/ipsec.d", "/etc/ipsec.d"). - Symlink(fst.Tmp+"/etc/issue", "/etc/issue"). - Symlink(fst.Tmp+"/etc/kbd", "/etc/kbd"). - Symlink(fst.Tmp+"/etc/libblockdev", "/etc/libblockdev"). - Symlink(fst.Tmp+"/etc/locale.conf", "/etc/locale.conf"). - Symlink(fst.Tmp+"/etc/localtime", "/etc/localtime"). - Symlink(fst.Tmp+"/etc/login.defs", "/etc/login.defs"). - Symlink(fst.Tmp+"/etc/lsb-release", "/etc/lsb-release"). - Symlink(fst.Tmp+"/etc/lvm", "/etc/lvm"). - Symlink(fst.Tmp+"/etc/machine-id", "/etc/machine-id"). - Symlink(fst.Tmp+"/etc/man_db.conf", "/etc/man_db.conf"). - Symlink(fst.Tmp+"/etc/modprobe.d", "/etc/modprobe.d"). - Symlink(fst.Tmp+"/etc/modules-load.d", "/etc/modules-load.d"). - Symlink("/proc/mounts", "/etc/mtab"). - Symlink(fst.Tmp+"/etc/nanorc", "/etc/nanorc"). - Symlink(fst.Tmp+"/etc/netgroup", "/etc/netgroup"). - Symlink(fst.Tmp+"/etc/NetworkManager", "/etc/NetworkManager"). - Symlink(fst.Tmp+"/etc/nix", "/etc/nix"). - Symlink(fst.Tmp+"/etc/nixos", "/etc/nixos"). - Symlink(fst.Tmp+"/etc/NIXOS", "/etc/NIXOS"). - Symlink(fst.Tmp+"/etc/nscd.conf", "/etc/nscd.conf"). - Symlink(fst.Tmp+"/etc/nsswitch.conf", "/etc/nsswitch.conf"). - Symlink(fst.Tmp+"/etc/opensnitchd", "/etc/opensnitchd"). - Symlink(fst.Tmp+"/etc/os-release", "/etc/os-release"). - Symlink(fst.Tmp+"/etc/pam", "/etc/pam"). - Symlink(fst.Tmp+"/etc/pam.d", "/etc/pam.d"). - Symlink(fst.Tmp+"/etc/pipewire", "/etc/pipewire"). - Symlink(fst.Tmp+"/etc/pki", "/etc/pki"). - Symlink(fst.Tmp+"/etc/polkit-1", "/etc/polkit-1"). - Symlink(fst.Tmp+"/etc/profile", "/etc/profile"). - Symlink(fst.Tmp+"/etc/protocols", "/etc/protocols"). - Symlink(fst.Tmp+"/etc/qemu", "/etc/qemu"). - Symlink(fst.Tmp+"/etc/resolv.conf", "/etc/resolv.conf"). - Symlink(fst.Tmp+"/etc/resolvconf.conf", "/etc/resolvconf.conf"). - Symlink(fst.Tmp+"/etc/rpc", "/etc/rpc"). - Symlink(fst.Tmp+"/etc/samba", "/etc/samba"). - Symlink(fst.Tmp+"/etc/sddm.conf", "/etc/sddm.conf"). - Symlink(fst.Tmp+"/etc/secureboot", "/etc/secureboot"). - Symlink(fst.Tmp+"/etc/services", "/etc/services"). - Symlink(fst.Tmp+"/etc/set-environment", "/etc/set-environment"). - Symlink(fst.Tmp+"/etc/shadow", "/etc/shadow"). - Symlink(fst.Tmp+"/etc/shells", "/etc/shells"). - Symlink(fst.Tmp+"/etc/ssh", "/etc/ssh"). - Symlink(fst.Tmp+"/etc/ssl", "/etc/ssl"). - Symlink(fst.Tmp+"/etc/static", "/etc/static"). - Symlink(fst.Tmp+"/etc/subgid", "/etc/subgid"). - Symlink(fst.Tmp+"/etc/subuid", "/etc/subuid"). - Symlink(fst.Tmp+"/etc/sudoers", "/etc/sudoers"). - Symlink(fst.Tmp+"/etc/sysctl.d", "/etc/sysctl.d"). - Symlink(fst.Tmp+"/etc/systemd", "/etc/systemd"). - Symlink(fst.Tmp+"/etc/terminfo", "/etc/terminfo"). - Symlink(fst.Tmp+"/etc/tmpfiles.d", "/etc/tmpfiles.d"). - Symlink(fst.Tmp+"/etc/udev", "/etc/udev"). - Symlink(fst.Tmp+"/etc/udisks2", "/etc/udisks2"). - Symlink(fst.Tmp+"/etc/UPower", "/etc/UPower"). - Symlink(fst.Tmp+"/etc/vconsole.conf", "/etc/vconsole.conf"). - Symlink(fst.Tmp+"/etc/X11", "/etc/X11"). - Symlink(fst.Tmp+"/etc/zfs", "/etc/zfs"). - Symlink(fst.Tmp+"/etc/zinputrc", "/etc/zinputrc"). - Symlink(fst.Tmp+"/etc/zoneinfo", "/etc/zoneinfo"). - Symlink(fst.Tmp+"/etc/zprofile", "/etc/zprofile"). - Symlink(fst.Tmp+"/etc/zshenv", "/etc/zshenv"). - Symlink(fst.Tmp+"/etc/zshrc", "/etc/zshrc"). - Tmpfs("/run/user", 1048576). - Tmpfs("/run/user/65534", 8388608). - Bind("/tmp/fortify.1971/tmpdir/9", "/tmp", false, true). - Bind("/home/chronos", "/home/chronos", false, true). - 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")). - Bind("/tmp/fortify.1971/wayland/ebf083d1b175911782d413369b64ce7c", "/run/user/65534/wayland-0"). - Bind("/run/user/1971/fortify/ebf083d1b175911782d413369b64ce7c/pulse", "/run/user/65534/pulse/native"). - CopyBind(fst.Tmp+"/pulse-cookie", nil). - Bind("/tmp/fortify.1971/ebf083d1b175911782d413369b64ce7c/bus", "/run/user/65534/bus"). - 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"), + Ops: new(sandbox.Ops). + Proc("/proc"). + Tmpfs(fst.Tmp, 4096, 0755). + Dev("/dev").Mqueue("/dev/mqueue"). + Bind("/bin", "/bin", sandbox.BindWritable). + Bind("/boot", "/boot", sandbox.BindWritable). + Bind("/home", "/home", sandbox.BindWritable). + Bind("/lib", "/lib", sandbox.BindWritable). + Bind("/lib64", "/lib64", sandbox.BindWritable). + Bind("/nix", "/nix", sandbox.BindWritable). + Bind("/root", "/root", sandbox.BindWritable). + Bind("/run", "/run", sandbox.BindWritable). + Bind("/srv", "/srv", sandbox.BindWritable). + Bind("/sys", "/sys", sandbox.BindWritable). + Bind("/usr", "/usr", sandbox.BindWritable). + Bind("/var", "/var", sandbox.BindWritable). + Bind("/dev/dri", "/dev/dri", sandbox.BindWritable|sandbox.BindDevice|sandbox.BindOptional). + Bind("/dev/kvm", "/dev/kvm", sandbox.BindWritable|sandbox.BindDevice|sandbox.BindOptional). + Tmpfs("/run/user/1971", 8192, 0755). + Tmpfs("/run/dbus", 8192, 0755). + Bind("/etc", fst.Tmp+"/etc", 0). + Mkdir("/etc", 0700). + Link(fst.Tmp+"/etc/alsa", "/etc/alsa"). + Link(fst.Tmp+"/etc/bashrc", "/etc/bashrc"). + Link(fst.Tmp+"/etc/binfmt.d", "/etc/binfmt.d"). + Link(fst.Tmp+"/etc/dbus-1", "/etc/dbus-1"). + Link(fst.Tmp+"/etc/default", "/etc/default"). + Link(fst.Tmp+"/etc/ethertypes", "/etc/ethertypes"). + Link(fst.Tmp+"/etc/fonts", "/etc/fonts"). + Link(fst.Tmp+"/etc/fstab", "/etc/fstab"). + Link(fst.Tmp+"/etc/fuse.conf", "/etc/fuse.conf"). + Link(fst.Tmp+"/etc/host.conf", "/etc/host.conf"). + Link(fst.Tmp+"/etc/hostid", "/etc/hostid"). + Link(fst.Tmp+"/etc/hostname", "/etc/hostname"). + Link(fst.Tmp+"/etc/hostname.CHECKSUM", "/etc/hostname.CHECKSUM"). + Link(fst.Tmp+"/etc/hosts", "/etc/hosts"). + Link(fst.Tmp+"/etc/inputrc", "/etc/inputrc"). + Link(fst.Tmp+"/etc/ipsec.d", "/etc/ipsec.d"). + Link(fst.Tmp+"/etc/issue", "/etc/issue"). + Link(fst.Tmp+"/etc/kbd", "/etc/kbd"). + Link(fst.Tmp+"/etc/libblockdev", "/etc/libblockdev"). + Link(fst.Tmp+"/etc/locale.conf", "/etc/locale.conf"). + Link(fst.Tmp+"/etc/localtime", "/etc/localtime"). + Link(fst.Tmp+"/etc/login.defs", "/etc/login.defs"). + Link(fst.Tmp+"/etc/lsb-release", "/etc/lsb-release"). + Link(fst.Tmp+"/etc/lvm", "/etc/lvm"). + Link(fst.Tmp+"/etc/machine-id", "/etc/machine-id"). + Link(fst.Tmp+"/etc/man_db.conf", "/etc/man_db.conf"). + Link(fst.Tmp+"/etc/modprobe.d", "/etc/modprobe.d"). + Link(fst.Tmp+"/etc/modules-load.d", "/etc/modules-load.d"). + Link("/proc/mounts", "/etc/mtab"). + Link(fst.Tmp+"/etc/nanorc", "/etc/nanorc"). + Link(fst.Tmp+"/etc/netgroup", "/etc/netgroup"). + Link(fst.Tmp+"/etc/NetworkManager", "/etc/NetworkManager"). + Link(fst.Tmp+"/etc/nix", "/etc/nix"). + Link(fst.Tmp+"/etc/nixos", "/etc/nixos"). + Link(fst.Tmp+"/etc/NIXOS", "/etc/NIXOS"). + Link(fst.Tmp+"/etc/nscd.conf", "/etc/nscd.conf"). + Link(fst.Tmp+"/etc/nsswitch.conf", "/etc/nsswitch.conf"). + Link(fst.Tmp+"/etc/opensnitchd", "/etc/opensnitchd"). + Link(fst.Tmp+"/etc/os-release", "/etc/os-release"). + Link(fst.Tmp+"/etc/pam", "/etc/pam"). + Link(fst.Tmp+"/etc/pam.d", "/etc/pam.d"). + Link(fst.Tmp+"/etc/pipewire", "/etc/pipewire"). + Link(fst.Tmp+"/etc/pki", "/etc/pki"). + Link(fst.Tmp+"/etc/polkit-1", "/etc/polkit-1"). + Link(fst.Tmp+"/etc/profile", "/etc/profile"). + Link(fst.Tmp+"/etc/protocols", "/etc/protocols"). + Link(fst.Tmp+"/etc/qemu", "/etc/qemu"). + Link(fst.Tmp+"/etc/resolv.conf", "/etc/resolv.conf"). + Link(fst.Tmp+"/etc/resolvconf.conf", "/etc/resolvconf.conf"). + Link(fst.Tmp+"/etc/rpc", "/etc/rpc"). + Link(fst.Tmp+"/etc/samba", "/etc/samba"). + Link(fst.Tmp+"/etc/sddm.conf", "/etc/sddm.conf"). + Link(fst.Tmp+"/etc/secureboot", "/etc/secureboot"). + Link(fst.Tmp+"/etc/services", "/etc/services"). + Link(fst.Tmp+"/etc/set-environment", "/etc/set-environment"). + Link(fst.Tmp+"/etc/shadow", "/etc/shadow"). + Link(fst.Tmp+"/etc/shells", "/etc/shells"). + Link(fst.Tmp+"/etc/ssh", "/etc/ssh"). + Link(fst.Tmp+"/etc/ssl", "/etc/ssl"). + Link(fst.Tmp+"/etc/static", "/etc/static"). + Link(fst.Tmp+"/etc/subgid", "/etc/subgid"). + Link(fst.Tmp+"/etc/subuid", "/etc/subuid"). + Link(fst.Tmp+"/etc/sudoers", "/etc/sudoers"). + Link(fst.Tmp+"/etc/sysctl.d", "/etc/sysctl.d"). + Link(fst.Tmp+"/etc/systemd", "/etc/systemd"). + Link(fst.Tmp+"/etc/terminfo", "/etc/terminfo"). + Link(fst.Tmp+"/etc/tmpfiles.d", "/etc/tmpfiles.d"). + Link(fst.Tmp+"/etc/udev", "/etc/udev"). + Link(fst.Tmp+"/etc/udisks2", "/etc/udisks2"). + Link(fst.Tmp+"/etc/UPower", "/etc/UPower"). + Link(fst.Tmp+"/etc/vconsole.conf", "/etc/vconsole.conf"). + Link(fst.Tmp+"/etc/X11", "/etc/X11"). + Link(fst.Tmp+"/etc/zfs", "/etc/zfs"). + Link(fst.Tmp+"/etc/zinputrc", "/etc/zinputrc"). + Link(fst.Tmp+"/etc/zoneinfo", "/etc/zoneinfo"). + Link(fst.Tmp+"/etc/zprofile", "/etc/zprofile"). + Link(fst.Tmp+"/etc/zshenv", "/etc/zshenv"). + Link(fst.Tmp+"/etc/zshrc", "/etc/zshrc"). + Tmpfs("/run/user", 4096, 0755). + Tmpfs("/run/user/65534", 8388608, 0755). + Bind("/tmp/fortify.1971/tmpdir/9", "/tmp", sandbox.BindWritable). + Bind("/home/chronos", "/home/chronos", sandbox.BindWritable). + Place("/etc/passwd", []byte("chronos:x:65534:65534:Fortify:/home/chronos:/run/current-system/sw/bin/zsh\n")). + Place("/etc/group", []byte("fortify:x:65534:\n")). + Bind("/tmp/fortify.1971/wayland/ebf083d1b175911782d413369b64ce7c", "/run/user/65534/wayland-0", 0). + Bind("/run/user/1971/fortify/ebf083d1b175911782d413369b64ce7c/pulse", "/run/user/65534/pulse/native", 0). + Place(fst.Tmp+"/pulse-cookie", nil). + Bind("/tmp/fortify.1971/ebf083d1b175911782d413369b64ce7c/bus", "/run/user/65534/bus", 0). + Bind("/tmp/fortify.1971/ebf083d1b175911782d413369b64ce7c/system_bus_socket", "/run/dbus/system_bus_socket", 0). + Tmpfs("/var/run/nscd", 8192, 0755), + }, }, } diff --git a/internal/app/app_stub_test.go b/internal/app/app_stub_test.go index be49617..665dfa0 100644 --- a/internal/app/app_stub_test.go +++ b/internal/app/app_stub_test.go @@ -55,10 +55,8 @@ func (s *stubNixOS) LookPath(file string) (string, error) { } switch file { - case "sudo": - return "/run/wrappers/bin/sudo", nil - case "machinectl": - return "/home/ophestra/.nix-profile/bin/machinectl", nil + case "zsh": + return "/run/current-system/sw/bin/zsh", nil default: panic(fmt.Sprintf("attempted to look up unexpected executable %q", file)) } diff --git a/internal/app/app_test.go b/internal/app/app_test.go index b448153..fa13ddd 100644 --- a/internal/app/app_test.go +++ b/internal/app/app_test.go @@ -8,19 +8,19 @@ import ( "time" "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/sys" + "git.gensokyo.uk/security/fortify/sandbox" "git.gensokyo.uk/security/fortify/system" ) type sealTestCase struct { - name string - os sys.State - config *fst.Config - id fst.ID - wantSys *system.I - wantBwrap *bwrap.Config + name string + os sys.State + config *fst.Config + id fst.ID + wantSys *system.I + wantContainer *sandbox.Params } func TestApp(t *testing.T) { @@ -30,15 +30,15 @@ func TestApp(t *testing.T) { t.Run(tc.name, func(t *testing.T) { a := app.NewWithID(tc.id, tc.os) var ( - gotSys *system.I - gotBwrap *bwrap.Config + gotSys *system.I + gotContainer *sandbox.Params ) if !t.Run("seal", func(t *testing.T) { if sa, err := a.Seal(tc.config); err != nil { t.Errorf("Seal: error = %v", err) return } else { - gotSys, gotBwrap = app.AppSystemBwrap(a, sa) + gotSys, gotContainer = app.AppIParams(a, sa) } }) { return @@ -51,10 +51,10 @@ func TestApp(t *testing.T) { } }) - t.Run("compare bwrap", func(t *testing.T) { - if !reflect.DeepEqual(gotBwrap, tc.wantBwrap) { - t.Errorf("seal: bwrap =\n%s\n, want\n%s", - mustMarshal(gotBwrap), mustMarshal(tc.wantBwrap)) + t.Run("compare params", func(t *testing.T) { + if !reflect.DeepEqual(gotContainer, tc.wantContainer) { + t.Errorf("seal: params =\n%s\n, want\n%s", + mustMarshal(gotContainer), mustMarshal(tc.wantContainer)) } }) }) diff --git a/internal/app/export_test.go b/internal/app/export_test.go index 199b6c1..60d97b3 100644 --- a/internal/app/export_test.go +++ b/internal/app/export_test.go @@ -2,8 +2,8 @@ package app import ( "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/sandbox" "git.gensokyo.uk/security/fortify/system" ) @@ -14,7 +14,7 @@ func NewWithID(id fst.ID, os sys.State) fst.App { 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) seal := sa.(*outcome) if v.outcome != seal || v.id != seal.id { diff --git a/internal/app/init0/early.go b/internal/app/init0/early.go deleted file mode 100644 index 0bcf094..0000000 --- a/internal/app/init0/early.go +++ /dev/null @@ -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) - } -} diff --git a/internal/app/init0/main.go b/internal/app/init0/main.go deleted file mode 100644 index 7b8d27c..0000000 --- a/internal/app/init0/main.go +++ /dev/null @@ -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) - } - } -} diff --git a/internal/app/init0/payload.go b/internal/app/init0/payload.go deleted file mode 100644 index d1dc9ec..0000000 --- a/internal/app/init0/payload.go +++ /dev/null @@ -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 -} diff --git a/internal/app/process.go b/internal/app/process.go index 2755230..6c33714 100644 --- a/internal/app/process.go +++ b/internal/app/process.go @@ -3,15 +3,12 @@ package app import ( "context" "errors" - "fmt" "log" "os/exec" - "path/filepath" "strings" "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/app/shim" "git.gensokyo.uk/security/fortify/internal/fmsg" @@ -21,7 +18,7 @@ import ( 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) { // 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 @@ -37,33 +34,11 @@ func (seal *outcome) Run(ctx context.Context, rs *fst.RunState) error { fmsg.Verbosef("version %s", internal.Version()) 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 */ - if err := seal.sys.Commit(ctx); err != nil { + if err := seal.sys.Commit(seal.ctx); err != nil { return err } 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( seal.user.aid.String(), seal.user.supp, - seal.bwrapSync, ); err != nil { return err } else { @@ -145,7 +119,7 @@ func (seal *outcome) Run(ctx context.Context, rs *fst.RunState) error { rs.Time = startTime } - c, cancel := context.WithTimeout(ctx, shimSetupTimeout) + ctx, cancel := context.WithTimeout(seal.ctx, shimSetupTimeout) defer cancel() go func() { @@ -154,11 +128,9 @@ func (seal *outcome) Run(ctx context.Context, rs *fst.RunState) error { cancel() }() - if err := cmd.Serve(c, &shim.Payload{ - Argv: seal.command, - Exec: shimExec, - Bwrap: seal.container, - Home: seal.user.data, + if err := cmd.Serve(ctx, &shim.Params{ + Container: seal.container, + Home: seal.user.data, Verbose: fmsg.Load(), }); 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 // 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 - case err := <-cmd.WaitFallback(): + case err := <-cmd.Fallback(): rs.ExitCode = 255 log.Printf("cannot terminate shim on faulted setup: %v", err) // 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.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 { - // dump dbus message buffer seal.dbusMsg() } diff --git a/internal/app/seal.go b/internal/app/seal.go index 3070212..a25897d 100644 --- a/internal/app/seal.go +++ b/internal/app/seal.go @@ -2,24 +2,28 @@ package app import ( "bytes" + "context" "encoding/gob" "errors" "fmt" "io" "io/fs" + "maps" "os" "path" "regexp" + "slices" "strings" "sync/atomic" + "syscall" "git.gensokyo.uk/security/fortify/acl" "git.gensokyo.uk/security/fortify/dbus" "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/fmsg" "git.gensokyo.uk/security/fortify/internal/sys" + "git.gensokyo.uk/security/fortify/sandbox" "git.gensokyo.uk/security/fortify/system" "git.gensokyo.uk/security/fortify/wl" ) @@ -65,19 +69,19 @@ type outcome struct { // copied from [sys.State] response runDirPath string - // passed through from [fst.Config] - command []string - // 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 // dump dbus proxy message buffer dbusMsg func() - user fsuUser - sys *system.I - container *bwrap.Config - bwrapSync *os.File + user fsuUser + sys *system.I + ctx context.Context + + container *sandbox.Params + env map[string]string + sync *os.File f atomic.Bool } @@ -100,7 +104,17 @@ type fsuUser struct { 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 ct := new(bytes.Buffer) @@ -111,9 +125,6 @@ func (seal *outcome) finalise(sys sys.State, config *fst.Config) error { 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 if config.Confinement.AppID < 0 || config.Confinement.AppID > 9999 { return fmsg.WrapError(ErrUser, @@ -167,12 +178,24 @@ func (seal *outcome) finalise(sys sys.State, config *fst.Config) error { if config.Confinement.Sandbox == nil { 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{ - UserNS: true, - Net: true, - Syscall: new(bwrap.SyscallPolicy), - NoNewSession: true, - AutoEtc: true, + Userns: true, + Net: true, + Tty: true, + AutoEtc: true, } // bind entries in / 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 nscd := "/var/run/nscd" 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 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 } - var mapuid *stringPair[int] + var mapuid, mapgid *stringPair[int] { - var uid int + var uid, gid int 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 { - 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) - if seal.container.SetEnv == nil { - seal.container.SetEnv = make(map[string]string) + mapgid = newInt(gid) + 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 innerRuntimeDir := path.Join("/run/user", mapuid.String()) - seal.container.Tmpfs("/run/user", 1*1024*1024) - seal.container.Tmpfs(innerRuntimeDir, 8*1024*1024) - seal.container.SetEnv[xdgRuntimeDir] = innerRuntimeDir - seal.container.SetEnv[xdgSessionClass] = "user" - seal.container.SetEnv[xdgSessionType] = "tty" + seal.container.Tmpfs("/run/user", 1<<12, 0755) + seal.container.Tmpfs(innerRuntimeDir, 1<<23, 0755) + seal.env[xdgRuntimeDir] = innerRuntimeDir + seal.env[xdgSessionClass] = "user" + seal.env[xdgSessionType] = "tty" // outer path for inner /tmp { tmpdir := path.Join(sc.SharePath, "tmpdir") seal.sys.Ensure(tmpdir, 0700) seal.sys.UpdatePermType(system.User, tmpdir, acl.Execute) - tmpdirProc := path.Join(tmpdir, seal.user.aid.String()) - seal.sys.Ensure(tmpdirProc, 01700) - seal.sys.UpdatePermType(system.User, tmpdirProc, acl.Read, acl.Write, acl.Execute) - seal.container.Bind(tmpdirProc, "/tmp", false, true) + tmpdirInst := path.Join(tmpdir, seal.user.aid.String()) + seal.sys.Ensure(tmpdirInst, 01700) + seal.sys.UpdatePermType(system.User, tmpdirInst, acl.Read, acl.Write, acl.Execute) + seal.container.Bind(tmpdirInst, "/tmp", sandbox.BindWritable) } /* 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" if 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 != "" { username = seal.user.username } - seal.container.Bind(seal.user.data, homeDir, false, true) - seal.container.Chdir = homeDir - seal.container.SetEnv["HOME"] = homeDir - seal.container.SetEnv["USER"] = username + seal.container.Bind(seal.user.data, homeDir, sandbox.BindWritable) + seal.container.Dir = homeDir + seal.env["HOME"] = homeDir + seal.env["USER"] = username - // generate /etc/passwd and /etc/group - seal.container.CopyBind("/etc/passwd", - []byte(username+":x:"+mapuid.String()+":"+mapuid.String()+":Fortify:"+homeDir+":"+sh+"\n")) - seal.container.CopyBind("/etc/group", - []byte("fortify:x:"+mapuid.String()+":\n")) + seal.container.Place("/etc/passwd", + []byte(username+":x:"+mapuid.String()+":"+mapgid.String()+":Fortify:"+homeDir+":"+shellPath+"\n")) + seal.container.Place("/etc/group", + []byte("fortify:x:"+mapgid.String()+":\n")) /* Display servers */ - // pass $TERM to launcher + // pass $TERM for proper terminal I/O in shell 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) { // outer wayland socket (usually `/run/user/%d/wayland-%d`) var socketPath string @@ -326,7 +351,7 @@ func (seal *outcome) finalise(sys sys.State, config *fst.Config) error { } 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 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 appID = "uk.gensokyo.fortify." + seal.id.String() } - seal.sys.Wayland(&seal.bwrapSync, outerPath, socketPath, appID, seal.id.String()) - seal.container.Bind(outerPath, innerPath) + seal.sys.Wayland(&seal.sync, outerPath, socketPath, appID, seal.id.String()) + seal.container.Bind(outerPath, innerPath, 0) } else { // bind mount wayland socket (insecure) 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) } } - // set up X11 if config.Confinement.Enablements.Has(system.EX11) { - // discover X11 and grant user permission via the `ChangeHosts` command if d, ok := sys.LookupEnv(display); !ok { return fmsg.WrapError(ErrXDisplay, "DISPLAY is not set") } else { seal.sys.ChangeHosts("#" + seal.user.uid.String()) - seal.container.SetEnv[display] = d - seal.container.Bind("/tmp/.X11-unix", "/tmp/.X11-unix") + seal.env[display] = d + 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") innerPulseSocket := path.Join(innerRuntimeDir, "pulse", "native") seal.sys.Link(pulseSocket, innerPulseRuntimeDir) - seal.container.Bind(innerPulseRuntimeDir, innerPulseSocket) - seal.container.SetEnv[pulseServer] = "unix:" + innerPulseSocket + seal.container.Bind(innerPulseRuntimeDir, innerPulseSocket, 0) + seal.env[pulseServer] = "unix:" + innerPulseSocket // publish current user's pulse cookie for target user 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())) } else { innerDst := fst.Tmp + "/pulse-cookie" - seal.container.SetEnv[pulseCookie] = innerDst - payload := new([]byte) - seal.container.CopyBindRef(innerDst, &payload) + seal.env[pulseCookie] = innerDst + var payload *[]byte + seal.container.PlaceP(innerDst, &payload) 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 sessionInner := path.Join(innerRuntimeDir, "bus") - seal.container.SetEnv[dbusSessionBusAddress] = "unix:path=" + sessionInner - seal.container.Bind(sessionPath, sessionInner) + seal.env[dbusSessionBusAddress] = "unix:path=" + sessionInner + seal.container.Bind(sessionPath, sessionInner, 0) seal.sys.UpdatePerm(sessionPath, acl.Read, acl.Write) if config.Confinement.SystemBus != nil { systemInner := "/run/dbus/system_bus_socket" - seal.container.SetEnv[dbusSystemBusAddress] = "unix:path=" + systemInner - seal.container.Bind(systemPath, systemInner) + seal.env[dbusSystemBusAddress] = "unix:path=" + systemInner + seal.container.Bind(systemPath, systemInner, 0) seal.sys.UpdatePerm(systemPath, acl.Read, acl.Write) } } @@ -452,9 +475,8 @@ func (seal *outcome) finalise(sys sys.State, config *fst.Config) error { Miscellaneous */ - // queue overriding tmpfs at the end of seal.container.Filesystem - for _, dest := range config.Confinement.Sandbox.Override { - seal.container.Tmpfs(dest, 8*1024) + for _, dest := range config.Confinement.Sandbox.Cover { + seal.container.Tmpfs(dest, 1<<13, 0755) } // 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...) } - // mount fortify in sandbox for init - seal.container.Bind(sys.MustExecutable(), path.Join(fst.Tmp, "sbin/fortify")) - seal.container.Symlink("fortify", path.Join(fst.Tmp, "sbin/init0")) + // flatten and sort env for deterministic behaviour + seal.container.Env = make([]string, 0, len(seal.env)) + 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", - seal.user.uid, seal.user.username, config.Confinement.Groups, config.Command) + fmsg.Verbosef("created application seal for uid %s (%s) groups: %v, argv: %s", + seal.user.uid, seal.user.username, config.Confinement.Groups, seal.container.Args) return nil } diff --git a/internal/app/shim/main.go b/internal/app/shim/main.go index 5634be4..17f346a 100644 --- a/internal/app/shim/main.go +++ b/internal/app/shim/main.go @@ -7,18 +7,26 @@ import ( "os" "os/exec" "os/signal" - "path" - "strconv" "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/app/init0" "git.gensokyo.uk/security/fortify/internal/fmsg" "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 // proceed with caution! @@ -27,17 +35,15 @@ func Main() { // USE WITH CAUTION fmsg.Prepare("shim") - // setting this prevents ptrace if err := sandbox.SetDumpable(sandbox.SUID_DUMP_DISABLE); err != nil { log.Fatalf("cannot set SUID_DUMP_DISABLE: %s", err) } - // receive setup payload var ( - payload Payload + params Params closeSetup func() error ) - if f, err := sandbox.Receive(Env, &payload, nil); err != nil { + if f, err := sandbox.Receive(Env, ¶ms, nil); err != nil { if errors.Is(err, sandbox.ErrInvalid) { log.Fatal("invalid config descriptor") } @@ -45,32 +51,26 @@ func Main() { 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 { - internal.InstallFmsg(payload.Verbose) + internal.InstallFmsg(params.Verbose) closeSetup = f } - if payload.Bwrap == nil { - log.Fatal("bwrap config not supplied") - } - - // restore bwrap sync fd - var syncFd *os.File - if payload.Sync != nil { - syncFd = os.NewFile(*payload.Sync, "sync") + if params.Container == nil || params.Container.Ops == nil { + log.Fatal("invalid container params") } // close setup socket if err := closeSetup(); err != nil { - log.Println("cannot close setup pipe:", err) + log.Printf("cannot close setup pipe: %v", err) // not fatal } // 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 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) } } else { @@ -79,72 +79,37 @@ func Main() { // home directory is created, proceed } 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 - - // resolve argv0 - 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} + var name string + if len(params.Container.Args) > 0 { + name = params.Container.Args[0] } - - 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) defer stop() // unreachable - if b, err := helper.NewBwrap( - ctx, path.Join(fst.Tmp, "sbin/init0"), - nil, false, - func(int, int) []string { return make([]string, 0) }, - func(cmd *exec.Cmd) { cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr }, - extraFiles, - conf, syncFd, - ); err != nil { - log.Fatalf("malformed sandbox config: %v", err) - } else { - // run and pass through exit code - if err = b.Start(); err != nil { - log.Fatalf("cannot start target process: %v", err) - } else if err = b.Wait(); err != nil { - var exitError *exec.ExitError - if !errors.As(err, &exitError) { - log.Printf("wait: %v", err) - internal.Exit(127) - panic("unreachable") + container := sandbox.New(ctx, name) + container.Params = *params.Container + container.Stdin, container.Stdout, container.Stderr = os.Stdin, os.Stdout, os.Stderr + container.Cancel = func(cmd *exec.Cmd) error { return cmd.Process.Signal(os.Interrupt) } + container.WaitDelay = 2 * time.Second + + if err := container.Start(); err != nil { + fmsg.PrintBaseError(err, "cannot start container:") + os.Exit(1) + } + if err := container.Serve(); err != nil { + fmsg.PrintBaseError(err, "cannot configure container:") + } + if err := container.Wait(); err != nil { + var exitError *exec.ExitError + if !errors.As(err, &exitError) { + if errors.Is(err, context.Canceled) { + os.Exit(2) } - internal.Exit(exitError.ExitCode()) - panic("unreachable") + log.Printf("wait: %v", err) + os.Exit(127) } + os.Exit(exitError.ExitCode()) } } diff --git a/internal/app/shim/payload.go b/internal/app/shim/payload.go deleted file mode 100644 index e659e3f..0000000 --- a/internal/app/shim/payload.go +++ /dev/null @@ -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 -} diff --git a/internal/app/shim/manager.go b/internal/app/shim/proc.go similarity index 76% rename from internal/app/shim/manager.go rename to internal/app/shim/proc.go index 4ed75e5..8c73e69 100644 --- a/internal/app/shim/manager.go +++ b/internal/app/shim/proc.go @@ -8,9 +8,9 @@ import ( "os/exec" "strconv" "strings" + "syscall" "time" - "git.gensokyo.uk/security/fortify/helper/proc" "git.gensokyo.uk/security/fortify/internal" "git.gensokyo.uk/security/fortify/internal/fmsg" "git.gensokyo.uk/security/fortify/sandbox" @@ -25,10 +25,11 @@ type Shim struct { killFallback chan error // monitor to shim 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 { if s.cmd == nil { return "(unused shim manager)" @@ -36,21 +37,9 @@ func (s *Shim) String() 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( - // string representation of application id aid string, - // string representation of supplementary group ids supp []string, - // bwrap --sync-fd - syncFd *os.File, ) (*time.Time, error) { // prepare user switcher invocation 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.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) // withhold messages to stderr fmsg.Suspend() @@ -90,10 +73,11 @@ func (s *Shim) Start( "cannot start fsu:") } startTime := time.Now().UTC() + 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 s.killFallback = make(chan error, 1) killShim := func() { @@ -103,9 +87,8 @@ func (s *Shim) Serve(ctx context.Context, payload *Payload) error { } defer func() { killShim() }() - payload.Sync = s.sync encodeErr := make(chan error) - go func() { encodeErr <- s.encoder.Encode(payload) }() + go func() { encodeErr <- s.encoder.Encode(params) }() select { // encode return indicates setup completion @@ -121,11 +104,11 @@ func (s *Shim) Serve(ctx context.Context, payload *Payload) error { case <-ctx.Done(): err := ctx.Err() if errors.Is(err, context.Canceled) { - return fmsg.WrapError(errors.New("shim setup canceled"), + return fmsg.WrapError(syscall.ECANCELED, "shim setup canceled") } 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") } // unreachable diff --git a/internal/state/state_test.go b/internal/state/state_test.go index ee2a88c..22fd3cd 100644 --- a/internal/state/state_test.go +++ b/internal/state/state_test.go @@ -96,7 +96,7 @@ func testStore(t *testing.T, s state.Store) { } else { slices.Sort(aids) want := []int{0, 1} - if slices.Compare(aids, want) != 0 { + if !slices.Equal(aids, want) { t.Fatalf("List() = %#v, want %#v", aids, want) } } diff --git a/main.go b/main.go index 0505e59..cd9489a 100644 --- a/main.go +++ b/main.go @@ -20,7 +20,6 @@ import ( "git.gensokyo.uk/security/fortify/fst" "git.gensokyo.uk/security/fortify/internal" "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/fmsg" "git.gensokyo.uk/security/fortify/internal/state" @@ -43,7 +42,6 @@ var std sys.State = new(sys.Std) func main() { // early init path, skips root check and duplicate PR_SET_DUMPABLE sandbox.TryArgv0(fmsg.Output{}, fmsg.Prepare, internal.InstallFmsg) - init0.TryArgv0() if err := sandbox.SetDumpable(sandbox.SUID_DUMP_DISABLE); err != nil { 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(&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("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 { if len(args) < 1 { @@ -87,10 +83,9 @@ func buildCommand(out io.Writer) command.Command { // config extraArgs... config := tryPath(args[0]) - config.Command = append(config.Command, args[1:]...) + config.Args = append(config.Args, args[1:]...) - // invoke app - runApp(app.MustNew(std), config) + runApp(config) 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 { // initialise config from flags config := &fst.Config{ - ID: fid, - Command: args, + ID: fid, + Args: args, } if aid < 0 || aid > 9999 { @@ -199,7 +194,7 @@ func buildCommand(out io.Writer) command.Command { } // invoke app - runApp(app.MustNew(std), config) + runApp(config) panic("unreachable") }). Flag(&dbusConfigSession, "dbus-config", command.StringFlag("builtin"), @@ -279,10 +274,11 @@ func buildCommand(out io.Writer) command.Command { return c } -func runApp(a fst.App, config *fst.Config) { +func runApp(config *fst.Config) { ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM) defer stop() // unreachable + a := app.MustNew(ctx, std) rs := new(fst.RunState) if sa, err := a.Seal(config); err != nil { @@ -290,7 +286,7 @@ func runApp(a fst.App, config *fst.Config) { rs.ExitCode = 1 } else { // this updates ExitCode - app.PrintRunStateErr(rs, sa.Run(ctx, rs)) + app.PrintRunStateErr(rs, sa.Run(rs)) } internal.Exit(rs.ExitCode) } diff --git a/nixos.nix b/nixos.nix index 37ef556..2c5b5db 100644 --- a/nixos.nix +++ b/nixos.nix @@ -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); conf = { inherit (app) id; - command = [ - (pkgs.writeScript "${app.name}-start" '' - #!${pkgs.zsh}${pkgs.zsh.shellPath} - ${script} - '') - ]; + path = pkgs.writeScript "${app.name}-start" '' + #!${pkgs.zsh}${pkgs.zsh.shellPath} + ${script} + ''; + args = [ "${app.name}-start" ]; confinement = { app_id = aid; inherit (app) groups; @@ -99,17 +98,15 @@ in home = getsubhome fid aid; sandbox = { inherit (app) + devel userns net dev + tty + multiarch env ; - syscall = { - inherit (app) compat multiarch bluetooth; - deny_devel = !app.devel; - }; map_real_uid = app.mapRealUid; - no_new_session = app.tty; direct_wayland = app.insecureWayland; filesystem = let @@ -149,7 +146,7 @@ in ] ++ app.extraPaths; auto_etc = true; - override = [ "/var/run/nscd" ]; + cover = [ "/var/run/nscd" ]; }; inherit enablements; inherit (dbusConfig) session_bus system_bus; diff --git a/options.nix b/options.nix index d9ccfb7..9e36773 100644 --- a/options.nix +++ b/options.nix @@ -148,21 +148,19 @@ in ''; }; - nix = mkEnableOption "nix daemon"; - userns = mkEnableOption "user namespace"; - mapRealUid = mkEnableOption "mapping to priv-user uid"; - dev = mkEnableOption "access to all devices"; + devel = mkEnableOption "debugging-related kernel interfaces"; + userns = mkEnableOption "user namespace creation"; 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" // { default = true; }; - compat = mkEnableOption "disable syscall filter extensions"; - devel = mkEnableOption "development kernel APIs"; - multiarch = mkEnableOption "multiarch kernel support"; - bluetooth = mkEnableOption "AF_BLUETOOTH socket operations"; + nix = mkEnableOption "nix daemon access"; + mapRealUid = mkEnableOption "mapping to priv-user uid"; + dev = mkEnableOption "access to all devices"; + insecureWayland = mkEnableOption "direct access to the Wayland socket"; gpu = mkOption { type = nullOr bool; diff --git a/print.go b/print.go index 198136b..7c6663b 100644 --- a/print.go +++ b/print.go @@ -89,10 +89,10 @@ func printShowInstance( flags = append(flags, name) } } - writeFlag("userns", sandbox.UserNS) + writeFlag("userns", sandbox.Userns) writeFlag("net", sandbox.Net) writeFlag("dev", sandbox.Dev) - writeFlag("tty", sandbox.NoNewSession) + writeFlag("tty", sandbox.Tty) writeFlag("mapuid", sandbox.MapRealUID) writeFlag("directwl", sandbox.DirectWayland) writeFlag("autoetc", sandbox.AutoEtc) @@ -107,14 +107,14 @@ func printShowInstance( } t.Printf(" Etc:\t%s\n", etc) - if len(sandbox.Override) > 0 { - t.Printf(" Overrides:\t%s\n", strings.Join(sandbox.Override, " ")) + if len(sandbox.Cover) > 0 { + t.Printf(" Cover:\t%s\n", strings.Join(sandbox.Cover, " ")) } // Env map[string]string `json:"env"` // 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") if !short { @@ -256,7 +256,7 @@ func printPs(output io.Writer, now time.Time, s state.Store, short, flagJSON boo ) if e.Config != nil { 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) } t.Printf("\t%s\t%d\t%s\t%s\t%s\t%s\n", diff --git a/print_test.go b/print_test.go index 9e83722..3c70899 100644 --- a/print_test.go +++ b/print_test.go @@ -43,7 +43,7 @@ func Test_printShowInstance(t *testing.T) { Hostname: "localhost" Flags: userns net dev tty mapuid autoetc Etc: /etc - Overrides: /var/run/nscd + Cover: /var/run/nscd Command: chromium --ignore-gpu-blocklist --disable-smooth-scrolling --enable-features=UseOzonePlatform --ozone-platform=wayland Filesystem @@ -127,7 +127,7 @@ App Hostname: "localhost" Flags: userns net dev tty mapuid autoetc Etc: /etc - Overrides: /var/run/nscd + Cover: /var/run/nscd Command: chromium --ignore-gpu-blocklist --disable-smooth-scrolling --enable-features=UseOzonePlatform --ozone-platform=wayland Filesystem @@ -192,7 +192,8 @@ App "pid": 3735928559, "config": { "id": "org.chromium.Chromium", - "command": [ + "path": "/run/current-system/sw/bin/chromium", + "args": [ "chromium", "--ignore-gpu-blocklist", "--disable-smooth-scrolling", @@ -209,24 +210,19 @@ App "home": "/var/lib/persist/home/org.chromium.Chromium", "sandbox": { "hostname": "localhost", + "seccomp": 32, + "devel": true, "userns": true, "net": true, - "dev": true, - "syscall": { - "compat": false, - "deny_devel": true, - "multiarch": true, - "linux32": false, - "can": false, - "bluetooth": false - }, - "no_new_session": true, - "map_real_uid": true, + "tty": true, + "multiarch": true, "env": { "GOOGLE_API_KEY": "AIzaSyBHDrl33hwRp4rMQY0ziRbj8K9LPA6vUCY", "GOOGLE_DEFAULT_CLIENT_ID": "77185425430.apps.googleusercontent.com", "GOOGLE_DEFAULT_CLIENT_SECRET": "OTJgUOQcT7lO7GsGZq2G4IlT" }, + "map_real_uid": true, + "dev": true, "filesystem": [ { "src": "/nix/store" @@ -259,7 +255,7 @@ App ], "etc": "/etc", "auto_etc": true, - "override": [ + "cover": [ "/var/run/nscd" ] }, @@ -320,7 +316,8 @@ App `}, {"json config", nil, fst.Template(), false, true, `{ "id": "org.chromium.Chromium", - "command": [ + "path": "/run/current-system/sw/bin/chromium", + "args": [ "chromium", "--ignore-gpu-blocklist", "--disable-smooth-scrolling", @@ -337,24 +334,19 @@ App "home": "/var/lib/persist/home/org.chromium.Chromium", "sandbox": { "hostname": "localhost", + "seccomp": 32, + "devel": true, "userns": true, "net": true, - "dev": true, - "syscall": { - "compat": false, - "deny_devel": true, - "multiarch": true, - "linux32": false, - "can": false, - "bluetooth": false - }, - "no_new_session": true, - "map_real_uid": true, + "tty": true, + "multiarch": true, "env": { "GOOGLE_API_KEY": "AIzaSyBHDrl33hwRp4rMQY0ziRbj8K9LPA6vUCY", "GOOGLE_DEFAULT_CLIENT_ID": "77185425430.apps.googleusercontent.com", "GOOGLE_DEFAULT_CLIENT_SECRET": "OTJgUOQcT7lO7GsGZq2G4IlT" }, + "map_real_uid": true, + "dev": true, "filesystem": [ { "src": "/nix/store" @@ -387,7 +379,7 @@ App ], "etc": "/etc", "auto_etc": true, - "override": [ + "cover": [ "/var/run/nscd" ] }, @@ -506,7 +498,8 @@ func Test_printPs(t *testing.T) { "pid": 3735928559, "config": { "id": "org.chromium.Chromium", - "command": [ + "path": "/run/current-system/sw/bin/chromium", + "args": [ "chromium", "--ignore-gpu-blocklist", "--disable-smooth-scrolling", @@ -523,24 +516,19 @@ func Test_printPs(t *testing.T) { "home": "/var/lib/persist/home/org.chromium.Chromium", "sandbox": { "hostname": "localhost", + "seccomp": 32, + "devel": true, "userns": true, "net": true, - "dev": true, - "syscall": { - "compat": false, - "deny_devel": true, - "multiarch": true, - "linux32": false, - "can": false, - "bluetooth": false - }, - "no_new_session": true, - "map_real_uid": true, + "tty": true, + "multiarch": true, "env": { "GOOGLE_API_KEY": "AIzaSyBHDrl33hwRp4rMQY0ziRbj8K9LPA6vUCY", "GOOGLE_DEFAULT_CLIENT_ID": "77185425430.apps.googleusercontent.com", "GOOGLE_DEFAULT_CLIENT_SECRET": "OTJgUOQcT7lO7GsGZq2G4IlT" }, + "map_real_uid": true, + "dev": true, "filesystem": [ { "src": "/nix/store" @@ -573,7 +561,7 @@ func Test_printPs(t *testing.T) { ], "etc": "/etc", "auto_etc": true, - "override": [ + "cover": [ "/var/run/nscd" ] }, diff --git a/sandbox/seccomp/export_test.go b/sandbox/seccomp/export_test.go index 5b23106..761d0f8 100644 --- a/sandbox/seccomp/export_test.go +++ b/sandbox/seccomp/export_test.go @@ -93,7 +93,7 @@ func TestExport(t *testing.T) { t.Errorf("Close: error = %v", err) 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", got, tc.want) return diff --git a/sandbox/sequential.go b/sandbox/sequential.go index d696703..d072962 100644 --- a/sandbox/sequential.go +++ b/sandbox/sequential.go @@ -301,7 +301,15 @@ func (l *Symlink) apply(*Params) error { return msg.WrapErr(syscall.EBADE, fmt.Sprintf("path %q is not absolute", l[1])) } - if err := os.Symlink(l[0], toSysroot(l[1])); err != nil { + + target := toSysroot(l[1]) + if err := ensureFile(target, 0444, 0755); err != nil { + return err + } + if err := os.Remove(target); err != nil { + return msg.WrapErr(err, err.Error()) + } + if err := os.Symlink(l[0], target); err != nil { return msg.WrapErr(err, err.Error()) } return nil diff --git a/sandbox/syscall.go b/sandbox/syscall.go index dd1679f..d477dbc 100644 --- a/sandbox/syscall.go +++ b/sandbox/syscall.go @@ -22,14 +22,6 @@ func SetDumpable(dumpable uintptr) error { 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 // EINTR error. This appears to be required even though we install all // signal handlers with SA_RESTART: see #22838, #38033, #38836, #40846. diff --git a/test/sandbox/case/mapuid.nix b/test/sandbox/case/mapuid.nix index 11e1e73..634824b 100644 --- a/test/sandbox/case/mapuid.nix +++ b/test/sandbox/case/mapuid.nix @@ -12,14 +12,9 @@ fs = fs "dead" { ".fortify" = fs "800001ed" { etc = fs "800001ed" null null; - sbin = fs "800001c0" { - fortify = fs "16d" null null; - init0 = fs "80001ff" null null; - } null; } null; bin = fs "800001ed" { sh = fs "80001ff" null null; } null; dev = fs "800001ed" { - console = fs "4200190" null null; core = fs "80001ff" null null; dri = fs "800001ed" { by-path = fs "800001ed" { @@ -59,7 +54,7 @@ "fstab" = fs "80001ff" null null; "fsurc" = 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; "hostname" = fs "80001ff" null null; "hosts" = fs "80001ff" null null; @@ -84,7 +79,7 @@ "os-release" = fs "80001ff" null null; "pam" = 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; "pki" = fs "80001ff" null null; "polkit-1" = fs "80001ff" null null; @@ -185,10 +180,10 @@ } null; 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 "/" "/.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 "/zero" "/dev/zero" "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 "/tty" "/dev/tty" "rw,nosuid" "devtmpfs" "devtmpfs" ignore) (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,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 "/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") @@ -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 "/dri" "/dev/dri" "rw,nosuid" "devtmpfs" "devtmpfs" ignore) (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 "/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 ignore "/etc/passwd" "ro,nosuid,nodev,relatime" "tmpfs" "tmpfs" "rw,uid=1000003,gid=1000003") - (ent ignore "/etc/group" "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" "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/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 "/" "/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; diff --git a/test/sandbox/case/preset.nix b/test/sandbox/case/preset.nix index ecb35e2..c3ea564 100644 --- a/test/sandbox/case/preset.nix +++ b/test/sandbox/case/preset.nix @@ -12,14 +12,9 @@ fs = fs "dead" { ".fortify" = fs "800001ed" { etc = fs "800001ed" null null; - sbin = fs "800001c0" { - fortify = fs "16d" null null; - init0 = fs "80001ff" null null; - } null; } null; bin = fs "800001ed" { sh = fs "80001ff" null null; } null; dev = fs "800001ed" { - console = fs "4200190" null null; core = fs "80001ff" null null; dri = fs "800001ed" { by-path = fs "800001ed" { @@ -185,10 +180,10 @@ } null; 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 "/" "/.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 "/zero" "/dev/zero" "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 "/tty" "/dev/tty" "rw,nosuid" "devtmpfs" "devtmpfs" ignore) (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,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 "/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") @@ -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 "/dri" "/dev/dri" "rw,nosuid" "devtmpfs" "devtmpfs" ignore) (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 "/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 ignore "/etc/passwd" "ro,nosuid,nodev,relatime" "tmpfs" "tmpfs" "rw,uid=1000001,gid=1000001") - (ent ignore "/etc/group" "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" "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/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 "/" "/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; diff --git a/test/sandbox/case/tty.nix b/test/sandbox/case/tty.nix index 85320d4..1f29e1d 100644 --- a/test/sandbox/case/tty.nix +++ b/test/sandbox/case/tty.nix @@ -12,10 +12,6 @@ fs = fs "dead" { ".fortify" = fs "800001ed" { etc = fs "800001ed" null null; - sbin = fs "800001c0" { - fortify = fs "16d" null null; - init0 = fs "80001ff" null null; - } null; } null; bin = fs "800001ed" { sh = fs "80001ff" null null; } null; dev = fs "800001ed" { @@ -185,10 +181,10 @@ } null; 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 "/" "/.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 "/zero" "/dev/zero" "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 "/" "/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,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 "/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") @@ -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 "/dri" "/dev/dri" "rw,nosuid" "devtmpfs" "devtmpfs" ignore) (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 "/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 ignore "/etc/passwd" "ro,nosuid,nodev,relatime" "tmpfs" "tmpfs" "rw,uid=1000002,gid=1000002") - (ent ignore "/etc/group" "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" "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/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 "/" "/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; diff --git a/test/test.py b/test/test.py index b3f8e9e..e53d592 100644 --- a/test/test.py +++ b/test/test.py @@ -62,9 +62,12 @@ def check_state(name, enablements): config = instance['config'] - if len(config['command']) != 1 or not (config['command'][0].startswith("/nix/store/")) or not ( - config['command'][0].endswith(f"{name}-start")): - raise Exception(f"unexpected command {instance['config']['command']}") + command = f"{name}-start" + if not (config['path'].startswith("/nix/store/")) or not (config['path'].endswith(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: raise Exception(f"unexpected enablements {instance['config']['confinement']['enablements']}")