diff options
Diffstat (limited to 'internal/sys')
| -rw-r--r-- | internal/sys/interface.go | 70 | ||||
| -rw-r--r-- | internal/sys/std.go | 107 |
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 + } +} |
