internal: remove sys package
All checks were successful
Test / Create distribution (push) Successful in 33s
Test / Sandbox (push) Successful in 2m13s
Test / Hakurei (push) Successful in 3m14s
Test / Hpkg (push) Successful in 4m2s
Test / Sandbox (race detector) (push) Successful in 4m39s
Test / Hakurei (race detector) (push) Successful in 5m19s
Test / Flake checks (push) Successful in 1m19s
All checks were successful
Test / Create distribution (push) Successful in 33s
Test / Sandbox (push) Successful in 2m13s
Test / Hakurei (push) Successful in 3m14s
Test / Hpkg (push) Successful in 4m2s
Test / Sandbox (race detector) (push) Successful in 4m39s
Test / Hakurei (race detector) (push) Successful in 5m19s
Test / Flake checks (push) Successful in 1m19s
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>
This commit is contained in:
parent
6e3f34f2ec
commit
ae2df2c450
@ -18,7 +18,6 @@ import (
|
|||||||
"hakurei.app/internal/app"
|
"hakurei.app/internal/app"
|
||||||
"hakurei.app/internal/app/state"
|
"hakurei.app/internal/app/state"
|
||||||
"hakurei.app/internal/hlog"
|
"hakurei.app/internal/hlog"
|
||||||
"hakurei.app/internal/sys"
|
|
||||||
"hakurei.app/system"
|
"hakurei.app/system"
|
||||||
"hakurei.app/system/dbus"
|
"hakurei.app/system/dbus"
|
||||||
)
|
)
|
||||||
@ -43,7 +42,7 @@ func buildCommand(ctx context.Context, out io.Writer) command.Command {
|
|||||||
config := tryPath(args[0])
|
config := tryPath(args[0])
|
||||||
config.Args = append(config.Args, args[1:]...)
|
config.Args = append(config.Args, args[1:]...)
|
||||||
|
|
||||||
app.Main(ctx, std, config)
|
app.Main(ctx, config)
|
||||||
panic("unreachable")
|
panic("unreachable")
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -79,7 +78,7 @@ func buildCommand(ctx context.Context, out io.Writer) command.Command {
|
|||||||
passwd *user.User
|
passwd *user.User
|
||||||
passwdOnce sync.Once
|
passwdOnce sync.Once
|
||||||
passwdFunc = func() {
|
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 {
|
if u, err := user.LookupId(us); err != nil {
|
||||||
hlog.Verbosef("cannot look up uid %s", us)
|
hlog.Verbosef("cannot look up uid %s", us)
|
||||||
passwd = &user.User{
|
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")
|
panic("unreachable")
|
||||||
}).
|
}).
|
||||||
Flag(&flagDBusConfigSession, "dbus-config", command.StringFlag("builtin"),
|
Flag(&flagDBusConfigSession, "dbus-config", command.StringFlag("builtin"),
|
||||||
@ -219,7 +218,9 @@ func buildCommand(ctx context.Context, out io.Writer) command.Command {
|
|||||||
{
|
{
|
||||||
var flagShort bool
|
var flagShort bool
|
||||||
c.NewCommand("ps", "List active instances", func(args []string) error {
|
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
|
return errSuccess
|
||||||
}).Flag(&flagShort, "short", command.BoolFlag(false), "Print instance id")
|
}).Flag(&flagShort, "short", command.BoolFlag(false), "Print instance id")
|
||||||
}
|
}
|
||||||
|
@ -15,7 +15,6 @@ import (
|
|||||||
"hakurei.app/container"
|
"hakurei.app/container"
|
||||||
"hakurei.app/internal"
|
"hakurei.app/internal"
|
||||||
"hakurei.app/internal/hlog"
|
"hakurei.app/internal/hlog"
|
||||||
"hakurei.app/internal/sys"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -27,8 +26,6 @@ var (
|
|||||||
|
|
||||||
func init() { hlog.Prepare("hakurei") }
|
func init() { hlog.Prepare("hakurei") }
|
||||||
|
|
||||||
var std sys.State = new(sys.Std)
|
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
// early init path, skips root check and duplicate PR_SET_DUMPABLE
|
// early init path, skips root check and duplicate PR_SET_DUMPABLE
|
||||||
container.TryArgv0(hlog.Output{}, hlog.Prepare, internal.InstallOutput)
|
container.TryArgv0(hlog.Output{}, hlog.Prepare, internal.InstallOutput)
|
||||||
|
@ -11,6 +11,7 @@ import (
|
|||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"hakurei.app/hst"
|
"hakurei.app/hst"
|
||||||
|
"hakurei.app/internal/app"
|
||||||
"hakurei.app/internal/app/state"
|
"hakurei.app/internal/app/state"
|
||||||
"hakurei.app/internal/hlog"
|
"hakurei.app/internal/hlog"
|
||||||
)
|
)
|
||||||
@ -87,7 +88,9 @@ func tryShort(name string) (config *hst.Config, entry *state.State) {
|
|||||||
if likePrefix && len(name) >= 8 {
|
if likePrefix && len(name) >= 8 {
|
||||||
hlog.Verbose("argument looks like prefix")
|
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 {
|
if entries, err := state.Join(s); err != nil {
|
||||||
log.Printf("cannot join store: %v", err)
|
log.Printf("cannot join store: %v", err)
|
||||||
// drop to fetch from file
|
// drop to fetch from file
|
||||||
|
@ -12,8 +12,8 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"hakurei.app/hst"
|
"hakurei.app/hst"
|
||||||
|
"hakurei.app/internal/app"
|
||||||
"hakurei.app/internal/app/state"
|
"hakurei.app/internal/app/state"
|
||||||
"hakurei.app/internal/sys"
|
|
||||||
"hakurei.app/system/dbus"
|
"hakurei.app/system/dbus"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -21,10 +21,8 @@ func printShowSystem(output io.Writer, short, flagJSON bool) {
|
|||||||
t := newPrinter(output)
|
t := newPrinter(output)
|
||||||
defer t.MustFlush()
|
defer t.MustFlush()
|
||||||
|
|
||||||
info := &hst.Info{
|
info := &hst.Info{User: new(app.Hsu).MustID()}
|
||||||
Paths: std.Paths(),
|
app.CopyPaths(&info.Paths, info.User)
|
||||||
User: sys.MustGetUserID(std),
|
|
||||||
}
|
|
||||||
|
|
||||||
if flagJSON {
|
if flagJSON {
|
||||||
printJSON(output, short, info)
|
printJSON(output, short, info)
|
||||||
|
@ -8,19 +8,17 @@ import (
|
|||||||
|
|
||||||
"hakurei.app/hst"
|
"hakurei.app/hst"
|
||||||
"hakurei.app/internal/app/state"
|
"hakurei.app/internal/app/state"
|
||||||
"hakurei.app/internal/sys"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Main runs an app according to [hst.Config] and terminates. Main does not return.
|
// 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
|
var id state.ID
|
||||||
if err := state.NewAppID(&id); err != nil {
|
if err := state.NewAppID(&id); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var seal outcome
|
seal := outcome{id: &stringPair[state.ID]{id, id.String()}, syscallDispatcher: direct{}}
|
||||||
seal.id = &stringPair[state.ID]{id, id.String()}
|
if err := seal.finalise(ctx, config); err != nil {
|
||||||
if err := seal.finalise(ctx, k, config); err != nil {
|
|
||||||
printMessageError("cannot seal app:", err)
|
printMessageError("cannot seal app:", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
@ -1,34 +1,24 @@
|
|||||||
package app_test
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"log"
|
"log"
|
||||||
|
"os/exec"
|
||||||
"os/user"
|
"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 {
|
type stubNixOS struct {
|
||||||
lookPathErr map[string]error
|
lookPathErr map[string]error
|
||||||
usernameErr map[string]error
|
usernameErr map[string]error
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *stubNixOS) Getuid() int { return 1971 }
|
func (k *stubNixOS) new(func(k syscallDispatcher)) { panic("not implemented") }
|
||||||
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 (s *stubNixOS) Println(v ...any) { log.Println(v...) }
|
func (k *stubNixOS) getuid() int { return 1971 }
|
||||||
func (s *stubNixOS) Printf(format string, v ...any) { log.Printf(format, v...) }
|
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 {
|
switch key {
|
||||||
case "SHELL":
|
case "SHELL":
|
||||||
return "/run/current-system/sw/bin/zsh", true
|
return "/run/current-system/sw/bin/zsh", true
|
||||||
@ -40,6 +30,8 @@ func (s *stubNixOS) LookupEnv(key string) (string, bool) {
|
|||||||
return "", false
|
return "", false
|
||||||
case "HOME":
|
case "HOME":
|
||||||
return "/home/ophestra", true
|
return "/home/ophestra", true
|
||||||
|
case "XDG_RUNTIME_DIR":
|
||||||
|
return "/run/user/1971", true
|
||||||
case "XDG_CONFIG_HOME":
|
case "XDG_CONFIG_HOME":
|
||||||
return "/home/ophestra/xdg/config", true
|
return "/home/ophestra/xdg/config", true
|
||||||
default:
|
default:
|
||||||
@ -47,61 +39,7 @@ func (s *stubNixOS) LookupEnv(key string) (string, bool) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *stubNixOS) LookPath(file string) (string, error) {
|
func (k *stubNixOS) stat(name string) (fs.FileInfo, 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) {
|
|
||||||
switch name {
|
|
||||||
case "video":
|
|
||||||
return &user.Group{Gid: "26", Name: "video"}, nil
|
|
||||||
default:
|
|
||||||
return nil, user.UnknownGroupError(name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *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",
|
|
||||||
"libvirt", "libvirtd.pid", "lock", "log", "lvm", "mount", "NetworkManager",
|
|
||||||
"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",
|
|
||||||
"hostname", "hostname.CHECKSUM", "hosts", "inputrc", "ipsec.d", "issue", "kbd",
|
|
||||||
"libblockdev", "locale.conf", "localtime", "login.defs", "lsb-release", "lvm",
|
|
||||||
"machine-id", "man_db.conf", "modprobe.d", "modules-load.d", "mtab", "nanorc",
|
|
||||||
"netgroup", "NetworkManager", "nix", "nixos", "NIXOS", "nscd.conf", "nsswitch.conf",
|
|
||||||
"opensnitchd", "os-release", "pam", "pam.d", "passwd", "pipewire", "pki", "polkit-1",
|
|
||||||
"profile", "protocols", "qemu", "resolv.conf", "resolvconf.conf", "rpc", "samba",
|
|
||||||
"sddm.conf", "secureboot", "services", "set-environment", "shadow", "shells", "ssh",
|
|
||||||
"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 {
|
switch name {
|
||||||
case "/var/run/nscd":
|
case "/var/run/nscd":
|
||||||
return nil, nil
|
return nil, nil
|
||||||
@ -118,17 +56,144 @@ func (s *stubNixOS) Stat(name string) (fs.FileInfo, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *stubNixOS) Open(name string) (fs.File, error) {
|
func (k *stubNixOS) readdir(name string) ([]fs.DirEntry, error) {
|
||||||
switch name {
|
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",
|
||||||
|
"libvirt", "libvirtd.pid", "lock", "log", "lvm", "mount", "NetworkManager",
|
||||||
|
"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",
|
||||||
|
"hostname", "hostname.CHECKSUM", "hosts", "inputrc", "ipsec.d", "issue", "kbd",
|
||||||
|
"libblockdev", "locale.conf", "localtime", "login.defs", "lsb-release", "lvm",
|
||||||
|
"machine-id", "man_db.conf", "modprobe.d", "modules-load.d", "mtab", "nanorc",
|
||||||
|
"netgroup", "NetworkManager", "nix", "nixos", "NIXOS", "nscd.conf", "nsswitch.conf",
|
||||||
|
"opensnitchd", "os-release", "pam", "pam.d", "passwd", "pipewire", "pki", "polkit-1",
|
||||||
|
"profile", "protocols", "qemu", "resolv.conf", "resolvconf.conf", "rpc", "samba",
|
||||||
|
"sddm.conf", "secureboot", "services", "set-environment", "shadow", "shells", "ssh",
|
||||||
|
"ssl", "static", "subgid", "subuid", "sudoers", "sysctl.d", "systemd", "terminfo",
|
||||||
|
"tmpfiles.d", "udev", "udisks2", "UPower", "vconsole.conf", "X11", "zfs", "zinputrc",
|
||||||
|
"zoneinfo", "zprofile", "zshenv", "zshrc")
|
||||||
|
|
||||||
default:
|
default:
|
||||||
panic(fmt.Sprintf("attempted to open unexpected file %q", name))
|
panic(fmt.Sprintf("attempted to read unexpected directory %q", name))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *stubNixOS) Paths() hst.Paths {
|
func (k *stubNixOS) tempdir() string { return "/tmp/" }
|
||||||
return hst.Paths{
|
|
||||||
SharePath: m("/tmp/hakurei.1971"),
|
func (k *stubNixOS) evalSymlinks(path string) (string, error) {
|
||||||
RuntimePath: m("/run/user/1971"),
|
switch path {
|
||||||
RunDirPath: m("/run/user/1971/hakurei"),
|
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 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 (k *stubNixOS) lookupGroupId(name string) (string, error) {
|
||||||
|
switch name {
|
||||||
|
case "video":
|
||||||
|
return "26", nil
|
||||||
|
default:
|
||||||
|
return "", user.UnknownGroupError(name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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...) }
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
package app_test
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@ -13,9 +13,7 @@ import (
|
|||||||
"hakurei.app/container"
|
"hakurei.app/container"
|
||||||
"hakurei.app/container/seccomp"
|
"hakurei.app/container/seccomp"
|
||||||
"hakurei.app/hst"
|
"hakurei.app/hst"
|
||||||
"hakurei.app/internal/app"
|
|
||||||
"hakurei.app/internal/app/state"
|
"hakurei.app/internal/app/state"
|
||||||
"hakurei.app/internal/sys"
|
|
||||||
"hakurei.app/system"
|
"hakurei.app/system"
|
||||||
"hakurei.app/system/acl"
|
"hakurei.app/system/acl"
|
||||||
"hakurei.app/system/dbus"
|
"hakurei.app/system/dbus"
|
||||||
@ -24,7 +22,7 @@ import (
|
|||||||
func TestApp(t *testing.T) {
|
func TestApp(t *testing.T) {
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
name string
|
name string
|
||||||
os sys.State
|
k syscallDispatcher
|
||||||
config *hst.Config
|
config *hst.Config
|
||||||
id state.ID
|
id state.ID
|
||||||
wantSys *system.I
|
wantSys *system.I
|
||||||
@ -40,11 +38,11 @@ func TestApp(t *testing.T) {
|
|||||||
0xb9, 0xa6, 0x07, 0xac,
|
0xb9, 0xa6, 0x07, 0xac,
|
||||||
},
|
},
|
||||||
system.New(context.TODO(), 1000000).
|
system.New(context.TODO(), 1000000).
|
||||||
Ensure("/tmp/hakurei.1971", 0711).
|
Ensure("/tmp/hakurei.0", 0711).
|
||||||
Ensure("/tmp/hakurei.1971/runtime", 0700).UpdatePermType(system.User, "/tmp/hakurei.1971/runtime", acl.Execute).
|
Ensure("/tmp/hakurei.0/runtime", 0700).UpdatePermType(system.User, "/tmp/hakurei.0/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.0/runtime/0", 0700).UpdatePermType(system.User, "/tmp/hakurei.0/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.0/tmpdir", 0700).UpdatePermType(system.User, "/tmp/hakurei.0/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/tmpdir/0", 01700).UpdatePermType(system.User, "/tmp/hakurei.0/tmpdir/0", acl.Read, acl.Write, acl.Execute),
|
||||||
&container.Params{
|
&container.Params{
|
||||||
Dir: m("/home/chronos"),
|
Dir: m("/home/chronos"),
|
||||||
Path: m("/run/current-system/sw/bin/zsh"),
|
Path: m("/run/current-system/sw/bin/zsh"),
|
||||||
@ -71,8 +69,8 @@ func TestApp(t *testing.T) {
|
|||||||
Tmpfs(m("/run/dbus"), 8192, 0755).
|
Tmpfs(m("/run/dbus"), 8192, 0755).
|
||||||
Remount(m("/dev/"), syscall.MS_RDONLY).
|
Remount(m("/dev/"), syscall.MS_RDONLY).
|
||||||
Tmpfs(m("/run/user/"), 4096, 0755).
|
Tmpfs(m("/run/user/"), 4096, 0755).
|
||||||
Bind(m("/tmp/hakurei.1971/runtime/0"), m("/run/user/65534"), container.BindWritable).
|
Bind(m("/tmp/hakurei.0/runtime/0"), m("/run/user/65534"), container.BindWritable).
|
||||||
Bind(m("/tmp/hakurei.1971/tmpdir/0"), m("/tmp/"), 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/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")).
|
Place(m("/etc/group"), []byte("hakurei:x:65534:\n")).
|
||||||
Remount(m("/"), syscall.MS_RDONLY),
|
Remount(m("/"), syscall.MS_RDONLY),
|
||||||
@ -132,19 +130,19 @@ func TestApp(t *testing.T) {
|
|||||||
0x9b, 0x64, 0xce, 0x7c,
|
0x9b, 0x64, 0xce, 0x7c,
|
||||||
},
|
},
|
||||||
system.New(context.TODO(), 1000009).
|
system.New(context.TODO(), 1000009).
|
||||||
Ensure("/tmp/hakurei.1971", 0711).
|
Ensure("/tmp/hakurei.0", 0711).
|
||||||
Ensure("/tmp/hakurei.1971/runtime", 0700).UpdatePermType(system.User, "/tmp/hakurei.1971/runtime", acl.Execute).
|
Ensure("/tmp/hakurei.0/runtime", 0700).UpdatePermType(system.User, "/tmp/hakurei.0/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.0/runtime/9", 0700).UpdatePermType(system.User, "/tmp/hakurei.0/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.0/tmpdir", 0700).UpdatePermType(system.User, "/tmp/hakurei.0/tmpdir", acl.Execute).
|
||||||
Ensure("/tmp/hakurei.1971/tmpdir/9", 01700).UpdatePermType(system.User, "/tmp/hakurei.1971/tmpdir/9", acl.Read, acl.Write, 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.1971/ebf083d1b175911782d413369b64ce7c", 0711).
|
Ephemeral(system.Process, "/tmp/hakurei.0/ebf083d1b175911782d413369b64ce7c", 0711).
|
||||||
Wayland(new(*os.File), "/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/wayland", "/run/user/1971/wayland-0", "org.chromium.Chromium", "ebf083d1b175911782d413369b64ce7c").
|
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/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
|
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).
|
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").
|
Link("/run/user/1971/pulse/native", "/run/user/1971/hakurei/ebf083d1b175911782d413369b64ce7c/pulse").
|
||||||
CopyFile(new([]byte), "/home/ophestra/xdg/config/pulse/cookie", 256, 256).
|
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{
|
Talk: []string{
|
||||||
"org.freedesktop.Notifications",
|
"org.freedesktop.Notifications",
|
||||||
"org.freedesktop.FileManager1",
|
"org.freedesktop.FileManager1",
|
||||||
@ -166,7 +164,7 @@ func TestApp(t *testing.T) {
|
|||||||
"org.freedesktop.portal.*": "@/org/freedesktop/portal/*",
|
"org.freedesktop.portal.*": "@/org/freedesktop/portal/*",
|
||||||
},
|
},
|
||||||
Filter: true,
|
Filter: true,
|
||||||
}, "/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/system_bus_socket", &dbus.Config{
|
}, "/tmp/hakurei.0/ebf083d1b175911782d413369b64ce7c/system_bus_socket", &dbus.Config{
|
||||||
Talk: []string{
|
Talk: []string{
|
||||||
"org.bluez",
|
"org.bluez",
|
||||||
"org.freedesktop.Avahi",
|
"org.freedesktop.Avahi",
|
||||||
@ -174,8 +172,8 @@ func TestApp(t *testing.T) {
|
|||||||
},
|
},
|
||||||
Filter: true,
|
Filter: true,
|
||||||
}).
|
}).
|
||||||
UpdatePerm("/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/bus", acl.Read, acl.Write).
|
UpdatePerm("/tmp/hakurei.0/ebf083d1b175911782d413369b64ce7c/bus", acl.Read, acl.Write).
|
||||||
UpdatePerm("/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/system_bus_socket", acl.Read, acl.Write),
|
UpdatePerm("/tmp/hakurei.0/ebf083d1b175911782d413369b64ce7c/system_bus_socket", acl.Read, acl.Write),
|
||||||
&container.Params{
|
&container.Params{
|
||||||
Dir: m("/home/chronos"),
|
Dir: m("/home/chronos"),
|
||||||
Path: m("/run/current-system/sw/bin/zsh"),
|
Path: m("/run/current-system/sw/bin/zsh"),
|
||||||
@ -208,15 +206,15 @@ func TestApp(t *testing.T) {
|
|||||||
Tmpfs(m("/run/dbus"), 8192, 0755).
|
Tmpfs(m("/run/dbus"), 8192, 0755).
|
||||||
Remount(m("/dev/"), syscall.MS_RDONLY).
|
Remount(m("/dev/"), syscall.MS_RDONLY).
|
||||||
Tmpfs(m("/run/user/"), 4096, 0755).
|
Tmpfs(m("/run/user/"), 4096, 0755).
|
||||||
Bind(m("/tmp/hakurei.1971/runtime/9"), m("/run/user/65534"), container.BindWritable).
|
Bind(m("/tmp/hakurei.0/runtime/9"), m("/run/user/65534"), container.BindWritable).
|
||||||
Bind(m("/tmp/hakurei.1971/tmpdir/9"), m("/tmp/"), 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/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")).
|
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).
|
Bind(m("/run/user/1971/hakurei/ebf083d1b175911782d413369b64ce7c/pulse"), m("/run/user/65534/pulse/native"), 0).
|
||||||
Place(m(hst.Tmp+"/pulse-cookie"), nil).
|
Place(m(hst.Tmp+"/pulse-cookie"), nil).
|
||||||
Bind(m("/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/bus"), m("/run/user/65534/bus"), 0).
|
Bind(m("/tmp/hakurei.0/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/system_bus_socket"), m("/run/dbus/system_bus_socket"), 0).
|
||||||
Remount(m("/"), syscall.MS_RDONLY),
|
Remount(m("/"), syscall.MS_RDONLY),
|
||||||
SeccompPresets: seccomp.PresetExt | seccomp.PresetDenyDevel,
|
SeccompPresets: seccomp.PresetExt | seccomp.PresetDenyDevel,
|
||||||
HostNet: true,
|
HostNet: true,
|
||||||
@ -283,19 +281,19 @@ func TestApp(t *testing.T) {
|
|||||||
0xb4, 0x6e, 0xb5, 0xc1,
|
0xb4, 0x6e, 0xb5, 0xc1,
|
||||||
},
|
},
|
||||||
system.New(context.TODO(), 1000001).
|
system.New(context.TODO(), 1000001).
|
||||||
Ensure("/tmp/hakurei.1971", 0711).
|
Ensure("/tmp/hakurei.0", 0711).
|
||||||
Ensure("/tmp/hakurei.1971/runtime", 0700).UpdatePermType(system.User, "/tmp/hakurei.1971/runtime", acl.Execute).
|
Ensure("/tmp/hakurei.0/runtime", 0700).UpdatePermType(system.User, "/tmp/hakurei.0/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.0/runtime/1", 0700).UpdatePermType(system.User, "/tmp/hakurei.0/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.0/tmpdir", 0700).UpdatePermType(system.User, "/tmp/hakurei.0/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/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/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
|
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).
|
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).
|
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").
|
Link("/run/user/1971/pulse/native", "/run/user/1971/hakurei/8e2c76b066dabe574cf073bdb46eb5c1/pulse").
|
||||||
CopyFile(nil, "/home/ophestra/xdg/config/pulse/cookie", 256, 256).
|
CopyFile(nil, "/home/ophestra/xdg/config/pulse/cookie", 256, 256).
|
||||||
Ephemeral(system.Process, "/tmp/hakurei.1971/8e2c76b066dabe574cf073bdb46eb5c1", 0711).
|
Ephemeral(system.Process, "/tmp/hakurei.0/8e2c76b066dabe574cf073bdb46eb5c1", 0711).
|
||||||
MustProxyDBus("/tmp/hakurei.1971/8e2c76b066dabe574cf073bdb46eb5c1/bus", &dbus.Config{
|
MustProxyDBus("/tmp/hakurei.0/8e2c76b066dabe574cf073bdb46eb5c1/bus", &dbus.Config{
|
||||||
Talk: []string{
|
Talk: []string{
|
||||||
"org.freedesktop.FileManager1", "org.freedesktop.Notifications",
|
"org.freedesktop.FileManager1", "org.freedesktop.Notifications",
|
||||||
"org.freedesktop.ScreenSaver", "org.freedesktop.secrets",
|
"org.freedesktop.ScreenSaver", "org.freedesktop.secrets",
|
||||||
@ -308,7 +306,7 @@ func TestApp(t *testing.T) {
|
|||||||
},
|
},
|
||||||
Call: map[string]string{}, Broadcast: map[string]string{},
|
Call: map[string]string{}, Broadcast: map[string]string{},
|
||||||
Filter: true,
|
Filter: true,
|
||||||
}, "/tmp/hakurei.1971/8e2c76b066dabe574cf073bdb46eb5c1/system_bus_socket", &dbus.Config{
|
}, "/tmp/hakurei.0/8e2c76b066dabe574cf073bdb46eb5c1/system_bus_socket", &dbus.Config{
|
||||||
Talk: []string{
|
Talk: []string{
|
||||||
"org.bluez",
|
"org.bluez",
|
||||||
"org.freedesktop.Avahi",
|
"org.freedesktop.Avahi",
|
||||||
@ -316,8 +314,8 @@ func TestApp(t *testing.T) {
|
|||||||
},
|
},
|
||||||
Filter: true,
|
Filter: true,
|
||||||
}).
|
}).
|
||||||
UpdatePerm("/tmp/hakurei.1971/8e2c76b066dabe574cf073bdb46eb5c1/bus", acl.Read, acl.Write).
|
UpdatePerm("/tmp/hakurei.0/8e2c76b066dabe574cf073bdb46eb5c1/bus", acl.Read, acl.Write).
|
||||||
UpdatePerm("/tmp/hakurei.1971/8e2c76b066dabe574cf073bdb46eb5c1/system_bus_socket", acl.Read, acl.Write),
|
UpdatePerm("/tmp/hakurei.0/8e2c76b066dabe574cf073bdb46eb5c1/system_bus_socket", acl.Read, acl.Write),
|
||||||
&container.Params{
|
&container.Params{
|
||||||
Uid: 1971,
|
Uid: 1971,
|
||||||
Gid: 100,
|
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).
|
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).
|
Remount(m("/dev/"), syscall.MS_RDONLY).
|
||||||
Tmpfs(m("/run/user/"), 4096, 0755).
|
Tmpfs(m("/run/user/"), 4096, 0755).
|
||||||
Bind(m("/tmp/hakurei.1971/runtime/1"), m("/run/user/1971"), container.BindWritable).
|
Bind(m("/tmp/hakurei.0/runtime/1"), m("/run/user/1971"), container.BindWritable).
|
||||||
Bind(m("/tmp/hakurei.1971/tmpdir/1"), m("/tmp/"), 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/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")).
|
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/wayland-0"), m("/run/user/1971/wayland-0"), 0).
|
||||||
Bind(m("/run/user/1971/hakurei/8e2c76b066dabe574cf073bdb46eb5c1/pulse"), m("/run/user/1971/pulse/native"), 0).
|
Bind(m("/run/user/1971/hakurei/8e2c76b066dabe574cf073bdb46eb5c1/pulse"), m("/run/user/1971/pulse/native"), 0).
|
||||||
Place(m(hst.Tmp+"/pulse-cookie"), nil).
|
Place(m(hst.Tmp+"/pulse-cookie"), nil).
|
||||||
Bind(m("/tmp/hakurei.1971/8e2c76b066dabe574cf073bdb46eb5c1/bus"), m("/run/user/1971/bus"), 0).
|
Bind(m("/tmp/hakurei.0/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/system_bus_socket"), m("/run/dbus/system_bus_socket"), 0).
|
||||||
Remount(m("/"), syscall.MS_RDONLY),
|
Remount(m("/"), syscall.MS_RDONLY),
|
||||||
SeccompPresets: seccomp.PresetExt | seccomp.PresetDenyTTY | seccomp.PresetDenyDevel,
|
SeccompPresets: seccomp.PresetExt | seccomp.PresetDenyTTY | seccomp.PresetDenyDevel,
|
||||||
HostNet: true,
|
HostNet: true,
|
||||||
@ -378,7 +376,8 @@ func TestApp(t *testing.T) {
|
|||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
t.Run("finalise", 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 err != nil {
|
||||||
if s, ok := container.GetErrorMessage(err); !ok {
|
if s, ok := container.GetErrorMessage(err); !ok {
|
||||||
t.Fatalf("Seal: error = %v", err)
|
t.Fatalf("Seal: error = %v", err)
|
||||||
@ -388,14 +387,14 @@ func TestApp(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
t.Run("sys", func(t *testing.T) {
|
t.Run("sys", func(t *testing.T) {
|
||||||
if !sys.Equal(tc.wantSys) {
|
if !seal.sys.Equal(tc.wantSys) {
|
||||||
t.Errorf("Seal: sys = %#v, want %#v", sys, tc.wantSys)
|
t.Errorf("Seal: sys = %#v, want %#v", seal.sys, tc.wantSys)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("params", func(t *testing.T) {
|
t.Run("params", func(t *testing.T) {
|
||||||
if !reflect.DeepEqual(params, tc.wantParams) {
|
if !reflect.DeepEqual(seal.container, tc.wantParams) {
|
||||||
t.Errorf("seal: params =\n%s\n, want\n%s", mustMarshal(params), mustMarshal(tc.wantParams))
|
t.Errorf("seal: container =\n%s\n, want\n%s", mustMarshal(seal.container), mustMarshal(tc.wantParams))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -11,7 +11,6 @@ import (
|
|||||||
"hakurei.app/container"
|
"hakurei.app/container"
|
||||||
"hakurei.app/container/seccomp"
|
"hakurei.app/container/seccomp"
|
||||||
"hakurei.app/hst"
|
"hakurei.app/hst"
|
||||||
"hakurei.app/internal/sys"
|
|
||||||
"hakurei.app/system/dbus"
|
"hakurei.app/system/dbus"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -20,7 +19,13 @@ const preallocateOpsCount = 1 << 5
|
|||||||
|
|
||||||
// newContainer initialises [container.Params] via [hst.ContainerConfig].
|
// newContainer initialises [container.Params] via [hst.ContainerConfig].
|
||||||
// Note that remaining container setup must be queued by the caller.
|
// 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 {
|
if s == nil {
|
||||||
return nil, nil, newWithMessage("invalid container configuration")
|
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,
|
ForwardCancel: s.WaitDelay >= 0,
|
||||||
}
|
}
|
||||||
|
|
||||||
as := &hst.ApplyState{
|
as := &hst.ApplyState{AutoEtcPrefix: prefix}
|
||||||
AutoEtcPrefix: prefix,
|
|
||||||
}
|
|
||||||
{
|
{
|
||||||
ops := make(container.Ops, 0, preallocateOpsCount+len(s.Filesystem))
|
ops := make(container.Ops, 0, preallocateOpsCount+len(s.Filesystem))
|
||||||
params.Ops = &ops
|
params.Ops = &ops
|
||||||
@ -65,13 +68,13 @@ func newContainer(s *hst.ContainerConfig, os sys.State, prefix string, uid, gid
|
|||||||
}
|
}
|
||||||
|
|
||||||
if s.MapRealUID {
|
if s.MapRealUID {
|
||||||
params.Uid = os.Getuid()
|
params.Uid = k.getuid()
|
||||||
*uid = params.Uid
|
*uid = params.Uid
|
||||||
params.Gid = os.Getgid()
|
params.Gid = k.getgid()
|
||||||
*gid = params.Gid
|
*gid = params.Gid
|
||||||
} else {
|
} else {
|
||||||
*uid = container.OverflowUid()
|
*uid = k.overflowUid()
|
||||||
*gid = container.OverflowGid()
|
*gid = k.overflowGid()
|
||||||
}
|
}
|
||||||
|
|
||||||
filesystem := s.Filesystem
|
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
|
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 */
|
and should not be treated as such, ALWAYS be careful with what you bind */
|
||||||
var hidePaths []string
|
var hidePaths []string
|
||||||
sc := os.Paths()
|
|
||||||
hidePaths = append(hidePaths, sc.RuntimePath.String(), sc.SharePath.String())
|
hidePaths = append(hidePaths, sc.RuntimePath.String(), sc.SharePath.String())
|
||||||
_, systemBusAddr := dbus.Address()
|
_, systemBusAddr := dbus.Address()
|
||||||
if entries, err := dbus.Parse([]byte(systemBusAddr)); err != nil {
|
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
|
// get parent dir of socket
|
||||||
dir := path.Dir(pair[1])
|
dir := path.Dir(pair[1])
|
||||||
if dir == "." || dir == container.FHSRoot {
|
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)
|
hidePaths = append(hidePaths, dir)
|
||||||
} else {
|
} 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))
|
hidePathMatch := make([]bool, len(hidePaths))
|
||||||
for i := range 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
|
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
|
// AutoRootOp is a collection of many BindMountOp internally
|
||||||
var autoRootEntries []fs.DirEntry
|
var autoRootEntries []fs.DirEntry
|
||||||
if autoroot != nil {
|
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
|
return nil, nil, err
|
||||||
} else {
|
} else {
|
||||||
// autoroot counter
|
// 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()}
|
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
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -207,7 +209,7 @@ func newContainer(s *hst.ContainerConfig, os sys.State, prefix string, uid, gid
|
|||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
} else if ok {
|
} else if ok {
|
||||||
hidePathMatch[i] = true
|
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
|
return params, maps.Clone(s.Env), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func evalSymlinks(os sys.State, v *string) error {
|
// evalSymlinks calls syscallDispatcher.evalSymlinks but discards errors unwrapping to [fs.ErrNotExist].
|
||||||
if p, err := os.EvalSymlinks(*v); err != nil {
|
func evalSymlinks(k syscallDispatcher, v *string) error {
|
||||||
|
if p, err := k.evalSymlinks(*v); err != nil {
|
||||||
if !errors.Is(err, fs.ErrNotExist) {
|
if !errors.Is(err, fs.ErrNotExist) {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
os.Printf("path %q does not yet exist", *v)
|
k.verbosef("path %q does not yet exist", *v)
|
||||||
} else {
|
} else {
|
||||||
*v = p
|
*v = p
|
||||||
}
|
}
|
||||||
|
99
internal/app/dispatcher.go
Normal file
99
internal/app/dispatcher.go
Normal file
@ -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...) }
|
@ -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)
|
|
||||||
}
|
|
@ -21,7 +21,6 @@ import (
|
|||||||
"hakurei.app/hst"
|
"hakurei.app/hst"
|
||||||
"hakurei.app/internal/app/state"
|
"hakurei.app/internal/app/state"
|
||||||
"hakurei.app/internal/hlog"
|
"hakurei.app/internal/hlog"
|
||||||
"hakurei.app/internal/sys"
|
|
||||||
"hakurei.app/system"
|
"hakurei.app/system"
|
||||||
"hakurei.app/system/acl"
|
"hakurei.app/system/acl"
|
||||||
"hakurei.app/system/dbus"
|
"hakurei.app/system/dbus"
|
||||||
@ -54,8 +53,9 @@ type outcome struct {
|
|||||||
container *container.Params
|
container *container.Params
|
||||||
env map[string]string
|
env map[string]string
|
||||||
sync *os.File
|
sync *os.File
|
||||||
|
active atomic.Bool
|
||||||
|
|
||||||
f atomic.Bool
|
syscallDispatcher
|
||||||
}
|
}
|
||||||
|
|
||||||
// shareHost holds optional share directory state that must not be accessed directly
|
// shareHost holds optional share directory state that must not be accessed directly
|
||||||
@ -120,7 +120,7 @@ type hsuUser struct {
|
|||||||
username string
|
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 (
|
const (
|
||||||
home = "HOME"
|
home = "HOME"
|
||||||
shell = "SHELL"
|
shell = "SHELL"
|
||||||
@ -144,14 +144,13 @@ func (seal *outcome) finalise(ctx context.Context, k sys.State, config *hst.Conf
|
|||||||
// unreachable
|
// unreachable
|
||||||
panic("invalid call to finalise")
|
panic("invalid call to finalise")
|
||||||
}
|
}
|
||||||
if seal.ctx != nil {
|
if k.ctx != nil {
|
||||||
// unreachable
|
// unreachable
|
||||||
panic("attempting to finalise twice")
|
panic("attempting to finalise twice")
|
||||||
}
|
}
|
||||||
seal.ctx = ctx
|
k.ctx = ctx
|
||||||
|
|
||||||
if config == nil {
|
if config == nil {
|
||||||
// unreachable
|
|
||||||
return newWithMessage("invalid configuration")
|
return newWithMessage("invalid configuration")
|
||||||
}
|
}
|
||||||
if config.Home == nil {
|
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 {
|
if err := gob.NewEncoder(ct).Encode(config); err != nil {
|
||||||
return &hst.AppError{Step: "encode initial config", Err: err}
|
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
|
// 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))
|
return newWithMessage(fmt.Sprintf("identity %d out of range", config.Identity))
|
||||||
}
|
}
|
||||||
|
|
||||||
seal.user = hsuUser{
|
k.user = hsuUser{
|
||||||
identity: newInt(config.Identity),
|
identity: newInt(config.Identity),
|
||||||
home: config.Home,
|
home: config.Home,
|
||||||
username: config.Username,
|
username: config.Username,
|
||||||
}
|
}
|
||||||
|
|
||||||
if seal.user.username == "" {
|
hsu := Hsu{k: k}
|
||||||
seal.user.username = "chronos"
|
if k.user.username == "" {
|
||||||
} else if !isValidUsername(seal.user.username) {
|
k.user.username = "chronos"
|
||||||
return newWithMessage(fmt.Sprintf("invalid user name %q", seal.user.username))
|
} 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 {
|
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
|
var unknownGroupError user.UnknownGroupError
|
||||||
if errors.As(err, &unknownGroupError) {
|
if errors.As(err, &unknownGroupError) {
|
||||||
return newWithMessageError(fmt.Sprintf("unknown group %q", name), 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}
|
return &hst.AppError{Step: "look up group by name", Err: err}
|
||||||
}
|
}
|
||||||
} else {
|
} 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 {
|
if config.Shell == nil {
|
||||||
config.Shell = container.AbsFHSRoot.Append("bin", "sh")
|
config.Shell = container.AbsFHSRoot.Append("bin", "sh")
|
||||||
s, _ := k.LookupEnv(shell)
|
s, _ := k.lookupEnv(shell)
|
||||||
if a, err := container.NewAbs(s); err == nil {
|
if a, err := container.NewAbs(s); err == nil {
|
||||||
config.Shell = a
|
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
|
// hsu clears the environment so resolve paths early
|
||||||
if config.Path == nil {
|
if config.Path == nil {
|
||||||
if len(config.Args) > 0 {
|
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}
|
return &hst.AppError{Step: "look up executable file", Err: err}
|
||||||
} else if config.Path, err = container.NewAbs(p); err != nil {
|
} else if config.Path, err = container.NewAbs(p); err != nil {
|
||||||
return newWithMessageError(err.Error(), err)
|
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
|
// hide nscd from container if present
|
||||||
nscd := container.AbsFHSVar.Append("run/nscd")
|
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}})
|
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")
|
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 mapuid, mapgid *stringPair[int]
|
||||||
{
|
{
|
||||||
var uid, gid int
|
var uid, gid int
|
||||||
var err error
|
var err error
|
||||||
seal.container, seal.env, err = newContainer(config.Container, k, seal.id.String(), &uid, &gid)
|
k.container, k.env, err = newContainer(k, config.Container, k.id.String(), &share.sc, &uid, &gid)
|
||||||
seal.waitDelay = config.Container.WaitDelay
|
k.waitDelay = config.Container.WaitDelay
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &hst.AppError{Step: "initialise container configuration", Err: err}
|
return &hst.AppError{Step: "initialise container configuration", Err: err}
|
||||||
}
|
}
|
||||||
if len(config.Args) == 0 {
|
if len(config.Args) == 0 {
|
||||||
config.Args = []string{config.Path.String()}
|
config.Args = []string{config.Path.String()}
|
||||||
}
|
}
|
||||||
seal.container.Path = config.Path
|
k.container.Path = config.Path
|
||||||
seal.container.Args = config.Args
|
k.container.Args = config.Args
|
||||||
|
|
||||||
mapuid = newInt(uid)
|
mapuid = newInt(uid)
|
||||||
mapgid = newInt(gid)
|
mapgid = newInt(gid)
|
||||||
if seal.env == nil {
|
if k.env == nil {
|
||||||
seal.env = make(map[string]string, 1<<6)
|
k.env = make(map[string]string, 1<<6)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// inner XDG_RUNTIME_DIR default formatting of `/run/user/%d` as mapped uid
|
// inner XDG_RUNTIME_DIR default formatting of `/run/user/%d` as mapped uid
|
||||||
innerRuntimeDir := container.AbsFHSRunUser.Append(mapuid.String())
|
innerRuntimeDir := container.AbsFHSRunUser.Append(mapuid.String())
|
||||||
seal.env[xdgRuntimeDir] = innerRuntimeDir.String()
|
k.env[xdgRuntimeDir] = innerRuntimeDir.String()
|
||||||
seal.env[xdgSessionClass] = "user"
|
k.env[xdgSessionClass] = "user"
|
||||||
seal.env[xdgSessionType] = "tty"
|
k.env[xdgSessionType] = "tty"
|
||||||
|
|
||||||
share := &shareHost{seal: seal, sc: k.Paths()}
|
k.runDirPath = share.sc.RunDirPath
|
||||||
seal.runDirPath = share.sc.RunDirPath
|
k.sys = system.New(k.ctx, k.user.uid.unwrap())
|
||||||
seal.sys = system.New(seal.ctx, seal.user.uid.unwrap())
|
k.sys.Ensure(share.sc.SharePath.String(), 0711)
|
||||||
seal.sys.Ensure(share.sc.SharePath.String(), 0711)
|
|
||||||
|
|
||||||
{
|
{
|
||||||
runtimeDir := share.sc.SharePath.Append("runtime")
|
runtimeDir := share.sc.SharePath.Append("runtime")
|
||||||
seal.sys.Ensure(runtimeDir.String(), 0700)
|
k.sys.Ensure(runtimeDir.String(), 0700)
|
||||||
seal.sys.UpdatePermType(system.User, runtimeDir.String(), acl.Execute)
|
k.sys.UpdatePermType(system.User, runtimeDir.String(), acl.Execute)
|
||||||
runtimeDirInst := runtimeDir.Append(seal.user.identity.String())
|
runtimeDirInst := runtimeDir.Append(k.user.identity.String())
|
||||||
seal.sys.Ensure(runtimeDirInst.String(), 0700)
|
k.sys.Ensure(runtimeDirInst.String(), 0700)
|
||||||
seal.sys.UpdatePermType(system.User, runtimeDirInst.String(), acl.Read, acl.Write, acl.Execute)
|
k.sys.UpdatePermType(system.User, runtimeDirInst.String(), acl.Read, acl.Write, acl.Execute)
|
||||||
seal.container.Tmpfs(container.AbsFHSRunUser, 1<<12, 0755)
|
k.container.Tmpfs(container.AbsFHSRunUser, 1<<12, 0755)
|
||||||
seal.container.Bind(runtimeDirInst, innerRuntimeDir, container.BindWritable)
|
k.container.Bind(runtimeDirInst, innerRuntimeDir, container.BindWritable)
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
tmpdir := share.sc.SharePath.Append("tmpdir")
|
tmpdir := share.sc.SharePath.Append("tmpdir")
|
||||||
seal.sys.Ensure(tmpdir.String(), 0700)
|
k.sys.Ensure(tmpdir.String(), 0700)
|
||||||
seal.sys.UpdatePermType(system.User, tmpdir.String(), acl.Execute)
|
k.sys.UpdatePermType(system.User, tmpdir.String(), acl.Execute)
|
||||||
tmpdirInst := tmpdir.Append(seal.user.identity.String())
|
tmpdirInst := tmpdir.Append(k.user.identity.String())
|
||||||
seal.sys.Ensure(tmpdirInst.String(), 01700)
|
k.sys.Ensure(tmpdirInst.String(), 01700)
|
||||||
seal.sys.UpdatePermType(system.User, tmpdirInst.String(), acl.Read, acl.Write, acl.Execute)
|
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
|
// 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"
|
username := "chronos"
|
||||||
if seal.user.username != "" {
|
if k.user.username != "" {
|
||||||
username = seal.user.username
|
username = k.user.username
|
||||||
}
|
}
|
||||||
seal.container.Dir = seal.user.home
|
k.container.Dir = k.user.home
|
||||||
seal.env["HOME"] = seal.user.home.String()
|
k.env["HOME"] = k.user.home.String()
|
||||||
seal.env["USER"] = username
|
k.env["USER"] = username
|
||||||
seal.env[shell] = config.Shell.String()
|
k.env[shell] = config.Shell.String()
|
||||||
|
|
||||||
seal.container.Place(container.AbsFHSEtc.Append("passwd"),
|
k.container.Place(container.AbsFHSEtc.Append("passwd"),
|
||||||
[]byte(username+":x:"+mapuid.String()+":"+mapgid.String()+":Hakurei:"+seal.user.home.String()+":"+config.Shell.String()+"\n"))
|
[]byte(username+":x:"+mapuid.String()+":"+mapgid.String()+":Hakurei:"+k.user.home.String()+":"+config.Shell.String()+"\n"))
|
||||||
seal.container.Place(container.AbsFHSEtc.Append("group"),
|
k.container.Place(container.AbsFHSEtc.Append("group"),
|
||||||
[]byte("hakurei:x:"+mapgid.String()+":\n"))
|
[]byte("hakurei:x:"+mapgid.String()+":\n"))
|
||||||
}
|
}
|
||||||
|
|
||||||
// pass TERM for proper terminal I/O in initial process
|
// pass TERM for proper terminal I/O in initial process
|
||||||
if t, ok := k.LookupEnv(term); ok {
|
if t, ok := k.lookupEnv(term); ok {
|
||||||
seal.env[term] = t
|
k.env[term] = t
|
||||||
}
|
}
|
||||||
|
|
||||||
if config.Enablements.Unwrap()&system.EWayland != 0 {
|
if config.Enablements.Unwrap()&system.EWayland != 0 {
|
||||||
// outer wayland socket (usually `/run/user/%d/wayland-%d`)
|
// outer wayland socket (usually `/run/user/%d/wayland-%d`)
|
||||||
var socketPath *container.Absolute
|
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)
|
hlog.Verbose(wayland.WaylandDisplay + " is not set, assuming " + wayland.FallbackName)
|
||||||
socketPath = share.sc.RuntimePath.Append(wayland.FallbackName)
|
socketPath = share.sc.RuntimePath.Append(wayland.FallbackName)
|
||||||
} else if a, err := container.NewAbs(name); err != nil {
|
} 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)
|
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
|
if !config.DirectWayland { // set up security-context-v1
|
||||||
appID := config.ID
|
appID := config.ID
|
||||||
if appID == "" {
|
if appID == "" {
|
||||||
// use instance ID in case app id is not set
|
// 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
|
// downstream socket paths
|
||||||
outerPath := share.instance().Append("wayland")
|
outerPath := share.instance().Append("wayland")
|
||||||
seal.sys.Wayland(&seal.sync, outerPath.String(), socketPath.String(), appID, seal.id.String())
|
k.sys.Wayland(&k.sync, outerPath.String(), socketPath.String(), appID, k.id.String())
|
||||||
seal.container.Bind(outerPath, innerPath, 0)
|
k.container.Bind(outerPath, innerPath, 0)
|
||||||
} else { // bind mount wayland socket (insecure)
|
} else { // bind mount wayland socket (insecure)
|
||||||
hlog.Verbose("direct wayland access, PROCEED WITH CAUTION")
|
hlog.Verbose("direct wayland access, PROCEED WITH CAUTION")
|
||||||
share.ensureRuntimeDir()
|
share.ensureRuntimeDir()
|
||||||
seal.container.Bind(socketPath, innerPath, 0)
|
k.container.Bind(socketPath, innerPath, 0)
|
||||||
seal.sys.UpdatePermType(system.EWayland, socketPath.String(), acl.Read, acl.Write, acl.Execute)
|
k.sys.UpdatePermType(system.EWayland, socketPath.String(), acl.Read, acl.Write, acl.Execute)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if config.Enablements.Unwrap()&system.EX11 != 0 {
|
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")
|
return newWithMessage("DISPLAY is not set")
|
||||||
} else {
|
} else {
|
||||||
socketDir := container.AbsFHSTmp.Append(".X11-unix")
|
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 socketPath != nil {
|
||||||
if _, err := k.Stat(socketPath.String()); err != nil {
|
if _, err := k.stat(socketPath.String()); err != nil {
|
||||||
if !errors.Is(err, fs.ErrNotExist) {
|
if !errors.Is(err, fs.ErrNotExist) {
|
||||||
return &hst.AppError{Step: fmt.Sprintf("access X11 socket %q", socketPath), Err: err}
|
return &hst.AppError{Step: fmt.Sprintf("access X11 socket %q", socketPath), Err: err}
|
||||||
}
|
}
|
||||||
} else {
|
} 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 {
|
if !config.Container.HostAbstract {
|
||||||
d = "unix:" + socketPath.String()
|
d = "unix:" + socketPath.String()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
seal.sys.ChangeHosts("#" + seal.user.uid.String())
|
k.sys.ChangeHosts("#" + k.user.uid.String())
|
||||||
seal.env[display] = d
|
k.env[display] = d
|
||||||
seal.container.Bind(socketDir, socketDir, 0)
|
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`)
|
// PulseAudio socket (usually `/run/user/%d/pulse/native`)
|
||||||
pulseSocket := pulseRuntimeDir.Append("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) {
|
if !errors.Is(err, fs.ErrNotExist) {
|
||||||
return &hst.AppError{Step: fmt.Sprintf("access PulseAudio directory %q", pulseRuntimeDir), Err: err}
|
return &hst.AppError{Step: fmt.Sprintf("access PulseAudio directory %q", pulseRuntimeDir), Err: err}
|
||||||
}
|
}
|
||||||
return newWithMessage(fmt.Sprintf("PulseAudio directory %q not found", pulseRuntimeDir))
|
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) {
|
if !errors.Is(err, fs.ErrNotExist) {
|
||||||
return &hst.AppError{Step: fmt.Sprintf("access PulseAudio socket %q", pulseSocket), Err: err}
|
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
|
// hard link pulse socket into target-executable share
|
||||||
innerPulseRuntimeDir := share.runtime().Append("pulse")
|
innerPulseRuntimeDir := share.runtime().Append("pulse")
|
||||||
innerPulseSocket := innerRuntimeDir.Append("pulse", "native")
|
innerPulseSocket := innerRuntimeDir.Append("pulse", "native")
|
||||||
seal.sys.Link(pulseSocket.String(), innerPulseRuntimeDir.String())
|
k.sys.Link(pulseSocket.String(), innerPulseRuntimeDir.String())
|
||||||
seal.container.Bind(innerPulseRuntimeDir, innerPulseSocket, 0)
|
k.container.Bind(innerPulseRuntimeDir, innerPulseSocket, 0)
|
||||||
seal.env[pulseServer] = "unix:" + innerPulseSocket.String()
|
k.env[pulseServer] = "unix:" + innerPulseSocket.String()
|
||||||
|
|
||||||
// publish current user's pulse cookie for target user
|
// publish current user's pulse cookie for target user
|
||||||
var paCookiePath *container.Absolute
|
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"
|
const paLocateStep = "locate PulseAudio cookie"
|
||||||
|
|
||||||
// from environment
|
// from environment
|
||||||
if p, ok := k.LookupEnv(pulseCookie); ok {
|
if p, ok := k.lookupEnv(pulseCookie); ok {
|
||||||
if a, err := container.NewAbs(p); err != nil {
|
if a, err := container.NewAbs(p); err != nil {
|
||||||
return &hst.AppError{Step: paLocateStep, Err: err}
|
return &hst.AppError{Step: paLocateStep, Err: err}
|
||||||
} else {
|
} else {
|
||||||
@ -468,14 +471,14 @@ func (seal *outcome) finalise(ctx context.Context, k sys.State, config *hst.Conf
|
|||||||
}
|
}
|
||||||
|
|
||||||
// $HOME/.pulse-cookie
|
// $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 {
|
if a, err := container.NewAbs(p); err != nil {
|
||||||
return &hst.AppError{Step: paLocateStep, Err: err}
|
return &hst.AppError{Step: paLocateStep, Err: err}
|
||||||
} else {
|
} else {
|
||||||
paCookiePath = a.Append(".pulse-cookie")
|
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
|
paCookiePath = nil
|
||||||
if !errors.Is(err, fs.ErrNotExist) {
|
if !errors.Is(err, fs.ErrNotExist) {
|
||||||
return &hst.AppError{Step: "access PulseAudio cookie", Err: err}
|
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
|
// $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 {
|
if a, err := container.NewAbs(p); err != nil {
|
||||||
return &hst.AppError{Step: paLocateStep, Err: err}
|
return &hst.AppError{Step: paLocateStep, Err: err}
|
||||||
} else {
|
} else {
|
||||||
paCookiePath = a.Append("pulse", "cookie")
|
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
|
paCookiePath = nil
|
||||||
if !errors.Is(err, fs.ErrNotExist) {
|
if !errors.Is(err, fs.ErrNotExist) {
|
||||||
return &hst.AppError{Step: "access PulseAudio cookie", Err: err}
|
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 {
|
if paCookiePath != nil {
|
||||||
innerDst := hst.AbsTmp.Append("/pulse-cookie")
|
innerDst := hst.AbsTmp.Append("/pulse-cookie")
|
||||||
seal.env[pulseCookie] = innerDst.String()
|
k.env[pulseCookie] = innerDst.String()
|
||||||
var payload *[]byte
|
var payload *[]byte
|
||||||
seal.container.PlaceP(innerDst, &payload)
|
k.container.PlaceP(innerDst, &payload)
|
||||||
seal.sys.CopyFile(payload, paCookiePath.String(), 256, 256)
|
k.sys.CopyFile(payload, paCookiePath.String(), 256, 256)
|
||||||
} else {
|
} else {
|
||||||
hlog.Verbose("cannot locate PulseAudio cookie (tried " +
|
hlog.Verbose("cannot locate PulseAudio cookie (tried " +
|
||||||
"$PULSE_COOKIE, " +
|
"$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")
|
sessionPath, systemPath := share.instance().Append("bus"), share.instance().Append("system_bus_socket")
|
||||||
|
|
||||||
// configure dbus proxy
|
// configure dbus proxy
|
||||||
if f, err := seal.sys.ProxyDBus(
|
if f, err := k.sys.ProxyDBus(
|
||||||
config.SessionBus, config.SystemBus,
|
config.SessionBus, config.SystemBus,
|
||||||
sessionPath.String(), systemPath.String(),
|
sessionPath.String(), systemPath.String(),
|
||||||
); err != nil {
|
); err != nil {
|
||||||
return err
|
return err
|
||||||
} else {
|
} else {
|
||||||
seal.dbusMsg = f
|
k.dbusMsg = f
|
||||||
}
|
}
|
||||||
|
|
||||||
// share proxy sockets
|
// share proxy sockets
|
||||||
sessionInner := innerRuntimeDir.Append("bus")
|
sessionInner := innerRuntimeDir.Append("bus")
|
||||||
seal.env[dbusSessionBusAddress] = "unix:path=" + sessionInner.String()
|
k.env[dbusSessionBusAddress] = "unix:path=" + sessionInner.String()
|
||||||
seal.container.Bind(sessionPath, sessionInner, 0)
|
k.container.Bind(sessionPath, sessionInner, 0)
|
||||||
seal.sys.UpdatePerm(sessionPath.String(), acl.Read, acl.Write)
|
k.sys.UpdatePerm(sessionPath.String(), acl.Read, acl.Write)
|
||||||
if config.SystemBus != nil {
|
if config.SystemBus != nil {
|
||||||
systemInner := container.AbsFHSRun.Append("dbus/system_bus_socket")
|
systemInner := container.AbsFHSRun.Append("dbus/system_bus_socket")
|
||||||
seal.env[dbusSystemBusAddress] = "unix:path=" + systemInner.String()
|
k.env[dbusSystemBusAddress] = "unix:path=" + systemInner.String()
|
||||||
seal.container.Bind(systemPath, systemInner, 0)
|
k.container.Bind(systemPath, systemInner, 0)
|
||||||
seal.sys.UpdatePerm(systemPath.String(), acl.Read, acl.Write)
|
k.sys.UpdatePerm(systemPath.String(), acl.Read, acl.Write)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// mount root read-only as the final setup Op
|
// 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
|
// append ExtraPerms last
|
||||||
for _, p := range config.ExtraPerms {
|
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 {
|
if p.Ensure {
|
||||||
seal.sys.Ensure(p.Path.String(), 0700)
|
k.sys.Ensure(p.Path.String(), 0700)
|
||||||
}
|
}
|
||||||
|
|
||||||
perms := make(acl.Perms, 0, 3)
|
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 {
|
if p.Execute {
|
||||||
perms = append(perms, acl.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
|
// flatten and sort env for deterministic behaviour
|
||||||
seal.container.Env = make([]string, 0, len(seal.env))
|
k.container.Env = make([]string, 0, len(k.env))
|
||||||
for k, v := range seal.env {
|
for key, value := range k.env {
|
||||||
if strings.IndexByte(k, '=') != -1 {
|
if strings.IndexByte(key, '=') != -1 {
|
||||||
return &hst.AppError{Step: "flatten environment", Err: syscall.EINVAL,
|
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() {
|
if hlog.Load() {
|
||||||
hlog.Verbosef("created application seal for uid %s (%s) groups: %v, argv: %s, ops: %d",
|
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
|
return nil
|
@ -1,4 +1,4 @@
|
|||||||
package sys
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
@ -11,7 +11,6 @@ import (
|
|||||||
|
|
||||||
"hakurei.app/container"
|
"hakurei.app/container"
|
||||||
"hakurei.app/hst"
|
"hakurei.app/hst"
|
||||||
"hakurei.app/internal"
|
|
||||||
"hakurei.app/internal/hlog"
|
"hakurei.app/internal/hlog"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -20,15 +19,28 @@ type Hsu struct {
|
|||||||
idOnce sync.Once
|
idOnce sync.Once
|
||||||
idErr error
|
idErr error
|
||||||
id int
|
id int
|
||||||
|
|
||||||
|
kOnce sync.Once
|
||||||
|
k syscallDispatcher
|
||||||
}
|
}
|
||||||
|
|
||||||
var ErrHsuAccess = errors.New("current user is not in the hsurc file")
|
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.
|
// ID returns the current user hsurc identifier. ErrHsuAccess is returned if the current user is not in hsurc.
|
||||||
func (h *Hsu) ID() (int, error) {
|
func (h *Hsu) ID() (int, error) {
|
||||||
|
h.ensureDispatcher()
|
||||||
h.idOnce.Do(func() {
|
h.idOnce.Do(func() {
|
||||||
h.id = -1
|
h.id = -1
|
||||||
hsuPath := internal.MustHsuPath()
|
hsuPath := h.k.mustHsuPath()
|
||||||
|
|
||||||
cmd := exec.Command(hsuPath)
|
cmd := exec.Command(hsuPath)
|
||||||
cmd.Path = hsuPath
|
cmd.Path = hsuPath
|
||||||
@ -41,7 +53,7 @@ func (h *Hsu) ID() (int, error) {
|
|||||||
)
|
)
|
||||||
|
|
||||||
const step = "obtain uid from hsu"
|
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))
|
h.id, h.idErr = strconv.Atoi(string(p))
|
||||||
if h.idErr != nil {
|
if h.idErr != nil {
|
||||||
h.idErr = &hst.AppError{Step: step, Err: h.idErr, Msg: "invalid uid string from hsu"}
|
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
|
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()
|
id, err := h.ID()
|
||||||
if err == nil {
|
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) {
|
if errors.Is(err, ErrHsuAccess) {
|
||||||
hlog.Verbose("*"+fallback, err)
|
hlog.Verbose("*"+fallback, err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
@ -86,3 +90,7 @@ func MustUid(s State, identity int) int {
|
|||||||
return -0xdeadbeef
|
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 }
|
36
internal/app/paths.go
Normal file
36
internal/app/paths.go
Normal file
@ -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)
|
||||||
|
}
|
@ -33,12 +33,12 @@ type mainState struct {
|
|||||||
// Time is nil if no process was ever created.
|
// Time is nil if no process was ever created.
|
||||||
Time *time.Time
|
Time *time.Time
|
||||||
|
|
||||||
seal *outcome
|
|
||||||
store state.Store
|
store state.Store
|
||||||
cancel context.CancelFunc
|
cancel context.CancelFunc
|
||||||
cmd *exec.Cmd
|
cmd *exec.Cmd
|
||||||
cmdWait chan error
|
cmdWait chan error
|
||||||
|
|
||||||
|
k *outcome
|
||||||
uintptr
|
uintptr
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -79,13 +79,13 @@ func (ms mainState) beforeExit(isFault bool) {
|
|||||||
waitDone := make(chan struct{})
|
waitDone := make(chan struct{})
|
||||||
// TODO(ophestra): enforce this limit early so it does not have to be done twice
|
// TODO(ophestra): enforce this limit early so it does not have to be done twice
|
||||||
shimTimeoutCompensated := shimWaitTimeout
|
shimTimeoutCompensated := shimWaitTimeout
|
||||||
if ms.seal.waitDelay > MaxShimWaitDelay {
|
if ms.k.waitDelay > MaxShimWaitDelay {
|
||||||
shimTimeoutCompensated += MaxShimWaitDelay
|
shimTimeoutCompensated += MaxShimWaitDelay
|
||||||
} else {
|
} else {
|
||||||
shimTimeoutCompensated += ms.seal.waitDelay
|
shimTimeoutCompensated += ms.k.waitDelay
|
||||||
}
|
}
|
||||||
// this ties waitDone to ctx with the additional compensated timeout duration
|
// 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 {
|
select {
|
||||||
case err := <-ms.cmdWait:
|
case err := <-ms.cmdWait:
|
||||||
@ -128,20 +128,20 @@ func (ms mainState) beforeExit(isFault bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
hlog.Resume()
|
hlog.Resume()
|
||||||
if ms.seal.sync != nil {
|
if ms.k.sync != nil {
|
||||||
if err := ms.seal.sync.Close(); err != nil {
|
if err := ms.k.sync.Close(); err != nil {
|
||||||
perror(err, "close wayland security context")
|
perror(err, "close wayland security context")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ms.seal.dbusMsg != nil {
|
if ms.k.dbusMsg != nil {
|
||||||
ms.seal.dbusMsg()
|
ms.k.dbusMsg()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ms.uintptr&mainNeedsRevert != 0 {
|
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 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")
|
perror(err, "destroy state entry")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -151,7 +151,7 @@ func (ms mainState) beforeExit(isFault bool) {
|
|||||||
// it is impossible to continue from this point;
|
// it is impossible to continue from this point;
|
||||||
// revert per-process state here to limit damage
|
// revert per-process state here to limit damage
|
||||||
ec := system.Process
|
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 {
|
var joinError interface {
|
||||||
Unwrap() []error
|
Unwrap() []error
|
||||||
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")
|
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.
|
// main carries out outcome and terminates. main does not return.
|
||||||
func (seal *outcome) main() {
|
func (k *outcome) main() {
|
||||||
if !seal.f.CompareAndSwap(false, true) {
|
if !k.active.CompareAndSwap(false, true) {
|
||||||
panic("outcome: attempted to run twice")
|
panic("outcome: attempted to run twice")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -228,15 +228,15 @@ func (seal *outcome) main() {
|
|||||||
hsuPath := internal.MustHsuPath()
|
hsuPath := internal.MustHsuPath()
|
||||||
|
|
||||||
// ms.beforeExit required beyond this point
|
// 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.fatal("cannot commit system setup:", err)
|
||||||
}
|
}
|
||||||
ms.uintptr |= mainNeedsRevert
|
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()
|
defer cancel()
|
||||||
ms.cancel = cancel
|
ms.cancel = cancel
|
||||||
|
|
||||||
@ -255,14 +255,14 @@ func (seal *outcome) main() {
|
|||||||
// passed through to shim by hsu
|
// passed through to shim by hsu
|
||||||
shimEnv + "=" + strconv.Itoa(fd),
|
shimEnv + "=" + strconv.Itoa(fd),
|
||||||
// interpreted by hsu
|
// interpreted by hsu
|
||||||
"HAKUREI_IDENTITY=" + seal.user.identity.String(),
|
"HAKUREI_IDENTITY=" + k.user.identity.String(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(seal.user.supp) > 0 {
|
if len(k.user.supp) > 0 {
|
||||||
hlog.Verbosef("attaching supplementary group ids %s", seal.user.supp)
|
hlog.Verbosef("attaching supplementary group ids %s", k.user.supp)
|
||||||
// interpreted by hsu
|
// 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)
|
hlog.Verbosef("setuid helper at %s", hsuPath)
|
||||||
@ -284,8 +284,8 @@ func (seal *outcome) main() {
|
|||||||
go func() {
|
go func() {
|
||||||
setupErr <- e.Encode(&shimParams{
|
setupErr <- e.Encode(&shimParams{
|
||||||
os.Getpid(),
|
os.Getpid(),
|
||||||
seal.waitDelay,
|
k.waitDelay,
|
||||||
seal.container,
|
k.container,
|
||||||
hlog.Load(),
|
hlog.Load(),
|
||||||
})
|
})
|
||||||
}()
|
}()
|
||||||
@ -302,12 +302,12 @@ func (seal *outcome) main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// shim accepted setup payload, create process state
|
// 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{
|
if err := c.Save(&state.State{
|
||||||
ID: seal.id.unwrap(),
|
ID: k.id.unwrap(),
|
||||||
PID: ms.cmd.Process.Pid,
|
PID: ms.cmd.Process.Pid,
|
||||||
Time: *ms.Time,
|
Time: *ms.Time,
|
||||||
}, seal.ct); err != nil {
|
}, k.ct); err != nil {
|
||||||
ms.fatal("cannot save state entry:", err)
|
ms.fatal("cannot save state entry:", err)
|
||||||
}
|
}
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
|
@ -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)
|
|
||||||
}
|
|
@ -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
|
|
||||||
}
|
|
@ -100,7 +100,7 @@ print(machine.fail("sudo -u alice -i hsu"))
|
|||||||
# Verify hsu fault behaviour:
|
# Verify hsu fault behaviour:
|
||||||
if denyOutput != "hsu: uid 1001 is not in the hsurc file\n":
|
if denyOutput != "hsu: uid 1001 is not in the hsurc file\n":
|
||||||
raise Exception(f"unexpected deny output:\n{denyOutput}")
|
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}")
|
raise Exception(f"unexpected deny verbose output:\n{denyOutputVerbose}")
|
||||||
|
|
||||||
# Verify timeout behaviour:
|
# Verify timeout behaviour:
|
||||||
|
Loading…
x
Reference in New Issue
Block a user