internal: wrap calls to os standard library functions
test / test (push) Successful in 19s
Details
test / test (push) Successful in 19s
Details
This change helps tests stub out and simulate OS behaviour during the sealing process. This also removes dependency on XDG_RUNTIME_DIR as the internal.System implementation provided to App provides a compat directory inside the tmpdir-based share when XDG_RUNTIME_DIR is unavailable. Signed-off-by: Ophestra Umiker <cat@ophivana.moe>
This commit is contained in:
parent
e35c5fe3ed
commit
6bc5be7e5a
|
@ -4,7 +4,6 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
|
||||||
|
|
||||||
"git.ophivana.moe/security/fortify/dbus"
|
"git.ophivana.moe/security/fortify/dbus"
|
||||||
"git.ophivana.moe/security/fortify/internal"
|
"git.ophivana.moe/security/fortify/internal"
|
||||||
|
|
|
@ -3,6 +3,8 @@ package app
|
||||||
import (
|
import (
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
"git.ophivana.moe/security/fortify/internal"
|
||||||
)
|
)
|
||||||
|
|
||||||
type App interface {
|
type App interface {
|
||||||
|
@ -22,6 +24,8 @@ type App interface {
|
||||||
type app struct {
|
type app struct {
|
||||||
// application unique identifier
|
// application unique identifier
|
||||||
id *ID
|
id *ID
|
||||||
|
// operating system interface
|
||||||
|
os internal.System
|
||||||
// underlying user switcher process
|
// underlying user switcher process
|
||||||
cmd *exec.Cmd
|
cmd *exec.Cmd
|
||||||
// shim setup abort reason and completion
|
// shim setup abort reason and completion
|
||||||
|
@ -61,8 +65,9 @@ func (a *app) WaitErr() error {
|
||||||
return a.waitErr
|
return a.waitErr
|
||||||
}
|
}
|
||||||
|
|
||||||
func New() (App, error) {
|
func New(os internal.System) (App, error) {
|
||||||
a := new(app)
|
a := new(app)
|
||||||
a.id = new(ID)
|
a.id = new(ID)
|
||||||
|
a.os = os
|
||||||
return a, newAppID(a.id)
|
return a, newAppID(a.id)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package app
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os/exec"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"git.ophivana.moe/security/fortify/internal/fmsg"
|
"git.ophivana.moe/security/fortify/internal/fmsg"
|
||||||
|
@ -31,7 +30,7 @@ func (a *app) commandBuilderMachineCtl(shimEnv string) (args []string) {
|
||||||
args = append(args, "--", ".host")
|
args = append(args, "--", ".host")
|
||||||
|
|
||||||
// /bin/sh -c
|
// /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
|
// hardcode /bin/sh path since it exists more often than not
|
||||||
args = append(args, "/bin/sh", "-c")
|
args = append(args, "/bin/sh", "-c")
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
package app
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
|
||||||
|
|
||||||
"git.ophivana.moe/security/fortify/internal/fmsg"
|
"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)
|
args = append(args, "-Hiu", a.seal.sys.user.Username)
|
||||||
|
|
||||||
// -A?
|
// -A?
|
||||||
if _, ok := os.LookupEnv(sudoAskPass); ok {
|
if _, ok := a.os.LookupEnv(sudoAskPass); ok {
|
||||||
fmsg.VPrintln(sudoAskPass, "set, adding askpass flag")
|
fmsg.VPrintln(sudoAskPass, "set, adding askpass flag")
|
||||||
args = append(args, "-A")
|
args = append(args, "-A")
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,8 +2,7 @@ package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"os"
|
"io/fs"
|
||||||
"os/exec"
|
|
||||||
"os/user"
|
"os/user"
|
||||||
"path"
|
"path"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
@ -67,8 +66,7 @@ type appSeal struct {
|
||||||
// seal system-level component
|
// seal system-level component
|
||||||
sys *appSealSys
|
sys *appSealSys
|
||||||
|
|
||||||
// used in various sealing operations
|
internal.Paths
|
||||||
internal.SystemConstants
|
|
||||||
|
|
||||||
// protected by upstream mutex
|
// protected by upstream mutex
|
||||||
}
|
}
|
||||||
|
@ -91,7 +89,7 @@ func (a *app) Seal(config *Config) error {
|
||||||
seal := new(appSeal)
|
seal := new(appSeal)
|
||||||
|
|
||||||
// fetch system constants
|
// fetch system constants
|
||||||
seal.SystemConstants = internal.GetSC()
|
seal.Paths = a.os.Paths()
|
||||||
|
|
||||||
// pass through config values
|
// pass through config values
|
||||||
seal.id = a.id.String()
|
seal.id = a.id.String()
|
||||||
|
@ -102,7 +100,7 @@ func (a *app) Seal(config *Config) error {
|
||||||
switch config.Method {
|
switch config.Method {
|
||||||
case method[LaunchMethodSudo]:
|
case method[LaunchMethodSudo]:
|
||||||
seal.launchOption = 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,
|
return fmsg.WrapError(ErrSudo,
|
||||||
"sudo not found")
|
"sudo not found")
|
||||||
} else {
|
} else {
|
||||||
|
@ -115,7 +113,7 @@ func (a *app) Seal(config *Config) error {
|
||||||
"system has not been booted with systemd as init system")
|
"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,
|
return fmsg.WrapError(ErrMachineCtl,
|
||||||
"machinectl not found")
|
"machinectl not found")
|
||||||
} else {
|
} else {
|
||||||
|
@ -130,14 +128,14 @@ func (a *app) Seal(config *Config) error {
|
||||||
seal.sys = new(appSealSys)
|
seal.sys = new(appSealSys)
|
||||||
|
|
||||||
// look up fortify executable path
|
// 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:")
|
return fmsg.WrapErrorSuffix(err, "cannot look up fortify executable path:")
|
||||||
} else {
|
} else {
|
||||||
seal.sys.executable = p
|
seal.sys.executable = p
|
||||||
}
|
}
|
||||||
|
|
||||||
// look up user from system
|
// 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)) {
|
if errors.As(err, new(user.UnknownUserError)) {
|
||||||
return fmsg.WrapError(ErrUser, "unknown user", config.User)
|
return fmsg.WrapError(ErrUser, "unknown user", config.User)
|
||||||
} else {
|
} else {
|
||||||
|
@ -160,7 +158,7 @@ func (a *app) Seal(config *Config) error {
|
||||||
NoNewSession: true,
|
NoNewSession: true,
|
||||||
}
|
}
|
||||||
// bind entries in /
|
// bind entries in /
|
||||||
if d, err := os.ReadDir("/"); err != nil {
|
if d, err := a.os.ReadDir("/"); err != nil {
|
||||||
return err
|
return err
|
||||||
} else {
|
} else {
|
||||||
b := make([]*FilesystemConfig, 0, len(d))
|
b := make([]*FilesystemConfig, 0, len(d))
|
||||||
|
@ -180,7 +178,7 @@ func (a *app) Seal(config *Config) error {
|
||||||
conf.Filesystem = append(conf.Filesystem, b...)
|
conf.Filesystem = append(conf.Filesystem, b...)
|
||||||
}
|
}
|
||||||
// bind entries in /run
|
// bind entries in /run
|
||||||
if d, err := os.ReadDir("/run"); err != nil {
|
if d, err := a.os.ReadDir("/run"); err != nil {
|
||||||
return err
|
return err
|
||||||
} else {
|
} else {
|
||||||
b := make([]*FilesystemConfig, 0, len(d))
|
b := make([]*FilesystemConfig, 0, len(d))
|
||||||
|
@ -198,7 +196,7 @@ func (a *app) Seal(config *Config) error {
|
||||||
}
|
}
|
||||||
// hide nscd from sandbox if present
|
// hide nscd from sandbox if present
|
||||||
nscd := "/var/run/nscd"
|
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)
|
conf.Override = append(conf.Override, nscd)
|
||||||
}
|
}
|
||||||
// bind GPU stuff
|
// bind GPU stuff
|
||||||
|
@ -222,7 +220,7 @@ func (a *app) Seal(config *Config) error {
|
||||||
// open process state store
|
// open process state store
|
||||||
// the simple store only starts holding an open file after first action
|
// the simple store only starts holding an open file after first action
|
||||||
// store activity begins after Start is called and must end before Wait
|
// 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
|
// parse string UID
|
||||||
if u, err := strconv.Atoi(seal.sys.user.Uid); err != nil {
|
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
|
seal.et = config.Confinement.Enablements
|
||||||
|
|
||||||
// this method calls all share methods in sequence
|
// 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
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,10 +2,10 @@ package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"os"
|
|
||||||
"path"
|
"path"
|
||||||
|
|
||||||
"git.ophivana.moe/security/fortify/acl"
|
"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/fmsg"
|
||||||
"git.ophivana.moe/security/fortify/internal/system"
|
"git.ophivana.moe/security/fortify/internal/system"
|
||||||
)
|
)
|
||||||
|
@ -23,7 +23,7 @@ var (
|
||||||
ErrXDisplay = errors.New(display + " unset")
|
ErrXDisplay = errors.New(display + " unset")
|
||||||
)
|
)
|
||||||
|
|
||||||
func (seal *appSeal) shareDisplay() error {
|
func (seal *appSeal) shareDisplay(os internal.System) error {
|
||||||
// pass $TERM to launcher
|
// pass $TERM to launcher
|
||||||
if t, ok := os.LookupEnv(term); ok {
|
if t, ok := os.LookupEnv(term); ok {
|
||||||
seal.sys.bwrap.SetEnv[term] = t
|
seal.sys.bwrap.SetEnv[term] = t
|
||||||
|
|
|
@ -4,9 +4,9 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"os"
|
|
||||||
"path"
|
"path"
|
||||||
|
|
||||||
|
"git.ophivana.moe/security/fortify/internal"
|
||||||
"git.ophivana.moe/security/fortify/internal/fmsg"
|
"git.ophivana.moe/security/fortify/internal/fmsg"
|
||||||
"git.ophivana.moe/security/fortify/internal/system"
|
"git.ophivana.moe/security/fortify/internal/system"
|
||||||
)
|
)
|
||||||
|
@ -25,7 +25,7 @@ var (
|
||||||
ErrPulseMode = errors.New("unexpected pulse socket mode")
|
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) {
|
if !seal.et.Has(system.EPulse) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -65,7 +65,7 @@ func (seal *appSeal) sharePulse() error {
|
||||||
seal.sys.bwrap.SetEnv[pulseServer] = "unix:" + p
|
seal.sys.bwrap.SetEnv[pulseServer] = "unix:" + p
|
||||||
|
|
||||||
// publish current user's pulse cookie for target user
|
// publish current user's pulse cookie for target user
|
||||||
if src, err := discoverPulseCookie(); err != nil {
|
if src, err := discoverPulseCookie(os); err != nil {
|
||||||
return err
|
return err
|
||||||
} else {
|
} else {
|
||||||
dst := path.Join(seal.share, "pulse-cookie")
|
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
|
// 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 {
|
if p, ok := os.LookupEnv(pulseCookie); ok {
|
||||||
return p, nil
|
return p, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,6 +29,7 @@ func (seal *appSeal) shareRuntime() {
|
||||||
seal.sys.UpdatePermType(system.User, seal.RunDirPath, acl.Execute)
|
seal.sys.UpdatePermType(system.User, seal.RunDirPath, acl.Execute)
|
||||||
|
|
||||||
// ensure runtime directory ACL (e.g. `/run/user/%d`)
|
// 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)
|
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`)
|
// ensure process-specific share local to XDG_RUNTIME_DIR (e.g. `/run/user/%d/fortify/%s`)
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
package app
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
|
||||||
"path"
|
"path"
|
||||||
|
|
||||||
"git.ophivana.moe/security/fortify/acl"
|
"git.ophivana.moe/security/fortify/acl"
|
||||||
|
"git.ophivana.moe/security/fortify/internal"
|
||||||
"git.ophivana.moe/security/fortify/internal/system"
|
"git.ophivana.moe/security/fortify/internal/system"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -38,7 +38,7 @@ func (seal *appSeal) shareSystem() {
|
||||||
seal.sys.bwrap.Tmpfs(seal.SharePath, 1*1024*1024)
|
seal.sys.bwrap.Tmpfs(seal.SharePath, 1*1024*1024)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (seal *appSeal) sharePasswd() {
|
func (seal *appSeal) sharePasswd(os internal.System) {
|
||||||
// look up shell
|
// look up shell
|
||||||
sh := "/bin/sh"
|
sh := "/bin/sh"
|
||||||
if s, ok := os.LookupEnv(shell); ok {
|
if s, ok := os.LookupEnv(shell); ok {
|
||||||
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
|
|
||||||
"git.ophivana.moe/security/fortify/dbus"
|
"git.ophivana.moe/security/fortify/dbus"
|
||||||
"git.ophivana.moe/security/fortify/helper/bwrap"
|
"git.ophivana.moe/security/fortify/helper/bwrap"
|
||||||
|
"git.ophivana.moe/security/fortify/internal"
|
||||||
"git.ophivana.moe/security/fortify/internal/system"
|
"git.ophivana.moe/security/fortify/internal/system"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -27,7 +28,7 @@ type appSealSys struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// shareAll calls all share methods in sequence
|
// 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 {
|
if seal.shared {
|
||||||
panic("seal shared twice")
|
panic("seal shared twice")
|
||||||
}
|
}
|
||||||
|
@ -35,11 +36,11 @@ func (seal *appSeal) shareAll(bus [2]*dbus.Config) error {
|
||||||
|
|
||||||
seal.shareSystem()
|
seal.shareSystem()
|
||||||
seal.shareRuntime()
|
seal.shareRuntime()
|
||||||
seal.sharePasswd()
|
seal.sharePasswd(os)
|
||||||
if err := seal.shareDisplay(); err != nil {
|
if err := seal.shareDisplay(os); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := seal.sharePulse(); err != nil {
|
if err := seal.sharePulse(os); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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
|
||||||
|
}
|
|
@ -4,7 +4,6 @@ import (
|
||||||
_ "embed"
|
_ "embed"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|
11
main.go
11
main.go
|
@ -2,7 +2,6 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"flag"
|
"flag"
|
||||||
"os"
|
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"git.ophivana.moe/security/fortify/internal"
|
"git.ophivana.moe/security/fortify/internal"
|
||||||
|
@ -20,6 +19,8 @@ func init() {
|
||||||
flag.BoolVar(&flagVerbose, "v", false, "Verbose output")
|
flag.BoolVar(&flagVerbose, "v", false, "Verbose output")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var os = new(internal.Std)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
// linux/sched/coredump.h
|
// linux/sched/coredump.h
|
||||||
if _, _, errno := syscall.RawSyscall(syscall.SYS_PRCTL, syscall.PR_SET_DUMPABLE, 0, 0); errno != 0 {
|
if _, _, errno := syscall.RawSyscall(syscall.SYS_PRCTL, syscall.PR_SET_DUMPABLE, 0, 0); errno != 0 {
|
||||||
|
@ -38,9 +39,9 @@ func main() {
|
||||||
shim.Try()
|
shim.Try()
|
||||||
|
|
||||||
// root check
|
// root check
|
||||||
if os.Getuid() == 0 {
|
if os.Geteuid() == 0 {
|
||||||
fmsg.Println("this program must not run as root")
|
fmsg.Fatal("this program must not run as root")
|
||||||
os.Exit(1)
|
panic("unreachable")
|
||||||
}
|
}
|
||||||
|
|
||||||
// version/license/template command early exit
|
// version/license/template command early exit
|
||||||
|
@ -53,7 +54,7 @@ func main() {
|
||||||
|
|
||||||
// invoke app
|
// invoke app
|
||||||
r := 1
|
r := 1
|
||||||
a, err := app.New()
|
a, err := app.New(os)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmsg.Fatalf("cannot create app: %s\n", err)
|
fmsg.Fatalf("cannot create app: %s\n", err)
|
||||||
} else if err = a.Seal(loadConfig()); err != nil {
|
} else if err = a.Seal(loadConfig()); err != nil {
|
||||||
|
|
4
state.go
4
state.go
|
@ -3,10 +3,8 @@ package main
|
||||||
import (
|
import (
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
|
||||||
"text/tabwriter"
|
"text/tabwriter"
|
||||||
|
|
||||||
"git.ophivana.moe/security/fortify/internal"
|
|
||||||
"git.ophivana.moe/security/fortify/internal/fmsg"
|
"git.ophivana.moe/security/fortify/internal/fmsg"
|
||||||
"git.ophivana.moe/security/fortify/internal/state"
|
"git.ophivana.moe/security/fortify/internal/state"
|
||||||
)
|
)
|
||||||
|
@ -23,7 +21,7 @@ func init() {
|
||||||
func tryState() {
|
func tryState() {
|
||||||
if stateActionEarly {
|
if stateActionEarly {
|
||||||
var w *tabwriter.Writer
|
var w *tabwriter.Writer
|
||||||
state.MustPrintLauncherStateSimpleGlobal(&w, internal.GetSC().RunDirPath)
|
state.MustPrintLauncherStateSimpleGlobal(&w, os.Paths().RunDirPath)
|
||||||
if w != nil {
|
if w != nil {
|
||||||
if err := w.Flush(); err != nil {
|
if err := w.Flush(); err != nil {
|
||||||
fmsg.Println("cannot format output:", err)
|
fmsg.Println("cannot format output:", err)
|
||||||
|
|
|
@ -3,7 +3,6 @@ package main
|
||||||
import (
|
import (
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|
Loading…
Reference in New Issue