aboutsummaryrefslogtreecommitdiffhomepage
path: root/internal
diff options
context:
space:
mode:
Diffstat (limited to 'internal')
-rw-r--r--internal/app/app.go7
-rw-r--r--internal/app/launch.machinectl.go3
-rw-r--r--internal/app/launch.sudo.go4
-rw-r--r--internal/app/seal.go26
-rw-r--r--internal/app/share.display.go4
-rw-r--r--internal/app/share.pulse.go8
-rw-r--r--internal/app/share.runtime.go1
-rw-r--r--internal/app/share.system.go4
-rw-r--r--internal/app/system.go9
-rw-r--r--internal/environ.go58
-rw-r--r--internal/system.go120
11 files changed, 154 insertions, 90 deletions
diff --git a/internal/app/app.go b/internal/app/app.go
index 0055dd76..de2c461f 100644
--- a/internal/app/app.go
+++ b/internal/app/app.go
@@ -3,6 +3,8 @@ package app
import (
"os/exec"
"sync"
+
+ "git.ophivana.moe/security/fortify/internal"
)
type App interface {
@@ -22,6 +24,8 @@ type App interface {
type app struct {
// application unique identifier
id *ID
+ // operating system interface
+ os internal.System
// underlying user switcher process
cmd *exec.Cmd
// shim setup abort reason and completion
@@ -61,8 +65,9 @@ func (a *app) WaitErr() error {
return a.waitErr
}
-func New() (App, error) {
+func New(os internal.System) (App, error) {
a := new(app)
a.id = new(ID)
+ a.os = os
return a, newAppID(a.id)
}
diff --git a/internal/app/launch.machinectl.go b/internal/app/launch.machinectl.go
index 32f06740..c1d26101 100644
--- a/internal/app/launch.machinectl.go
+++ b/internal/app/launch.machinectl.go
@@ -1,7 +1,6 @@
package app
import (
- "os/exec"
"strings"
"git.ophivana.moe/security/fortify/internal/fmsg"
@@ -31,7 +30,7 @@ func (a *app) commandBuilderMachineCtl(shimEnv string) (args []string) {
args = append(args, "--", ".host")
// /bin/sh -c
- if sh, err := exec.LookPath("sh"); err != nil {
+ if sh, err := a.os.LookPath("sh"); err != nil {
// hardcode /bin/sh path since it exists more often than not
args = append(args, "/bin/sh", "-c")
} else {
diff --git a/internal/app/launch.sudo.go b/internal/app/launch.sudo.go
index 2df7be0f..e7a20672 100644
--- a/internal/app/launch.sudo.go
+++ b/internal/app/launch.sudo.go
@@ -1,8 +1,6 @@
package app
import (
- "os"
-
"git.ophivana.moe/security/fortify/internal/fmsg"
)
@@ -17,7 +15,7 @@ func (a *app) commandBuilderSudo(shimEnv string) (args []string) {
args = append(args, "-Hiu", a.seal.sys.user.Username)
// -A?
- if _, ok := os.LookupEnv(sudoAskPass); ok {
+ if _, ok := a.os.LookupEnv(sudoAskPass); ok {
fmsg.VPrintln(sudoAskPass, "set, adding askpass flag")
args = append(args, "-A")
}
diff --git a/internal/app/seal.go b/internal/app/seal.go
index f0fe5582..45010607 100644
--- a/internal/app/seal.go
+++ b/internal/app/seal.go
@@ -2,8 +2,7 @@ package app
import (
"errors"
- "os"
- "os/exec"
+ "io/fs"
"os/user"
"path"
"strconv"
@@ -67,8 +66,7 @@ type appSeal struct {
// seal system-level component
sys *appSealSys
- // used in various sealing operations
- internal.SystemConstants
+ internal.Paths
// protected by upstream mutex
}
@@ -91,7 +89,7 @@ func (a *app) Seal(config *Config) error {
seal := new(appSeal)
// fetch system constants
- seal.SystemConstants = internal.GetSC()
+ seal.Paths = a.os.Paths()
// pass through config values
seal.id = a.id.String()
@@ -102,7 +100,7 @@ func (a *app) Seal(config *Config) error {
switch config.Method {
case method[LaunchMethodSudo]:
seal.launchOption = LaunchMethodSudo
- if sudoPath, err := exec.LookPath("sudo"); err != nil {
+ if sudoPath, err := a.os.LookPath("sudo"); err != nil {
return fmsg.WrapError(ErrSudo,
"sudo not found")
} else {
@@ -115,7 +113,7 @@ func (a *app) Seal(config *Config) error {
"system has not been booted with systemd as init system")
}
- if machineCtlPath, err := exec.LookPath("machinectl"); err != nil {
+ if machineCtlPath, err := a.os.LookPath("machinectl"); err != nil {
return fmsg.WrapError(ErrMachineCtl,
"machinectl not found")
} else {
@@ -130,14 +128,14 @@ func (a *app) Seal(config *Config) error {
seal.sys = new(appSealSys)
// look up fortify executable path
- if p, err := os.Executable(); err != nil {
+ if p, err := a.os.Executable(); err != nil {
return fmsg.WrapErrorSuffix(err, "cannot look up fortify executable path:")
} else {
seal.sys.executable = p
}
// look up user from system
- if u, err := user.Lookup(config.User); err != nil {
+ if u, err := a.os.Lookup(config.User); err != nil {
if errors.As(err, new(user.UnknownUserError)) {
return fmsg.WrapError(ErrUser, "unknown user", config.User)
} else {
@@ -160,7 +158,7 @@ func (a *app) Seal(config *Config) error {
NoNewSession: true,
}
// bind entries in /
- if d, err := os.ReadDir("/"); err != nil {
+ if d, err := a.os.ReadDir("/"); err != nil {
return err
} else {
b := make([]*FilesystemConfig, 0, len(d))
@@ -180,7 +178,7 @@ func (a *app) Seal(config *Config) error {
conf.Filesystem = append(conf.Filesystem, b...)
}
// bind entries in /run
- if d, err := os.ReadDir("/run"); err != nil {
+ if d, err := a.os.ReadDir("/run"); err != nil {
return err
} else {
b := make([]*FilesystemConfig, 0, len(d))
@@ -198,7 +196,7 @@ func (a *app) Seal(config *Config) error {
}
// hide nscd from sandbox if present
nscd := "/var/run/nscd"
- if _, err := os.Stat(nscd); !errors.Is(err, os.ErrNotExist) {
+ if _, err := a.os.Stat(nscd); !errors.Is(err, fs.ErrNotExist) {
conf.Override = append(conf.Override, nscd)
}
// bind GPU stuff
@@ -222,7 +220,7 @@ func (a *app) Seal(config *Config) error {
// open process state store
// the simple store only starts holding an open file after first action
// store activity begins after Start is called and must end before Wait
- seal.store = state.NewSimple(seal.SystemConstants.RunDirPath, seal.sys.user.Uid)
+ seal.store = state.NewSimple(seal.RunDirPath, seal.sys.user.Uid)
// parse string UID
if u, err := strconv.Atoi(seal.sys.user.Uid); err != nil {
@@ -236,7 +234,7 @@ func (a *app) Seal(config *Config) error {
seal.et = config.Confinement.Enablements
// this method calls all share methods in sequence
- if err := seal.shareAll([2]*dbus.Config{config.Confinement.SessionBus, config.Confinement.SystemBus}); err != nil {
+ if err := seal.shareAll([2]*dbus.Config{config.Confinement.SessionBus, config.Confinement.SystemBus}, a.os); err != nil {
return err
}
diff --git a/internal/app/share.display.go b/internal/app/share.display.go
index 18fad7a7..95132119 100644
--- a/internal/app/share.display.go
+++ b/internal/app/share.display.go
@@ -2,10 +2,10 @@ package app
import (
"errors"
- "os"
"path"
"git.ophivana.moe/security/fortify/acl"
+ "git.ophivana.moe/security/fortify/internal"
"git.ophivana.moe/security/fortify/internal/fmsg"
"git.ophivana.moe/security/fortify/internal/system"
)
@@ -23,7 +23,7 @@ var (
ErrXDisplay = errors.New(display + " unset")
)
-func (seal *appSeal) shareDisplay() error {
+func (seal *appSeal) shareDisplay(os internal.System) error {
// pass $TERM to launcher
if t, ok := os.LookupEnv(term); ok {
seal.sys.bwrap.SetEnv[term] = t
diff --git a/internal/app/share.pulse.go b/internal/app/share.pulse.go
index 83c16fde..fa5c0a7a 100644
--- a/internal/app/share.pulse.go
+++ b/internal/app/share.pulse.go
@@ -4,9 +4,9 @@ import (
"errors"
"fmt"
"io/fs"
- "os"
"path"
+ "git.ophivana.moe/security/fortify/internal"
"git.ophivana.moe/security/fortify/internal/fmsg"
"git.ophivana.moe/security/fortify/internal/system"
)
@@ -25,7 +25,7 @@ var (
ErrPulseMode = errors.New("unexpected pulse socket mode")
)
-func (seal *appSeal) sharePulse() error {
+func (seal *appSeal) sharePulse(os internal.System) error {
if !seal.et.Has(system.EPulse) {
return nil
}
@@ -65,7 +65,7 @@ func (seal *appSeal) sharePulse() error {
seal.sys.bwrap.SetEnv[pulseServer] = "unix:" + p
// publish current user's pulse cookie for target user
- if src, err := discoverPulseCookie(); err != nil {
+ if src, err := discoverPulseCookie(os); err != nil {
return err
} else {
dst := path.Join(seal.share, "pulse-cookie")
@@ -78,7 +78,7 @@ func (seal *appSeal) sharePulse() error {
}
// discoverPulseCookie attempts various standard methods to discover the current user's PulseAudio authentication cookie
-func discoverPulseCookie() (string, error) {
+func discoverPulseCookie(os internal.System) (string, error) {
if p, ok := os.LookupEnv(pulseCookie); ok {
return p, nil
}
diff --git a/internal/app/share.runtime.go b/internal/app/share.runtime.go
index 1c65aa77..dd3a6b13 100644
--- a/internal/app/share.runtime.go
+++ b/internal/app/share.runtime.go
@@ -29,6 +29,7 @@ func (seal *appSeal) shareRuntime() {
seal.sys.UpdatePermType(system.User, seal.RunDirPath, acl.Execute)
// ensure runtime directory ACL (e.g. `/run/user/%d`)
+ seal.sys.Ensure(seal.RuntimePath, 0700) // ensure this dir in case XDG_RUNTIME_DIR is unset
seal.sys.UpdatePermType(system.User, seal.RuntimePath, acl.Execute)
// ensure process-specific share local to XDG_RUNTIME_DIR (e.g. `/run/user/%d/fortify/%s`)
diff --git a/internal/app/share.system.go b/internal/app/share.system.go
index b8c5f063..600119ee 100644
--- a/internal/app/share.system.go
+++ b/internal/app/share.system.go
@@ -1,10 +1,10 @@
package app
import (
- "os"
"path"
"git.ophivana.moe/security/fortify/acl"
+ "git.ophivana.moe/security/fortify/internal"
"git.ophivana.moe/security/fortify/internal/system"
)
@@ -38,7 +38,7 @@ func (seal *appSeal) shareSystem() {
seal.sys.bwrap.Tmpfs(seal.SharePath, 1*1024*1024)
}
-func (seal *appSeal) sharePasswd() {
+func (seal *appSeal) sharePasswd(os internal.System) {
// look up shell
sh := "/bin/sh"
if s, ok := os.LookupEnv(shell); ok {
diff --git a/internal/app/system.go b/internal/app/system.go
index 2fc413e7..7ccc2eeb 100644
--- a/internal/app/system.go
+++ b/internal/app/system.go
@@ -5,6 +5,7 @@ import (
"git.ophivana.moe/security/fortify/dbus"
"git.ophivana.moe/security/fortify/helper/bwrap"
+ "git.ophivana.moe/security/fortify/internal"
"git.ophivana.moe/security/fortify/internal/system"
)
@@ -27,7 +28,7 @@ type appSealSys struct {
}
// shareAll calls all share methods in sequence
-func (seal *appSeal) shareAll(bus [2]*dbus.Config) error {
+func (seal *appSeal) shareAll(bus [2]*dbus.Config, os internal.System) error {
if seal.shared {
panic("seal shared twice")
}
@@ -35,11 +36,11 @@ func (seal *appSeal) shareAll(bus [2]*dbus.Config) error {
seal.shareSystem()
seal.shareRuntime()
- seal.sharePasswd()
- if err := seal.shareDisplay(); err != nil {
+ seal.sharePasswd(os)
+ if err := seal.shareDisplay(os); err != nil {
return err
}
- if err := seal.sharePulse(); err != nil {
+ if err := seal.sharePulse(os); err != nil {
return err
}
diff --git a/internal/environ.go b/internal/environ.go
deleted file mode 100644
index 8f03cb34..00000000
--- a/internal/environ.go
+++ /dev/null
@@ -1,58 +0,0 @@
-package internal
-
-import (
- "os"
- "path"
- "strconv"
- "sync"
-
- "git.ophivana.moe/security/fortify/internal/fmsg"
-)
-
-// state that remain constant for the lifetime of the process
-// fetched and cached here
-
-const (
- xdgRuntimeDir = "XDG_RUNTIME_DIR"
-)
-
-// SystemConstants contains state from the operating system
-type SystemConstants struct {
- // path to shared directory e.g. /tmp/fortify.%d
- SharePath string `json:"share_path"`
- // XDG_RUNTIME_DIR value e.g. /run/user/%d
- RuntimePath string `json:"runtime_path"`
- // application runtime directory e.g. /run/user/%d/fortify
- RunDirPath string `json:"run_dir_path"`
-}
-
-var (
- scVal SystemConstants
- scOnce sync.Once
-)
-
-func copySC() {
- sc := SystemConstants{
- SharePath: path.Join(os.TempDir(), "fortify."+strconv.Itoa(os.Geteuid())),
- }
-
- fmsg.VPrintf("process share directory at %q", sc.SharePath)
-
- // runtimePath, runDirPath
- if r, ok := os.LookupEnv(xdgRuntimeDir); !ok {
- fmsg.Println("variable", xdgRuntimeDir, "unset")
- os.Exit(1)
- } else {
- sc.RuntimePath = r
- sc.RunDirPath = path.Join(sc.RuntimePath, "fortify")
- fmsg.VPrintf("XDG runtime directory at %q", sc.RunDirPath)
- }
-
- scVal = sc
-}
-
-// GetSC returns a populated SystemConstants value
-func GetSC() SystemConstants {
- scOnce.Do(copySC)
- return scVal
-}
diff --git a/internal/system.go b/internal/system.go
new file mode 100644
index 00000000..6467ea05
--- /dev/null
+++ b/internal/system.go
@@ -0,0 +1,120 @@
+package internal
+
+import (
+ "io/fs"
+ "os"
+ "os/exec"
+ "os/user"
+ "path"
+ "strconv"
+ "sync"
+
+ "git.ophivana.moe/security/fortify/internal/fmsg"
+)
+
+// System provides safe access to operating system resources.
+type System interface {
+ // Geteuid provides [os.Geteuid].
+ Geteuid() int
+ // LookupEnv provides [os.LookupEnv].
+ LookupEnv(key string) (string, bool)
+ // TempDir provides [os.TempDir].
+ TempDir() string
+ // LookPath provides [exec.LookPath].
+ LookPath(file string) (string, error)
+ // Executable provides [os.Executable].
+ Executable() (string, error)
+ // Lookup provides [user.Lookup].
+ Lookup(username string) (*user.User, error)
+ // ReadDir provides [os.ReadDir].
+ ReadDir(name string) ([]os.DirEntry, error)
+ // Stat provides [os.Stat].
+ Stat(name string) (fs.FileInfo, error)
+ // Open provides [os.Open]
+ Open(name string) (fs.File, error)
+ // Exit provides [os.Exit].
+ Exit(code int)
+
+ // Paths returns a populated [Paths] struct.
+ Paths() Paths
+}
+
+// Paths contains environment dependent paths used by fortify.
+type Paths struct {
+ // path to shared directory e.g. /tmp/fortify.%d
+ SharePath string `json:"share_path"`
+ // XDG_RUNTIME_DIR value e.g. /run/user/%d
+ RuntimePath string `json:"runtime_path"`
+ // application runtime directory e.g. /run/user/%d/fortify
+ RunDirPath string `json:"run_dir_path"`
+}
+
+// CopyPaths is a generic implementation of [System.Paths].
+func CopyPaths(os System, v *Paths) {
+ v.SharePath = path.Join(os.TempDir(), "fortify."+strconv.Itoa(os.Geteuid()))
+
+ fmsg.VPrintf("process share directory at %q", v.SharePath)
+
+ if r, ok := os.LookupEnv(xdgRuntimeDir); !ok {
+ // fall back to path in share since fortify has no hard XDG dependency
+ v.RunDirPath = path.Join(v.SharePath, "run")
+ v.RuntimePath = path.Join(v.RunDirPath, "compat")
+ } else {
+ v.RuntimePath = r
+ v.RunDirPath = path.Join(v.RuntimePath, "fortify")
+ }
+
+ fmsg.VPrintf("runtime directory at %q", v.RunDirPath)
+}
+
+// Std implements System using the standard library.
+type Std struct {
+ paths Paths
+ pathsOnce sync.Once
+}
+
+func (s *Std) Geteuid() int {
+ return os.Geteuid()
+}
+
+func (s *Std) LookupEnv(key string) (string, bool) {
+ return os.LookupEnv(key)
+}
+
+func (s *Std) TempDir() string {
+ return os.TempDir()
+}
+
+func (s *Std) LookPath(file string) (string, error) {
+ return exec.LookPath(file)
+}
+
+func (s *Std) Executable() (string, error) {
+ return os.Executable()
+}
+
+func (s *Std) Lookup(username string) (*user.User, error) {
+ return user.Lookup(username)
+}
+
+func (s *Std) ReadDir(name string) ([]os.DirEntry, error) {
+ return os.ReadDir(name)
+}
+
+func (s *Std) Stat(name string) (fs.FileInfo, error) {
+ return os.Stat(name)
+}
+
+func (s *Std) Open(name string) (fs.File, error) {
+ return os.Open(name)
+}
+func (s *Std) Exit(code int) {
+ os.Exit(code)
+}
+
+const xdgRuntimeDir = "XDG_RUNTIME_DIR"
+
+func (s *Std) Paths() Paths {
+ s.pathsOnce.Do(func() { CopyPaths(s, &s.paths) })
+ return s.paths
+}