aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorOphestra <cat@gensokyo.uk>2025-09-25 13:46:21 +0900
committerOphestra <cat@gensokyo.uk>2025-09-25 13:51:54 +0900
commitae2df2c450dd655b93a950124b6ab16e2b4d967b (patch)
tree849ab40049228e17891351d97097f8c9d0e18c10
parent6e3f34f2ecfe0f3cd024a6e73bf6560080feac53 (diff)
internal: remove sys package
This package is replaced by container/stub. Remove and replace it with unexported implementation for the upcoming test suite rewrite. Signed-off-by: Ophestra <cat@gensokyo.uk>
-rw-r--r--cmd/hakurei/command.go11
-rw-r--r--cmd/hakurei/main.go3
-rw-r--r--cmd/hakurei/parse.go5
-rw-r--r--cmd/hakurei/print.go8
-rw-r--r--internal/app/app.go8
-rw-r--r--internal/app/app_stub_test.go177
-rw-r--r--internal/app/app_test.go91
-rw-r--r--internal/app/container.go41
-rw-r--r--internal/app/dispatcher.go99
-rw-r--r--internal/app/export_test.go16
-rw-r--r--internal/app/finalise.go (renamed from internal/app/seal.go)203
-rw-r--r--internal/app/hsu.go (renamed from internal/sys/hsu.go)40
-rw-r--r--internal/app/paths.go36
-rw-r--r--internal/app/process.go54
-rw-r--r--internal/sys/interface.go76
-rw-r--r--internal/sys/std.go44
-rw-r--r--test/test.py2
17 files changed, 494 insertions, 420 deletions
diff --git a/cmd/hakurei/command.go b/cmd/hakurei/command.go
index 0600a559..1671902e 100644
--- a/cmd/hakurei/command.go
+++ b/cmd/hakurei/command.go
@@ -18,7 +18,6 @@ import (
"hakurei.app/internal/app"
"hakurei.app/internal/app/state"
"hakurei.app/internal/hlog"
- "hakurei.app/internal/sys"
"hakurei.app/system"
"hakurei.app/system/dbus"
)
@@ -43,7 +42,7 @@ func buildCommand(ctx context.Context, out io.Writer) command.Command {
config := tryPath(args[0])
config.Args = append(config.Args, args[1:]...)
- app.Main(ctx, std, config)
+ app.Main(ctx, config)
panic("unreachable")
})
@@ -79,7 +78,7 @@ func buildCommand(ctx context.Context, out io.Writer) command.Command {
passwd *user.User
passwdOnce sync.Once
passwdFunc = func() {
- us := strconv.Itoa(sys.MustUid(std, flagIdentity))
+ us := strconv.Itoa(app.HsuUid(new(app.Hsu).MustID(), flagIdentity))
if u, err := user.LookupId(us); err != nil {
hlog.Verbosef("cannot look up uid %s", us)
passwd = &user.User{
@@ -163,7 +162,7 @@ func buildCommand(ctx context.Context, out io.Writer) command.Command {
}
}
- app.Main(ctx, std, config)
+ app.Main(ctx, config)
panic("unreachable")
}).
Flag(&flagDBusConfigSession, "dbus-config", command.StringFlag("builtin"),
@@ -219,7 +218,9 @@ func buildCommand(ctx context.Context, out io.Writer) command.Command {
{
var flagShort bool
c.NewCommand("ps", "List active instances", func(args []string) error {
- printPs(os.Stdout, time.Now().UTC(), state.NewMulti(std.Paths().RunDirPath.String()), flagShort, flagJSON)
+ var sc hst.Paths
+ app.CopyPaths(&sc, new(app.Hsu).MustID())
+ printPs(os.Stdout, time.Now().UTC(), state.NewMulti(sc.RunDirPath.String()), flagShort, flagJSON)
return errSuccess
}).Flag(&flagShort, "short", command.BoolFlag(false), "Print instance id")
}
diff --git a/cmd/hakurei/main.go b/cmd/hakurei/main.go
index b71f50c8..2b9193b6 100644
--- a/cmd/hakurei/main.go
+++ b/cmd/hakurei/main.go
@@ -15,7 +15,6 @@ import (
"hakurei.app/container"
"hakurei.app/internal"
"hakurei.app/internal/hlog"
- "hakurei.app/internal/sys"
)
var (
@@ -27,8 +26,6 @@ var (
func init() { hlog.Prepare("hakurei") }
-var std sys.State = new(sys.Std)
-
func main() {
// early init path, skips root check and duplicate PR_SET_DUMPABLE
container.TryArgv0(hlog.Output{}, hlog.Prepare, internal.InstallOutput)
diff --git a/cmd/hakurei/parse.go b/cmd/hakurei/parse.go
index 56ff83e1..897382ea 100644
--- a/cmd/hakurei/parse.go
+++ b/cmd/hakurei/parse.go
@@ -11,6 +11,7 @@ import (
"syscall"
"hakurei.app/hst"
+ "hakurei.app/internal/app"
"hakurei.app/internal/app/state"
"hakurei.app/internal/hlog"
)
@@ -87,7 +88,9 @@ func tryShort(name string) (config *hst.Config, entry *state.State) {
if likePrefix && len(name) >= 8 {
hlog.Verbose("argument looks like prefix")
- s := state.NewMulti(std.Paths().RunDirPath.String())
+ var sc hst.Paths
+ app.CopyPaths(&sc, new(app.Hsu).MustID())
+ s := state.NewMulti(sc.RunDirPath.String())
if entries, err := state.Join(s); err != nil {
log.Printf("cannot join store: %v", err)
// drop to fetch from file
diff --git a/cmd/hakurei/print.go b/cmd/hakurei/print.go
index 387b5c25..938517b8 100644
--- a/cmd/hakurei/print.go
+++ b/cmd/hakurei/print.go
@@ -12,8 +12,8 @@ import (
"time"
"hakurei.app/hst"
+ "hakurei.app/internal/app"
"hakurei.app/internal/app/state"
- "hakurei.app/internal/sys"
"hakurei.app/system/dbus"
)
@@ -21,10 +21,8 @@ func printShowSystem(output io.Writer, short, flagJSON bool) {
t := newPrinter(output)
defer t.MustFlush()
- info := &hst.Info{
- Paths: std.Paths(),
- User: sys.MustGetUserID(std),
- }
+ info := &hst.Info{User: new(app.Hsu).MustID()}
+ app.CopyPaths(&info.Paths, info.User)
if flagJSON {
printJSON(output, short, info)
diff --git a/internal/app/app.go b/internal/app/app.go
index 4ecf1cc0..b5331568 100644
--- a/internal/app/app.go
+++ b/internal/app/app.go
@@ -8,19 +8,17 @@ import (
"hakurei.app/hst"
"hakurei.app/internal/app/state"
- "hakurei.app/internal/sys"
)
// Main runs an app according to [hst.Config] and terminates. Main does not return.
-func Main(ctx context.Context, k sys.State, config *hst.Config) {
+func Main(ctx context.Context, config *hst.Config) {
var id state.ID
if err := state.NewAppID(&id); err != nil {
log.Fatal(err)
}
- var seal outcome
- seal.id = &stringPair[state.ID]{id, id.String()}
- if err := seal.finalise(ctx, k, config); err != nil {
+ seal := outcome{id: &stringPair[state.ID]{id, id.String()}, syscallDispatcher: direct{}}
+ if err := seal.finalise(ctx, config); err != nil {
printMessageError("cannot seal app:", err)
os.Exit(1)
}
diff --git a/internal/app/app_stub_test.go b/internal/app/app_stub_test.go
index f386eb01..ff218492 100644
--- a/internal/app/app_stub_test.go
+++ b/internal/app/app_stub_test.go
@@ -1,34 +1,24 @@
-package app_test
+package app
import (
"fmt"
"io/fs"
"log"
+ "os/exec"
"os/user"
- "strconv"
-
- "hakurei.app/hst"
)
-// fs methods are not implemented using a real FS
-// to help better understand filesystem access behaviour
type stubNixOS struct {
lookPathErr map[string]error
usernameErr map[string]error
}
-func (s *stubNixOS) Getuid() int { return 1971 }
-func (s *stubNixOS) Getgid() int { return 100 }
-func (s *stubNixOS) TempDir() string { return "/tmp" }
-func (s *stubNixOS) MustExecutable() string { return "/run/wrappers/bin/hakurei" }
-func (s *stubNixOS) Exit(code int) { panic("called exit on stub with code " + strconv.Itoa(code)) }
-func (s *stubNixOS) EvalSymlinks(path string) (string, error) { return path, nil }
-func (s *stubNixOS) Uid(aid int) (int, error) { return 1000000 + 0*10000 + aid, nil }
+func (k *stubNixOS) new(func(k syscallDispatcher)) { panic("not implemented") }
-func (s *stubNixOS) Println(v ...any) { log.Println(v...) }
-func (s *stubNixOS) Printf(format string, v ...any) { log.Printf(format, v...) }
+func (k *stubNixOS) getuid() int { return 1971 }
+func (k *stubNixOS) getgid() int { return 100 }
-func (s *stubNixOS) LookupEnv(key string) (string, bool) {
+func (k *stubNixOS) lookupEnv(key string) (string, bool) {
switch key {
case "SHELL":
return "/run/current-system/sw/bin/zsh", true
@@ -40,6 +30,8 @@ func (s *stubNixOS) LookupEnv(key string) (string, bool) {
return "", false
case "HOME":
return "/home/ophestra", true
+ case "XDG_RUNTIME_DIR":
+ return "/run/user/1971", true
case "XDG_CONFIG_HOME":
return "/home/ophestra/xdg/config", true
default:
@@ -47,35 +39,29 @@ func (s *stubNixOS) LookupEnv(key string) (string, bool) {
}
}
-func (s *stubNixOS) LookPath(file string) (string, error) {
- if s.lookPathErr != nil {
- if err, ok := s.lookPathErr[file]; ok {
- return "", err
- }
- }
-
- switch file {
- case "zsh":
- return "/run/current-system/sw/bin/zsh", nil
- default:
- panic(fmt.Sprintf("attempted to look up unexpected executable %q", file))
- }
-}
-
-func (s *stubNixOS) LookupGroup(name string) (*user.Group, error) {
+func (k *stubNixOS) stat(name string) (fs.FileInfo, error) {
switch name {
- case "video":
- return &user.Group{Gid: "26", Name: "video"}, nil
+ case "/var/run/nscd":
+ return nil, nil
+ case "/run/user/1971/pulse":
+ return nil, nil
+ case "/run/user/1971/pulse/native":
+ return stubFileInfoMode(0666), nil
+ case "/home/ophestra/.pulse-cookie":
+ return stubFileInfoIsDir(true), nil
+ case "/home/ophestra/xdg/config/pulse/cookie":
+ return stubFileInfoIsDir(false), nil
default:
- return nil, user.UnknownGroupError(name)
+ panic(fmt.Sprintf("attempted to stat unexpected path %q", name))
}
}
-func (s *stubNixOS) ReadDir(name string) ([]fs.DirEntry, error) {
+func (k *stubNixOS) readdir(name string) ([]fs.DirEntry, error) {
switch name {
case "/":
return stubDirEntries("bin", "boot", "dev", "etc", "home", "lib",
"lib64", "nix", "proc", "root", "run", "srv", "sys", "tmp", "usr", "var")
+
case "/run":
return stubDirEntries("agetty.reload", "binfmt", "booted-system",
"credentials", "cryptsetup", "current-system", "dbus", "host", "keys",
@@ -83,6 +69,7 @@ func (s *stubNixOS) ReadDir(name string) ([]fs.DirEntry, error) {
"nginx", "nixos", "nscd", "opengl-driver", "pppd", "resolvconf", "sddm",
"store", "syncoid", "system", "systemd", "tmpfiles.d", "udev", "udisks2",
"user", "utmp", "virtlogd.pid", "wrappers", "zed.pid", "zed.state")
+
case "/etc":
return stubDirEntries("alsa", "bashrc", "binfmt.d", "dbus-1", "default",
"ethertypes", "fonts", "fstab", "fuse.conf", "group", "host.conf", "hostid",
@@ -96,39 +83,117 @@ func (s *stubNixOS) ReadDir(name string) ([]fs.DirEntry, error) {
"ssl", "static", "subgid", "subuid", "sudoers", "sysctl.d", "systemd", "terminfo",
"tmpfiles.d", "udev", "udisks2", "UPower", "vconsole.conf", "X11", "zfs", "zinputrc",
"zoneinfo", "zprofile", "zshenv", "zshrc")
+
default:
panic(fmt.Sprintf("attempted to read unexpected directory %q", name))
}
}
-func (s *stubNixOS) Stat(name string) (fs.FileInfo, error) {
- switch name {
- case "/var/run/nscd":
- return nil, nil
- case "/run/user/1971/pulse":
- return nil, nil
- case "/run/user/1971/pulse/native":
- return stubFileInfoMode(0666), nil
- case "/home/ophestra/.pulse-cookie":
- return stubFileInfoIsDir(true), nil
- case "/home/ophestra/xdg/config/pulse/cookie":
- return stubFileInfoIsDir(false), nil
+func (k *stubNixOS) tempdir() string { return "/tmp/" }
+
+func (k *stubNixOS) evalSymlinks(path string) (string, error) {
+ switch path {
+ case "/run/user/1971":
+ return "/run/user/1971", nil
+ case "/tmp/hakurei.0":
+ return "/tmp/hakurei.0", nil
+ case "/run/dbus":
+ return "/run/dbus", nil
+ case "/dev/kvm":
+ return "/dev/kvm", nil
+ case "/etc/":
+ return "/etc/", nil
+ case "/bin":
+ return "/bin", nil
+ case "/boot":
+ return "/boot", nil
+ case "/home":
+ return "/home", nil
+ case "/lib":
+ return "/lib", nil
+ case "/lib64":
+ return "/lib64", nil
+ case "/nix":
+ return "/nix", nil
+ case "/root":
+ return "/root", nil
+ case "/run":
+ return "/run", nil
+ case "/srv":
+ return "/srv", nil
+ case "/sys":
+ return "/sys", nil
+ case "/usr":
+ return "/usr", nil
+ case "/var":
+ return "/var", nil
+ case "/dev/dri":
+ return "/dev/dri", nil
+ case "/usr/bin/":
+ return "/usr/bin/", nil
+ case "/nix/store":
+ return "/nix/store", nil
+ case "/run/current-system":
+ return "/nix/store/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa-nixos-system-satori-25.05.99999999.aaaaaaa", nil
+ case "/sys/block":
+ return "/sys/block", nil
+ case "/sys/bus":
+ return "/sys/bus", nil
+ case "/sys/class":
+ return "/sys/class", nil
+ case "/sys/dev":
+ return "/sys/dev", nil
+ case "/sys/devices":
+ return "/sys/devices", nil
+ case "/run/opengl-driver":
+ return "/nix/store/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa-graphics-drivers", nil
+ case "/var/lib/persist/module/hakurei/0/1":
+ return "/var/lib/persist/module/hakurei/0/1", nil
default:
- panic(fmt.Sprintf("attempted to stat unexpected path %q", name))
+ panic(fmt.Sprintf("attempted to evaluate unexpected path %q", path))
+ }
+}
+
+func (k *stubNixOS) lookPath(file string) (string, error) {
+ if k.lookPathErr != nil {
+ if err, ok := k.lookPathErr[file]; ok {
+ return "", err
+ }
+ }
+
+ switch file {
+ case "zsh":
+ return "/run/current-system/sw/bin/zsh", nil
+ default:
+ panic(fmt.Sprintf("attempted to look up unexpected executable %q", file))
}
}
-func (s *stubNixOS) Open(name string) (fs.File, error) {
+func (k *stubNixOS) lookupGroupId(name string) (string, error) {
switch name {
+ case "video":
+ return "26", nil
default:
- panic(fmt.Sprintf("attempted to open unexpected file %q", name))
+ return "", user.UnknownGroupError(name)
}
}
-func (s *stubNixOS) Paths() hst.Paths {
- return hst.Paths{
- SharePath: m("/tmp/hakurei.1971"),
- RuntimePath: m("/run/user/1971"),
- RunDirPath: m("/run/user/1971/hakurei"),
+func (k *stubNixOS) cmdOutput(cmd *exec.Cmd) ([]byte, error) {
+ switch cmd.Path {
+ case "/proc/nonexistent/hsu":
+ return []byte{'0'}, nil
+ default:
+ panic(fmt.Sprintf("unexpected cmd %#v", cmd))
}
}
+
+func (k *stubNixOS) overflowUid() int { return 65534 }
+func (k *stubNixOS) overflowGid() int { return 65534 }
+
+func (k *stubNixOS) mustHsuPath() string { return "/proc/nonexistent/hsu" }
+
+func (k *stubNixOS) fatalf(format string, v ...any) { panic(fmt.Sprintf(format, v...)) }
+
+func (k *stubNixOS) isVerbose() bool { return true }
+func (k *stubNixOS) verbose(v ...any) { log.Print(v...) }
+func (k *stubNixOS) verbosef(format string, v ...any) { log.Printf(format, v...) }
diff --git a/internal/app/app_test.go b/internal/app/app_test.go
index 9da72395..005b721d 100644
--- a/internal/app/app_test.go
+++ b/internal/app/app_test.go
@@ -1,4 +1,4 @@
-package app_test
+package app
import (
"context"
@@ -13,9 +13,7 @@ import (
"hakurei.app/container"
"hakurei.app/container/seccomp"
"hakurei.app/hst"
- "hakurei.app/internal/app"
"hakurei.app/internal/app/state"
- "hakurei.app/internal/sys"
"hakurei.app/system"
"hakurei.app/system/acl"
"hakurei.app/system/dbus"
@@ -24,7 +22,7 @@ import (
func TestApp(t *testing.T) {
testCases := []struct {
name string
- os sys.State
+ k syscallDispatcher
config *hst.Config
id state.ID
wantSys *system.I
@@ -40,11 +38,11 @@ func TestApp(t *testing.T) {
0xb9, 0xa6, 0x07, 0xac,
},
system.New(context.TODO(), 1000000).
- Ensure("/tmp/hakurei.1971", 0711).
- Ensure("/tmp/hakurei.1971/runtime", 0700).UpdatePermType(system.User, "/tmp/hakurei.1971/runtime", acl.Execute).
- Ensure("/tmp/hakurei.1971/runtime/0", 0700).UpdatePermType(system.User, "/tmp/hakurei.1971/runtime/0", acl.Read, acl.Write, acl.Execute).
- Ensure("/tmp/hakurei.1971/tmpdir", 0700).UpdatePermType(system.User, "/tmp/hakurei.1971/tmpdir", acl.Execute).
- Ensure("/tmp/hakurei.1971/tmpdir/0", 01700).UpdatePermType(system.User, "/tmp/hakurei.1971/tmpdir/0", acl.Read, acl.Write, acl.Execute),
+ Ensure("/tmp/hakurei.0", 0711).
+ Ensure("/tmp/hakurei.0/runtime", 0700).UpdatePermType(system.User, "/tmp/hakurei.0/runtime", acl.Execute).
+ Ensure("/tmp/hakurei.0/runtime/0", 0700).UpdatePermType(system.User, "/tmp/hakurei.0/runtime/0", acl.Read, acl.Write, acl.Execute).
+ Ensure("/tmp/hakurei.0/tmpdir", 0700).UpdatePermType(system.User, "/tmp/hakurei.0/tmpdir", acl.Execute).
+ Ensure("/tmp/hakurei.0/tmpdir/0", 01700).UpdatePermType(system.User, "/tmp/hakurei.0/tmpdir/0", acl.Read, acl.Write, acl.Execute),
&container.Params{
Dir: m("/home/chronos"),
Path: m("/run/current-system/sw/bin/zsh"),
@@ -71,8 +69,8 @@ func TestApp(t *testing.T) {
Tmpfs(m("/run/dbus"), 8192, 0755).
Remount(m("/dev/"), syscall.MS_RDONLY).
Tmpfs(m("/run/user/"), 4096, 0755).
- Bind(m("/tmp/hakurei.1971/runtime/0"), m("/run/user/65534"), container.BindWritable).
- Bind(m("/tmp/hakurei.1971/tmpdir/0"), m("/tmp/"), container.BindWritable).
+ Bind(m("/tmp/hakurei.0/runtime/0"), m("/run/user/65534"), container.BindWritable).
+ Bind(m("/tmp/hakurei.0/tmpdir/0"), m("/tmp/"), container.BindWritable).
Place(m("/etc/passwd"), []byte("chronos:x:65534:65534:Hakurei:/home/chronos:/run/current-system/sw/bin/zsh\n")).
Place(m("/etc/group"), []byte("hakurei:x:65534:\n")).
Remount(m("/"), syscall.MS_RDONLY),
@@ -132,19 +130,19 @@ func TestApp(t *testing.T) {
0x9b, 0x64, 0xce, 0x7c,
},
system.New(context.TODO(), 1000009).
- Ensure("/tmp/hakurei.1971", 0711).
- Ensure("/tmp/hakurei.1971/runtime", 0700).UpdatePermType(system.User, "/tmp/hakurei.1971/runtime", acl.Execute).
- Ensure("/tmp/hakurei.1971/runtime/9", 0700).UpdatePermType(system.User, "/tmp/hakurei.1971/runtime/9", acl.Read, acl.Write, acl.Execute).
- Ensure("/tmp/hakurei.1971/tmpdir", 0700).UpdatePermType(system.User, "/tmp/hakurei.1971/tmpdir", acl.Execute).
- Ensure("/tmp/hakurei.1971/tmpdir/9", 01700).UpdatePermType(system.User, "/tmp/hakurei.1971/tmpdir/9", acl.Read, acl.Write, acl.Execute).
- Ephemeral(system.Process, "/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c", 0711).
- Wayland(new(*os.File), "/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/wayland", "/run/user/1971/wayland-0", "org.chromium.Chromium", "ebf083d1b175911782d413369b64ce7c").
+ Ensure("/tmp/hakurei.0", 0711).
+ Ensure("/tmp/hakurei.0/runtime", 0700).UpdatePermType(system.User, "/tmp/hakurei.0/runtime", acl.Execute).
+ Ensure("/tmp/hakurei.0/runtime/9", 0700).UpdatePermType(system.User, "/tmp/hakurei.0/runtime/9", acl.Read, acl.Write, acl.Execute).
+ Ensure("/tmp/hakurei.0/tmpdir", 0700).UpdatePermType(system.User, "/tmp/hakurei.0/tmpdir", acl.Execute).
+ Ensure("/tmp/hakurei.0/tmpdir/9", 01700).UpdatePermType(system.User, "/tmp/hakurei.0/tmpdir/9", acl.Read, acl.Write, acl.Execute).
+ Ephemeral(system.Process, "/tmp/hakurei.0/ebf083d1b175911782d413369b64ce7c", 0711).
+ Wayland(new(*os.File), "/tmp/hakurei.0/ebf083d1b175911782d413369b64ce7c/wayland", "/run/user/1971/wayland-0", "org.chromium.Chromium", "ebf083d1b175911782d413369b64ce7c").
Ensure("/run/user/1971/hakurei", 0700).UpdatePermType(system.User, "/run/user/1971/hakurei", 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/hakurei/ebf083d1b175911782d413369b64ce7c", 0700).UpdatePermType(system.Process, "/run/user/1971/hakurei/ebf083d1b175911782d413369b64ce7c", acl.Execute).
Link("/run/user/1971/pulse/native", "/run/user/1971/hakurei/ebf083d1b175911782d413369b64ce7c/pulse").
CopyFile(new([]byte), "/home/ophestra/xdg/config/pulse/cookie", 256, 256).
- MustProxyDBus("/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/bus", &dbus.Config{
+ MustProxyDBus("/tmp/hakurei.0/ebf083d1b175911782d413369b64ce7c/bus", &dbus.Config{
Talk: []string{
"org.freedesktop.Notifications",
"org.freedesktop.FileManager1",
@@ -166,7 +164,7 @@ func TestApp(t *testing.T) {
"org.freedesktop.portal.*": "@/org/freedesktop/portal/*",
},
Filter: true,
- }, "/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/system_bus_socket", &dbus.Config{
+ }, "/tmp/hakurei.0/ebf083d1b175911782d413369b64ce7c/system_bus_socket", &dbus.Config{
Talk: []string{
"org.bluez",
"org.freedesktop.Avahi",
@@ -174,8 +172,8 @@ func TestApp(t *testing.T) {
},
Filter: true,
}).
- UpdatePerm("/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/bus", acl.Read, acl.Write).
- UpdatePerm("/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/system_bus_socket", acl.Read, acl.Write),
+ UpdatePerm("/tmp/hakurei.0/ebf083d1b175911782d413369b64ce7c/bus", acl.Read, acl.Write).
+ UpdatePerm("/tmp/hakurei.0/ebf083d1b175911782d413369b64ce7c/system_bus_socket", acl.Read, acl.Write),
&container.Params{
Dir: m("/home/chronos"),
Path: m("/run/current-system/sw/bin/zsh"),
@@ -208,15 +206,15 @@ func TestApp(t *testing.T) {
Tmpfs(m("/run/dbus"), 8192, 0755).
Remount(m("/dev/"), syscall.MS_RDONLY).
Tmpfs(m("/run/user/"), 4096, 0755).
- Bind(m("/tmp/hakurei.1971/runtime/9"), m("/run/user/65534"), container.BindWritable).
- Bind(m("/tmp/hakurei.1971/tmpdir/9"), m("/tmp/"), container.BindWritable).
+ Bind(m("/tmp/hakurei.0/runtime/9"), m("/run/user/65534"), container.BindWritable).
+ Bind(m("/tmp/hakurei.0/tmpdir/9"), m("/tmp/"), container.BindWritable).
Place(m("/etc/passwd"), []byte("chronos:x:65534:65534:Hakurei:/home/chronos:/run/current-system/sw/bin/zsh\n")).
Place(m("/etc/group"), []byte("hakurei:x:65534:\n")).
- Bind(m("/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/wayland"), m("/run/user/65534/wayland-0"), 0).
+ Bind(m("/tmp/hakurei.0/ebf083d1b175911782d413369b64ce7c/wayland"), m("/run/user/65534/wayland-0"), 0).
Bind(m("/run/user/1971/hakurei/ebf083d1b175911782d413369b64ce7c/pulse"), m("/run/user/65534/pulse/native"), 0).
Place(m(hst.Tmp+"/pulse-cookie"), nil).
- Bind(m("/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/bus"), m("/run/user/65534/bus"), 0).
- Bind(m("/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/system_bus_socket"), m("/run/dbus/system_bus_socket"), 0).
+ Bind(m("/tmp/hakurei.0/ebf083d1b175911782d413369b64ce7c/bus"), m("/run/user/65534/bus"), 0).
+ Bind(m("/tmp/hakurei.0/ebf083d1b175911782d413369b64ce7c/system_bus_socket"), m("/run/dbus/system_bus_socket"), 0).
Remount(m("/"), syscall.MS_RDONLY),
SeccompPresets: seccomp.PresetExt | seccomp.PresetDenyDevel,
HostNet: true,
@@ -283,19 +281,19 @@ func TestApp(t *testing.T) {
0xb4, 0x6e, 0xb5, 0xc1,
},
system.New(context.TODO(), 1000001).
- Ensure("/tmp/hakurei.1971", 0711).
- Ensure("/tmp/hakurei.1971/runtime", 0700).UpdatePermType(system.User, "/tmp/hakurei.1971/runtime", acl.Execute).
- Ensure("/tmp/hakurei.1971/runtime/1", 0700).UpdatePermType(system.User, "/tmp/hakurei.1971/runtime/1", acl.Read, acl.Write, acl.Execute).
- Ensure("/tmp/hakurei.1971/tmpdir", 0700).UpdatePermType(system.User, "/tmp/hakurei.1971/tmpdir", acl.Execute).
- Ensure("/tmp/hakurei.1971/tmpdir/1", 01700).UpdatePermType(system.User, "/tmp/hakurei.1971/tmpdir/1", acl.Read, acl.Write, acl.Execute).
+ Ensure("/tmp/hakurei.0", 0711).
+ Ensure("/tmp/hakurei.0/runtime", 0700).UpdatePermType(system.User, "/tmp/hakurei.0/runtime", acl.Execute).
+ Ensure("/tmp/hakurei.0/runtime/1", 0700).UpdatePermType(system.User, "/tmp/hakurei.0/runtime/1", acl.Read, acl.Write, acl.Execute).
+ Ensure("/tmp/hakurei.0/tmpdir", 0700).UpdatePermType(system.User, "/tmp/hakurei.0/tmpdir", acl.Execute).
+ Ensure("/tmp/hakurei.0/tmpdir/1", 01700).UpdatePermType(system.User, "/tmp/hakurei.0/tmpdir/1", acl.Read, acl.Write, acl.Execute).
Ensure("/run/user/1971/hakurei", 0700).UpdatePermType(system.User, "/run/user/1971/hakurei", 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
UpdatePermType(system.EWayland, "/run/user/1971/wayland-0", acl.Read, acl.Write, acl.Execute).
Ephemeral(system.Process, "/run/user/1971/hakurei/8e2c76b066dabe574cf073bdb46eb5c1", 0700).UpdatePermType(system.Process, "/run/user/1971/hakurei/8e2c76b066dabe574cf073bdb46eb5c1", acl.Execute).
Link("/run/user/1971/pulse/native", "/run/user/1971/hakurei/8e2c76b066dabe574cf073bdb46eb5c1/pulse").
CopyFile(nil, "/home/ophestra/xdg/config/pulse/cookie", 256, 256).
- Ephemeral(system.Process, "/tmp/hakurei.1971/8e2c76b066dabe574cf073bdb46eb5c1", 0711).
- MustProxyDBus("/tmp/hakurei.1971/8e2c76b066dabe574cf073bdb46eb5c1/bus", &dbus.Config{
+ Ephemeral(system.Process, "/tmp/hakurei.0/8e2c76b066dabe574cf073bdb46eb5c1", 0711).
+ MustProxyDBus("/tmp/hakurei.0/8e2c76b066dabe574cf073bdb46eb5c1/bus", &dbus.Config{
Talk: []string{
"org.freedesktop.FileManager1", "org.freedesktop.Notifications",
"org.freedesktop.ScreenSaver", "org.freedesktop.secrets",
@@ -308,7 +306,7 @@ func TestApp(t *testing.T) {
},
Call: map[string]string{}, Broadcast: map[string]string{},
Filter: true,
- }, "/tmp/hakurei.1971/8e2c76b066dabe574cf073bdb46eb5c1/system_bus_socket", &dbus.Config{
+ }, "/tmp/hakurei.0/8e2c76b066dabe574cf073bdb46eb5c1/system_bus_socket", &dbus.Config{
Talk: []string{
"org.bluez",
"org.freedesktop.Avahi",
@@ -316,8 +314,8 @@ func TestApp(t *testing.T) {
},
Filter: true,
}).
- UpdatePerm("/tmp/hakurei.1971/8e2c76b066dabe574cf073bdb46eb5c1/bus", acl.Read, acl.Write).
- UpdatePerm("/tmp/hakurei.1971/8e2c76b066dabe574cf073bdb46eb5c1/system_bus_socket", acl.Read, acl.Write),
+ UpdatePerm("/tmp/hakurei.0/8e2c76b066dabe574cf073bdb46eb5c1/bus", acl.Read, acl.Write).
+ UpdatePerm("/tmp/hakurei.0/8e2c76b066dabe574cf073bdb46eb5c1/system_bus_socket", acl.Read, acl.Write),
&container.Params{
Uid: 1971,
Gid: 100,
@@ -358,15 +356,15 @@ func TestApp(t *testing.T) {
Bind(m("/var/lib/persist/module/hakurei/0/1"), m("/var/lib/persist/module/hakurei/0/1"), container.BindWritable|container.BindEnsure).
Remount(m("/dev/"), syscall.MS_RDONLY).
Tmpfs(m("/run/user/"), 4096, 0755).
- Bind(m("/tmp/hakurei.1971/runtime/1"), m("/run/user/1971"), container.BindWritable).
- Bind(m("/tmp/hakurei.1971/tmpdir/1"), m("/tmp/"), container.BindWritable).
+ Bind(m("/tmp/hakurei.0/runtime/1"), m("/run/user/1971"), container.BindWritable).
+ Bind(m("/tmp/hakurei.0/tmpdir/1"), m("/tmp/"), container.BindWritable).
Place(m("/etc/passwd"), []byte("u0_a1:x:1971:100:Hakurei:/var/lib/persist/module/hakurei/0/1:/run/current-system/sw/bin/zsh\n")).
Place(m("/etc/group"), []byte("hakurei:x:100:\n")).
Bind(m("/run/user/1971/wayland-0"), m("/run/user/1971/wayland-0"), 0).
Bind(m("/run/user/1971/hakurei/8e2c76b066dabe574cf073bdb46eb5c1/pulse"), m("/run/user/1971/pulse/native"), 0).
Place(m(hst.Tmp+"/pulse-cookie"), nil).
- Bind(m("/tmp/hakurei.1971/8e2c76b066dabe574cf073bdb46eb5c1/bus"), m("/run/user/1971/bus"), 0).
- Bind(m("/tmp/hakurei.1971/8e2c76b066dabe574cf073bdb46eb5c1/system_bus_socket"), m("/run/dbus/system_bus_socket"), 0).
+ Bind(m("/tmp/hakurei.0/8e2c76b066dabe574cf073bdb46eb5c1/bus"), m("/run/user/1971/bus"), 0).
+ Bind(m("/tmp/hakurei.0/8e2c76b066dabe574cf073bdb46eb5c1/system_bus_socket"), m("/run/dbus/system_bus_socket"), 0).
Remount(m("/"), syscall.MS_RDONLY),
SeccompPresets: seccomp.PresetExt | seccomp.PresetDenyTTY | seccomp.PresetDenyDevel,
HostNet: true,
@@ -378,7 +376,8 @@ func TestApp(t *testing.T) {
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
t.Run("finalise", func(t *testing.T) {
- sys, params, err := app.FinaliseIParams(t.Context(), tc.os, tc.config, &tc.id)
+ seal := outcome{syscallDispatcher: tc.k, id: &stringPair[state.ID]{tc.id, tc.id.String()}}
+ err := seal.finalise(t.Context(), tc.config)
if err != nil {
if s, ok := container.GetErrorMessage(err); !ok {
t.Fatalf("Seal: error = %v", err)
@@ -388,14 +387,14 @@ func TestApp(t *testing.T) {
}
t.Run("sys", func(t *testing.T) {
- if !sys.Equal(tc.wantSys) {
- t.Errorf("Seal: sys = %#v, want %#v", sys, tc.wantSys)
+ if !seal.sys.Equal(tc.wantSys) {
+ t.Errorf("Seal: sys = %#v, want %#v", seal.sys, tc.wantSys)
}
})
t.Run("params", func(t *testing.T) {
- if !reflect.DeepEqual(params, tc.wantParams) {
- t.Errorf("seal: params =\n%s\n, want\n%s", mustMarshal(params), mustMarshal(tc.wantParams))
+ if !reflect.DeepEqual(seal.container, tc.wantParams) {
+ t.Errorf("seal: container =\n%s\n, want\n%s", mustMarshal(seal.container), mustMarshal(tc.wantParams))
}
})
})
diff --git a/internal/app/container.go b/internal/app/container.go
index 26bda265..d406d211 100644
--- a/internal/app/container.go
+++ b/internal/app/container.go
@@ -11,7 +11,6 @@ import (
"hakurei.app/container"
"hakurei.app/container/seccomp"
"hakurei.app/hst"
- "hakurei.app/internal/sys"
"hakurei.app/system/dbus"
)
@@ -20,7 +19,13 @@ const preallocateOpsCount = 1 << 5
// newContainer initialises [container.Params] via [hst.ContainerConfig].
// Note that remaining container setup must be queued by the caller.
-func newContainer(s *hst.ContainerConfig, os sys.State, prefix string, uid, gid *int) (*container.Params, map[string]string, error) {
+func newContainer(
+ k syscallDispatcher,
+ s *hst.ContainerConfig,
+ prefix string,
+ sc *hst.Paths,
+ uid, gid *int,
+) (*container.Params, map[string]string, error) {
if s == nil {
return nil, nil, newWithMessage("invalid container configuration")
}
@@ -38,9 +43,7 @@ func newContainer(s *hst.ContainerConfig, os sys.State, prefix string, uid, gid
ForwardCancel: s.WaitDelay >= 0,
}
- as := &hst.ApplyState{
- AutoEtcPrefix: prefix,
- }
+ as := &hst.ApplyState{AutoEtcPrefix: prefix}
{
ops := make(container.Ops, 0, preallocateOpsCount+len(s.Filesystem))
params.Ops = &ops
@@ -65,13 +68,13 @@ func newContainer(s *hst.ContainerConfig, os sys.State, prefix string, uid, gid
}
if s.MapRealUID {
- params.Uid = os.Getuid()
+ params.Uid = k.getuid()
*uid = params.Uid
- params.Gid = os.Getgid()
+ params.Gid = k.getgid()
*gid = params.Gid
} else {
- *uid = container.OverflowUid()
- *gid = container.OverflowGid()
+ *uid = k.overflowUid()
+ *gid = k.overflowGid()
}
filesystem := s.Filesystem
@@ -107,7 +110,6 @@ func newContainer(s *hst.ContainerConfig, os sys.State, prefix string, uid, gid
to warn about issues in custom configuration; it is NOT a security feature
and should not be treated as such, ALWAYS be careful with what you bind */
var hidePaths []string
- sc := os.Paths()
hidePaths = append(hidePaths, sc.RuntimePath.String(), sc.SharePath.String())
_, systemBusAddr := dbus.Address()
if entries, err := dbus.Parse([]byte(systemBusAddr)); err != nil {
@@ -124,11 +126,11 @@ func newContainer(s *hst.ContainerConfig, os sys.State, prefix string, uid, gid
// get parent dir of socket
dir := path.Dir(pair[1])
if dir == "." || dir == container.FHSRoot {
- os.Printf("dbus socket %q is in an unusual location", pair[1])
+ k.verbosef("dbus socket %q is in an unusual location", pair[1])
}
hidePaths = append(hidePaths, dir)
} else {
- os.Printf("dbus socket %q is not absolute", pair[1])
+ k.verbosef("dbus socket %q is not absolute", pair[1])
}
}
}
@@ -136,7 +138,7 @@ func newContainer(s *hst.ContainerConfig, os sys.State, prefix string, uid, gid
}
hidePathMatch := make([]bool, len(hidePaths))
for i := range hidePaths {
- if err := evalSymlinks(os, &hidePaths[i]); err != nil {
+ if err := evalSymlinks(k, &hidePaths[i]); err != nil {
return nil, nil, err
}
}
@@ -155,7 +157,7 @@ func newContainer(s *hst.ContainerConfig, os sys.State, prefix string, uid, gid
// AutoRootOp is a collection of many BindMountOp internally
var autoRootEntries []fs.DirEntry
if autoroot != nil {
- if d, err := os.ReadDir(autoroot.Source.String()); err != nil {
+ if d, err := k.readdir(autoroot.Source.String()); err != nil {
return nil, nil, err
} else {
// autoroot counter
@@ -191,7 +193,7 @@ func newContainer(s *hst.ContainerConfig, os sys.State, prefix string, uid, gid
}
hidePathSourceEval[i] = [2]string{a.String(), a.String()}
- if err := evalSymlinks(os, &hidePathSourceEval[i][0]); err != nil {
+ if err := evalSymlinks(k, &hidePathSourceEval[i][0]); err != nil {
return nil, nil, err
}
}
@@ -207,7 +209,7 @@ func newContainer(s *hst.ContainerConfig, os sys.State, prefix string, uid, gid
return nil, nil, err
} else if ok {
hidePathMatch[i] = true
- os.Printf("hiding path %q from %q", hidePaths[i], p[1])
+ k.verbosef("hiding path %q from %q", hidePaths[i], p[1])
}
}
}
@@ -238,12 +240,13 @@ func newContainer(s *hst.ContainerConfig, os sys.State, prefix string, uid, gid
return params, maps.Clone(s.Env), nil
}
-func evalSymlinks(os sys.State, v *string) error {
- if p, err := os.EvalSymlinks(*v); err != nil {
+// evalSymlinks calls syscallDispatcher.evalSymlinks but discards errors unwrapping to [fs.ErrNotExist].
+func evalSymlinks(k syscallDispatcher, v *string) error {
+ if p, err := k.evalSymlinks(*v); err != nil {
if !errors.Is(err, fs.ErrNotExist) {
return err
}
- os.Printf("path %q does not yet exist", *v)
+ k.verbosef("path %q does not yet exist", *v)
} else {
*v = p
}
diff --git a/internal/app/dispatcher.go b/internal/app/dispatcher.go
new file mode 100644
index 00000000..deecfa92
--- /dev/null
+++ b/internal/app/dispatcher.go
@@ -0,0 +1,99 @@
+package app
+
+import (
+ "log"
+ "os"
+ "os/exec"
+ "os/user"
+ "path/filepath"
+
+ "hakurei.app/container"
+ "hakurei.app/internal"
+ "hakurei.app/internal/hlog"
+)
+
+// syscallDispatcher provides methods that make state-dependent system calls as part of their behaviour.
+type syscallDispatcher interface {
+ // new starts a goroutine with a new instance of syscallDispatcher.
+ // A syscallDispatcher must never be used in any goroutine other than the one owning it,
+ // just synchronising access is not enough, as this is for test instrumentation.
+ new(f func(k syscallDispatcher))
+
+ // getuid provides [os.Getuid].
+ getuid() int
+ // getgid provides [os.Getgid].
+ getgid() int
+ // lookupEnv provides [os.LookupEnv].
+ lookupEnv(key string) (string, bool)
+ // stat provides [os.Stat].
+ stat(name string) (os.FileInfo, error)
+ // readdir provides [os.ReadDir].
+ readdir(name string) ([]os.DirEntry, error)
+ // tempdir provides [os.TempDir].
+ tempdir() string
+
+ // evalSymlinks provides [filepath.EvalSymlinks].
+ evalSymlinks(path string) (string, error)
+
+ // lookPath provides exec.LookPath.
+ lookPath(file string) (string, error)
+
+ // lookupGroupId calls [user.LookupGroup] and returns the Gid field of the resulting [user.Group] struct.
+ lookupGroupId(name string) (string, error)
+
+ // cmdOutput provides the Output method of [exec.Cmd].
+ cmdOutput(cmd *exec.Cmd) ([]byte, error)
+
+ // overflowUid provides [container.OverflowUid].
+ overflowUid() int
+ // overflowGid provides [container.OverflowGid].
+ overflowGid() int
+
+ // mustHsuPath provides [internal.MustHsuPath].
+ mustHsuPath() string
+
+ // fatalf provides [log.Fatalf].
+ fatalf(format string, v ...any)
+
+ isVerbose() bool
+ verbose(v ...any)
+ verbosef(format string, v ...any)
+}
+
+// direct implements syscallDispatcher on the current kernel.
+type direct struct{}
+
+func (k direct) new(f func(k syscallDispatcher)) { go f(k) }
+
+func (direct) getuid() int { return os.Getuid() }
+func (direct) getgid() int { return os.Getgid() }
+func (direct) lookupEnv(key string) (string, bool) { return os.LookupEnv(key) }
+func (direct) stat(name string) (os.FileInfo, error) { return os.Stat(name) }
+func (direct) readdir(name string) ([]os.DirEntry, error) { return os.ReadDir(name) }
+func (direct) tempdir() string { return os.TempDir() }
+
+func (direct) evalSymlinks(path string) (string, error) { return filepath.EvalSymlinks(path) }
+
+func (direct) lookPath(file string) (string, error) { return exec.LookPath(file) }
+
+func (direct) lookupGroupId(name string) (gid string, err error) {
+ var group *user.Group
+ group, err = user.LookupGroup(name)
+ if group != nil {
+ gid = group.Gid
+ }
+ return
+}
+
+func (direct) cmdOutput(cmd *exec.Cmd) ([]byte, error) { return cmd.Output() }
+
+func (direct) overflowUid() int { return container.OverflowUid() }
+func (direct) overflowGid() int { return container.OverflowGid() }
+
+func (direct) mustHsuPath() string { return internal.MustHsuPath() }
+
+func (direct) fatalf(format string, v ...any) { log.Fatalf(format, v...) }
+
+func (k direct) isVerbose() bool { return hlog.Load() }
+func (direct) verbose(v ...any) { hlog.Verbose(v...) }
+func (direct) verbosef(format string, v ...any) { hlog.Verbosef(format, v...) }
diff --git a/internal/app/export_test.go b/internal/app/export_test.go
deleted file mode 100644
index 1bfba48a..00000000
--- a/internal/app/export_test.go
+++ /dev/null
@@ -1,16 +0,0 @@
-package app
-
-import (
- "context"
-
- "hakurei.app/container"
- "hakurei.app/hst"
- "hakurei.app/internal/app/state"
- "hakurei.app/internal/sys"
- "hakurei.app/system"
-)
-
-func FinaliseIParams(ctx context.Context, k sys.State, config *hst.Config, id *state.ID) (*system.I, *container.Params, error) {
- seal := outcome{id: &stringPair[state.ID]{*id, id.String()}}
- return seal.sys, seal.container, seal.finalise(ctx, k, config)
-}
diff --git a/internal/app/seal.go b/internal/app/finalise.go
index c7dd9d63..2a0e5e6e 100644
--- a/internal/app/seal.go
+++ b/internal/app/finalise.go
@@ -21,7 +21,6 @@ import (
"hakurei.app/hst"
"hakurei.app/internal/app/state"
"hakurei.app/internal/hlog"
- "hakurei.app/internal/sys"
"hakurei.app/system"
"hakurei.app/system/acl"
"hakurei.app/system/dbus"
@@ -54,8 +53,9 @@ type outcome struct {
container *container.Params
env map[string]string
sync *os.File
+ active atomic.Bool
- f atomic.Bool
+ syscallDispatcher
}
// shareHost holds optional share directory state that must not be accessed directly
@@ -120,7 +120,7 @@ type hsuUser struct {
username string
}
-func (seal *outcome) finalise(ctx context.Context, k sys.State, config *hst.Config) error {
+func (k *outcome) finalise(ctx context.Context, config *hst.Config) error {
const (
home = "HOME"
shell = "SHELL"
@@ -144,14 +144,13 @@ func (seal *outcome) finalise(ctx context.Context, k sys.State, config *hst.Conf
// unreachable
panic("invalid call to finalise")
}
- if seal.ctx != nil {
+ if k.ctx != nil {
// unreachable
panic("attempting to finalise twice")
}
- seal.ctx = ctx
+ k.ctx = ctx
if config == nil {
- // unreachable
return newWithMessage("invalid configuration")
}
if config.Home == nil {
@@ -164,7 +163,7 @@ func (seal *outcome) finalise(ctx context.Context, k sys.State, config *hst.Conf
if err := gob.NewEncoder(ct).Encode(config); err != nil {
return &hst.AppError{Step: "encode initial config", Err: err}
}
- seal.ct = ct
+ k.ct = ct
}
// allowed identity range 0 to 9999, this is checked again in hsu
@@ -172,22 +171,23 @@ func (seal *outcome) finalise(ctx context.Context, k sys.State, config *hst.Conf
return newWithMessage(fmt.Sprintf("identity %d out of range", config.Identity))
}
- seal.user = hsuUser{
+ k.user = hsuUser{
identity: newInt(config.Identity),
home: config.Home,
username: config.Username,
}
- if seal.user.username == "" {
- seal.user.username = "chronos"
- } else if !isValidUsername(seal.user.username) {
- return newWithMessage(fmt.Sprintf("invalid user name %q", seal.user.username))
+ hsu := Hsu{k: k}
+ if k.user.username == "" {
+ k.user.username = "chronos"
+ } else if !isValidUsername(k.user.username) {
+ return newWithMessage(fmt.Sprintf("invalid user name %q", k.user.username))
}
- seal.user.uid = newInt(sys.MustUid(k, seal.user.identity.unwrap()))
+ k.user.uid = newInt(HsuUid(hsu.MustID(), k.user.identity.unwrap()))
- seal.user.supp = make([]string, len(config.Groups))
+ k.user.supp = make([]string, len(config.Groups))
for i, name := range config.Groups {
- if g, err := k.LookupGroup(name); err != nil {
+ if gid, err := k.lookupGroupId(name); err != nil {
var unknownGroupError user.UnknownGroupError
if errors.As(err, &unknownGroupError) {
return newWithMessageError(fmt.Sprintf("unknown group %q", name), unknownGroupError)
@@ -195,7 +195,7 @@ func (seal *outcome) finalise(ctx context.Context, k sys.State, config *hst.Conf
return &hst.AppError{Step: "look up group by name", Err: err}
}
} else {
- seal.user.supp[i] = g.Gid
+ k.user.supp[i] = gid
}
}
@@ -205,7 +205,7 @@ func (seal *outcome) finalise(ctx context.Context, k sys.State, config *hst.Conf
if config.Shell == nil {
config.Shell = container.AbsFHSRoot.Append("bin", "sh")
- s, _ := k.LookupEnv(shell)
+ s, _ := k.lookupEnv(shell)
if a, err := container.NewAbs(s); err == nil {
config.Shell = a
}
@@ -214,7 +214,7 @@ func (seal *outcome) finalise(ctx context.Context, k sys.State, config *hst.Conf
// hsu clears the environment so resolve paths early
if config.Path == nil {
if len(config.Args) > 0 {
- if p, err := k.LookPath(config.Args[0]); err != nil {
+ if p, err := k.lookPath(config.Args[0]); err != nil {
return &hst.AppError{Step: "look up executable file", Err: err}
} else if config.Path, err = container.NewAbs(p); err != nil {
return newWithMessageError(err.Error(), err)
@@ -250,7 +250,7 @@ func (seal *outcome) finalise(ctx context.Context, k sys.State, config *hst.Conf
// hide nscd from container if present
nscd := container.AbsFHSVar.Append("run/nscd")
- if _, err := k.Stat(nscd.String()); !errors.Is(err, fs.ErrNotExist) {
+ if _, err := k.stat(nscd.String()); !errors.Is(err, fs.ErrNotExist) {
conf.Filesystem = append(conf.Filesystem, hst.FilesystemConfigJSON{FilesystemConfig: &hst.FSEphemeral{Target: nscd}})
}
@@ -274,86 +274,89 @@ func (seal *outcome) finalise(ctx context.Context, k sys.State, config *hst.Conf
return newWithMessage("invalid program path")
}
+ // TODO(ophestra): revert this after params to shim
+ share := &shareHost{seal: k}
+ copyPaths(k.syscallDispatcher, &share.sc, hsu.MustID())
+
var mapuid, mapgid *stringPair[int]
{
var uid, gid int
var err error
- seal.container, seal.env, err = newContainer(config.Container, k, seal.id.String(), &uid, &gid)
- seal.waitDelay = config.Container.WaitDelay
+ k.container, k.env, err = newContainer(k, config.Container, k.id.String(), &share.sc, &uid, &gid)
+ k.waitDelay = config.Container.WaitDelay
if err != nil {
return &hst.AppError{Step: "initialise container configuration", Err: err}
}
if len(config.Args) == 0 {
config.Args = []string{config.Path.String()}
}
- seal.container.Path = config.Path
- seal.container.Args = config.Args
+ k.container.Path = config.Path
+ k.container.Args = config.Args
mapuid = newInt(uid)
mapgid = newInt(gid)
- if seal.env == nil {
- seal.env = make(map[string]string, 1<<6)
+ if k.env == nil {
+ k.env = make(map[string]string, 1<<6)
}
}
// inner XDG_RUNTIME_DIR default formatting of `/run/user/%d` as mapped uid
innerRuntimeDir := container.AbsFHSRunUser.Append(mapuid.String())
- seal.env[xdgRuntimeDir] = innerRuntimeDir.String()
- seal.env[xdgSessionClass] = "user"
- seal.env[xdgSessionType] = "tty"
+ k.env[xdgRuntimeDir] = innerRuntimeDir.String()
+ k.env[xdgSessionClass] = "user"
+ k.env[xdgSessionType] = "tty"
- share := &shareHost{seal: seal, sc: k.Paths()}
- seal.runDirPath = share.sc.RunDirPath
- seal.sys = system.New(seal.ctx, seal.user.uid.unwrap())
- seal.sys.Ensure(share.sc.SharePath.String(), 0711)
+ k.runDirPath = share.sc.RunDirPath
+ k.sys = system.New(k.ctx, k.user.uid.unwrap())
+ k.sys.Ensure(share.sc.SharePath.String(), 0711)
{
runtimeDir := share.sc.SharePath.Append("runtime")
- seal.sys.Ensure(runtimeDir.String(), 0700)
- seal.sys.UpdatePermType(system.User, runtimeDir.String(), acl.Execute)
- runtimeDirInst := runtimeDir.Append(seal.user.identity.String())
- seal.sys.Ensure(runtimeDirInst.String(), 0700)
- seal.sys.UpdatePermType(system.User, runtimeDirInst.String(), acl.Read, acl.Write, acl.Execute)
- seal.container.Tmpfs(container.AbsFHSRunUser, 1<<12, 0755)
- seal.container.Bind(runtimeDirInst, innerRuntimeDir, container.BindWritable)
+ k.sys.Ensure(runtimeDir.String(), 0700)
+ k.sys.UpdatePermType(system.User, runtimeDir.String(), acl.Execute)
+ runtimeDirInst := runtimeDir.Append(k.user.identity.String())
+ k.sys.Ensure(runtimeDirInst.String(), 0700)
+ k.sys.UpdatePermType(system.User, runtimeDirInst.String(), acl.Read, acl.Write, acl.Execute)
+ k.container.Tmpfs(container.AbsFHSRunUser, 1<<12, 0755)
+ k.container.Bind(runtimeDirInst, innerRuntimeDir, container.BindWritable)
}
{
tmpdir := share.sc.SharePath.Append("tmpdir")
- seal.sys.Ensure(tmpdir.String(), 0700)
- seal.sys.UpdatePermType(system.User, tmpdir.String(), acl.Execute)
- tmpdirInst := tmpdir.Append(seal.user.identity.String())
- seal.sys.Ensure(tmpdirInst.String(), 01700)
- seal.sys.UpdatePermType(system.User, tmpdirInst.String(), acl.Read, acl.Write, acl.Execute)
+ k.sys.Ensure(tmpdir.String(), 0700)
+ k.sys.UpdatePermType(system.User, tmpdir.String(), acl.Execute)
+ tmpdirInst := tmpdir.Append(k.user.identity.String())
+ k.sys.Ensure(tmpdirInst.String(), 01700)
+ k.sys.UpdatePermType(system.User, tmpdirInst.String(), acl.Read, acl.Write, acl.Execute)
// mount inner /tmp from share so it shares persistence and storage behaviour of host /tmp
- seal.container.Bind(tmpdirInst, container.AbsFHSTmp, container.BindWritable)
+ k.container.Bind(tmpdirInst, container.AbsFHSTmp, container.BindWritable)
}
{
username := "chronos"
- if seal.user.username != "" {
- username = seal.user.username
+ if k.user.username != "" {
+ username = k.user.username
}
- seal.container.Dir = seal.user.home
- seal.env["HOME"] = seal.user.home.String()
- seal.env["USER"] = username
- seal.env[shell] = config.Shell.String()
+ k.container.Dir = k.user.home
+ k.env["HOME"] = k.user.home.String()
+ k.env["USER"] = username
+ k.env[shell] = config.Shell.String()
- seal.container.Place(container.AbsFHSEtc.Append("passwd"),
- []byte(username+":x:"+mapuid.String()+":"+mapgid.String()+":Hakurei:"+seal.user.home.String()+":"+config.Shell.String()+"\n"))
- seal.container.Place(container.AbsFHSEtc.Append("group"),
+ k.container.Place(container.AbsFHSEtc.Append("passwd"),
+ []byte(username+":x:"+mapuid.String()+":"+mapgid.String()+":Hakurei:"+k.user.home.String()+":"+config.Shell.String()+"\n"))
+ k.container.Place(container.AbsFHSEtc.Append("group"),
[]byte("hakurei:x:"+mapgid.String()+":\n"))
}
// pass TERM for proper terminal I/O in initial process
- if t, ok := k.LookupEnv(term); ok {
- seal.env[term] = t
+ if t, ok := k.lookupEnv(term); ok {
+ k.env[term] = t
}
if config.Enablements.Unwrap()&system.EWayland != 0 {
// outer wayland socket (usually `/run/user/%d/wayland-%d`)
var socketPath *container.Absolute
- if name, ok := k.LookupEnv(wayland.WaylandDisplay); !ok {
+ if name, ok := k.lookupEnv(wayland.WaylandDisplay); !ok {
hlog.Verbose(wayland.WaylandDisplay + " is not set, assuming " + wayland.FallbackName)
socketPath = share.sc.RuntimePath.Append(wayland.FallbackName)
} else if a, err := container.NewAbs(name); err != nil {
@@ -363,28 +366,28 @@ func (seal *outcome) finalise(ctx context.Context, k sys.State, config *hst.Conf
}
innerPath := innerRuntimeDir.Append(wayland.FallbackName)
- seal.env[wayland.WaylandDisplay] = wayland.FallbackName
+ k.env[wayland.WaylandDisplay] = wayland.FallbackName
if !config.DirectWayland { // set up security-context-v1
appID := config.ID
if appID == "" {
// use instance ID in case app id is not set
- appID = "app.hakurei." + seal.id.String()
+ appID = "app.hakurei." + k.id.String()
}
// downstream socket paths
outerPath := share.instance().Append("wayland")
- seal.sys.Wayland(&seal.sync, outerPath.String(), socketPath.String(), appID, seal.id.String())
- seal.container.Bind(outerPath, innerPath, 0)
+ k.sys.Wayland(&k.sync, outerPath.String(), socketPath.String(), appID, k.id.String())
+ k.container.Bind(outerPath, innerPath, 0)
} else { // bind mount wayland socket (insecure)
hlog.Verbose("direct wayland access, PROCEED WITH CAUTION")
share.ensureRuntimeDir()
- seal.container.Bind(socketPath, innerPath, 0)
- seal.sys.UpdatePermType(system.EWayland, socketPath.String(), acl.Read, acl.Write, acl.Execute)
+ k.container.Bind(socketPath, innerPath, 0)
+ k.sys.UpdatePermType(system.EWayland, socketPath.String(), acl.Read, acl.Write, acl.Execute)
}
}
if config.Enablements.Unwrap()&system.EX11 != 0 {
- if d, ok := k.LookupEnv(display); !ok {
+ if d, ok := k.lookupEnv(display); !ok {
return newWithMessage("DISPLAY is not set")
} else {
socketDir := container.AbsFHSTmp.Append(".X11-unix")
@@ -402,21 +405,21 @@ func (seal *outcome) finalise(ctx context.Context, k sys.State, config *hst.Conf
}
}
if socketPath != nil {
- if _, err := k.Stat(socketPath.String()); err != nil {
+ if _, err := k.stat(socketPath.String()); err != nil {
if !errors.Is(err, fs.ErrNotExist) {
return &hst.AppError{Step: fmt.Sprintf("access X11 socket %q", socketPath), Err: err}
}
} else {
- seal.sys.UpdatePermType(system.EX11, socketPath.String(), acl.Read, acl.Write, acl.Execute)
+ k.sys.UpdatePermType(system.EX11, socketPath.String(), acl.Read, acl.Write, acl.Execute)
if !config.Container.HostAbstract {
d = "unix:" + socketPath.String()
}
}
}
- seal.sys.ChangeHosts("#" + seal.user.uid.String())
- seal.env[display] = d
- seal.container.Bind(socketDir, socketDir, 0)
+ k.sys.ChangeHosts("#" + k.user.uid.String())
+ k.env[display] = d
+ k.container.Bind(socketDir, socketDir, 0)
}
}
@@ -426,14 +429,14 @@ func (seal *outcome) finalise(ctx context.Context, k sys.State, config *hst.Conf
// PulseAudio socket (usually `/run/user/%d/pulse/native`)
pulseSocket := pulseRuntimeDir.Append("native")
- if _, err := k.Stat(pulseRuntimeDir.String()); err != nil {
+ if _, err := k.stat(pulseRuntimeDir.String()); err != nil {
if !errors.Is(err, fs.ErrNotExist) {
return &hst.AppError{Step: fmt.Sprintf("access PulseAudio directory %q", pulseRuntimeDir), Err: err}
}
return newWithMessage(fmt.Sprintf("PulseAudio directory %q not found", pulseRuntimeDir))
}
- if s, err := k.Stat(pulseSocket.String()); err != nil {
+ if s, err := k.stat(pulseSocket.String()); err != nil {
if !errors.Is(err, fs.ErrNotExist) {
return &hst.AppError{Step: fmt.Sprintf("access PulseAudio socket %q", pulseSocket), Err: err}
}
@@ -447,9 +450,9 @@ func (seal *outcome) finalise(ctx context.Context, k sys.State, config *hst.Conf
// hard link pulse socket into target-executable share
innerPulseRuntimeDir := share.runtime().Append("pulse")
innerPulseSocket := innerRuntimeDir.Append("pulse", "native")
- seal.sys.Link(pulseSocket.String(), innerPulseRuntimeDir.String())
- seal.container.Bind(innerPulseRuntimeDir, innerPulseSocket, 0)
- seal.env[pulseServer] = "unix:" + innerPulseSocket.String()
+ k.sys.Link(pulseSocket.String(), innerPulseRuntimeDir.String())
+ k.container.Bind(innerPulseRuntimeDir, innerPulseSocket, 0)
+ k.env[pulseServer] = "unix:" + innerPulseSocket.String()
// publish current user's pulse cookie for target user
var paCookiePath *container.Absolute
@@ -457,7 +460,7 @@ func (seal *outcome) finalise(ctx context.Context, k sys.State, config *hst.Conf
const paLocateStep = "locate PulseAudio cookie"
// from environment
- if p, ok := k.LookupEnv(pulseCookie); ok {
+ if p, ok := k.lookupEnv(pulseCookie); ok {
if a, err := container.NewAbs(p); err != nil {
return &hst.AppError{Step: paLocateStep, Err: err}
} else {
@@ -468,14 +471,14 @@ func (seal *outcome) finalise(ctx context.Context, k sys.State, config *hst.Conf
}
// $HOME/.pulse-cookie
- if p, ok := k.LookupEnv(home); ok {
+ if p, ok := k.lookupEnv(home); ok {
if a, err := container.NewAbs(p); err != nil {
return &hst.AppError{Step: paLocateStep, Err: err}
} else {
paCookiePath = a.Append(".pulse-cookie")
}
- if s, err := k.Stat(paCookiePath.String()); err != nil {
+ if s, err := k.stat(paCookiePath.String()); err != nil {
paCookiePath = nil
if !errors.Is(err, fs.ErrNotExist) {
return &hst.AppError{Step: "access PulseAudio cookie", Err: err}
@@ -489,13 +492,13 @@ func (seal *outcome) finalise(ctx context.Context, k sys.State, config *hst.Conf
}
// $XDG_CONFIG_HOME/pulse/cookie
- if p, ok := k.LookupEnv(xdgConfigHome); ok {
+ if p, ok := k.lookupEnv(xdgConfigHome); ok {
if a, err := container.NewAbs(p); err != nil {
return &hst.AppError{Step: paLocateStep, Err: err}
} else {
paCookiePath = a.Append("pulse", "cookie")
}
- if s, err := k.Stat(paCookiePath.String()); err != nil {
+ if s, err := k.stat(paCookiePath.String()); err != nil {
paCookiePath = nil
if !errors.Is(err, fs.ErrNotExist) {
return &hst.AppError{Step: "access PulseAudio cookie", Err: err}
@@ -512,10 +515,10 @@ func (seal *outcome) finalise(ctx context.Context, k sys.State, config *hst.Conf
if paCookiePath != nil {
innerDst := hst.AbsTmp.Append("/pulse-cookie")
- seal.env[pulseCookie] = innerDst.String()
+ k.env[pulseCookie] = innerDst.String()
var payload *[]byte
- seal.container.PlaceP(innerDst, &payload)
- seal.sys.CopyFile(payload, paCookiePath.String(), 256, 256)
+ k.container.PlaceP(innerDst, &payload)
+ k.sys.CopyFile(payload, paCookiePath.String(), 256, 256)
} else {
hlog.Verbose("cannot locate PulseAudio cookie (tried " +
"$PULSE_COOKIE, " +
@@ -534,30 +537,30 @@ func (seal *outcome) finalise(ctx context.Context, k sys.State, config *hst.Conf
sessionPath, systemPath := share.instance().Append("bus"), share.instance().Append("system_bus_socket")
// configure dbus proxy
- if f, err := seal.sys.ProxyDBus(
+ if f, err := k.sys.ProxyDBus(
config.SessionBus, config.SystemBus,
sessionPath.String(), systemPath.String(),
); err != nil {
return err
} else {
- seal.dbusMsg = f
+ k.dbusMsg = f
}
// share proxy sockets
sessionInner := innerRuntimeDir.Append("bus")
- seal.env[dbusSessionBusAddress] = "unix:path=" + sessionInner.String()
- seal.container.Bind(sessionPath, sessionInner, 0)
- seal.sys.UpdatePerm(sessionPath.String(), acl.Read, acl.Write)
+ k.env[dbusSessionBusAddress] = "unix:path=" + sessionInner.String()
+ k.container.Bind(sessionPath, sessionInner, 0)
+ k.sys.UpdatePerm(sessionPath.String(), acl.Read, acl.Write)
if config.SystemBus != nil {
systemInner := container.AbsFHSRun.Append("dbus/system_bus_socket")
- seal.env[dbusSystemBusAddress] = "unix:path=" + systemInner.String()
- seal.container.Bind(systemPath, systemInner, 0)
- seal.sys.UpdatePerm(systemPath.String(), acl.Read, acl.Write)
+ k.env[dbusSystemBusAddress] = "unix:path=" + systemInner.String()
+ k.container.Bind(systemPath, systemInner, 0)
+ k.sys.UpdatePerm(systemPath.String(), acl.Read, acl.Write)
}
}
// mount root read-only as the final setup Op
- seal.container.Remount(container.AbsFHSRoot, syscall.MS_RDONLY)
+ k.container.Remount(container.AbsFHSRoot, syscall.MS_RDONLY)
// append ExtraPerms last
for _, p := range config.ExtraPerms {
@@ -566,7 +569,7 @@ func (seal *outcome) finalise(ctx context.Context, k sys.State, config *hst.Conf
}
if p.Ensure {
- seal.sys.Ensure(p.Path.String(), 0700)
+ k.sys.Ensure(p.Path.String(), 0700)
}
perms := make(acl.Perms, 0, 3)
@@ -579,23 +582,23 @@ func (seal *outcome) finalise(ctx context.Context, k sys.State, config *hst.Conf
if p.Execute {
perms = append(perms, acl.Execute)
}
- seal.sys.UpdatePermType(system.User, p.Path.String(), perms...)
+ k.sys.UpdatePermType(system.User, p.Path.String(), perms...)
}
// flatten and sort env for deterministic behaviour
- seal.container.Env = make([]string, 0, len(seal.env))
- for k, v := range seal.env {
- if strings.IndexByte(k, '=') != -1 {
+ k.container.Env = make([]string, 0, len(k.env))
+ for key, value := range k.env {
+ if strings.IndexByte(key, '=') != -1 {
return &hst.AppError{Step: "flatten environment", Err: syscall.EINVAL,
- Msg: fmt.Sprintf("invalid environment variable %s", k)}
+ Msg: fmt.Sprintf("invalid environment variable %s", key)}
}
- seal.container.Env = append(seal.container.Env, k+"="+v)
+ k.container.Env = append(k.container.Env, key+"="+value)
}
- slices.Sort(seal.container.Env)
+ slices.Sort(k.container.Env)
if hlog.Load() {
hlog.Verbosef("created application seal for uid %s (%s) groups: %v, argv: %s, ops: %d",
- seal.user.uid, seal.user.username, config.Groups, seal.container.Args, len(*seal.container.Ops))
+ k.user.uid, k.user.username, config.Groups, k.container.Args, len(*k.container.Ops))
}
return nil
diff --git a/internal/sys/hsu.go b/internal/app/hsu.go
index 5ae12046..0078ef0d 100644
--- a/internal/sys/hsu.go
+++ b/internal/app/hsu.go
@@ -1,4 +1,4 @@
-package sys
+package app
import (
"errors"
@@ -11,7 +11,6 @@ import (
"hakurei.app/container"
"hakurei.app/hst"
- "hakurei.app/internal"
"hakurei.app/internal/hlog"
)
@@ -20,15 +19,28 @@ type Hsu struct {
idOnce sync.Once
idErr error
id int
+
+ kOnce sync.Once
+ k syscallDispatcher
}
var ErrHsuAccess = errors.New("current user is not in the hsurc file")
+// ensureDispatcher ensures Hsu.k is not nil.
+func (h *Hsu) ensureDispatcher() {
+ h.kOnce.Do(func() {
+ if h.k == nil {
+ h.k = direct{}
+ }
+ })
+}
+
// ID returns the current user hsurc identifier. ErrHsuAccess is returned if the current user is not in hsurc.
func (h *Hsu) ID() (int, error) {
+ h.ensureDispatcher()
h.idOnce.Do(func() {
h.id = -1
- hsuPath := internal.MustHsuPath()
+ hsuPath := h.k.mustHsuPath()
cmd := exec.Command(hsuPath)
cmd.Path = hsuPath
@@ -41,7 +53,7 @@ func (h *Hsu) ID() (int, error) {
)
const step = "obtain uid from hsu"
- if p, h.idErr = cmd.Output(); h.idErr == nil {
+ if p, h.idErr = h.k.cmdOutput(cmd); h.idErr == nil {
h.id, h.idErr = strconv.Atoi(string(p))
if h.idErr != nil {
h.idErr = &hst.AppError{Step: step, Err: h.idErr, Msg: "invalid uid string from hsu"}
@@ -58,22 +70,14 @@ func (h *Hsu) ID() (int, error) {
return h.id, h.idErr
}
-func (h *Hsu) Uid(identity int) (int, error) {
+// MustID calls [Hsu.ID] and terminates on error.
+func (h *Hsu) MustID() int {
id, err := h.ID()
if err == nil {
- return 1000000 + id*10000 + identity, nil
+ return id
}
- return id, err
-}
-// MustUid calls [State.Uid] and terminates on error.
-func MustUid(s State, identity int) int {
- uid, err := s.Uid(identity)
- if err == nil {
- return uid
- }
-
- const fallback = "cannot obtain uid from setuid wrapper:"
+ const fallback = "cannot retrieve user id from setuid wrapper:"
if errors.Is(err, ErrHsuAccess) {
hlog.Verbose("*"+fallback, err)
os.Exit(1)
@@ -86,3 +90,7 @@ func MustUid(s State, identity int) int {
return -0xdeadbeef
}
}
+
+// HsuUid returns target uid for the stable hsu uid format.
+// No bounds check is performed, a value retrieved from hsu is expected.
+func HsuUid(id, identity int) int { return 1000000 + id*10000 + identity }
diff --git a/internal/app/paths.go b/internal/app/paths.go
new file mode 100644
index 00000000..3316a22e
--- /dev/null
+++ b/internal/app/paths.go
@@ -0,0 +1,36 @@
+package app
+
+import (
+ "strconv"
+
+ "hakurei.app/container"
+ "hakurei.app/hst"
+)
+
+// CopyPaths populates a [hst.Paths] struct.
+func CopyPaths(v *hst.Paths, userid int) { copyPaths(direct{}, v, userid) }
+
+// copyPaths populates a [hst.Paths] struct.
+func copyPaths(k syscallDispatcher, v *hst.Paths, userid int) {
+ const xdgRuntimeDir = "XDG_RUNTIME_DIR"
+
+ if tempDir, err := container.NewAbs(k.tempdir()); err != nil {
+ k.fatalf("invalid TMPDIR: %v", err)
+ } else {
+ v.TempDir = tempDir
+ }
+
+ v.SharePath = v.TempDir.Append("hakurei." + strconv.Itoa(userid))
+ k.verbosef("process share directory at %q", v.SharePath)
+
+ r, _ := k.lookupEnv(xdgRuntimeDir)
+ if a, err := container.NewAbs(r); err != nil {
+ // fall back to path in share since hakurei has no hard XDG dependency
+ v.RunDirPath = v.SharePath.Append("run")
+ v.RuntimePath = v.RunDirPath.Append("compat")
+ } else {
+ v.RuntimePath = a
+ v.RunDirPath = v.RuntimePath.Append("hakurei")
+ }
+ k.verbosef("runtime directory at %q", v.RunDirPath)
+}
diff --git a/internal/app/process.go b/internal/app/process.go
index 368b655f..8ac05855 100644
--- a/internal/app/process.go
+++ b/internal/app/process.go
@@ -33,12 +33,12 @@ type mainState struct {
// Time is nil if no process was ever created.
Time *time.Time
- seal *outcome
store state.Store
cancel context.CancelFunc
cmd *exec.Cmd
cmdWait chan error
+ k *outcome
uintptr
}
@@ -79,13 +79,13 @@ func (ms mainState) beforeExit(isFault bool) {
waitDone := make(chan struct{})
// TODO(ophestra): enforce this limit early so it does not have to be done twice
shimTimeoutCompensated := shimWaitTimeout
- if ms.seal.waitDelay > MaxShimWaitDelay {
+ if ms.k.waitDelay > MaxShimWaitDelay {
shimTimeoutCompensated += MaxShimWaitDelay
} else {
- shimTimeoutCompensated += ms.seal.waitDelay
+ shimTimeoutCompensated += ms.k.waitDelay
}
// this ties waitDone to ctx with the additional compensated timeout duration
- go func() { <-ms.seal.ctx.Done(); time.Sleep(shimTimeoutCompensated); close(waitDone) }()
+ go func() { <-ms.k.ctx.Done(); time.Sleep(shimTimeoutCompensated); close(waitDone) }()
select {
case err := <-ms.cmdWait:
@@ -128,20 +128,20 @@ func (ms mainState) beforeExit(isFault bool) {
}
hlog.Resume()
- if ms.seal.sync != nil {
- if err := ms.seal.sync.Close(); err != nil {
+ if ms.k.sync != nil {
+ if err := ms.k.sync.Close(); err != nil {
perror(err, "close wayland security context")
}
}
- if ms.seal.dbusMsg != nil {
- ms.seal.dbusMsg()
+ if ms.k.dbusMsg != nil {
+ ms.k.dbusMsg()
}
}
if ms.uintptr&mainNeedsRevert != 0 {
- if ok, err := ms.store.Do(ms.seal.user.identity.unwrap(), func(c state.Cursor) {
+ if ok, err := ms.store.Do(ms.k.user.identity.unwrap(), func(c state.Cursor) {
if ms.uintptr&mainNeedsDestroy != 0 {
- if err := c.Destroy(ms.seal.id.unwrap()); err != nil {
+ if err := c.Destroy(ms.k.id.unwrap()); err != nil {
perror(err, "destroy state entry")
}
}
@@ -151,7 +151,7 @@ func (ms mainState) beforeExit(isFault bool) {
// it is impossible to continue from this point;
// revert per-process state here to limit damage
ec := system.Process
- if revertErr := ms.seal.sys.Revert((*system.Criteria)(&ec)); revertErr != nil {
+ if revertErr := ms.k.sys.Revert((*system.Criteria)(&ec)); revertErr != nil {
var joinError interface {
Unwrap() []error
error
@@ -189,7 +189,7 @@ func (ms mainState) beforeExit(isFault bool) {
}
}
- if err = ms.seal.sys.Revert((*system.Criteria)(&ec)); err != nil {
+ if err = ms.k.sys.Revert((*system.Criteria)(&ec)); err != nil {
perror(err, "revert system setup")
}
}
@@ -219,8 +219,8 @@ func (ms mainState) fatal(fallback string, ferr error) {
}
// main carries out outcome and terminates. main does not return.
-func (seal *outcome) main() {
- if !seal.f.CompareAndSwap(false, true) {
+func (k *outcome) main() {
+ if !k.active.CompareAndSwap(false, true) {
panic("outcome: attempted to run twice")
}
@@ -228,15 +228,15 @@ func (seal *outcome) main() {
hsuPath := internal.MustHsuPath()
// ms.beforeExit required beyond this point
- ms := &mainState{seal: seal}
+ ms := &mainState{k: k}
- if err := seal.sys.Commit(); err != nil {
+ if err := k.sys.Commit(); err != nil {
ms.fatal("cannot commit system setup:", err)
}
ms.uintptr |= mainNeedsRevert
- ms.store = state.NewMulti(seal.runDirPath.String())
+ ms.store = state.NewMulti(k.runDirPath.String())
- ctx, cancel := context.WithCancel(seal.ctx)
+ ctx, cancel := context.WithCancel(k.ctx)
defer cancel()
ms.cancel = cancel
@@ -255,14 +255,14 @@ func (seal *outcome) main() {
// passed through to shim by hsu
shimEnv + "=" + strconv.Itoa(fd),
// interpreted by hsu
- "HAKUREI_IDENTITY=" + seal.user.identity.String(),
+ "HAKUREI_IDENTITY=" + k.user.identity.String(),
}
}
- if len(seal.user.supp) > 0 {
- hlog.Verbosef("attaching supplementary group ids %s", seal.user.supp)
+ if len(k.user.supp) > 0 {
+ hlog.Verbosef("attaching supplementary group ids %s", k.user.supp)
// interpreted by hsu
- ms.cmd.Env = append(ms.cmd.Env, "HAKUREI_GROUPS="+strings.Join(seal.user.supp, " "))
+ ms.cmd.Env = append(ms.cmd.Env, "HAKUREI_GROUPS="+strings.Join(k.user.supp, " "))
}
hlog.Verbosef("setuid helper at %s", hsuPath)
@@ -284,8 +284,8 @@ func (seal *outcome) main() {
go func() {
setupErr <- e.Encode(&shimParams{
os.Getpid(),
- seal.waitDelay,
- seal.container,
+ k.waitDelay,
+ k.container,
hlog.Load(),
})
}()
@@ -302,12 +302,12 @@ func (seal *outcome) main() {
}
// shim accepted setup payload, create process state
- if ok, err := ms.store.Do(seal.user.identity.unwrap(), func(c state.Cursor) {
+ if ok, err := ms.store.Do(k.user.identity.unwrap(), func(c state.Cursor) {
if err := c.Save(&state.State{
- ID: seal.id.unwrap(),
+ ID: k.id.unwrap(),
PID: ms.cmd.Process.Pid,
Time: *ms.Time,
- }, seal.ct); err != nil {
+ }, k.ct); err != nil {
ms.fatal("cannot save state entry:", err)
}
}); err != nil {
diff --git a/internal/sys/interface.go b/internal/sys/interface.go
deleted file mode 100644
index f2465e62..00000000
--- a/internal/sys/interface.go
+++ /dev/null
@@ -1,76 +0,0 @@
-// Package sys wraps OS interaction library functions.
-package sys
-
-import (
- "io/fs"
- "log"
- "os/user"
- "strconv"
-
- "hakurei.app/container"
- "hakurei.app/hst"
- "hakurei.app/internal/hlog"
-)
-
-// State provides safe interaction with operating system state.
-type State interface {
- // Getuid provides [os.Getuid].
- Getuid() int
- // Getgid provides [os.Getgid].
- Getgid() 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 [container.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)
-
- Println(v ...any)
- Printf(format string, v ...any)
-
- // Paths returns a populated [hst.Paths] struct.
- Paths() hst.Paths
- // Uid invokes hsu and returns target uid.
- // Any errors returned by Uid is already wrapped [hlog.BaseError].
- Uid(identity int) (int, error)
-}
-
-// MustGetUserID obtains user id from hsu by querying uid of identity 0.
-func MustGetUserID(os State) int { return (MustUid(os, 0) / 10000) - 100 }
-
-// CopyPaths is a generic implementation of [hst.Paths].
-func CopyPaths(os State, v *hst.Paths, userid int) {
- if tempDir, err := container.NewAbs(os.TempDir()); err != nil {
- log.Fatalf("invalid TMPDIR: %v", err)
- } else {
- v.TempDir = tempDir
- }
-
- v.SharePath = v.TempDir.Append("hakurei." + strconv.Itoa(userid))
- hlog.Verbosef("process share directory at %q", v.SharePath)
-
- r, _ := os.LookupEnv(xdgRuntimeDir)
- if a, err := container.NewAbs(r); err != nil {
- // fall back to path in share since hakurei has no hard XDG dependency
- v.RunDirPath = v.SharePath.Append("run")
- v.RuntimePath = v.RunDirPath.Append("compat")
- } else {
- v.RuntimePath = a
- v.RunDirPath = v.RuntimePath.Append("hakurei")
- }
- hlog.Verbosef("runtime directory at %q", v.RunDirPath)
-}
diff --git a/internal/sys/std.go b/internal/sys/std.go
deleted file mode 100644
index 11334c72..00000000
--- a/internal/sys/std.go
+++ /dev/null
@@ -1,44 +0,0 @@
-package sys
-
-import (
- "io/fs"
- "os"
- "os/exec"
- "os/user"
- "path/filepath"
- "sync"
-
- "hakurei.app/container"
- "hakurei.app/hst"
- "hakurei.app/internal"
- "hakurei.app/internal/hlog"
-)
-
-// Std implements System using the standard library.
-type Std struct {
- paths hst.Paths
- pathsOnce sync.Once
- Hsu
-}
-
-func (s *Std) Getuid() int { return os.Getuid() }
-func (s *Std) Getgid() int { return os.Getgid() }
-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 container.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) }
-func (s *Std) Println(v ...any) { hlog.Verbose(v...) }
-func (s *Std) Printf(format string, v ...any) { hlog.Verbosef(format, v...) }
-
-const xdgRuntimeDir = "XDG_RUNTIME_DIR"
-
-func (s *Std) Paths() hst.Paths {
- s.pathsOnce.Do(func() { CopyPaths(s, &s.paths, MustGetUserID(s)) })
- return s.paths
-}
diff --git a/test/test.py b/test/test.py
index ae4dfdac..a1e6b0f5 100644
--- a/test/test.py
+++ b/test/test.py
@@ -100,7 +100,7 @@ print(machine.fail("sudo -u alice -i hsu"))
# Verify hsu fault behaviour:
if denyOutput != "hsu: uid 1001 is not in the hsurc file\n":
raise Exception(f"unexpected deny output:\n{denyOutput}")
-if denyOutputVerbose != "hsu: uid 1001 is not in the hsurc file\nhakurei: *cannot obtain uid from setuid wrapper: current user is not in the hsurc file\n":
+if denyOutputVerbose != "hsu: uid 1001 is not in the hsurc file\nhakurei: *cannot retrieve user id from setuid wrapper: current user is not in the hsurc file\n":
raise Exception(f"unexpected deny verbose output:\n{denyOutputVerbose}")
# Verify timeout behaviour: