aboutsummaryrefslogtreecommitdiffhomepage
path: root/internal/app
diff options
context:
space:
mode:
authorOphestra Umiker <cat@ophivana.moe>2024-10-11 02:01:03 +0900
committerOphestra Umiker <cat@ophivana.moe>2024-10-11 02:01:03 +0900
commit6220f7e197f01846f5e4e3090acf9251f3ae6ade (patch)
treeb6282fefd49f321ca341c61019b144b2ea4260e5 /internal/app
parentb86fa6b4c91a2bf331ae4a50a5aa14b668efc4e6 (diff)
app: migrate to new shim implementation
Both machinectl and sudo launch methods launch shim as shim is now responsible for setting up the sandbox. Various app structures are adapted to accommodate bwrap configuration and mediated wayland access. Signed-off-by: Ophestra Umiker <cat@ophivana.moe>
Diffstat (limited to 'internal/app')
-rw-r--r--internal/app/app.go3
-rw-r--r--internal/app/config.go6
-rw-r--r--internal/app/launch.machinectl.go4
-rw-r--r--internal/app/launch.sudo.go11
-rw-r--r--internal/app/seal.go6
-rw-r--r--internal/app/share.display.go5
-rw-r--r--internal/app/share.runtime.go8
-rw-r--r--internal/app/shim.go83
-rw-r--r--internal/app/start.go68
-rw-r--r--internal/app/system.go17
10 files changed, 103 insertions, 108 deletions
diff --git a/internal/app/app.go b/internal/app/app.go
index 9d1165f4..50d59da9 100644
--- a/internal/app/app.go
+++ b/internal/app/app.go
@@ -1,6 +1,7 @@
package app
import (
+ "net"
"os/exec"
"sync"
)
@@ -18,6 +19,8 @@ type app struct {
seal *appSeal
// underlying fortified child process
cmd *exec.Cmd
+ // wayland connection if wayland mediation is enabled
+ wayland *net.UnixConn
// error returned waiting for process
wait error
diff --git a/internal/app/config.go b/internal/app/config.go
index d9648740..17203a46 100644
--- a/internal/app/config.go
+++ b/internal/app/config.go
@@ -2,6 +2,7 @@ package app
import (
"git.ophivana.moe/cat/fortify/dbus"
+ "git.ophivana.moe/cat/fortify/helper/bwrap"
"git.ophivana.moe/cat/fortify/internal/state"
)
@@ -22,6 +23,11 @@ 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"`
+
// reference to a system D-Bus proxy configuration,
// nil value disables system bus proxy
SystemBus *dbus.Config `json:"system_bus,omitempty"`
diff --git a/internal/app/launch.machinectl.go b/internal/app/launch.machinectl.go
index 21b9d40b..19a81aab 100644
--- a/internal/app/launch.machinectl.go
+++ b/internal/app/launch.machinectl.go
@@ -8,7 +8,7 @@ import (
"git.ophivana.moe/cat/fortify/internal/verbose"
)
-func (a *app) commandBuilderMachineCtl() (args []string) {
+func (a *app) commandBuilderMachineCtl(shimEnv string) (args []string) {
args = make([]string, 0, 9+len(a.seal.env))
// shell --uid=$USER
@@ -25,7 +25,7 @@ func (a *app) commandBuilderMachineCtl() (args []string) {
envQ[i] = "-E" + e
}
// add shim payload to environment for shim path
- envQ[len(a.seal.env)] = "-E" + a.shimPayloadEnv()
+ envQ[len(a.seal.env)] = "-E" + shimEnv
args = append(args, envQ...)
// -- .host
diff --git a/internal/app/launch.sudo.go b/internal/app/launch.sudo.go
index 666d0b3a..777cb2a7 100644
--- a/internal/app/launch.sudo.go
+++ b/internal/app/launch.sudo.go
@@ -10,8 +10,8 @@ const (
sudoAskPass = "SUDO_ASKPASS"
)
-func (a *app) commandBuilderSudo() (args []string) {
- args = make([]string, 0, 4+len(a.seal.env)+len(a.seal.command))
+func (a *app) commandBuilderSudo(shimEnv string) (args []string) {
+ args = make([]string, 0, 8)
// -Hiu $USER
args = append(args, "-Hiu", a.seal.sys.Username)
@@ -22,12 +22,11 @@ func (a *app) commandBuilderSudo() (args []string) {
args = append(args, "-A")
}
- // environ
- args = append(args, a.seal.env...)
+ // shim payload
+ args = append(args, shimEnv)
// -- $@
- args = append(args, "--")
- args = append(args, a.seal.command...)
+ args = append(args, "--", a.seal.sys.executable, "-V", "--license") // magic for shim.Try()
return
}
diff --git a/internal/app/seal.go b/internal/app/seal.go
index ffbd7488..d325e049 100644
--- a/internal/app/seal.go
+++ b/internal/app/seal.go
@@ -63,6 +63,12 @@ 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 {
diff --git a/internal/app/share.display.go b/internal/app/share.display.go
index 51ac54d3..01ae00a9 100644
--- a/internal/app/share.display.go
+++ b/internal/app/share.display.go
@@ -34,7 +34,7 @@ func (seal *appSeal) shareDisplay() error {
if seal.et.Has(state.EnableWayland) {
if wd, ok := os.LookupEnv(waylandDisplay); !ok {
return (*ErrDisplayEnv)(wrapError(ErrWayland, "WAYLAND_DISPLAY is not set"))
- } else {
+ } else if seal.wlDone == nil {
// hardlink wayland socket
wp := path.Join(seal.RuntimePath, wd)
wpi := path.Join(seal.shareLocal, "wayland")
@@ -43,6 +43,9 @@ func (seal *appSeal) shareDisplay() error {
// ensure Wayland socket ACL (e.g. `/run/user/%d/wayland-%d`)
seal.sys.updatePermTag(state.EnableWayland, wp, acl.Read, acl.Write, acl.Execute)
+ } else {
+ // set wayland socket path (e.g. `/run/user/%d/wayland-%d`)
+ seal.wl = path.Join(seal.RuntimePath, wd)
}
}
diff --git a/internal/app/share.runtime.go b/internal/app/share.runtime.go
index b015d45c..c25a89bc 100644
--- a/internal/app/share.runtime.go
+++ b/internal/app/share.runtime.go
@@ -1,6 +1,7 @@
package app
import (
+ "os"
"path"
"git.ophivana.moe/cat/fortify/acl"
@@ -11,10 +12,17 @@ const (
xdgRuntimeDir = "XDG_RUNTIME_DIR"
xdgSessionClass = "XDG_SESSION_CLASS"
xdgSessionType = "XDG_SESSION_TYPE"
+
+ shell = "SHELL"
)
// shareRuntime queues actions for sharing/ensuring the runtime and share directories
func (seal *appSeal) shareRuntime() {
+ // look up shell
+ if s, ok := os.LookupEnv(shell); ok {
+ seal.appendEnv(shell, s)
+ }
+
// ensure RunDir (e.g. `/run/user/%d/fortify`)
seal.sys.ensure(seal.RunDirPath, 0700)
seal.sys.updatePermTag(state.EnableLength, seal.RunDirPath, acl.Execute)
diff --git a/internal/app/shim.go b/internal/app/shim.go
deleted file mode 100644
index 00f711a1..00000000
--- a/internal/app/shim.go
+++ /dev/null
@@ -1,83 +0,0 @@
-package app
-
-import (
- "bytes"
- "encoding/base64"
- "encoding/gob"
- "fmt"
- "os"
- "os/exec"
- "strings"
- "syscall"
-)
-
-const shimPayload = "FORTIFY_SHIM_PAYLOAD"
-
-func (a *app) shimPayloadEnv() string {
- r := &bytes.Buffer{}
- enc := base64.NewEncoder(base64.StdEncoding, r)
-
- if err := gob.NewEncoder(enc).Encode(a.seal.command); err != nil {
- // should be unreachable
- panic(err)
- }
-
- _ = enc.Close()
- return shimPayload + "=" + r.String()
-}
-
-// TryShim attempts the early hidden launcher shim path
-func TryShim() {
- // environment variable contains encoded argv
- if r, ok := os.LookupEnv(shimPayload); ok {
- // everything beyond this point runs as target user
- // proceed with caution!
-
- // parse base64 revealing underlying gob stream
- dec := base64.NewDecoder(base64.StdEncoding, strings.NewReader(r))
-
- // decode argv gob stream
- var argv []string
- if err := gob.NewDecoder(dec).Decode(&argv); err != nil {
- fmt.Println("fortify-shim: cannot decode shim payload:", err)
- os.Exit(1)
- }
-
- // remove payload variable since the child does not need to see it
- if err := os.Unsetenv(shimPayload); err != nil {
- fmt.Println("fortify-shim: cannot unset shim payload:", err)
- // not fatal, do not fail
- }
-
- // look up argv0
- var argv0 string
-
- if len(argv) > 0 {
- // look up program from $PATH
- if p, err := exec.LookPath(argv[0]); err != nil {
- fmt.Printf("%s not found: %s\n", argv[0], err)
- os.Exit(1)
- } else {
- argv0 = p
- }
- } else {
- // no argv, look up shell instead
- if argv0, ok = os.LookupEnv("SHELL"); !ok {
- fmt.Println("fortify-shim: no command was specified and $SHELL was unset")
- os.Exit(1)
- }
-
- argv = []string{argv0}
- }
-
- // exec target process
- if err := syscall.Exec(argv0, argv, os.Environ()); err != nil {
- fmt.Println("fortify-shim: cannot execute shim payload:", err)
- os.Exit(1)
- }
-
- // unreachable
- os.Exit(1)
- return
- }
-}
diff --git a/internal/app/start.go b/internal/app/start.go
index 02fad973..ed34b7c2 100644
--- a/internal/app/start.go
+++ b/internal/app/start.go
@@ -2,11 +2,16 @@ package app
import (
"errors"
+ "fmt"
"os"
"os/exec"
+ "path"
+ "path/filepath"
"strconv"
"time"
+ "git.ophivana.moe/cat/fortify/helper"
+ "git.ophivana.moe/cat/fortify/internal/shim"
"git.ophivana.moe/cat/fortify/internal/state"
"git.ophivana.moe/cat/fortify/internal/verbose"
)
@@ -14,6 +19,8 @@ import (
type (
// ProcessError encapsulates errors returned by starting *exec.Cmd
ProcessError BaseError
+ // ShimError encapsulates errors returned by shim.ServeConfig.
+ ShimError BaseError
)
// Start starts the fortified child
@@ -21,12 +28,30 @@ func (a *app) Start() error {
a.lock.Lock()
defer a.lock.Unlock()
+ // resolve exec paths
+ e := [2]string{helper.BubblewrapName}
+ if len(a.seal.command) > 0 {
+ e[1] = a.seal.command[0]
+ }
+ for i, n := range e {
+ if len(n) == 0 {
+ continue
+ }
+ if filepath.Base(n) == n {
+ if s, err := exec.LookPath(n); err == nil {
+ e[i] = s
+ } else {
+ return (*ProcessError)(wrapError(err, fmt.Sprintf("cannot find %q in PATH: %v", n, err)))
+ }
+ }
+ }
+
if err := a.seal.sys.commit(); err != nil {
return err
}
// select command builder
- var commandBuilder func() (args []string)
+ var commandBuilder func(shimEnv string) (args []string)
switch a.seal.launchOption {
case LaunchMethodSudo:
commandBuilder = a.commandBuilderSudo
@@ -37,15 +62,30 @@ func (a *app) Start() error {
}
// configure child process
- a.cmd = exec.Command(a.seal.toolPath, commandBuilder()...)
+ confSockPath := path.Join(a.seal.share, "shim")
+ a.cmd = exec.Command(a.seal.toolPath, commandBuilder(shim.EnvShim+"="+confSockPath)...)
a.cmd.Env = []string{}
a.cmd.Stdin = os.Stdin
a.cmd.Stdout = os.Stdout
a.cmd.Stderr = os.Stderr
a.cmd.Dir = a.seal.RunDirPath
- // start child process
- verbose.Println("starting main process:", a.cmd)
+ if wls, err := shim.ServeConfig(confSockPath, &shim.Payload{
+ Argv: a.seal.command,
+ Env: a.seal.env,
+ Exec: e,
+ Bwrap: a.seal.bwrap,
+ WL: a.seal.wlDone != nil,
+
+ Verbose: verbose.Get(),
+ }, a.seal.wl, a.seal.wlDone); err != nil {
+ return (*ShimError)(wrapError(err, "cannot listen on shim socket:", err))
+ } else {
+ a.wayland = wls
+ }
+
+ // start shim
+ verbose.Println("starting shim as target user:", a.cmd)
if err := a.cmd.Start(); err != nil {
return (*ProcessError)(wrapError(err, "cannot start process:", err))
}
@@ -62,11 +102,11 @@ func (a *app) Start() error {
}
// register process state
- var e = new(StateStoreError)
- e.Inner, e.DoErr = a.seal.store.Do(func(b state.Backend) {
- e.InnerErr = b.Save(&sd)
+ var err = new(StateStoreError)
+ err.Inner, err.DoErr = a.seal.store.Do(func(b state.Backend) {
+ err.InnerErr = b.Save(&sd)
})
- return e.equiv("cannot save process state:", e)
+ return err.equiv("cannot save process state:", e)
}
// StateStoreError is returned for a failed state save
@@ -146,6 +186,14 @@ func (a *app) Wait() (int, error) {
verbose.Println("process", strconv.Itoa(a.cmd.Process.Pid), "exited with exit code", r)
+ // close wayland connection
+ if a.wayland != nil {
+ close(a.seal.wlDone)
+ if err := a.wayland.Close(); err != nil {
+ fmt.Println("fortify: cannot close wayland connection:", err)
+ }
+ }
+
// update store and revert app setup transaction
e := new(StateStoreError)
e.Inner, e.DoErr = a.seal.store.Do(func(b state.Backend) {
@@ -187,7 +235,9 @@ func (a *app) Wait() (int, error) {
ct = append(ct, i)
}
}
- verbose.Println("will revert operations tagged", ct, "as no remaining launchers hold these enablements")
+ if len(ct) > 0 {
+ verbose.Println("will revert operations tagged", ct, "as no remaining launchers hold these enablements")
+ }
}
if err := a.seal.sys.revert(tags); err != nil {
diff --git a/internal/app/system.go b/internal/app/system.go
index aa62cfb0..b318b3a6 100644
--- a/internal/app/system.go
+++ b/internal/app/system.go
@@ -9,6 +9,7 @@ import (
"git.ophivana.moe/cat/fortify/acl"
"git.ophivana.moe/cat/fortify/dbus"
+ "git.ophivana.moe/cat/fortify/helper/bwrap"
"git.ophivana.moe/cat/fortify/internal"
"git.ophivana.moe/cat/fortify/internal/state"
"git.ophivana.moe/cat/fortify/internal/verbose"
@@ -19,6 +20,12 @@ 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
+ wlDone chan struct{}
// freedesktop application ID
fid string
@@ -187,7 +194,7 @@ func (tx *appSealTx) commit() error {
}
tx.complete = true
- txp := &appSealTx{}
+ txp := &appSealTx{User: tx.User}
defer func() {
// rollback partial commit
if txp != nil {
@@ -371,6 +378,8 @@ func (seal *appSeal) shareAll(bus [2]*dbus.Config) error {
seal.shared = true
seal.shareRuntime()
+ targetRuntime := seal.shareRuntimeChild()
+ verbose.Printf("child runtime data dir '%s' configured\n", targetRuntime)
if err := seal.shareDisplay(); err != nil {
return err
}
@@ -393,11 +402,5 @@ func (seal *appSeal) shareAll(bus [2]*dbus.Config) error {
verbose.Println("message bus proxy final args:", seal.sys.dbus)
}
- // workaround for launch method sudo
- if seal.launchOption == LaunchMethodSudo {
- targetRuntime := seal.shareRuntimeChild()
- verbose.Printf("child runtime data dir '%s' configured\n", targetRuntime)
- }
-
return nil
}