aboutsummaryrefslogtreecommitdiffhomepage
path: root/internal/app
diff options
context:
space:
mode:
authorOphestra Umiker <cat@ophivana.moe>2024-10-11 04:18:15 +0900
committerOphestra Umiker <cat@ophivana.moe>2024-10-11 04:18:15 +0900
commit662f2a9d2cea3f462cf5b508b3719d4d7a722146 (patch)
treeb1b1deedeeb22c3d09e600f87496cba107cfc939 /internal/app
parent3ddfd76cdf82475a3600f8e1c835bf3529ea6a31 (diff)
app: integrate bwrap into environment setup
Signed-off-by: Ophestra Umiker <cat@ophivana.moe>
Diffstat (limited to 'internal/app')
-rw-r--r--internal/app/config.go57
-rw-r--r--internal/app/launch.machinectl.go14
-rw-r--r--internal/app/seal.go66
-rw-r--r--internal/app/share.dbus.go8
-rw-r--r--internal/app/share.display.go9
-rw-r--r--internal/app/share.pulse.go6
-rw-r--r--internal/app/share.runtime.go25
-rw-r--r--internal/app/start.go3
-rw-r--r--internal/app/system.go33
9 files changed, 181 insertions, 40 deletions
diff --git a/internal/app/config.go b/internal/app/config.go
index 17203a46..550bc7c2 100644
--- a/internal/app/config.go
+++ b/internal/app/config.go
@@ -24,9 +24,7 @@ type Config struct {
// ConfinementConfig defines fortified child's confinement
type ConfinementConfig struct {
// bwrap sandbox confinement configuration
- Sandbox *bwrap.Config `json:"sandbox"`
- // mediated access to wayland socket
- Wayland bool `json:"wayland"`
+ Sandbox *SandboxConfig `json:"sandbox"`
// reference to a system D-Bus proxy configuration,
// nil value disables system bus proxy
@@ -38,3 +36,56 @@ type ConfinementConfig struct {
// child capability enablements
Enablements state.Enablements `json:"enablements"`
}
+
+// SandboxConfig describes resources made available to the sandbox.
+type SandboxConfig struct {
+ // unix hostname within sandbox
+ Hostname string `json:"hostname,omitempty"`
+ // userns availability within sandbox
+ UserNS bool `json:"userns,omitempty"`
+ // share net namespace
+ Net bool `json:"net,omitempty"`
+ // do not run in new session
+ NoNewSession bool `json:"no_new_session,omitempty"`
+ // mediated access to wayland socket
+ Wayland bool `json:"wayland,omitempty"`
+
+ UID int `json:"uid,omitempty"`
+ GID int `json:"gid,omitempty"`
+ // final environment variables
+ Env map[string]string `json:"env"`
+
+ // paths made available within the sandbox
+ Bind [][2]string `json:"bind"`
+ // paths made available read-only within the sandbox
+ ROBind [][2]string `json:"ro-bind"`
+}
+
+func (s *SandboxConfig) Bwrap() *bwrap.Config {
+ if s == nil {
+ return nil
+ }
+
+ conf := &bwrap.Config{
+ Net: s.Net,
+ UserNS: s.UserNS,
+ Hostname: s.Hostname,
+ Clearenv: true,
+ SetEnv: s.Env,
+ Bind: s.Bind,
+ ROBind: s.ROBind,
+ Procfs: []string{"/proc"},
+ DevTmpfs: []string{"/dev"},
+ Mqueue: []string{"/dev/mqueue"},
+ NewSession: !s.NoNewSession,
+ DieWithParent: true,
+ }
+ if s.UID > 0 {
+ conf.UID = &s.UID
+ }
+ if s.GID > 0 {
+ conf.GID = &s.GID
+ }
+
+ return conf
+}
diff --git a/internal/app/launch.machinectl.go b/internal/app/launch.machinectl.go
index 19a81aab..59efd553 100644
--- a/internal/app/launch.machinectl.go
+++ b/internal/app/launch.machinectl.go
@@ -9,7 +9,7 @@ import (
)
func (a *app) commandBuilderMachineCtl(shimEnv string) (args []string) {
- args = make([]string, 0, 9+len(a.seal.env))
+ args = make([]string, 0, 9+len(a.seal.sys.bwrap.SetEnv))
// shell --uid=$USER
args = append(args, "shell", "--uid="+a.seal.sys.Username)
@@ -20,12 +20,12 @@ func (a *app) commandBuilderMachineCtl(shimEnv string) (args []string) {
}
// environ
- envQ := make([]string, len(a.seal.env)+1)
- for i, e := range a.seal.env {
- envQ[i] = "-E" + e
+ envQ := make([]string, 0, len(a.seal.sys.bwrap.SetEnv)+1)
+ for k, v := range a.seal.sys.bwrap.SetEnv {
+ envQ = append(envQ, "-E"+k+"="+v)
}
// add shim payload to environment for shim path
- envQ[len(a.seal.env)] = "-E" + shimEnv
+ envQ = append(envQ, "-E"+shimEnv)
args = append(args, envQ...)
// -- .host
@@ -44,8 +44,8 @@ func (a *app) commandBuilderMachineCtl(shimEnv string) (args []string) {
// apply custom environment variables to activation environment
innerCommand.WriteString("dbus-update-activation-environment --systemd")
- for _, e := range a.seal.env {
- innerCommand.WriteString(" " + strings.SplitN(e, "=", 2)[0])
+ for k := range a.seal.sys.bwrap.SetEnv {
+ innerCommand.WriteString(" " + k)
}
innerCommand.WriteString("; ")
diff --git a/internal/app/seal.go b/internal/app/seal.go
index d325e049..0d5ee7ac 100644
--- a/internal/app/seal.go
+++ b/internal/app/seal.go
@@ -5,6 +5,7 @@ import (
"os"
"os/exec"
"os/user"
+ "path"
"strconv"
"git.ophivana.moe/cat/fortify/dbus"
@@ -63,12 +64,6 @@ func (a *app) Seal(config *Config) error {
// pass through config values
seal.fid = config.ID
seal.command = config.Command
- seal.bwrap = config.Confinement.Sandbox
-
- // create wayland client wait channel
- if config.Confinement.Wayland {
- seal.wlDone = make(chan struct{})
- }
// parses launch method text and looks up tool path
switch config.Method {
@@ -115,6 +110,65 @@ func (a *app) Seal(config *Config) error {
}
} else {
seal.sys.User = u
+ seal.sys.runtime = path.Join("/run/user", u.Uid)
+ }
+
+ // map sandbox config to bwrap
+ if config.Confinement.Sandbox == nil {
+ verbose.Println("sandbox configuration not supplied, PROCEED WITH CAUTION")
+
+ // permissive defaults
+ conf := &SandboxConfig{
+ UserNS: true,
+ Net: true,
+ NoNewSession: true,
+ }
+ // bind entries in /
+ if d, err := os.ReadDir("/"); err != nil {
+ return err
+ } else {
+ b := make([][2]string, 0, len(d))
+ for _, ent := range d {
+ name := ent.Name()
+ switch name {
+ case "proc":
+ case "dev":
+ case "run":
+ default:
+ p := "/" + name
+ b = append(b, [2]string{p, p})
+ }
+ }
+ conf.Bind = append(conf.Bind, b...)
+ }
+ // bind entries in /run
+ if d, err := os.ReadDir("/run"); err != nil {
+ return err
+ } else {
+ b := make([][2]string, 0, len(d))
+ for _, ent := range d {
+ name := ent.Name()
+ switch name {
+ case "user":
+ case "dbus":
+ default:
+ p := "/run/" + name
+ b = append(b, [2]string{p, p})
+ }
+ }
+ conf.Bind = append(conf.Bind, b...)
+ }
+ config.Confinement.Sandbox = conf
+ }
+ seal.sys.bwrap = config.Confinement.Sandbox.Bwrap()
+ if seal.sys.bwrap.SetEnv == nil {
+ seal.sys.bwrap.SetEnv = make(map[string]string)
+ }
+
+ // create wayland client wait channel if mediated wayland is enabled
+ // this channel being set enables mediated wayland setup later on
+ if config.Confinement.Sandbox.Wayland {
+ seal.wlDone = make(chan struct{})
}
// open process state store
diff --git a/internal/app/share.dbus.go b/internal/app/share.dbus.go
index 468a8afa..804dbd3f 100644
--- a/internal/app/share.dbus.go
+++ b/internal/app/share.dbus.go
@@ -63,10 +63,14 @@ func (seal *appSeal) shareDBus(config [2]*dbus.Config) error {
seal.sys.dbusAddr = &[2][2]string{sessionBus, systemBus}
// share proxy sockets
- seal.appendEnv(dbusSessionBusAddress, "unix:path="+sessionBus[1])
+ sessionInner := path.Join(seal.sys.runtime, "bus")
+ seal.sys.setEnv(dbusSessionBusAddress, "unix:path="+sessionInner)
+ seal.sys.bind(sessionBus[1], sessionInner, true)
seal.sys.updatePerm(sessionBus[1], acl.Read, acl.Write)
if seal.sys.dbusSystem {
- seal.appendEnv(dbusSystemBusAddress, "unix:path="+systemBus[1])
+ systemInner := "/run/dbus/system_bus_socket"
+ seal.sys.setEnv(dbusSystemBusAddress, "unix:path="+systemInner)
+ seal.sys.bind(systemBus[1], systemInner, true)
seal.sys.updatePerm(systemBus[1], acl.Read, acl.Write)
}
diff --git a/internal/app/share.display.go b/internal/app/share.display.go
index 01ae00a9..da9f6eff 100644
--- a/internal/app/share.display.go
+++ b/internal/app/share.display.go
@@ -27,7 +27,7 @@ type ErrDisplayEnv BaseError
func (seal *appSeal) shareDisplay() error {
// pass $TERM to launcher
if t, ok := os.LookupEnv(term); ok {
- seal.appendEnv(term, t)
+ seal.sys.setEnv(term, t)
}
// set up wayland
@@ -38,8 +38,10 @@ func (seal *appSeal) shareDisplay() error {
// hardlink wayland socket
wp := path.Join(seal.RuntimePath, wd)
wpi := path.Join(seal.shareLocal, "wayland")
+ w := path.Join(seal.sys.runtime, "wayland-0")
seal.sys.link(wp, wpi)
- seal.appendEnv(waylandDisplay, wpi)
+ seal.sys.setEnv(waylandDisplay, w)
+ seal.sys.bind(wpi, w, true)
// ensure Wayland socket ACL (e.g. `/run/user/%d/wayland-%d`)
seal.sys.updatePermTag(state.EnableWayland, wp, acl.Read, acl.Write, acl.Execute)
@@ -56,7 +58,8 @@ func (seal *appSeal) shareDisplay() error {
return (*ErrDisplayEnv)(wrapError(ErrXDisplay, "DISPLAY is not set"))
} else {
seal.sys.changeHosts(seal.sys.Username)
- seal.appendEnv(display, d)
+ seal.sys.setEnv(display, d)
+ seal.sys.bind("/tmp/.X11-unix", "/tmp/.X11-unix", true)
}
}
diff --git a/internal/app/share.pulse.go b/internal/app/share.pulse.go
index b432345f..39e6fc82 100644
--- a/internal/app/share.pulse.go
+++ b/internal/app/share.pulse.go
@@ -63,15 +63,17 @@ func (seal *appSeal) sharePulse() error {
// hard link pulse socket into target-executable share
psi := path.Join(seal.shareLocal, "pulse")
+ p := path.Join(seal.sys.runtime, "pulse", "native")
seal.sys.link(ps, psi)
- seal.appendEnv(pulseServer, "unix:"+psi)
+ seal.sys.bind(psi, p, true)
+ seal.sys.setEnv(pulseServer, "unix:"+p)
// publish current user's pulse cookie for target user
if src, err := discoverPulseCookie(); err != nil {
return err
} else {
dst := path.Join(seal.share, "pulse-cookie")
- seal.appendEnv(pulseCookie, dst)
+ seal.sys.setEnv(pulseCookie, dst)
seal.sys.copyFile(dst, src)
}
diff --git a/internal/app/share.runtime.go b/internal/app/share.runtime.go
index c25a89bc..86add794 100644
--- a/internal/app/share.runtime.go
+++ b/internal/app/share.runtime.go
@@ -5,6 +5,7 @@ import (
"path"
"git.ophivana.moe/cat/fortify/acl"
+ "git.ophivana.moe/cat/fortify/helper/bwrap"
"git.ophivana.moe/cat/fortify/internal/state"
)
@@ -20,9 +21,25 @@ const (
func (seal *appSeal) shareRuntime() {
// look up shell
if s, ok := os.LookupEnv(shell); ok {
- seal.appendEnv(shell, s)
+ seal.sys.setEnv(shell, s)
}
+ // mount tmpfs on inner runtime (e.g. `/run/user/%d`)
+ seal.sys.bwrap.Tmpfs = append(seal.sys.bwrap.Tmpfs,
+ bwrap.PermConfig[bwrap.TmpfsConfig]{
+ Path: bwrap.TmpfsConfig{
+ Size: 1 * 1024 * 1024,
+ Dir: "/run/user",
+ },
+ },
+ bwrap.PermConfig[bwrap.TmpfsConfig]{
+ Path: bwrap.TmpfsConfig{
+ Size: 8 * 1024 * 1024,
+ Dir: seal.sys.runtime,
+ },
+ },
+ )
+
// ensure RunDir (e.g. `/run/user/%d/fortify`)
seal.sys.ensure(seal.RunDirPath, 0700)
seal.sys.updatePermTag(state.EnableLength, seal.RunDirPath, acl.Execute)
@@ -57,9 +74,9 @@ func (seal *appSeal) shareRuntimeChild() string {
seal.sys.updatePermTag(state.EnableLength, targetRuntime, acl.Read, acl.Write, acl.Execute)
// point to ensured runtime path
- seal.appendEnv(xdgRuntimeDir, targetRuntime)
- seal.appendEnv(xdgSessionClass, "user")
- seal.appendEnv(xdgSessionType, "tty")
+ seal.sys.setEnv(xdgRuntimeDir, targetRuntime)
+ seal.sys.setEnv(xdgSessionClass, "user")
+ seal.sys.setEnv(xdgSessionType, "tty")
return targetRuntime
}
diff --git a/internal/app/start.go b/internal/app/start.go
index ed34b7c2..7325ee80 100644
--- a/internal/app/start.go
+++ b/internal/app/start.go
@@ -72,9 +72,8 @@ func (a *app) Start() error {
if wls, err := shim.ServeConfig(confSockPath, &shim.Payload{
Argv: a.seal.command,
- Env: a.seal.env,
Exec: e,
- Bwrap: a.seal.bwrap,
+ Bwrap: a.seal.sys.bwrap,
WL: a.seal.wlDone != nil,
Verbose: verbose.Get(),
diff --git a/internal/app/system.go b/internal/app/system.go
index b318b3a6..7fc73128 100644
--- a/internal/app/system.go
+++ b/internal/app/system.go
@@ -20,19 +20,16 @@ import (
type appSeal struct {
// application unique identifier
id *appID
- // bwrap config
- bwrap *bwrap.Config
// wayland socket path if mediated wayland is enabled
wl string
- // wait for wayland client to exit if mediated wayland is enabled
+ // wait for wayland client to exit if mediated wayland is enabled,
+ // (wlDone == nil) determines whether mediated wayland setup is performed
wlDone chan struct{}
// freedesktop application ID
fid string
// argv to start process with in the final confined environment
command []string
- // environment variables of fortified process
- env []string
// persistent process state store
store state.Store
@@ -59,13 +56,10 @@ type appSeal struct {
// protected by upstream mutex
}
-// appendEnv appends an environment variable for the child process
-func (seal *appSeal) appendEnv(k, v string) {
- seal.env = append(seal.env, k+"="+v)
-}
-
// appSealTx contains the system-level component of the app seal
type appSealTx struct {
+ bwrap *bwrap.Config
+
// reference to D-Bus proxy instance, nil if disabled
dbus *dbus.Proxy
// notification from goroutine waiting for dbus.Proxy
@@ -86,6 +80,8 @@ type appSealTx struct {
// dst, src pairs of temporarily hard linked files
hardlinks [][2]string
+ // default formatted XDG_RUNTIME_DIR of User
+ runtime string
// sealed path to fortify executable, used by shim
executable string
// target user UID as an integer
@@ -107,6 +103,20 @@ type appEnsureEntry struct {
remove bool
}
+// setEnv sets an environment variable for the child process
+func (tx *appSealTx) setEnv(k, v string) {
+ tx.bwrap.SetEnv[k] = v
+}
+
+// bind mounts a directory within the sandbox
+func (tx *appSealTx) bind(src, dest string, ro bool) {
+ if !ro {
+ tx.bwrap.Bind = append(tx.bwrap.Bind, [2]string{src, dest})
+ } else {
+ tx.bwrap.ROBind = append(tx.bwrap.ROBind, [2]string{src, dest})
+ }
+}
+
// ensure appends a directory ensure action
func (tx *appSealTx) ensure(path string, perm os.FileMode) {
tx.mkdir = append(tx.mkdir, appEnsureEntry{path, perm, false})
@@ -171,6 +181,7 @@ func (tx *appSealTx) changeHosts(username string) {
func (tx *appSealTx) copyFile(dst, src string) {
tx.tmpfiles = append(tx.tmpfiles, [2]string{dst, src})
tx.updatePerm(dst, acl.Read)
+ tx.bind(dst, dst, true)
}
// link appends a hardlink action
@@ -194,7 +205,7 @@ func (tx *appSealTx) commit() error {
}
tx.complete = true
- txp := &appSealTx{User: tx.User}
+ txp := &appSealTx{User: tx.User, bwrap: &bwrap.Config{SetEnv: make(map[string]string)}}
defer func() {
// rollback partial commit
if txp != nil {