70
internal/sys/interface.go
Normal file
70
internal/sys/interface.go
Normal file
@@ -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)
|
||||
}
|
||||
107
internal/sys/std.go
Normal file
107
internal/sys/std.go
Normal file
@@ -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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user