aboutsummaryrefslogtreecommitdiffhomepage
path: root/internal
diff options
context:
space:
mode:
Diffstat (limited to 'internal')
-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
}