aboutsummaryrefslogtreecommitdiffhomepage
path: root/internal
diff options
context:
space:
mode:
authorOphestra Umiker <cat@ophivana.moe>2024-11-16 21:19:45 +0900
committerOphestra Umiker <cat@ophivana.moe>2024-11-16 21:19:45 +0900
commitdf33123bd7f1e0cb4e98580b7e63818c82aa7206 (patch)
tree8b21831634e6169eb875cbfd359fa4006d6c66b3 /internal
parent1a09b55bd4753c6d5cbecf96d1b56f23b0e44b95 (diff)
app: integrate fsu
This removes the dependency on external user switchers like sudo/machinectl and decouples fortify user ids from the passwd database. Signed-off-by: Ophestra Umiker <cat@ophivana.moe>
Diffstat (limited to 'internal')
-rw-r--r--internal/app/app.go2
-rw-r--r--internal/app/app_nixos_test.go66
-rw-r--r--internal/app/config.go20
-rw-r--r--internal/app/launch.machinectl.go57
-rw-r--r--internal/app/launch.sudo.go30
-rw-r--r--internal/app/seal.go114
-rw-r--r--internal/app/share.display.go2
-rw-r--r--internal/app/share.system.go24
-rw-r--r--internal/app/start.go55
-rw-r--r--internal/app/system.go24
-rw-r--r--internal/fmsg/defer.go3
-rw-r--r--internal/linux/interface.go8
-rw-r--r--internal/linux/std.go85
-rw-r--r--internal/path.go1
-rw-r--r--internal/state/print.go12
-rw-r--r--internal/state/state.go2
16 files changed, 227 insertions, 278 deletions
diff --git a/internal/app/app.go b/internal/app/app.go
index 28564ebf..3260deab 100644
--- a/internal/app/app.go
+++ b/internal/app/app.go
@@ -53,7 +53,7 @@ func (a *app) String() string {
}
if a.seal != nil {
- return "(sealed fortified app as uid " + a.seal.sys.user.Uid + ")"
+ return "(sealed fortified app as uid " + a.seal.sys.user.us + ")"
}
return "(unsealed fortified app)"
diff --git a/internal/app/app_nixos_test.go b/internal/app/app_nixos_test.go
index a154486d..4a350caf 100644
--- a/internal/app/app_nixos_test.go
+++ b/internal/app/app_nixos_test.go
@@ -19,9 +19,12 @@ var testCasesNixos = []sealTestCase{
{
"nixos permissive defaults no enablements", new(stubNixOS),
&app.Config{
- User: "chronos",
Command: make([]string, 0),
- Method: "sudo",
+ Confinement: app.ConfinementConfig{
+ AppID: 0,
+ Username: "chronos",
+ Home: "/home/chronos",
+ },
},
app.ID{
0x4a, 0x45, 0x0b, 0x65,
@@ -29,11 +32,11 @@ var testCasesNixos = []sealTestCase{
0xbd, 0x01, 0x78, 0x0e,
0xb9, 0xa6, 0x07, 0xac,
},
- system.New(150).
- Ensure("/tmp/fortify.1971", 0701).
- Ephemeral(system.Process, "/tmp/fortify.1971/4a450b6596d7bc15bd01780eb9a607ac", 0701).
+ system.New(1000000).
+ Ensure("/tmp/fortify.1971", 0711).
+ Ephemeral(system.Process, "/tmp/fortify.1971/4a450b6596d7bc15bd01780eb9a607ac", 0711).
Ensure("/tmp/fortify.1971/tmpdir", 0700).UpdatePermType(system.User, "/tmp/fortify.1971/tmpdir", acl.Execute).
- Ensure("/tmp/fortify.1971/tmpdir/150", 01700).UpdatePermType(system.User, "/tmp/fortify.1971/tmpdir/150", acl.Read, acl.Write, acl.Execute).
+ Ensure("/tmp/fortify.1971/tmpdir/0", 01700).UpdatePermType(system.User, "/tmp/fortify.1971/tmpdir/0", acl.Read, acl.Write, acl.Execute).
Ensure("/run/user/1971/fortify", 0700).UpdatePermType(system.User, "/run/user/1971/fortify", acl.Execute).
Ensure("/run/user/1971", 0700).UpdatePermType(system.User, "/run/user/1971", acl.Execute). // this is ordered as is because the previous Ensure only calls mkdir if XDG_RUNTIME_DIR is unset
Ephemeral(system.Process, "/run/user/1971/fortify/4a450b6596d7bc15bd01780eb9a607ac", 0700).UpdatePermType(system.Process, "/run/user/1971/fortify/4a450b6596d7bc15bd01780eb9a607ac", acl.Execute).
@@ -43,6 +46,7 @@ var testCasesNixos = []sealTestCase{
Net: true,
UserNS: true,
Clearenv: true,
+ Chdir: "/home/chronos",
SetEnv: map[string]string{
"HOME": "/home/chronos",
"SHELL": "/run/current-system/sw/bin/zsh",
@@ -182,10 +186,11 @@ var testCasesNixos = []sealTestCase{
Symlink("/fortify/etc/zprofile", "/etc/zprofile").
Symlink("/fortify/etc/zshenv", "/etc/zshenv").
Symlink("/fortify/etc/zshrc", "/etc/zshrc").
- Bind("/tmp/fortify.1971/tmpdir/150", "/tmp", false, true).
+ Bind("/tmp/fortify.1971/tmpdir/0", "/tmp", false, true).
Tmpfs("/tmp/fortify.1971", 1048576).
Tmpfs("/run/user", 1048576).
Tmpfs("/run/user/65534", 8388608).
+ Bind("/home/chronos", "/home/chronos", false, true).
Bind("/tmp/fortify.1971/4a450b6596d7bc15bd01780eb9a607ac/passwd", "/etc/passwd").
Bind("/tmp/fortify.1971/4a450b6596d7bc15bd01780eb9a607ac/group", "/etc/group").
Tmpfs("/var/run/nscd", 8192),
@@ -194,9 +199,12 @@ var testCasesNixos = []sealTestCase{
"nixos permissive defaults chromium", new(stubNixOS),
&app.Config{
ID: "org.chromium.Chromium",
- User: "chronos",
Command: []string{"/run/current-system/sw/bin/zsh", "-c", "exec chromium "},
Confinement: app.ConfinementConfig{
+ AppID: 9,
+ Groups: []string{"video"},
+ Username: "chronos",
+ Home: "/home/chronos",
SessionBus: &dbus.Config{
Talk: []string{
"org.freedesktop.Notifications",
@@ -230,7 +238,6 @@ var testCasesNixos = []sealTestCase{
},
Enablements: system.EWayland.Mask() | system.EDBus.Mask() | system.EPulse.Mask(),
},
- Method: "systemd",
},
app.ID{
0xeb, 0xf0, 0x83, 0xd1,
@@ -238,11 +245,11 @@ var testCasesNixos = []sealTestCase{
0x82, 0xd4, 0x13, 0x36,
0x9b, 0x64, 0xce, 0x7c,
},
- system.New(150).
- Ensure("/tmp/fortify.1971", 0701).
- Ephemeral(system.Process, "/tmp/fortify.1971/ebf083d1b175911782d413369b64ce7c", 0701).
+ system.New(1000009).
+ Ensure("/tmp/fortify.1971", 0711).
+ Ephemeral(system.Process, "/tmp/fortify.1971/ebf083d1b175911782d413369b64ce7c", 0711).
Ensure("/tmp/fortify.1971/tmpdir", 0700).UpdatePermType(system.User, "/tmp/fortify.1971/tmpdir", acl.Execute).
- Ensure("/tmp/fortify.1971/tmpdir/150", 01700).UpdatePermType(system.User, "/tmp/fortify.1971/tmpdir/150", acl.Read, acl.Write, acl.Execute).
+ Ensure("/tmp/fortify.1971/tmpdir/9", 01700).UpdatePermType(system.User, "/tmp/fortify.1971/tmpdir/9", acl.Read, acl.Write, acl.Execute).
Ensure("/run/user/1971/fortify", 0700).UpdatePermType(system.User, "/run/user/1971/fortify", acl.Execute).
Ensure("/run/user/1971", 0700).UpdatePermType(system.User, "/run/user/1971", acl.Execute). // this is ordered as is because the previous Ensure only calls mkdir if XDG_RUNTIME_DIR is unset
Ephemeral(system.Process, "/run/user/1971/fortify/ebf083d1b175911782d413369b64ce7c", 0700).UpdatePermType(system.Process, "/run/user/1971/fortify/ebf083d1b175911782d413369b64ce7c", acl.Execute).
@@ -287,6 +294,7 @@ var testCasesNixos = []sealTestCase{
(&bwrap.Config{
Net: true,
UserNS: true,
+ Chdir: "/home/chronos",
Clearenv: true,
SetEnv: map[string]string{
"DBUS_SESSION_BUS_ADDRESS": "unix:path=/run/user/65534/bus",
@@ -434,10 +442,11 @@ var testCasesNixos = []sealTestCase{
Symlink("/fortify/etc/zprofile", "/etc/zprofile").
Symlink("/fortify/etc/zshenv", "/etc/zshenv").
Symlink("/fortify/etc/zshrc", "/etc/zshrc").
- Bind("/tmp/fortify.1971/tmpdir/150", "/tmp", false, true).
+ Bind("/tmp/fortify.1971/tmpdir/9", "/tmp", false, true).
Tmpfs("/tmp/fortify.1971", 1048576).
Tmpfs("/run/user", 1048576).
Tmpfs("/run/user/65534", 8388608).
+ Bind("/home/chronos", "/home/chronos", false, true).
Bind("/tmp/fortify.1971/ebf083d1b175911782d413369b64ce7c/passwd", "/etc/passwd").
Bind("/tmp/fortify.1971/ebf083d1b175911782d413369b64ce7c/group", "/etc/group").
Bind("/run/user/1971/fortify/ebf083d1b175911782d413369b64ce7c/wayland", "/run/user/65534/wayland-0").
@@ -504,23 +513,12 @@ func (s *stubNixOS) Executable() (string, error) {
return "/home/ophestra/.nix-profile/bin/fortify", nil
}
-func (s *stubNixOS) Lookup(username string) (*user.User, error) {
- if s.usernameErr != nil {
- if err, ok := s.usernameErr[username]; ok {
- return nil, err
- }
- }
-
- switch username {
- case "chronos":
- return &user.User{
- Uid: "150",
- Gid: "101",
- Username: "chronos",
- HomeDir: "/home/chronos",
- }, nil
+func (s *stubNixOS) LookupGroup(name string) (*user.Group, error) {
+ switch name {
+ case "video":
+ return &user.Group{Gid: "26", Name: "video"}, nil
default:
- return nil, user.UnknownUserError(username)
+ return nil, user.UnknownGroupError(name)
}
}
@@ -586,10 +584,6 @@ func (s *stubNixOS) Stdout() io.Writer {
panic("requested stdout")
}
-func (s *stubNixOS) FshimPath() string {
- return "/nix/store/00000000000000000000000000000000-fortify-0.0.10/bin/.fshim"
-}
-
func (s *stubNixOS) Paths() linux.Paths {
return linux.Paths{
SharePath: "/tmp/fortify.1971",
@@ -598,6 +592,10 @@ func (s *stubNixOS) Paths() linux.Paths {
}
}
+func (s *stubNixOS) Uid(aid int) (int, error) {
+ return 1000000 + 0*10000 + aid, nil
+}
+
func (s *stubNixOS) SdBooted() bool {
return true
}
diff --git a/internal/app/config.go b/internal/app/config.go
index 96c51ce7..dd45b023 100644
--- a/internal/app/config.go
+++ b/internal/app/config.go
@@ -15,12 +15,8 @@ const fTmp = "/fortify"
type Config struct {
// D-Bus application ID
ID string `json:"id"`
- // username of the target user to switch to
- User string `json:"user"`
// value passed through to the child process as its argv
Command []string `json:"command"`
- // string representation of the child's launch method
- Method string `json:"method"`
// child confinement configuration
Confinement ConfinementConfig `json:"confinement"`
@@ -28,6 +24,14 @@ type Config struct {
// ConfinementConfig defines fortified child's confinement
type ConfinementConfig struct {
+ // numerical application id, determines uid in the init namespace
+ AppID int `json:"app_id"`
+ // list of supplementary groups to inherit
+ Groups []string `json:"groups"`
+ // passwd username in the sandbox, defaults to chronos
+ Username string `json:"username,omitempty"`
+ // home directory in sandbox
+ Home string `json:"home"`
// bwrap sandbox confinement configuration
Sandbox *SandboxConfig `json:"sandbox"`
@@ -169,8 +173,7 @@ func (s *SandboxConfig) Bwrap(os linux.System) (*bwrap.Config, error) {
// Template returns a fully populated instance of Config.
func Template() *Config {
return &Config{
- ID: "org.chromium.Chromium",
- User: "chronos",
+ ID: "org.chromium.Chromium",
Command: []string{
"chromium",
"--ignore-gpu-blocklist",
@@ -178,8 +181,11 @@ func Template() *Config {
"--enable-features=UseOzonePlatform",
"--ozone-platform=wayland",
},
- Method: "sudo",
Confinement: ConfinementConfig{
+ AppID: 9,
+ Groups: []string{"video"},
+ Username: "chronos",
+ Home: "/var/lib/persist/home/org.chromium.Chromium",
Sandbox: &SandboxConfig{
Hostname: "localhost",
UserNS: true,
diff --git a/internal/app/launch.machinectl.go b/internal/app/launch.machinectl.go
deleted file mode 100644
index 3c196b84..00000000
--- a/internal/app/launch.machinectl.go
+++ /dev/null
@@ -1,57 +0,0 @@
-package app
-
-import (
- "strings"
-
- "git.ophivana.moe/security/fortify/internal/fmsg"
-)
-
-func (a *app) commandBuilderMachineCtl(shimEnv string) (args []string) {
- args = make([]string, 0, 9+len(a.seal.sys.bwrap.SetEnv))
-
- // shell --uid=$USER
- args = append(args, "shell", "--uid="+a.seal.sys.user.Username)
-
- // --quiet
- if !fmsg.Verbose() {
- args = append(args, "--quiet")
- }
-
- // environ
- envQ := make([]string, 0, len(a.seal.sys.bwrap.SetEnv)+1)
- for k, v := range a.seal.sys.bwrap.SetEnv {
- envQ = append(envQ, "-E"+k+"="+v)
- }
- // add shim payload to environment for shim path
- envQ = append(envQ, "-E"+shimEnv)
- args = append(args, envQ...)
-
- // -- .host
- args = append(args, "--", ".host")
-
- // /bin/sh -c
- if sh, err := a.os.LookPath("sh"); err != nil {
- // hardcode /bin/sh path since it exists more often than not
- args = append(args, "/bin/sh", "-c")
- } else {
- args = append(args, sh, "-c")
- }
-
- // build inner command expression ran as target user
- innerCommand := strings.Builder{}
-
- // apply custom environment variables to activation environment
- innerCommand.WriteString("dbus-update-activation-environment --systemd")
- for k := range a.seal.sys.bwrap.SetEnv {
- innerCommand.WriteString(" " + k)
- }
- innerCommand.WriteString("; ")
-
- // launch fortify shim
- innerCommand.WriteString("exec " + a.os.FshimPath())
-
- // append inner command
- args = append(args, innerCommand.String())
-
- return
-}
diff --git a/internal/app/launch.sudo.go b/internal/app/launch.sudo.go
deleted file mode 100644
index dbb49318..00000000
--- a/internal/app/launch.sudo.go
+++ /dev/null
@@ -1,30 +0,0 @@
-package app
-
-import (
- "git.ophivana.moe/security/fortify/internal/fmsg"
-)
-
-const (
- sudoAskPass = "SUDO_ASKPASS"
-)
-
-func (a *app) commandBuilderSudo(shimEnv string) (args []string) {
- args = make([]string, 0, 8)
-
- // -Hiu $USER
- args = append(args, "-Hiu", a.seal.sys.user.Username)
-
- // -A?
- if _, ok := a.os.LookupEnv(sudoAskPass); ok {
- fmsg.VPrintln(sudoAskPass, "set, adding askpass flag")
- args = append(args, "-A")
- }
-
- // shim payload
- args = append(args, shimEnv)
-
- // -- $@
- args = append(args, "--", a.os.FshimPath())
-
- return
-}
diff --git a/internal/app/seal.go b/internal/app/seal.go
index 0846a7f8..f01061af 100644
--- a/internal/app/seal.go
+++ b/internal/app/seal.go
@@ -2,8 +2,8 @@ package app
import (
"errors"
+ "fmt"
"io/fs"
- "os/user"
"path"
"strconv"
@@ -15,24 +15,10 @@ import (
"git.ophivana.moe/security/fortify/internal/system"
)
-const (
- LaunchMethodSudo uint8 = iota
- LaunchMethodMachineCtl
-)
-
-var method = [...]string{
- LaunchMethodSudo: "sudo",
- LaunchMethodMachineCtl: "systemd",
-}
-
var (
ErrConfig = errors.New("no configuration to seal")
- ErrUser = errors.New("unknown user")
- ErrLaunch = errors.New("invalid launch method")
-
- ErrSudo = errors.New("sudo not available")
- ErrSystemd = errors.New("systemd not available")
- ErrMachineCtl = errors.New("machinectl not available")
+ ErrUser = errors.New("invalid aid")
+ ErrHome = errors.New("invalid home directory")
)
// appSeal seals the application with child-related information
@@ -51,15 +37,11 @@ type appSeal struct {
// persistent process state store
store state.Store
- // uint8 representation of launch method sealed from config
- launchOption uint8
// process-specific share directory path
share string
// process-specific share directory path local to XDG_RUNTIME_DIR
shareLocal string
- // path to launcher program
- toolPath string
// pass-through enablement tracking from config
et system.Enablements
@@ -98,34 +80,6 @@ func (a *app) Seal(config *Config) error {
seal.fid = config.ID
seal.command = config.Command
- // parses launch method text and looks up tool path
- switch config.Method {
- case method[LaunchMethodSudo]:
- seal.launchOption = LaunchMethodSudo
- if sudoPath, err := a.os.LookPath("sudo"); err != nil {
- return fmsg.WrapError(ErrSudo,
- "sudo not found")
- } else {
- seal.toolPath = sudoPath
- }
- case method[LaunchMethodMachineCtl]:
- seal.launchOption = LaunchMethodMachineCtl
- if !a.os.SdBooted() {
- return fmsg.WrapError(ErrSystemd,
- "system has not been booted with systemd as init system")
- }
-
- if machineCtlPath, err := a.os.LookPath("machinectl"); err != nil {
- return fmsg.WrapError(ErrMachineCtl,
- "machinectl not found")
- } else {
- seal.toolPath = machineCtlPath
- }
- default:
- return fmsg.WrapError(ErrLaunch,
- "invalid launch method")
- }
-
// create seal system component
seal.sys = new(appSealSys)
@@ -138,16 +92,44 @@ func (a *app) Seal(config *Config) error {
seal.sys.mappedIDString = strconv.Itoa(seal.sys.mappedID)
seal.sys.runtime = path.Join("/run/user", seal.sys.mappedIDString)
- // look up user from system
- if u, err := a.os.Lookup(config.User); err != nil {
- if errors.As(err, new(user.UnknownUserError)) {
- return fmsg.WrapError(ErrUser, "unknown user", config.User)
+ // validate uid and set user info
+ if config.Confinement.AppID < 0 || config.Confinement.AppID > 9999 {
+ return fmsg.WrapError(ErrUser,
+ fmt.Sprintf("aid %d out of range", config.Confinement.AppID))
+ } else {
+ seal.sys.user = appUser{
+ aid: config.Confinement.AppID,
+ as: strconv.Itoa(config.Confinement.AppID),
+ home: config.Confinement.Home,
+ username: config.Confinement.Username,
+ }
+ if seal.sys.user.username == "" {
+ seal.sys.user.username = "chronos"
+ }
+ if seal.sys.user.home == "" || !path.IsAbs(seal.sys.user.home) {
+ return fmsg.WrapError(ErrHome,
+ fmt.Sprintf("invalid home directory %q", seal.sys.user.home))
+ }
+
+ // invoke fsu for full uid
+ if u, err := a.os.Uid(seal.sys.user.aid); err != nil {
+ return fmsg.WrapErrorSuffix(err,
+ "cannot obtain uid from fsu:")
} else {
- // unreachable
- panic(err)
+ seal.sys.user.uid = u
+ seal.sys.user.us = strconv.Itoa(u)
+ }
+
+ // resolve supplementary group ids from names
+ seal.sys.user.supp = make([]string, len(config.Confinement.Groups))
+ for i, name := range config.Confinement.Groups {
+ if g, err := a.os.LookupGroup(name); err != nil {
+ return fmsg.WrapError(err,
+ fmt.Sprintf("unknown group %q", name))
+ } else {
+ seal.sys.user.supp[i] = g.Gid
+ }
}
- } else {
- seal.sys.user = u
}
// map sandbox config to bwrap
@@ -230,15 +212,10 @@ func (a *app) Seal(config *Config) error {
// open process state store
// the simple store only starts holding an open file after first action
// store activity begins after Start is called and must end before Wait
- seal.store = state.NewSimple(seal.RunDirPath, seal.sys.user.Uid)
+ seal.store = state.NewSimple(seal.RunDirPath, seal.sys.user.as)
- // parse string UID
- if u, err := strconv.Atoi(seal.sys.user.Uid); err != nil {
- // unreachable unless kernel bug
- panic("uid parse")
- } else {
- seal.sys.I = system.New(u)
- }
+ // initialise system interface with full uid
+ seal.sys.I = system.New(seal.sys.user.uid)
// pass through enablements
seal.et = config.Confinement.Enablements
@@ -249,11 +226,8 @@ func (a *app) Seal(config *Config) error {
}
// verbose log seal information
- fmsg.VPrintln("created application seal as user",
- seal.sys.user.Username, "("+seal.sys.user.Uid+"),",
- "method:", config.Method+",",
- "launcher:", seal.toolPath+",",
- "command:", config.Command)
+ fmsg.VPrintf("created application seal for uid %s (%s) groups: %v, command: %s",
+ seal.sys.user.us, seal.sys.user.username, config.Confinement.Groups, config.Command)
// seal app and release lock
a.seal = seal
diff --git a/internal/app/share.display.go b/internal/app/share.display.go
index 7e814a83..073a29fe 100644
--- a/internal/app/share.display.go
+++ b/internal/app/share.display.go
@@ -58,7 +58,7 @@ func (seal *appSeal) shareDisplay(os linux.System) error {
return fmsg.WrapError(ErrXDisplay,
"DISPLAY is not set")
} else {
- seal.sys.ChangeHosts(seal.sys.user.Username)
+ seal.sys.ChangeHosts(seal.sys.user.us)
seal.sys.bwrap.SetEnv[display] = d
seal.sys.bwrap.Bind("/tmp/.X11-unix", "/tmp/.X11-unix")
}
diff --git a/internal/app/share.system.go b/internal/app/share.system.go
index 08b006c3..7c97c48a 100644
--- a/internal/app/share.system.go
+++ b/internal/app/share.system.go
@@ -16,12 +16,12 @@ const (
func (seal *appSeal) shareSystem() {
// ensure Share (e.g. `/tmp/fortify.%d`)
// acl is unnecessary as this directory is world executable
- seal.sys.Ensure(seal.SharePath, 0701)
+ seal.sys.Ensure(seal.SharePath, 0711)
// ensure process-specific share (e.g. `/tmp/fortify.%d/%s`)
// acl is unnecessary as this directory is world executable
seal.share = path.Join(seal.SharePath, seal.id)
- seal.sys.Ephemeral(system.Process, seal.share, 0701)
+ seal.sys.Ephemeral(system.Process, seal.share, 0711)
// ensure child tmpdir parent directory (e.g. `/tmp/fortify.%d/tmpdir`)
targetTmpdirParent := path.Join(seal.SharePath, "tmpdir")
@@ -29,7 +29,7 @@ func (seal *appSeal) shareSystem() {
seal.sys.UpdatePermType(system.User, targetTmpdirParent, acl.Execute)
// ensure child tmpdir (e.g. `/tmp/fortify.%d/tmpdir/%d`)
- targetTmpdir := path.Join(targetTmpdirParent, seal.sys.user.Uid)
+ targetTmpdir := path.Join(targetTmpdirParent, seal.sys.user.as)
seal.sys.Ensure(targetTmpdir, 01700)
seal.sys.UpdatePermType(system.User, targetTmpdir, acl.Read, acl.Write, acl.Execute)
seal.sys.bwrap.Bind(targetTmpdir, "/tmp", false, true)
@@ -49,15 +49,21 @@ func (seal *appSeal) sharePasswd(os linux.System) {
// generate /etc/passwd
passwdPath := path.Join(seal.share, "passwd")
username := "chronos"
- if seal.sys.user.Username != "" {
- username = seal.sys.user.Username
- seal.sys.bwrap.SetEnv["USER"] = seal.sys.user.Username
+ if seal.sys.user.username != "" {
+ username = seal.sys.user.username
}
homeDir := "/var/empty"
- if seal.sys.user.HomeDir != "" {
- homeDir = seal.sys.user.HomeDir
- seal.sys.bwrap.SetEnv["HOME"] = seal.sys.user.HomeDir
+ if seal.sys.user.home != "" {
+ homeDir = seal.sys.user.home
}
+
+ // bind home directory
+ seal.sys.bwrap.Bind(homeDir, homeDir, false, true)
+ seal.sys.bwrap.Chdir = homeDir
+
+ seal.sys.bwrap.SetEnv["USER"] = username
+ seal.sys.bwrap.SetEnv["HOME"] = homeDir
+
passwd := username + ":x:" + seal.sys.mappedIDString + ":" + seal.sys.mappedIDString + ":Fortify:" + homeDir + ":" + sh + "\n"
seal.sys.Write(passwdPath, passwd)
diff --git a/internal/app/start.go b/internal/app/start.go
index 67a091c6..0feb0ae6 100644
--- a/internal/app/start.go
+++ b/internal/app/start.go
@@ -41,19 +41,13 @@ func (a *app) Start() error {
}
}
- // select command builder
- var commandBuilder shim.CommandBuilder
- switch a.seal.launchOption {
- case LaunchMethodSudo:
- commandBuilder = a.commandBuilderSudo
- case LaunchMethodMachineCtl:
- commandBuilder = a.commandBuilderMachineCtl
- default:
- panic("unreachable")
- }
-
// construct shim manager
- a.shim = shim.New(a.seal.toolPath, uint32(a.seal.sys.UID()), path.Join(a.seal.share, "shim"), a.seal.wl,
+ a.shim = shim.New(
+ uint32(a.seal.sys.UID()),
+ a.seal.sys.user.as,
+ a.seal.sys.user.supp,
+ path.Join(a.seal.share, "shim"),
+ a.seal.wl,
&shim0.Payload{
Argv: a.seal.command,
Exec: shimExec,
@@ -62,9 +56,6 @@ func (a *app) Start() error {
Verbose: fmsg.Verbose(),
},
- // checkPid is impossible at the moment since there is no reliable way to obtain shim's pid
- // this feature is disabled here until sudo is replaced by fortify suid wrapper
- false,
)
// startup will go ahead, commit system setup
@@ -73,7 +64,7 @@ func (a *app) Start() error {
}
a.seal.sys.needRevert = true
- if startTime, err := a.shim.Start(commandBuilder); err != nil {
+ if startTime, err := a.shim.Start(); err != nil {
return err
} else {
// shim start and setup success, create process state
@@ -81,7 +72,6 @@ func (a *app) Start() error {
PID: a.shim.Unwrap().Process.Pid,
Command: a.seal.command,
Capability: a.seal.et,
- Method: method[a.seal.launchOption],
Argv: a.shim.Unwrap().Args,
Time: *startTime,
}
@@ -166,20 +156,31 @@ func (a *app) Wait() (int, error) {
// failure prior to process start
r = 255
} else {
+ wait := make(chan error, 1)
+ go func() { wait <- cmd.Wait() }()
+
+ select {
// wait for process and resolve exit code
- if err := cmd.Wait(); err != nil {
- var exitError *exec.ExitError
- if !errors.As(err, &exitError) {
- // should be unreachable
- a.waitErr = err
+ case err := <-wait:
+ if err != nil {
+ var exitError *exec.ExitError
+ if !errors.As(err, &exitError) {
+ // should be unreachable
+ a.waitErr = err
+ }
+
+ // store non-zero return code
+ r = exitError.ExitCode()
+ } else {
+ r = cmd.ProcessState.ExitCode()
}
+ fmsg.VPrintf("process %d exited with exit code %d", cmd.Process.Pid, r)
- // store non-zero return code
- r = exitError.ExitCode()
- } else {
- r = cmd.ProcessState.ExitCode()
+ // alternative exit path when kill was unsuccessful
+ case err := <-a.shim.WaitFallback():
+ r = 255
+ fmsg.Printf("cannot terminate shim on faulted setup: %v", err)
}
- fmsg.VPrintf("process %d exited with exit code %d", cmd.Process.Pid, r)
}
// child process exited, resume output
diff --git a/internal/app/system.go b/internal/app/system.go
index 47bbd7ed..6a03b92f 100644
--- a/internal/app/system.go
+++ b/internal/app/system.go
@@ -1,8 +1,6 @@
package app
import (
- "os/user"
-
"git.ophivana.moe/security/fortify/dbus"
"git.ophivana.moe/security/fortify/helper/bwrap"
"git.ophivana.moe/security/fortify/internal/linux"
@@ -18,7 +16,7 @@ type appSealSys struct {
// default formatted XDG_RUNTIME_DIR of User
runtime string
// target user sealed from config
- user *user.User
+ user appUser
// mapped uid and gid in user namespace
mappedID int
@@ -32,6 +30,26 @@ type appSealSys struct {
// protected by upstream mutex
}
+type appUser struct {
+ // full uid resolved by fsu
+ uid int
+ // string representation of uid
+ us string
+
+ // supplementary group ids
+ supp []string
+
+ // application id
+ aid int
+ // string representation of aid
+ as string
+
+ // app user home directory
+ home string
+ // passwd database username
+ username string
+}
+
// shareAll calls all share methods in sequence
func (seal *appSeal) shareAll(bus [2]*dbus.Config, os linux.System) error {
if seal.shared {
diff --git a/internal/fmsg/defer.go b/internal/fmsg/defer.go
index ed4a7735..0f3f5ad5 100644
--- a/internal/fmsg/defer.go
+++ b/internal/fmsg/defer.go
@@ -56,9 +56,10 @@ func Exit(code int) {
os.Exit(code)
}
-func Withhold() {
+func Suspend() {
dequeueOnce.Do(dequeue)
if wstate.CompareAndSwap(false, true) {
+ queueSync.Wait()
withhold <- struct{}{}
}
}
diff --git a/internal/linux/interface.go b/internal/linux/interface.go
index 36f61b1a..a920492f 100644
--- a/internal/linux/interface.go
+++ b/internal/linux/interface.go
@@ -22,8 +22,8 @@ type System interface {
LookPath(file string) (string, error)
// Executable provides [os.Executable].
Executable() (string, error)
- // Lookup provides [user.Lookup].
- Lookup(username string) (*user.User, error)
+ // LookupGroup provides [user.LookupGroup].
+ LookupGroup(name string) (*user.Group, error)
// ReadDir provides [os.ReadDir].
ReadDir(name string) ([]fs.DirEntry, error)
// Stat provides [os.Stat].
@@ -35,10 +35,10 @@ type System interface {
// Stdout provides [os.Stdout].
Stdout() io.Writer
- // FshimPath returns an absolute path to the fshim binary.
- FshimPath() string
// Paths returns a populated [Paths] struct.
Paths() Paths
+ // Uid invokes fsu and returns target uid.
+ Uid(aid int) (int, error)
// SdBooted implements https://www.freedesktop.org/software/systemd/man/sd_booted.html
SdBooted() bool
}
diff --git a/internal/linux/std.go b/internal/linux/std.go
index e0a0994c..4ea5b903 100644
--- a/internal/linux/std.go
+++ b/internal/linux/std.go
@@ -7,6 +7,7 @@ import (
"os"
"os/exec"
"os/user"
+ "strconv"
"sync"
"git.ophivana.moe/security/fortify/internal"
@@ -21,41 +22,75 @@ type Std struct {
sdBooted bool
sdBootedOnce sync.Once
- fshim string
- fshimOnce 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) 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) { fmsg.Exit(code) }
-func (s *Std) Stdout() io.Writer { return os.Stdout }
+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) 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) Exit(code int) { fmsg.Exit(code) }
+func (s *Std) Stdout() io.Writer { return os.Stdout }
const xdgRuntimeDir = "XDG_RUNTIME_DIR"
-func (s *Std) FshimPath() string {
- s.fshimOnce.Do(func() {
- p, ok := internal.Path(internal.Fshim)
- if !ok {
- fmsg.Fatal("invalid fshim path, this copy of fortify is not compiled correctly")
- }
- s.fshim = p
- })
-
- return s.fshim
-}
-
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()
+ if u, ok := s.uidCopy[aid]; ok {
+ s.uidMu.RUnlock()
+ return u.uid, u.err
+ }
+
+ s.uidMu.RUnlock()
+ 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.Fatal("invalid fsu path, this copy of fshim is not compiled correctly")
+ panic("unreachable")
+ } 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
+ if p, u.err = cmd.Output(); u.err == nil {
+ u.uid, u.err = strconv.Atoi(string(p))
+ }
+ return u.uid, u.err
+ }
+}
+
func (s *Std) SdBooted() bool {
s.sdBootedOnce.Do(func() { s.sdBooted = copySdBooted() })
return s.sdBooted
diff --git a/internal/path.go b/internal/path.go
index 3bc28216..51388536 100644
--- a/internal/path.go
+++ b/internal/path.go
@@ -4,7 +4,6 @@ import "path"
var (
Fsu = compPoison
- Fshim = compPoison
Finit = compPoison
)
diff --git a/internal/state/print.go b/internal/state/print.go
index 8ca3529e..174f0693 100644
--- a/internal/state/print.go
+++ b/internal/state/print.go
@@ -67,10 +67,10 @@ func (s *simpleStore) mustPrintLauncherState(w **tabwriter.Writer, now time.Time
// write header when initialising
if !fmsg.Verbose() {
- _, _ = fmt.Fprintln(*w, "\tUID\tPID\tUptime\tEnablements\tMethod\tCommand")
+ _, _ = fmt.Fprintln(*w, "\tPID\tApp\tUptime\tEnablements\tCommand")
} else {
// argv is emitted in body when verbose
- _, _ = fmt.Fprintln(*w, "\tUID\tPID\tArgv")
+ _, _ = fmt.Fprintln(*w, "\tPID\tApp\tArgv")
}
}
@@ -96,13 +96,13 @@ func (s *simpleStore) mustPrintLauncherState(w **tabwriter.Writer, now time.Time
}
if !fmsg.Verbose() {
- _, _ = fmt.Fprintf(*w, "\t%s\t%d\t%s\t%s\t%s\t%s\n",
- s.path[len(s.path)-1], state.PID, now.Sub(state.Time).Round(time.Second).String(), strings.TrimPrefix(ets.String(), ", "), state.Method,
+ _, _ = fmt.Fprintf(*w, "\t%d\t%s\t%s\t%s\t%s\n",
+ state.PID, s.path[len(s.path)-1], now.Sub(state.Time).Round(time.Second).String(), strings.TrimPrefix(ets.String(), ", "),
state.Command)
} else {
// emit argv instead when verbose
- _, _ = fmt.Fprintf(*w, "\t%s\t%d\t%s\n",
- s.path[len(s.path)-1], state.PID, state.Argv)
+ _, _ = fmt.Fprintf(*w, "\t%d\t%s\t%s\n",
+ state.PID, s.path[len(s.path)-1], state.Argv)
}
}
diff --git a/internal/state/state.go b/internal/state/state.go
index 81df9655..ee5a95f7 100644
--- a/internal/state/state.go
+++ b/internal/state/state.go
@@ -33,8 +33,6 @@ type State struct {
// capability enablements applied to child
Capability system.Enablements
- // user switch method
- Method string
// full argv whe launching
Argv []string
// process start time