aboutsummaryrefslogtreecommitdiffhomepage
path: root/internal/sys
diff options
context:
space:
mode:
authorOphestra <cat@gensokyo.uk>2025-02-18 18:47:48 +0900
committerOphestra <cat@gensokyo.uk>2025-02-18 18:47:48 +0900
commite0f321b2c4eafba27a93fce93f444b62822c74db (patch)
tree95a9517462a53902b71ecbfa8b0ec5d50c080195 /internal/sys
parent2c9c7fee5b42d8f32be9ff55ad92345ae62c0af7 (diff)
sys: rename from linux
Signed-off-by: Ophestra <cat@gensokyo.uk>
Diffstat (limited to 'internal/sys')
-rw-r--r--internal/sys/interface.go70
-rw-r--r--internal/sys/std.go107
2 files changed, 177 insertions, 0 deletions
diff --git a/internal/sys/interface.go b/internal/sys/interface.go
new file mode 100644
index 00000000..80a07ae6
--- /dev/null
+++ b/internal/sys/interface.go
@@ -0,0 +1,70 @@
+package sys
+
+import (
+ "io/fs"
+ "os/user"
+ "path"
+ "strconv"
+
+ "git.gensokyo.uk/security/fortify/internal/fmsg"
+)
+
+// State provides safe interaction with operating system state.
+type State 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)
+ // MustExecutable provides [proc.MustExecutable].
+ MustExecutable() string
+ // LookupGroup provides [user.LookupGroup].
+ LookupGroup(name string) (*user.Group, error)
+ // ReadDir provides [os.ReadDir].
+ ReadDir(name string) ([]fs.DirEntry, error)
+ // Stat provides [os.Stat].
+ Stat(name string) (fs.FileInfo, error)
+ // Open provides [os.Open]
+ Open(name string) (fs.File, error)
+ // EvalSymlinks provides [filepath.EvalSymlinks]
+ EvalSymlinks(path string) (string, error)
+ // Exit provides [os.Exit].
+ Exit(code int)
+
+ // Paths returns a populated [Paths] struct.
+ Paths() Paths
+ // Uid invokes fsu and returns target uid.
+ // Any errors returned by Uid is already wrapped [fmsg.BaseError].
+ Uid(aid int) (int, error)
+}
+
+// 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 State, v *Paths) {
+ v.SharePath = path.Join(os.TempDir(), "fortify."+strconv.Itoa(os.Geteuid()))
+
+ fmsg.Verbosef("process share directory at %q", v.SharePath)
+
+ if r, ok := os.LookupEnv(xdgRuntimeDir); !ok || r == "" || !path.IsAbs(r) {
+ // 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.Verbosef("runtime directory at %q", v.RunDirPath)
+}
diff --git a/internal/sys/std.go b/internal/sys/std.go
new file mode 100644
index 00000000..c7228599
--- /dev/null
+++ b/internal/sys/std.go
@@ -0,0 +1,107 @@
+package sys
+
+import (
+ "errors"
+ "fmt"
+ "io/fs"
+ "log"
+ "os"
+ "os/exec"
+ "os/user"
+ "path/filepath"
+ "strconv"
+ "sync"
+ "syscall"
+
+ "git.gensokyo.uk/security/fortify/internal"
+ "git.gensokyo.uk/security/fortify/internal/fmsg"
+)
+
+// Std implements System using the standard library.
+type Std struct {
+ paths Paths
+ pathsOnce sync.Once
+
+ uidOnce sync.Once
+ uidCopy map[int]struct {
+ uid int
+ err error
+ }
+ uidMu sync.RWMutex
+}
+
+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) MustExecutable() string { return internal.MustExecutable() }
+func (s *Std) LookupGroup(name string) (*user.Group, error) { return user.LookupGroup(name) }
+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) EvalSymlinks(path string) (string, error) { return filepath.EvalSymlinks(path) }
+func (s *Std) Exit(code int) { internal.Exit(code) }
+
+const xdgRuntimeDir = "XDG_RUNTIME_DIR"
+
+func (s *Std) Paths() Paths {
+ s.pathsOnce.Do(func() { CopyPaths(s, &s.paths) })
+ return s.paths
+}
+
+func (s *Std) Uid(aid int) (int, error) {
+ s.uidOnce.Do(func() {
+ s.uidCopy = make(map[int]struct {
+ uid int
+ err error
+ })
+ })
+
+ {
+ s.uidMu.RLock()
+ u, ok := s.uidCopy[aid]
+ s.uidMu.RUnlock()
+ if ok {
+ return u.uid, u.err
+ }
+ }
+
+ s.uidMu.Lock()
+ defer s.uidMu.Unlock()
+
+ u := struct {
+ uid int
+ err error
+ }{}
+ defer func() { s.uidCopy[aid] = u }()
+
+ u.uid = -1
+ if fsu, ok := internal.Check(internal.Fsu); !ok {
+ fmsg.BeforeExit()
+ log.Fatal("invalid fsu path, this copy of fortify is not compiled correctly")
+ // unreachable
+ return 0, syscall.EBADE
+ } else {
+ cmd := exec.Command(fsu)
+ cmd.Path = fsu
+ cmd.Stderr = os.Stderr // pass through fatal messages
+ cmd.Env = []string{"FORTIFY_APP_ID=" + strconv.Itoa(aid)}
+ cmd.Dir = "/"
+ var (
+ p []byte
+ exitError *exec.ExitError
+ )
+
+ if p, u.err = cmd.Output(); u.err == nil {
+ u.uid, u.err = strconv.Atoi(string(p))
+ if u.err != nil {
+ u.err = fmsg.WrapErrorSuffix(u.err, "cannot parse uid from fsu:")
+ }
+ } else if errors.As(u.err, &exitError) && exitError != nil && exitError.ExitCode() == 1 {
+ u.err = fmsg.WrapError(syscall.EACCES, "") // fsu prints to stderr in this case
+ } else if os.IsNotExist(u.err) {
+ u.err = fmsg.WrapError(os.ErrNotExist, fmt.Sprintf("the setuid helper is missing: %s", fsu))
+ }
+ return u.uid, u.err
+ }
+}