aboutsummaryrefslogtreecommitdiffhomepage
path: root/cmd
diff options
context:
space:
mode:
authorOphestra <cat@gensokyo.uk>2025-03-25 01:52:49 +0900
committerOphestra <cat@gensokyo.uk>2025-03-25 01:52:49 +0900
commit5c4058d5ac7e7944973ca8216c258fc50c194e22 (patch)
treec06597903233c75eca5a1edae8b06d93f0f81774 /cmd
parente732dca7629edb503df0c65df5f37a99600d567d (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.go57
-rw-r--r--cmd/fpkg/proc.go4
-rw-r--r--cmd/fpkg/test/test.py4
-rw-r--r--cmd/fpkg/with.go34
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)