diff options
| author | Ophestra <cat@gensokyo.uk> | 2025-03-25 01:52:49 +0900 |
|---|---|---|
| committer | Ophestra <cat@gensokyo.uk> | 2025-03-25 01:52:49 +0900 |
| commit | 5c4058d5ac7e7944973ca8216c258fc50c194e22 (patch) | |
| tree | c06597903233c75eca5a1edae8b06d93f0f81774 /cmd | |
| parent | e732dca7629edb503df0c65df5f37a99600d567d (diff) | |
app: run in native sandbox
Signed-off-by: Ophestra <cat@gensokyo.uk>
Diffstat (limited to 'cmd')
| -rw-r--r-- | cmd/fpkg/app.go (renamed from cmd/fpkg/bundle.go) | 77 | ||||
| -rw-r--r-- | cmd/fpkg/main.go | 57 | ||||
| -rw-r--r-- | cmd/fpkg/proc.go | 4 | ||||
| -rw-r--r-- | cmd/fpkg/test/test.py | 4 | ||||
| -rw-r--r-- | cmd/fpkg/with.go | 34 |
5 files changed, 94 insertions, 82 deletions
diff --git a/cmd/fpkg/bundle.go b/cmd/fpkg/app.go index d1641168..a1e5de91 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 e23ce500..268c2d48 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 ea106adf..311ae4f2 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 eaf07fc5..7d4e0d58 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 d7b0e968..d321de3f 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) |
