From e0f321b2c4eafba27a93fce93f444b62822c74db Mon Sep 17 00:00:00 2001 From: Ophestra Date: Tue, 18 Feb 2025 18:47:48 +0900 Subject: sys: rename from linux Signed-off-by: Ophestra --- fst/sandbox.go | 6 +-- internal/app/app.go | 6 +-- internal/app/app_stub_test.go | 6 +-- internal/app/app_test.go | 4 +- internal/app/export_test.go | 4 +- internal/app/seal.go | 4 +- internal/app/share.go | 6 +-- internal/linux/interface.go | 70 --------------------------- internal/linux/std.go | 107 ------------------------------------------ internal/sys/interface.go | 70 +++++++++++++++++++++++++++ internal/sys/std.go | 107 ++++++++++++++++++++++++++++++++++++++++++ main.go | 10 ++-- parse.go | 2 +- print.go | 2 +- 14 files changed, 202 insertions(+), 202 deletions(-) delete mode 100644 internal/linux/interface.go delete mode 100644 internal/linux/std.go create mode 100644 internal/sys/interface.go create mode 100644 internal/sys/std.go diff --git a/fst/sandbox.go b/fst/sandbox.go index a6b1eb56..add559c7 100644 --- a/fst/sandbox.go +++ b/fst/sandbox.go @@ -9,7 +9,7 @@ import ( "git.gensokyo.uk/security/fortify/dbus" "git.gensokyo.uk/security/fortify/helper/bwrap" "git.gensokyo.uk/security/fortify/internal/fmsg" - "git.gensokyo.uk/security/fortify/internal/linux" + "git.gensokyo.uk/security/fortify/internal/sys" ) // SandboxConfig describes resources made available to the sandbox. @@ -47,7 +47,7 @@ type SandboxConfig struct { // Bwrap returns the address of the corresponding bwrap.Config to s. // Note that remaining tmpfs entries must be queued by the caller prior to launch. -func (s *SandboxConfig) Bwrap(os linux.System) (*bwrap.Config, error) { +func (s *SandboxConfig) Bwrap(os sys.State) (*bwrap.Config, error) { if s == nil { return nil, errors.New("nil sandbox config") } @@ -216,7 +216,7 @@ func (s *SandboxConfig) Bwrap(os linux.System) (*bwrap.Config, error) { return conf, nil } -func evalSymlinks(os linux.System, v *string) error { +func evalSymlinks(os sys.State, v *string) error { if p, err := os.EvalSymlinks(*v); err != nil { if !errors.Is(err, fs.ErrNotExist) { return err diff --git a/internal/app/app.go b/internal/app/app.go index 20966b78..2a96c544 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -6,7 +6,7 @@ import ( "git.gensokyo.uk/security/fortify/fst" "git.gensokyo.uk/security/fortify/internal/app/shim" - "git.gensokyo.uk/security/fortify/internal/linux" + "git.gensokyo.uk/security/fortify/internal/sys" ) type App interface { @@ -32,7 +32,7 @@ type app struct { // application unique identifier id *fst.ID // operating system interface - os linux.System + os sys.State // shim process manager shim *shim.Shim // child process related information @@ -64,7 +64,7 @@ func (a *app) String() string { return "(unsealed fortified app)" } -func New(os linux.System) (App, error) { +func New(os sys.State) (App, error) { a := new(app) a.id = new(fst.ID) a.os = os diff --git a/internal/app/app_stub_test.go b/internal/app/app_stub_test.go index 4d4674db..1b6d5780 100644 --- a/internal/app/app_stub_test.go +++ b/internal/app/app_stub_test.go @@ -6,7 +6,7 @@ import ( "os/user" "strconv" - "git.gensokyo.uk/security/fortify/internal/linux" + "git.gensokyo.uk/security/fortify/internal/sys" ) // fs methods are not implemented using a real FS @@ -122,8 +122,8 @@ func (s *stubNixOS) Open(name string) (fs.File, error) { } } -func (s *stubNixOS) Paths() linux.Paths { - return linux.Paths{ +func (s *stubNixOS) Paths() sys.Paths { + return sys.Paths{ SharePath: "/tmp/fortify.1971", RuntimePath: "/run/user/1971", RunDirPath: "/run/user/1971/fortify", diff --git a/internal/app/app_test.go b/internal/app/app_test.go index 46409446..6bdc043d 100644 --- a/internal/app/app_test.go +++ b/internal/app/app_test.go @@ -10,13 +10,13 @@ import ( "git.gensokyo.uk/security/fortify/fst" "git.gensokyo.uk/security/fortify/helper/bwrap" "git.gensokyo.uk/security/fortify/internal/app" - "git.gensokyo.uk/security/fortify/internal/linux" + "git.gensokyo.uk/security/fortify/internal/sys" "git.gensokyo.uk/security/fortify/system" ) type sealTestCase struct { name string - os linux.System + os sys.State config *fst.Config id fst.ID wantSys *system.I diff --git a/internal/app/export_test.go b/internal/app/export_test.go index 8581d1c7..2e027b83 100644 --- a/internal/app/export_test.go +++ b/internal/app/export_test.go @@ -3,11 +3,11 @@ package app import ( "git.gensokyo.uk/security/fortify/fst" "git.gensokyo.uk/security/fortify/helper/bwrap" - "git.gensokyo.uk/security/fortify/internal/linux" + "git.gensokyo.uk/security/fortify/internal/sys" "git.gensokyo.uk/security/fortify/system" ) -func NewWithID(id fst.ID, os linux.System) App { +func NewWithID(id fst.ID, os sys.State) App { a := new(app) a.id = &id a.os = os diff --git a/internal/app/seal.go b/internal/app/seal.go index 3ac31c8d..e8c33a39 100644 --- a/internal/app/seal.go +++ b/internal/app/seal.go @@ -17,8 +17,8 @@ import ( "git.gensokyo.uk/security/fortify/helper/bwrap" "git.gensokyo.uk/security/fortify/internal" "git.gensokyo.uk/security/fortify/internal/fmsg" - "git.gensokyo.uk/security/fortify/internal/linux" "git.gensokyo.uk/security/fortify/internal/state" + "git.gensokyo.uk/security/fortify/internal/sys" "git.gensokyo.uk/security/fortify/system" ) @@ -64,7 +64,7 @@ type appSeal struct { // seal system-level component sys *appSealSys - linux.Paths + sys.Paths // protected by upstream mutex } diff --git a/internal/app/share.go b/internal/app/share.go index f3c6b5b7..bb244c83 100644 --- a/internal/app/share.go +++ b/internal/app/share.go @@ -11,7 +11,7 @@ import ( "git.gensokyo.uk/security/fortify/dbus" "git.gensokyo.uk/security/fortify/fst" "git.gensokyo.uk/security/fortify/internal/fmsg" - "git.gensokyo.uk/security/fortify/internal/linux" + "git.gensokyo.uk/security/fortify/internal/sys" "git.gensokyo.uk/security/fortify/system" "git.gensokyo.uk/security/fortify/wl" ) @@ -43,7 +43,7 @@ var ( ErrPulseMode = errors.New("unexpected pulse socket mode") ) -func (seal *appSeal) setupShares(bus [2]*dbus.Config, os linux.System) error { +func (seal *appSeal) setupShares(bus [2]*dbus.Config, os sys.State) error { if seal.shared { panic("seal shared twice") } @@ -300,7 +300,7 @@ func (seal *appSeal) setupShares(bus [2]*dbus.Config, os linux.System) error { } // discoverPulseCookie attempts various standard methods to discover the current user's PulseAudio authentication cookie -func discoverPulseCookie(os linux.System) (string, error) { +func discoverPulseCookie(os sys.State) (string, error) { if p, ok := os.LookupEnv(pulseCookie); ok { return p, nil } diff --git a/internal/linux/interface.go b/internal/linux/interface.go deleted file mode 100644 index 762414c8..00000000 --- a/internal/linux/interface.go +++ /dev/null @@ -1,70 +0,0 @@ -package linux - -import ( - "io/fs" - "os/user" - "path" - "strconv" - - "git.gensokyo.uk/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) - // 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 System, 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/linux/std.go b/internal/linux/std.go deleted file mode 100644 index 89338f19..00000000 --- a/internal/linux/std.go +++ /dev/null @@ -1,107 +0,0 @@ -package linux - -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 - } -} 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 + } +} diff --git a/main.go b/main.go index 8aec22d6..b4bfff97 100644 --- a/main.go +++ b/main.go @@ -24,8 +24,8 @@ import ( init0 "git.gensokyo.uk/security/fortify/internal/app/init" "git.gensokyo.uk/security/fortify/internal/app/shim" "git.gensokyo.uk/security/fortify/internal/fmsg" - "git.gensokyo.uk/security/fortify/internal/linux" "git.gensokyo.uk/security/fortify/internal/state" + "git.gensokyo.uk/security/fortify/internal/sys" "git.gensokyo.uk/security/fortify/system" ) @@ -44,7 +44,7 @@ func init() { flag.BoolVar(&flagJSON, "json", false, "Format output in JSON when applicable") } -var sys linux.System = new(linux.Std) +var std sys.State = new(sys.Std) type gl []string @@ -135,7 +135,7 @@ func main() { // Ignore errors; set is set for ExitOnError. _ = set.Parse(args[1:]) - printPs(os.Stdout, time.Now().UTC(), state.NewMulti(sys.Paths().RunDirPath), short) + printPs(os.Stdout, time.Now().UTC(), state.NewMulti(std.Paths().RunDirPath), short) internal.Exit(0) case "show": // pretty-print app info @@ -227,7 +227,7 @@ func main() { passwdOnce sync.Once passwdFunc = func() { var us string - if uid, err := sys.Uid(aid); err != nil { + if uid, err := std.Uid(aid); err != nil { fmsg.PrintBaseError(err, "cannot obtain uid from fsu:") os.Exit(1) } else { @@ -328,7 +328,7 @@ func runApp(config *fst.Config) { seccomp.CPrintln = log.Println } - if a, err := app.New(sys); err != nil { + if a, err := app.New(std); err != nil { log.Fatalf("cannot create app: %s", err) } else if err = a.Seal(config); err != nil { fmsg.PrintBaseError(err, "cannot seal app:") diff --git a/parse.go b/parse.go index 7212c92b..95775468 100644 --- a/parse.go +++ b/parse.go @@ -84,7 +84,7 @@ func tryShort(name string) (config *fst.Config, instance *state.State) { if likePrefix && len(name) >= 8 { fmsg.Verbose("argument looks like prefix") - s := state.NewMulti(sys.Paths().RunDirPath) + s := state.NewMulti(std.Paths().RunDirPath) if entries, err := state.Join(s); err != nil { log.Printf("cannot join store: %v", err) // drop to fetch from file diff --git a/print.go b/print.go index 1022ae11..32e8546d 100644 --- a/print.go +++ b/print.go @@ -25,7 +25,7 @@ func printShowSystem(output io.Writer, short bool) { info := new(fst.Info) // get fid by querying uid of aid 0 - if uid, err := sys.Uid(0); err != nil { + if uid, err := std.Uid(0); err != nil { fmsg.PrintBaseError(err, "cannot obtain uid from fsu:") os.Exit(1) } else { -- cgit v1.3.1