Compare commits
22 Commits
52e3324ef4
...
c0e860000a
| Author | SHA1 | Date | |
|---|---|---|---|
|
c0e860000a
|
|||
|
d87020f0ca
|
|||
|
e47aebb7a0
|
|||
|
543bf69102
|
|||
|
4cfb1fda8f
|
|||
|
c12183959a
|
|||
|
f5845e312e
|
|||
|
a103c4a7c7
|
|||
|
67ec82ae1b
|
|||
|
f6f0cb56ae
|
|||
|
d4284c109d
|
|||
|
030ad2a73b
|
|||
|
78d7955abd
|
|||
|
b066495a7d
|
|||
|
82299d34c6
|
|||
|
792013cefb
|
|||
|
3f39132935
|
|||
|
c922c3f80e
|
|||
|
6cf58ca1b3
|
|||
|
425421d9b1
|
|||
|
5e0f15d76b
|
|||
|
ae65491223
|
@@ -94,7 +94,7 @@ func buildCommand(ctx context.Context, msg message.Msg, early *earlyHardeningErr
|
|||||||
passwd *user.User
|
passwd *user.User
|
||||||
passwdOnce sync.Once
|
passwdOnce sync.Once
|
||||||
passwdFunc = func() {
|
passwdFunc = func() {
|
||||||
us := strconv.Itoa(app.HsuUid(new(app.Hsu).MustIDMsg(msg), flagIdentity))
|
us := strconv.Itoa(app.HsuUid(new(app.Hsu).MustID(msg), flagIdentity))
|
||||||
if u, err := user.LookupId(us); err != nil {
|
if u, err := user.LookupId(us); err != nil {
|
||||||
msg.Verbosef("cannot look up uid %s", us)
|
msg.Verbosef("cannot look up uid %s", us)
|
||||||
passwd = &user.User{
|
passwd = &user.User{
|
||||||
@@ -302,7 +302,7 @@ func buildCommand(ctx context.Context, msg message.Msg, early *earlyHardeningErr
|
|||||||
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 {
|
||||||
var sc hst.Paths
|
var sc hst.Paths
|
||||||
app.CopyPaths().Copy(&sc, new(app.Hsu).MustID())
|
app.CopyPaths().Copy(&sc, new(app.Hsu).MustID(nil))
|
||||||
printPs(os.Stdout, time.Now().UTC(), state.NewMulti(msg, sc.RunDirPath.String()), flagShort, flagJSON)
|
printPs(os.Stdout, time.Now().UTC(), state.NewMulti(msg, 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")
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ func tryShort(msg message.Msg, name string) (config *hst.Config, entry *state.St
|
|||||||
msg.Verbose("argument looks like prefix")
|
msg.Verbose("argument looks like prefix")
|
||||||
|
|
||||||
var sc hst.Paths
|
var sc hst.Paths
|
||||||
app.CopyPaths().Copy(&sc, new(app.Hsu).MustID())
|
app.CopyPaths().Copy(&sc, new(app.Hsu).MustID(nil))
|
||||||
s := state.NewMulti(msg, sc.RunDirPath.String())
|
s := state.NewMulti(msg, 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)
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ func printShowSystem(output io.Writer, short, flagJSON bool) {
|
|||||||
t := newPrinter(output)
|
t := newPrinter(output)
|
||||||
defer t.MustFlush()
|
defer t.MustFlush()
|
||||||
|
|
||||||
info := &hst.Info{User: new(app.Hsu).MustID()}
|
info := &hst.Info{User: new(app.Hsu).MustID(nil)}
|
||||||
app.CopyPaths().Copy(&info.Paths, info.User)
|
app.CopyPaths().Copy(&info.Paths, info.User)
|
||||||
|
|
||||||
if flagJSON {
|
if flagJSON {
|
||||||
|
|||||||
@@ -353,10 +353,14 @@ func initEntrypoint(k syscallDispatcher, msg message.Msg) {
|
|||||||
wpid int
|
wpid int
|
||||||
wstatus WaitStatus
|
wstatus WaitStatus
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// info is closed as the wait4 thread terminates
|
||||||
|
// when there are no longer any processes left to reap
|
||||||
info := make(chan winfo, 1)
|
info := make(chan winfo, 1)
|
||||||
done := make(chan struct{})
|
|
||||||
|
|
||||||
k.new(func(k syscallDispatcher) {
|
k.new(func(k syscallDispatcher) {
|
||||||
|
k.lockOSThread()
|
||||||
|
|
||||||
var (
|
var (
|
||||||
err error
|
err error
|
||||||
wpid = -2
|
wpid = -2
|
||||||
@@ -382,7 +386,7 @@ func initEntrypoint(k syscallDispatcher, msg message.Msg) {
|
|||||||
k.printf(msg, "unexpected wait4 response: %v", err)
|
k.printf(msg, "unexpected wait4 response: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
close(done)
|
close(info)
|
||||||
})
|
})
|
||||||
|
|
||||||
// handle signals to dump withheld messages
|
// handle signals to dump withheld messages
|
||||||
@@ -411,7 +415,13 @@ func initEntrypoint(k syscallDispatcher, msg message.Msg) {
|
|||||||
msg.BeforeExit()
|
msg.BeforeExit()
|
||||||
k.exit(0)
|
k.exit(0)
|
||||||
|
|
||||||
case w := <-info:
|
case w, ok := <-info:
|
||||||
|
if !ok {
|
||||||
|
msg.BeforeExit()
|
||||||
|
k.exit(r)
|
||||||
|
continue // unreachable
|
||||||
|
}
|
||||||
|
|
||||||
if w.wpid == cmd.Process.Pid {
|
if w.wpid == cmd.Process.Pid {
|
||||||
// initial process exited, output is most likely available again
|
// initial process exited, output is most likely available again
|
||||||
msg.Resume()
|
msg.Resume()
|
||||||
@@ -433,10 +443,6 @@ func initEntrypoint(k syscallDispatcher, msg message.Msg) {
|
|||||||
go func() { time.Sleep(params.AdoptWaitDelay); close(timeout) }()
|
go func() { time.Sleep(params.AdoptWaitDelay); close(timeout) }()
|
||||||
}
|
}
|
||||||
|
|
||||||
case <-done:
|
|
||||||
msg.BeforeExit()
|
|
||||||
k.exit(r)
|
|
||||||
|
|
||||||
case <-timeout:
|
case <-timeout:
|
||||||
k.printf(msg, "timeout exceeded waiting for lingering processes")
|
k.printf(msg, "timeout exceeded waiting for lingering processes")
|
||||||
msg.BeforeExit()
|
msg.BeforeExit()
|
||||||
|
|||||||
@@ -2081,6 +2081,8 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
|
|
||||||
/* wait4 */
|
/* wait4 */
|
||||||
Tracks: []stub.Expect{{Calls: []stub.Call{
|
Tracks: []stub.Expect{{Calls: []stub.Call{
|
||||||
|
call("lockOSThread", stub.ExpectArgs{}, nil, nil),
|
||||||
|
|
||||||
// magicWait4Signal as args[4] causes this to block until simulated signal is delivered
|
// magicWait4Signal as args[4] causes this to block until simulated signal is delivered
|
||||||
call("wait4", stub.ExpectArgs{-1, syscall.WaitStatus(0xfade01ce), 0, nil, magicWait4Signal}, 0xbad, nil),
|
call("wait4", stub.ExpectArgs{-1, syscall.WaitStatus(0xfade01ce), 0, nil, magicWait4Signal}, 0xbad, nil),
|
||||||
// this terminates the goroutine at the call, preventing it from leaking while preserving behaviour
|
// this terminates the goroutine at the call, preventing it from leaking while preserving behaviour
|
||||||
@@ -2174,6 +2176,8 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
|
|
||||||
/* wait4 */
|
/* wait4 */
|
||||||
Tracks: []stub.Expect{{Calls: []stub.Call{
|
Tracks: []stub.Expect{{Calls: []stub.Call{
|
||||||
|
call("lockOSThread", stub.ExpectArgs{}, nil, nil),
|
||||||
|
|
||||||
// this terminates the goroutine at the call, preventing it from leaking while preserving behaviour
|
// this terminates the goroutine at the call, preventing it from leaking while preserving behaviour
|
||||||
call("wait4", stub.ExpectArgs{-1, nil, 0, nil, stub.PanicExit}, 0, syscall.ECHILD),
|
call("wait4", stub.ExpectArgs{-1, nil, 0, nil, stub.PanicExit}, 0, syscall.ECHILD),
|
||||||
}}},
|
}}},
|
||||||
@@ -2266,6 +2270,8 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
|
|
||||||
/* wait4 */
|
/* wait4 */
|
||||||
Tracks: []stub.Expect{{Calls: []stub.Call{
|
Tracks: []stub.Expect{{Calls: []stub.Call{
|
||||||
|
call("lockOSThread", stub.ExpectArgs{}, nil, nil),
|
||||||
|
|
||||||
call("wait4", stub.ExpectArgs{-1, syscall.WaitStatus(0xfade01ce), 0, nil}, 0xbad, nil),
|
call("wait4", stub.ExpectArgs{-1, syscall.WaitStatus(0xfade01ce), 0, nil}, 0xbad, nil),
|
||||||
// this terminates the goroutine at the call, preventing it from leaking while preserving behaviour
|
// this terminates the goroutine at the call, preventing it from leaking while preserving behaviour
|
||||||
call("wait4", stub.ExpectArgs{-1, nil, 0, nil, 0xdeadbeef}, 0, syscall.ECHILD),
|
call("wait4", stub.ExpectArgs{-1, nil, 0, nil, 0xdeadbeef}, 0, syscall.ECHILD),
|
||||||
@@ -2358,6 +2364,8 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
|
|
||||||
/* wait4 */
|
/* wait4 */
|
||||||
Tracks: []stub.Expect{{Calls: []stub.Call{
|
Tracks: []stub.Expect{{Calls: []stub.Call{
|
||||||
|
call("lockOSThread", stub.ExpectArgs{}, nil, nil),
|
||||||
|
|
||||||
call("wait4", stub.ExpectArgs{-1, nil, 0, nil}, 0, syscall.EINTR),
|
call("wait4", stub.ExpectArgs{-1, nil, 0, nil}, 0, syscall.EINTR),
|
||||||
call("wait4", stub.ExpectArgs{-1, nil, 0, nil}, 0, syscall.EINTR),
|
call("wait4", stub.ExpectArgs{-1, nil, 0, nil}, 0, syscall.EINTR),
|
||||||
call("wait4", stub.ExpectArgs{-1, syscall.WaitStatus(0xdeaf), 0, nil}, 0xbabe, nil),
|
call("wait4", stub.ExpectArgs{-1, syscall.WaitStatus(0xdeaf), 0, nil}, 0xbabe, nil),
|
||||||
@@ -2494,6 +2502,8 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
|
|
||||||
/* wait4 */
|
/* wait4 */
|
||||||
Tracks: []stub.Expect{{Calls: []stub.Call{
|
Tracks: []stub.Expect{{Calls: []stub.Call{
|
||||||
|
call("lockOSThread", stub.ExpectArgs{}, nil, nil),
|
||||||
|
|
||||||
call("wait4", stub.ExpectArgs{-1, nil, 0, nil}, 0, syscall.EINTR),
|
call("wait4", stub.ExpectArgs{-1, nil, 0, nil}, 0, syscall.EINTR),
|
||||||
call("wait4", stub.ExpectArgs{-1, nil, 0, nil}, 0, syscall.EINTR),
|
call("wait4", stub.ExpectArgs{-1, nil, 0, nil}, 0, syscall.EINTR),
|
||||||
call("wait4", stub.ExpectArgs{-1, syscall.WaitStatus(0xdeaf), 0, nil}, 0xbabe, nil),
|
call("wait4", stub.ExpectArgs{-1, syscall.WaitStatus(0xdeaf), 0, nil}, 0xbabe, nil),
|
||||||
@@ -2634,6 +2644,8 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
|
|
||||||
/* wait4 */
|
/* wait4 */
|
||||||
Tracks: []stub.Expect{{Calls: []stub.Call{
|
Tracks: []stub.Expect{{Calls: []stub.Call{
|
||||||
|
call("lockOSThread", stub.ExpectArgs{}, nil, nil),
|
||||||
|
|
||||||
call("wait4", stub.ExpectArgs{-1, nil, 0, nil}, 0, syscall.EINTR),
|
call("wait4", stub.ExpectArgs{-1, nil, 0, nil}, 0, syscall.EINTR),
|
||||||
call("wait4", stub.ExpectArgs{-1, nil, 0, nil}, 0, syscall.EINTR),
|
call("wait4", stub.ExpectArgs{-1, nil, 0, nil}, 0, syscall.EINTR),
|
||||||
call("wait4", stub.ExpectArgs{-1, syscall.WaitStatus(0xdeaf), 0, nil}, 0xbabe, nil),
|
call("wait4", stub.ExpectArgs{-1, syscall.WaitStatus(0xdeaf), 0, nil}, 0xbabe, nil),
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package hst
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"hakurei.app/container/check"
|
"hakurei.app/container/check"
|
||||||
)
|
)
|
||||||
@@ -45,6 +46,9 @@ var (
|
|||||||
|
|
||||||
// ErrIdentityBounds is returned by [Config.Validate] for an out of bounds [Config.Identity] value.
|
// ErrIdentityBounds is returned by [Config.Validate] for an out of bounds [Config.Identity] value.
|
||||||
ErrIdentityBounds = errors.New("identity out of bounds")
|
ErrIdentityBounds = errors.New("identity out of bounds")
|
||||||
|
|
||||||
|
// ErrEnviron is returned by [Config.Validate] if an environment variable name contains '=' or NUL.
|
||||||
|
ErrEnviron = errors.New("invalid environment variable name")
|
||||||
)
|
)
|
||||||
|
|
||||||
// Validate checks [Config] and returns [AppError] if an invalid value is encountered.
|
// Validate checks [Config] and returns [AppError] if an invalid value is encountered.
|
||||||
@@ -83,6 +87,14 @@ func (config *Config) Validate() error {
|
|||||||
return &AppError{Step: "validate configuration", Err: ErrConfigNull,
|
return &AppError{Step: "validate configuration", Err: ErrConfigNull,
|
||||||
Msg: "container configuration missing path to initial program"}
|
Msg: "container configuration missing path to initial program"}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for key := range config.Container.Env {
|
||||||
|
if strings.IndexByte(key, '=') != -1 || strings.IndexByte(key, 0) != -1 {
|
||||||
|
return &AppError{Step: "validate configuration", Err: ErrEnviron,
|
||||||
|
Msg: "invalid environment variable " + strconv.Quote(key)}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -39,6 +39,20 @@ func TestConfigValidate(t *testing.T) {
|
|||||||
Shell: fhs.AbsTmp,
|
Shell: fhs.AbsTmp,
|
||||||
}}, &hst.AppError{Step: "validate configuration", Err: hst.ErrConfigNull,
|
}}, &hst.AppError{Step: "validate configuration", Err: hst.ErrConfigNull,
|
||||||
Msg: "container configuration missing path to initial program"}},
|
Msg: "container configuration missing path to initial program"}},
|
||||||
|
{"env equals", &hst.Config{Container: &hst.ContainerConfig{
|
||||||
|
Home: fhs.AbsTmp,
|
||||||
|
Shell: fhs.AbsTmp,
|
||||||
|
Path: fhs.AbsTmp,
|
||||||
|
Env: map[string]string{"TERM=": ""},
|
||||||
|
}}, &hst.AppError{Step: "validate configuration", Err: hst.ErrEnviron,
|
||||||
|
Msg: `invalid environment variable "TERM="`}},
|
||||||
|
{"env NUL", &hst.Config{Container: &hst.ContainerConfig{
|
||||||
|
Home: fhs.AbsTmp,
|
||||||
|
Shell: fhs.AbsTmp,
|
||||||
|
Path: fhs.AbsTmp,
|
||||||
|
Env: map[string]string{"TERM\x00": ""},
|
||||||
|
}}, &hst.AppError{Step: "validate configuration", Err: hst.ErrEnviron,
|
||||||
|
Msg: `invalid environment variable "TERM\x00"`}},
|
||||||
{"valid", &hst.Config{Container: &hst.ContainerConfig{
|
{"valid", &hst.Config{Container: &hst.ContainerConfig{
|
||||||
Home: fhs.AbsTmp,
|
Home: fhs.AbsTmp,
|
||||||
Shell: fhs.AbsTmp,
|
Shell: fhs.AbsTmp,
|
||||||
|
|||||||
@@ -24,18 +24,25 @@ const (
|
|||||||
IdentityMin = 0
|
IdentityMin = 0
|
||||||
// IdentityMax is the maximum value of [Config.Identity]. This is enforced by cmd/hsu.
|
// IdentityMax is the maximum value of [Config.Identity]. This is enforced by cmd/hsu.
|
||||||
IdentityMax = 9999
|
IdentityMax = 9999
|
||||||
|
)
|
||||||
|
|
||||||
// ShimExitRequest is returned when the priv side process requests shim exit.
|
const (
|
||||||
ShimExitRequest = 254
|
// ExitFailure is returned if the container fails to start.
|
||||||
// ShimExitOrphan is returned when the shim is orphaned before priv side delivers a signal.
|
ExitFailure = iota + 1
|
||||||
ShimExitOrphan = 3
|
// ExitCancel is returned if the container is terminated by a shim-directed signal which cancels its context.
|
||||||
|
ExitCancel
|
||||||
|
// ExitOrphan is returned when the shim is orphaned before priv side delivers a signal.
|
||||||
|
ExitOrphan
|
||||||
|
|
||||||
|
// ExitRequest is returned when the priv side process requests shim exit.
|
||||||
|
ExitRequest = 254
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// FMultiarch unblocks syscalls required for multiarch to work on applicable targets.
|
// FMultiarch unblocks syscalls required for multiarch to work on applicable targets.
|
||||||
FMultiarch uintptr = 1 << iota
|
FMultiarch uintptr = 1 << iota
|
||||||
|
|
||||||
// FSeccompCompat causes emitted seccomp filter programs to be identical to Flatpak.
|
// FSeccompCompat changes emitted seccomp filter programs to be identical to that of Flatpak.
|
||||||
FSeccompCompat
|
FSeccompCompat
|
||||||
// FDevel unblocks ptrace and friends.
|
// FDevel unblocks ptrace and friends.
|
||||||
FDevel
|
FDevel
|
||||||
|
|||||||
@@ -103,17 +103,17 @@ func TestApp(t *testing.T) {
|
|||||||
Tmpfs(hst.AbsPrivateTmp, 4096, 0755).
|
Tmpfs(hst.AbsPrivateTmp, 4096, 0755).
|
||||||
DevWritable(m("/dev/"), true).
|
DevWritable(m("/dev/"), true).
|
||||||
Tmpfs(m("/dev/shm"), 0, 01777).
|
Tmpfs(m("/dev/shm"), 0, 01777).
|
||||||
|
Tmpfs(m("/run/user/"), 4096, 0755).
|
||||||
|
Bind(m("/tmp/hakurei.0/runtime/0"), m("/run/user/65534"), bits.BindWritable).
|
||||||
|
Bind(m("/tmp/hakurei.0/tmpdir/0"), m("/tmp/"), bits.BindWritable).
|
||||||
|
Place(m("/etc/passwd"), []byte("chronos:x:65534:65534:Hakurei:/home/chronos:/run/current-system/sw/bin/zsh\n")).
|
||||||
|
Place(m("/etc/group"), []byte("hakurei:x:65534:\n")).
|
||||||
Bind(m("/dev/kvm"), m("/dev/kvm"), bits.BindWritable|bits.BindDevice|bits.BindOptional).
|
Bind(m("/dev/kvm"), m("/dev/kvm"), bits.BindWritable|bits.BindDevice|bits.BindOptional).
|
||||||
Etc(m("/etc/"), "4a450b6596d7bc15bd01780eb9a607ac").
|
Etc(m("/etc/"), "4a450b6596d7bc15bd01780eb9a607ac").
|
||||||
Tmpfs(m("/run/user/1971"), 8192, 0755).
|
Tmpfs(m("/run/user/1971"), 8192, 0755).
|
||||||
Tmpfs(m("/run/nscd"), 8192, 0755).
|
Tmpfs(m("/run/nscd"), 8192, 0755).
|
||||||
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).
|
|
||||||
Bind(m("/tmp/hakurei.0/runtime/0"), m("/run/user/65534"), bits.BindWritable).
|
|
||||||
Bind(m("/tmp/hakurei.0/tmpdir/0"), m("/tmp/"), bits.BindWritable).
|
|
||||||
Place(m("/etc/passwd"), []byte("chronos:x:65534:65534:Hakurei:/home/chronos:/run/current-system/sw/bin/zsh\n")).
|
|
||||||
Place(m("/etc/group"), []byte("hakurei:x:65534:\n")).
|
|
||||||
Remount(m("/"), syscall.MS_RDONLY),
|
Remount(m("/"), syscall.MS_RDONLY),
|
||||||
SeccompPresets: bits.PresetExt | bits.PresetDenyDevel,
|
SeccompPresets: bits.PresetExt | bits.PresetDenyDevel,
|
||||||
HostNet: true,
|
HostNet: true,
|
||||||
@@ -268,7 +268,7 @@ func TestApp(t *testing.T) {
|
|||||||
"WAYLAND_DISPLAY=wayland-0",
|
"WAYLAND_DISPLAY=wayland-0",
|
||||||
"XDG_RUNTIME_DIR=/run/user/65534",
|
"XDG_RUNTIME_DIR=/run/user/65534",
|
||||||
"XDG_SESSION_CLASS=user",
|
"XDG_SESSION_CLASS=user",
|
||||||
"XDG_SESSION_TYPE=tty",
|
"XDG_SESSION_TYPE=wayland",
|
||||||
},
|
},
|
||||||
Ops: new(container.Ops).
|
Ops: new(container.Ops).
|
||||||
Root(m("/"), bits.BindWritable).
|
Root(m("/"), bits.BindWritable).
|
||||||
@@ -276,13 +276,6 @@ func TestApp(t *testing.T) {
|
|||||||
Tmpfs(hst.AbsPrivateTmp, 4096, 0755).
|
Tmpfs(hst.AbsPrivateTmp, 4096, 0755).
|
||||||
DevWritable(m("/dev/"), true).
|
DevWritable(m("/dev/"), true).
|
||||||
Tmpfs(m("/dev/shm"), 0, 01777).
|
Tmpfs(m("/dev/shm"), 0, 01777).
|
||||||
Bind(m("/dev/dri"), m("/dev/dri"), bits.BindWritable|bits.BindDevice|bits.BindOptional).
|
|
||||||
Bind(m("/dev/kvm"), m("/dev/kvm"), bits.BindWritable|bits.BindDevice|bits.BindOptional).
|
|
||||||
Etc(m("/etc/"), "ebf083d1b175911782d413369b64ce7c").
|
|
||||||
Tmpfs(m("/run/user/1971"), 8192, 0755).
|
|
||||||
Tmpfs(m("/run/nscd"), 8192, 0755).
|
|
||||||
Tmpfs(m("/run/dbus"), 8192, 0755).
|
|
||||||
Remount(m("/dev/"), syscall.MS_RDONLY).
|
|
||||||
Tmpfs(m("/run/user/"), 4096, 0755).
|
Tmpfs(m("/run/user/"), 4096, 0755).
|
||||||
Bind(m("/tmp/hakurei.0/runtime/9"), m("/run/user/65534"), bits.BindWritable).
|
Bind(m("/tmp/hakurei.0/runtime/9"), m("/run/user/65534"), bits.BindWritable).
|
||||||
Bind(m("/tmp/hakurei.0/tmpdir/9"), m("/tmp/"), bits.BindWritable).
|
Bind(m("/tmp/hakurei.0/tmpdir/9"), m("/tmp/"), bits.BindWritable).
|
||||||
@@ -293,6 +286,13 @@ func TestApp(t *testing.T) {
|
|||||||
Place(m(hst.PrivateTmp+"/pulse-cookie"), bytes.Repeat([]byte{0}, pulseCookieSizeMax)).
|
Place(m(hst.PrivateTmp+"/pulse-cookie"), bytes.Repeat([]byte{0}, pulseCookieSizeMax)).
|
||||||
Bind(m("/tmp/hakurei.0/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.0/ebf083d1b175911782d413369b64ce7c/system_bus_socket"), m("/var/run/dbus/system_bus_socket"), 0).
|
Bind(m("/tmp/hakurei.0/ebf083d1b175911782d413369b64ce7c/system_bus_socket"), m("/var/run/dbus/system_bus_socket"), 0).
|
||||||
|
Bind(m("/dev/dri"), m("/dev/dri"), bits.BindWritable|bits.BindDevice|bits.BindOptional).
|
||||||
|
Bind(m("/dev/kvm"), m("/dev/kvm"), bits.BindWritable|bits.BindDevice|bits.BindOptional).
|
||||||
|
Etc(m("/etc/"), "ebf083d1b175911782d413369b64ce7c").
|
||||||
|
Tmpfs(m("/run/user/1971"), 8192, 0755).
|
||||||
|
Tmpfs(m("/run/nscd"), 8192, 0755).
|
||||||
|
Tmpfs(m("/run/dbus"), 8192, 0755).
|
||||||
|
Remount(m("/dev/"), syscall.MS_RDONLY).
|
||||||
Remount(m("/"), syscall.MS_RDONLY),
|
Remount(m("/"), syscall.MS_RDONLY),
|
||||||
SeccompPresets: bits.PresetExt | bits.PresetDenyDevel,
|
SeccompPresets: bits.PresetExt | bits.PresetDenyDevel,
|
||||||
HostNet: true,
|
HostNet: true,
|
||||||
@@ -420,13 +420,23 @@ func TestApp(t *testing.T) {
|
|||||||
"WAYLAND_DISPLAY=wayland-0",
|
"WAYLAND_DISPLAY=wayland-0",
|
||||||
"XDG_RUNTIME_DIR=/run/user/1971",
|
"XDG_RUNTIME_DIR=/run/user/1971",
|
||||||
"XDG_SESSION_CLASS=user",
|
"XDG_SESSION_CLASS=user",
|
||||||
"XDG_SESSION_TYPE=tty",
|
"XDG_SESSION_TYPE=wayland",
|
||||||
},
|
},
|
||||||
Ops: new(container.Ops).
|
Ops: new(container.Ops).
|
||||||
Proc(m("/proc/")).
|
Proc(m("/proc/")).
|
||||||
Tmpfs(hst.AbsPrivateTmp, 4096, 0755).
|
Tmpfs(hst.AbsPrivateTmp, 4096, 0755).
|
||||||
DevWritable(m("/dev/"), true).
|
DevWritable(m("/dev/"), true).
|
||||||
Tmpfs(m("/dev/shm"), 0, 01777).
|
Tmpfs(m("/dev/shm"), 0, 01777).
|
||||||
|
Tmpfs(m("/run/user/"), 4096, 0755).
|
||||||
|
Bind(m("/tmp/hakurei.0/runtime/1"), m("/run/user/1971"), bits.BindWritable).
|
||||||
|
Bind(m("/tmp/hakurei.0/tmpdir/1"), m("/tmp/"), bits.BindWritable).
|
||||||
|
Place(m("/etc/passwd"), []byte("u0_a1:x:1971:100:Hakurei:/var/lib/persist/module/hakurei/0/1:/run/current-system/sw/bin/zsh\n")).
|
||||||
|
Place(m("/etc/group"), []byte("hakurei:x:100:\n")).
|
||||||
|
Bind(m("/run/user/1971/wayland-0"), m("/run/user/1971/wayland-0"), 0).
|
||||||
|
Bind(m("/run/user/1971/hakurei/8e2c76b066dabe574cf073bdb46eb5c1/pulse"), m("/run/user/1971/pulse/native"), 0).
|
||||||
|
Place(m(hst.PrivateTmp+"/pulse-cookie"), bytes.Repeat([]byte{0}, pulseCookieSizeMax)).
|
||||||
|
Bind(m("/tmp/hakurei.0/8e2c76b066dabe574cf073bdb46eb5c1/bus"), m("/run/user/1971/bus"), 0).
|
||||||
|
Bind(m("/tmp/hakurei.0/8e2c76b066dabe574cf073bdb46eb5c1/system_bus_socket"), m("/var/run/dbus/system_bus_socket"), 0).
|
||||||
Bind(m("/bin"), m("/bin"), 0).
|
Bind(m("/bin"), m("/bin"), 0).
|
||||||
Bind(m("/usr/bin/"), m("/usr/bin/"), 0).
|
Bind(m("/usr/bin/"), m("/usr/bin/"), 0).
|
||||||
Bind(m("/nix/store"), m("/nix/store"), 0).
|
Bind(m("/nix/store"), m("/nix/store"), 0).
|
||||||
@@ -441,16 +451,6 @@ func TestApp(t *testing.T) {
|
|||||||
Etc(m("/etc/"), "8e2c76b066dabe574cf073bdb46eb5c1").
|
Etc(m("/etc/"), "8e2c76b066dabe574cf073bdb46eb5c1").
|
||||||
Bind(m("/var/lib/persist/module/hakurei/0/1"), m("/var/lib/persist/module/hakurei/0/1"), bits.BindWritable|bits.BindEnsure).
|
Bind(m("/var/lib/persist/module/hakurei/0/1"), m("/var/lib/persist/module/hakurei/0/1"), bits.BindWritable|bits.BindEnsure).
|
||||||
Remount(m("/dev/"), syscall.MS_RDONLY).
|
Remount(m("/dev/"), syscall.MS_RDONLY).
|
||||||
Tmpfs(m("/run/user/"), 4096, 0755).
|
|
||||||
Bind(m("/tmp/hakurei.0/runtime/1"), m("/run/user/1971"), bits.BindWritable).
|
|
||||||
Bind(m("/tmp/hakurei.0/tmpdir/1"), m("/tmp/"), bits.BindWritable).
|
|
||||||
Place(m("/etc/passwd"), []byte("u0_a1:x:1971:100:Hakurei:/var/lib/persist/module/hakurei/0/1:/run/current-system/sw/bin/zsh\n")).
|
|
||||||
Place(m("/etc/group"), []byte("hakurei:x:100:\n")).
|
|
||||||
Bind(m("/run/user/1971/wayland-0"), m("/run/user/1971/wayland-0"), 0).
|
|
||||||
Bind(m("/run/user/1971/hakurei/8e2c76b066dabe574cf073bdb46eb5c1/pulse"), m("/run/user/1971/pulse/native"), 0).
|
|
||||||
Place(m(hst.PrivateTmp+"/pulse-cookie"), bytes.Repeat([]byte{0}, pulseCookieSizeMax)).
|
|
||||||
Bind(m("/tmp/hakurei.0/8e2c76b066dabe574cf073bdb46eb5c1/bus"), m("/run/user/1971/bus"), 0).
|
|
||||||
Bind(m("/tmp/hakurei.0/8e2c76b066dabe574cf073bdb46eb5c1/system_bus_socket"), m("/var/run/dbus/system_bus_socket"), 0).
|
|
||||||
Remount(m("/"), syscall.MS_RDONLY),
|
Remount(m("/"), syscall.MS_RDONLY),
|
||||||
SeccompPresets: bits.PresetExt | bits.PresetDenyTTY | bits.PresetDenyDevel,
|
SeccompPresets: bits.PresetExt | bits.PresetDenyTTY | bits.PresetDenyDevel,
|
||||||
HostNet: true,
|
HostNet: true,
|
||||||
|
|||||||
@@ -2,8 +2,10 @@ package app
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"io"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"log"
|
"log"
|
||||||
|
"maps"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"reflect"
|
"reflect"
|
||||||
@@ -26,33 +28,135 @@ func call(name string, args stub.ExpectArgs, ret any, err error) stub.Call {
|
|||||||
return stub.Call{Name: name, Args: args, Ret: ret, Err: err}
|
return stub.Call{Name: name, Args: args, Ret: ret, Err: err}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
// checkExpectUid is the uid value used by checkOpBehaviour to initialise [system.I].
|
// checkExpectUid is the uid value used by checkOpBehaviour to initialise [system.I].
|
||||||
const checkExpectUid = 0xcafebabe
|
checkExpectUid = 0xcafebabe
|
||||||
|
|
||||||
// wantAutoEtcPrefix is the autoetc prefix corresponding to checkExpectInstanceId.
|
// wantAutoEtcPrefix is the autoetc prefix corresponding to checkExpectInstanceId.
|
||||||
const wantAutoEtcPrefix = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
wantAutoEtcPrefix = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
||||||
|
// wantInstancePrefix is the SharePath corresponding to checkExpectInstanceId.
|
||||||
|
wantInstancePrefix = container.Nonexistent + "/tmp/hakurei.0/" + wantAutoEtcPrefix
|
||||||
|
|
||||||
|
// wantRuntimePath is the XDG_RUNTIME_DIR value returned during testing.
|
||||||
|
wantRuntimePath = "/proc/nonexistent/xdg_runtime_dir"
|
||||||
|
// wantRunDirPath is the RunDirPath value resolved during testing.
|
||||||
|
wantRunDirPath = wantRuntimePath + "/hakurei"
|
||||||
|
// wantRuntimeSharePath is the runtimeSharePath value resolved during testing.
|
||||||
|
wantRuntimeSharePath = wantRunDirPath + "/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
||||||
|
)
|
||||||
|
|
||||||
// checkExpectInstanceId is the [state.ID] value used by checkOpBehaviour to initialise outcomeState.
|
// checkExpectInstanceId is the [state.ID] value used by checkOpBehaviour to initialise outcomeState.
|
||||||
var checkExpectInstanceId = *(*state.ID)(bytes.Repeat([]byte{0xaa}, len(state.ID{})))
|
var checkExpectInstanceId = *(*state.ID)(bytes.Repeat([]byte{0xaa}, len(state.ID{})))
|
||||||
|
|
||||||
|
type (
|
||||||
|
// pStateSysFunc is called before each test case is run to prepare outcomeStateSys.
|
||||||
|
pStateSysFunc = func(state *outcomeStateSys)
|
||||||
|
// pStateContainerFunc is called before each test case is run to prepare outcomeStateParams.
|
||||||
|
pStateContainerFunc = func(state *outcomeStateParams)
|
||||||
|
|
||||||
|
// extraCheckSysFunc is called to check outcomeStateSys and must not have side effects.
|
||||||
|
extraCheckSysFunc = func(t *testing.T, state *outcomeStateSys)
|
||||||
|
// extraCheckParamsFunc is called to check outcomeStateParams and must not have side effects.
|
||||||
|
extraCheckParamsFunc = func(t *testing.T, state *outcomeStateParams)
|
||||||
|
)
|
||||||
|
|
||||||
|
// insertsOps prepares outcomeStateParams to allow [container.Op] to be inserted.
|
||||||
|
func insertsOps(next pStateContainerFunc) pStateContainerFunc {
|
||||||
|
return func(state *outcomeStateParams) {
|
||||||
|
state.params.Ops = new(container.Ops)
|
||||||
|
|
||||||
|
if next != nil {
|
||||||
|
next(state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// afterSpRuntimeOp prepares outcomeStateParams for an outcomeOp meant to run after spRuntimeOp.
|
||||||
|
func afterSpRuntimeOp(next pStateContainerFunc) pStateContainerFunc {
|
||||||
|
return func(state *outcomeStateParams) {
|
||||||
|
// emulates spRuntimeOp
|
||||||
|
state.runtimeDir = m("/run/user/1000")
|
||||||
|
|
||||||
|
if next != nil {
|
||||||
|
next(state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// sysUsesInstance checks for use of the outcomeStateSys.instance method.
|
||||||
|
func sysUsesInstance(next extraCheckSysFunc) extraCheckSysFunc {
|
||||||
|
return func(t *testing.T, state *outcomeStateSys) {
|
||||||
|
if want := m(wantInstancePrefix); !reflect.DeepEqual(state.sharePath, want) {
|
||||||
|
t.Errorf("outcomeStateSys: sharePath = %v, want %v", state.sharePath, want)
|
||||||
|
}
|
||||||
|
|
||||||
|
if next != nil {
|
||||||
|
next(t, state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// sysUsesRuntime checks for use of the outcomeStateSys.runtime method.
|
||||||
|
func sysUsesRuntime(next extraCheckSysFunc) extraCheckSysFunc {
|
||||||
|
return func(t *testing.T, state *outcomeStateSys) {
|
||||||
|
if want := m(wantRuntimeSharePath); !reflect.DeepEqual(state.runtimeSharePath, want) {
|
||||||
|
t.Errorf("outcomeStateSys: runtimeSharePath = %v, want %v", state.runtimeSharePath, want)
|
||||||
|
}
|
||||||
|
|
||||||
|
if next != nil {
|
||||||
|
next(t, state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// paramsWantEnv checks outcomeStateParams.env for inserted entries on top of [hst.Config].
|
||||||
|
func paramsWantEnv(config *hst.Config, wantEnv map[string]string, next extraCheckParamsFunc) extraCheckParamsFunc {
|
||||||
|
want := make(map[string]string, len(wantEnv)+len(config.Container.Env))
|
||||||
|
maps.Copy(want, wantEnv)
|
||||||
|
maps.Copy(want, config.Container.Env)
|
||||||
|
return func(t *testing.T, state *outcomeStateParams) {
|
||||||
|
if !maps.Equal(state.env, want) {
|
||||||
|
t.Errorf("toContainer: env = %#v, want %#v", state.env, want)
|
||||||
|
}
|
||||||
|
|
||||||
|
if next != nil {
|
||||||
|
next(t, state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// opBehaviourTestCase checks outcomeOp behaviour against outcomeStateSys and outcomeStateParams.
|
||||||
type opBehaviourTestCase struct {
|
type opBehaviourTestCase struct {
|
||||||
name string
|
name string
|
||||||
|
// newOp returns a new instance of outcomeOp under testing that is safe to clobber.
|
||||||
newOp func(isShim, clearUnexported bool) outcomeOp
|
newOp func(isShim, clearUnexported bool) outcomeOp
|
||||||
|
// newConfig returns a new instance of [hst.Config] that is checked not to be clobbered by outcomeOp.
|
||||||
newConfig func() *hst.Config
|
newConfig func() *hst.Config
|
||||||
|
|
||||||
pStateSys func(state *outcomeStateSys)
|
// pStateSys is called before outcomeOp.toSystem to prepare outcomeStateSys.
|
||||||
|
pStateSys pStateSysFunc
|
||||||
|
// toSystem are expected syscallDispatcher calls during outcomeOp.toSystem.
|
||||||
toSystem []stub.Call
|
toSystem []stub.Call
|
||||||
|
// wantSys is the expected [system.I] state after outcomeOp.toSystem.
|
||||||
wantSys *system.I
|
wantSys *system.I
|
||||||
extraCheckSys func(t *testing.T, state *outcomeStateSys)
|
// extraCheckSys is called after outcomeOp.toSystem to check the state of outcomeStateSys.
|
||||||
|
extraCheckSys extraCheckSysFunc
|
||||||
|
// wantErrSystem is the expected error value returned by outcomeOp.toSystem.
|
||||||
|
// Further testing is skipped if not nil.
|
||||||
wantErrSystem error
|
wantErrSystem error
|
||||||
|
|
||||||
pStateContainer func(state *outcomeStateParams)
|
// pStateContainer is called before outcomeOp.toContainer to prepare outcomeStateParams.
|
||||||
|
pStateContainer pStateContainerFunc
|
||||||
|
// toContainer are expected syscallDispatcher calls during outcomeOp.toContainer.
|
||||||
toContainer []stub.Call
|
toContainer []stub.Call
|
||||||
|
// wantParams is the expected [container.Params] after outcomeOp.toContainer.
|
||||||
wantParams *container.Params
|
wantParams *container.Params
|
||||||
extraCheckParams func(t *testing.T, state *outcomeStateParams)
|
// extraCheckParams is called after outcomeOp.toContainer to check the state of outcomeStateParams.
|
||||||
|
extraCheckParams extraCheckParamsFunc
|
||||||
|
// wantErrContainer is the expected error value returned by outcomeOp.toContainer.
|
||||||
wantErrContainer error
|
wantErrContainer error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// checkOpBehaviour runs a slice of opBehaviourTestCase.
|
||||||
func checkOpBehaviour(t *testing.T, testCases []opBehaviourTestCase) {
|
func checkOpBehaviour(t *testing.T, testCases []opBehaviourTestCase) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
@@ -63,7 +167,7 @@ func checkOpBehaviour(t *testing.T, testCases []opBehaviourTestCase) {
|
|||||||
call("mustHsuPath", stub.ExpectArgs{}, m(container.Nonexistent), nil),
|
call("mustHsuPath", stub.ExpectArgs{}, m(container.Nonexistent), nil),
|
||||||
call("cmdOutput", stub.ExpectArgs{container.Nonexistent, os.Stderr, []string{}, "/"}, []byte("0"), nil),
|
call("cmdOutput", stub.ExpectArgs{container.Nonexistent, os.Stderr, []string{}, "/"}, []byte("0"), nil),
|
||||||
call("tempdir", stub.ExpectArgs{}, container.Nonexistent+"/tmp", nil),
|
call("tempdir", stub.ExpectArgs{}, container.Nonexistent+"/tmp", nil),
|
||||||
call("lookupEnv", stub.ExpectArgs{"XDG_RUNTIME_DIR"}, container.Nonexistent+"/xdg_runtime_dir", nil),
|
call("lookupEnv", stub.ExpectArgs{"XDG_RUNTIME_DIR"}, wantRuntimePath, nil),
|
||||||
call("getuid", stub.ExpectArgs{}, 1000, nil),
|
call("getuid", stub.ExpectArgs{}, 1000, nil),
|
||||||
call("getgid", stub.ExpectArgs{}, 100, nil),
|
call("getgid", stub.ExpectArgs{}, 100, nil),
|
||||||
|
|
||||||
@@ -169,6 +273,40 @@ func checkOpBehaviour(t *testing.T, testCases []opBehaviourTestCase) {
|
|||||||
|
|
||||||
func newI() *system.I { return system.New(panicMsgContext{}, panicMsgContext{}, checkExpectUid) }
|
func newI() *system.I { return system.New(panicMsgContext{}, panicMsgContext{}, checkExpectUid) }
|
||||||
|
|
||||||
|
// simpleTestCase is a simple freeform test case utilising kstub.
|
||||||
|
type simpleTestCase struct {
|
||||||
|
name string
|
||||||
|
f func(k *kstub) error
|
||||||
|
// want are expected syscallDispatcher calls during f.
|
||||||
|
want stub.Expect
|
||||||
|
// wantErr is the expected error value returned by f.
|
||||||
|
wantErr error
|
||||||
|
}
|
||||||
|
|
||||||
|
// checkSimple runs a slice of simpleTestCase.
|
||||||
|
func checkSimple(t *testing.T, fname string, testCases []simpleTestCase) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
t.Helper()
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
defer stub.HandleExit(t)
|
||||||
|
k := &kstub{panicDispatcher{}, stub.New(t, func(s *stub.Stub[syscallDispatcher]) syscallDispatcher { return &kstub{panicDispatcher{}, s} }, tc.want)}
|
||||||
|
if err := tc.f(k); !reflect.DeepEqual(err, tc.wantErr) {
|
||||||
|
t.Errorf("%s: error = %#v, want %#v", fname, err, tc.wantErr)
|
||||||
|
}
|
||||||
|
k.VisitIncomplete(func(s *stub.Stub[syscallDispatcher]) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
t.Errorf("%s: %d calls, want %d", fname, s.Pos(), s.Len())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// kstub partially implements syscallDispatcher via [stub.Stub].
|
||||||
type kstub struct {
|
type kstub struct {
|
||||||
panicDispatcher
|
panicDispatcher
|
||||||
*stub.Stub[syscallDispatcher]
|
*stub.Stub[syscallDispatcher]
|
||||||
@@ -189,6 +327,18 @@ func (k *kstub) lookupEnv(key string) (string, bool) {
|
|||||||
}
|
}
|
||||||
return expect.Ret.(string), true
|
return expect.Ret.(string), true
|
||||||
}
|
}
|
||||||
|
func (k *kstub) stat(name string) (os.FileInfo, error) {
|
||||||
|
k.Helper()
|
||||||
|
expect := k.Expects("stat")
|
||||||
|
return expect.Ret.(os.FileInfo), expect.Error(
|
||||||
|
stub.CheckArg(k.Stub, "name", name, 0))
|
||||||
|
}
|
||||||
|
func (k *kstub) open(name string) (osFile, error) {
|
||||||
|
k.Helper()
|
||||||
|
expect := k.Expects("open")
|
||||||
|
return expect.Ret.(osFile), expect.Error(
|
||||||
|
stub.CheckArg(k.Stub, "name", name, 0))
|
||||||
|
}
|
||||||
func (k *kstub) readdir(name string) ([]os.DirEntry, error) {
|
func (k *kstub) readdir(name string) ([]os.DirEntry, error) {
|
||||||
k.Helper()
|
k.Helper()
|
||||||
expect := k.Expects("readdir")
|
expect := k.Expects("readdir")
|
||||||
@@ -272,6 +422,32 @@ func (k *kstub) Suspend() bool { k.Helper(); return k.Expects("suspend").Ret.(bo
|
|||||||
func (k *kstub) Resume() bool { k.Helper(); return k.Expects("resume").Ret.(bool) }
|
func (k *kstub) Resume() bool { k.Helper(); return k.Expects("resume").Ret.(bool) }
|
||||||
func (k *kstub) BeforeExit() { k.Helper(); k.Expects("beforeExit") }
|
func (k *kstub) BeforeExit() { k.Helper(); k.Expects("beforeExit") }
|
||||||
|
|
||||||
|
// stubOsFile partially implements osFile.
|
||||||
|
type stubOsFile struct {
|
||||||
|
closeErr error
|
||||||
|
|
||||||
|
io.Reader
|
||||||
|
io.Writer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *stubOsFile) Close() error { return f.closeErr }
|
||||||
|
func (f *stubOsFile) Name() string { panic("unreachable") }
|
||||||
|
func (f *stubOsFile) Stat() (fs.FileInfo, error) { panic("unreachable") }
|
||||||
|
|
||||||
|
// stubFi partially implements [os.FileInfo]. Can be passed as nil to assert all methods unreachable.
|
||||||
|
type stubFi struct {
|
||||||
|
size int64
|
||||||
|
mode os.FileMode
|
||||||
|
isDir bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fi *stubFi) Name() string { panic("unreachable") }
|
||||||
|
func (fi *stubFi) ModTime() time.Time { panic("unreachable") }
|
||||||
|
func (fi *stubFi) Sys() any { panic("unreachable") }
|
||||||
|
func (fi *stubFi) Size() int64 { return fi.size }
|
||||||
|
func (fi *stubFi) Mode() os.FileMode { return fi.mode }
|
||||||
|
func (fi *stubFi) IsDir() bool { return fi.isDir }
|
||||||
|
|
||||||
// stubDir returns a slice of [os.DirEntry] with only their Name method implemented.
|
// stubDir returns a slice of [os.DirEntry] with only their Name method implemented.
|
||||||
func stubDir(names ...string) []os.DirEntry {
|
func stubDir(names ...string) []os.DirEntry {
|
||||||
d := make([]os.DirEntry, len(names))
|
d := make([]os.DirEntry, len(names))
|
||||||
@@ -289,6 +465,11 @@ func (nameDentry) IsDir() bool { panic("unreachable") }
|
|||||||
func (nameDentry) Type() fs.FileMode { panic("unreachable") }
|
func (nameDentry) Type() fs.FileMode { panic("unreachable") }
|
||||||
func (nameDentry) Info() (fs.FileInfo, error) { panic("unreachable") }
|
func (nameDentry) Info() (fs.FileInfo, error) { panic("unreachable") }
|
||||||
|
|
||||||
|
// errorReader implements [io.Reader] that unconditionally returns -1, val.
|
||||||
|
type errorReader struct{ val error }
|
||||||
|
|
||||||
|
func (r errorReader) Read([]byte) (int, error) { return -1, r.val }
|
||||||
|
|
||||||
// panicMsgContext implements [message.Msg] and [context.Context] with methods wrapping panic.
|
// panicMsgContext implements [message.Msg] and [context.Context] with methods wrapping panic.
|
||||||
// This should be assigned to test cases to be checked against.
|
// This should be assigned to test cases to be checked against.
|
||||||
type panicMsgContext struct{}
|
type panicMsgContext struct{}
|
||||||
|
|||||||
@@ -35,7 +35,8 @@ func (h *Hsu) ensureDispatcher() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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.ensureDispatcher()
|
||||||
h.idOnce.Do(func() {
|
h.idOnce.Do(func() {
|
||||||
@@ -61,8 +62,8 @@ func (h *Hsu) ID() (int, error) {
|
|||||||
} else if errors.As(h.idErr, &exitError) && exitError != nil && exitError.ExitCode() == 1 {
|
} else if errors.As(h.idErr, &exitError) && exitError != nil && exitError.ExitCode() == 1 {
|
||||||
// hsu prints an error message in this case
|
// hsu prints an error message in this case
|
||||||
h.idErr = &hst.AppError{Step: step, Err: ErrHsuAccess}
|
h.idErr = &hst.AppError{Step: step, Err: ErrHsuAccess}
|
||||||
} else if os.IsNotExist(h.idErr) {
|
} else if errors.Is(h.idErr, os.ErrNotExist) {
|
||||||
h.idErr = &hst.AppError{Step: step, Err: os.ErrNotExist,
|
h.idErr = &hst.AppError{Step: step, Err: h.idErr,
|
||||||
Msg: fmt.Sprintf("the setuid helper is missing: %s", hsuPath)}
|
Msg: fmt.Sprintf("the setuid helper is missing: %s", hsuPath)}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -71,10 +72,7 @@ func (h *Hsu) ID() (int, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// MustID calls [Hsu.ID] and terminates on error.
|
// MustID calls [Hsu.ID] and terminates on error.
|
||||||
func (h *Hsu) MustID() int { return h.MustIDMsg(nil) }
|
func (h *Hsu) MustID(msg message.Msg) int {
|
||||||
|
|
||||||
// MustIDMsg implements MustID with a custom [container.Msg].
|
|
||||||
func (h *Hsu) MustIDMsg(msg message.Msg) int {
|
|
||||||
id, err := h.ID()
|
id, err := h.ID()
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return id
|
return id
|
||||||
@@ -86,16 +84,16 @@ func (h *Hsu) MustIDMsg(msg message.Msg) int {
|
|||||||
msg.Verbose("*"+fallback, err)
|
msg.Verbose("*"+fallback, err)
|
||||||
}
|
}
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
return -0xdeadbeef
|
return -0xdeadbeef // not reached
|
||||||
} else if m, ok := message.GetMessage(err); ok {
|
} else if m, ok := message.GetMessage(err); ok {
|
||||||
log.Fatal(m)
|
log.Fatal(m)
|
||||||
return -0xdeadbeef
|
return -0xdeadbeef // not reached
|
||||||
} else {
|
} else {
|
||||||
log.Fatalln(fallback, err)
|
log.Fatalln(fallback, err)
|
||||||
return -0xdeadbeef
|
return -0xdeadbeef // not reached
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// HsuUid returns target uid for the stable hsu uid format.
|
// HsuUid returns target uid for the stable hsu uid format.
|
||||||
// No bounds check is performed, a value retrieved from hsu is expected.
|
// No bounds check is performed, a value retrieved by [Hsu] is expected.
|
||||||
func HsuUid(id, identity int) int { return 1000000 + id*10000 + identity }
|
func HsuUid(id, identity int) int { return 1000000 + id*10000 + identity }
|
||||||
|
|||||||
84
internal/app/hsu_test.go
Normal file
84
internal/app/hsu_test.go
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
"syscall"
|
||||||
|
"testing"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"hakurei.app/container/stub"
|
||||||
|
"hakurei.app/hst"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestHsu(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
t.Run("ensure dispatcher", func(t *testing.T) {
|
||||||
|
hsu := new(Hsu)
|
||||||
|
hsu.ensureDispatcher()
|
||||||
|
|
||||||
|
k := direct{}
|
||||||
|
if !reflect.DeepEqual(hsu.k, k) {
|
||||||
|
t.Errorf("ensureDispatcher: k = %#v, want %#v", hsu.k, k)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
fCheckID := func(k *kstub) error {
|
||||||
|
hsu := &Hsu{k: k}
|
||||||
|
id, err := hsu.ID()
|
||||||
|
k.Verbose(id)
|
||||||
|
if id0, err0 := hsu.ID(); id0 != id || !reflect.DeepEqual(err0, err) {
|
||||||
|
t.Fatalf("ID: id0 = %d, err0 = %#v, id = %d, err = %#v", id0, err0, id, err)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
checkSimple(t, "Hsu.ID", []simpleTestCase{
|
||||||
|
{"hsu nonexistent", fCheckID, stub.Expect{Calls: []stub.Call{
|
||||||
|
call("mustHsuPath", stub.ExpectArgs{}, m("/run/wrappers/bin/hsu"), nil),
|
||||||
|
call("cmdOutput", stub.ExpectArgs{"/run/wrappers/bin/hsu", os.Stderr, []string{}, "/"}, ([]byte)(nil), os.ErrNotExist),
|
||||||
|
call("verbose", stub.ExpectArgs{[]any{-1}}, nil, nil),
|
||||||
|
}}, &hst.AppError{
|
||||||
|
Step: "obtain uid from hsu",
|
||||||
|
Err: os.ErrNotExist,
|
||||||
|
Msg: "the setuid helper is missing: /run/wrappers/bin/hsu",
|
||||||
|
}},
|
||||||
|
|
||||||
|
{"access", fCheckID, stub.Expect{Calls: []stub.Call{
|
||||||
|
call("mustHsuPath", stub.ExpectArgs{}, m("/run/wrappers/bin/hsu"), nil),
|
||||||
|
call("cmdOutput", stub.ExpectArgs{"/run/wrappers/bin/hsu", os.Stderr, []string{}, "/"}, ([]byte)(nil), makeExitError(1<<8)),
|
||||||
|
call("verbose", stub.ExpectArgs{[]any{-1}}, nil, nil),
|
||||||
|
}}, &hst.AppError{
|
||||||
|
Step: "obtain uid from hsu",
|
||||||
|
Err: ErrHsuAccess,
|
||||||
|
}},
|
||||||
|
|
||||||
|
{"invalid output", fCheckID, stub.Expect{Calls: []stub.Call{
|
||||||
|
call("mustHsuPath", stub.ExpectArgs{}, m("/run/wrappers/bin/hsu"), nil),
|
||||||
|
call("cmdOutput", stub.ExpectArgs{"/run/wrappers/bin/hsu", os.Stderr, []string{}, "/"}, []byte{0}, nil),
|
||||||
|
call("verbose", stub.ExpectArgs{[]any{0}}, nil, nil),
|
||||||
|
}}, &hst.AppError{
|
||||||
|
Step: "obtain uid from hsu",
|
||||||
|
Err: &strconv.NumError{Func: "Atoi", Num: "\x00", Err: strconv.ErrSyntax},
|
||||||
|
Msg: "invalid uid string from hsu",
|
||||||
|
}},
|
||||||
|
|
||||||
|
{"success", fCheckID, stub.Expect{Calls: []stub.Call{
|
||||||
|
call("mustHsuPath", stub.ExpectArgs{}, m("/run/wrappers/bin/hsu"), nil),
|
||||||
|
call("cmdOutput", stub.ExpectArgs{"/run/wrappers/bin/hsu", os.Stderr, []string{}, "/"}, []byte{'0'}, nil),
|
||||||
|
call("verbose", stub.ExpectArgs{[]any{0}}, nil, nil),
|
||||||
|
}}, nil},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// makeExitError populates syscall.WaitStatus in an [exec.ExitError].
|
||||||
|
// Do not reuse this function in a cross-platform package.
|
||||||
|
func makeExitError(status syscall.WaitStatus) error {
|
||||||
|
ps := new(os.ProcessState)
|
||||||
|
statusV := reflect.ValueOf(ps).Elem().FieldByName("status")
|
||||||
|
*reflect.NewAt(statusV.Type(), unsafe.Pointer(statusV.UnsafeAddr())).Interface().(*syscall.WaitStatus) = status
|
||||||
|
return &exec.ExitError{ProcessState: ps}
|
||||||
|
}
|
||||||
@@ -14,6 +14,10 @@ import (
|
|||||||
"hakurei.app/system/acl"
|
"hakurei.app/system/acl"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// envAllocSize is the initial size of the env map pre-allocated when the configured env map is nil.
|
||||||
|
// It should be large enough to fit all insertions by outcomeOp.toContainer.
|
||||||
|
const envAllocSize = 1 << 6
|
||||||
|
|
||||||
func newInt(v int) *stringPair[int] { return &stringPair[int]{v, strconv.Itoa(v)} }
|
func newInt(v int) *stringPair[int] { return &stringPair[int]{v, strconv.Itoa(v)} }
|
||||||
|
|
||||||
// stringPair stores a value and its string representation.
|
// stringPair stores a value and its string representation.
|
||||||
@@ -78,7 +82,7 @@ func newOutcomeState(k syscallDispatcher, msg message.Msg, id *state.ID, config
|
|||||||
Shim: &shimParams{PrivPID: k.getpid(), Verbose: msg.IsVerbose()},
|
Shim: &shimParams{PrivPID: k.getpid(), Verbose: msg.IsVerbose()},
|
||||||
ID: id,
|
ID: id,
|
||||||
Identity: config.Identity,
|
Identity: config.Identity,
|
||||||
UserID: hsu.MustIDMsg(msg),
|
UserID: hsu.MustID(msg),
|
||||||
EnvPaths: copyPaths(k),
|
EnvPaths: copyPaths(k),
|
||||||
Container: config.Container,
|
Container: config.Container,
|
||||||
}
|
}
|
||||||
@@ -155,7 +159,7 @@ type outcomeStateSys struct {
|
|||||||
|
|
||||||
// Copied from [hst.Config]. Safe for read by spWaylandOp.toSystem only.
|
// Copied from [hst.Config]. Safe for read by spWaylandOp.toSystem only.
|
||||||
directWayland bool
|
directWayland bool
|
||||||
// Copied header from [hst.Config]. Safe for read by spFinalOp.toSystem only.
|
// Copied header from [hst.Config]. Safe for read by spFilesystemOp.toSystem only.
|
||||||
extraPerms []hst.ExtraPermConfig
|
extraPerms []hst.ExtraPermConfig
|
||||||
// Copied address from [hst.Config]. Safe for read by spDBusOp.toSystem only.
|
// Copied address from [hst.Config]. Safe for read by spDBusOp.toSystem only.
|
||||||
sessionBus, systemBus *hst.BusConfig
|
sessionBus, systemBus *hst.BusConfig
|
||||||
@@ -268,10 +272,7 @@ func (state *outcomeStateSys) toSystem() error {
|
|||||||
// must run first
|
// must run first
|
||||||
&spParamsOp{},
|
&spParamsOp{},
|
||||||
|
|
||||||
// TODO(ophestra): move this late for #8 and #9
|
&spRuntimeOp{},
|
||||||
&spFilesystemOp{},
|
|
||||||
|
|
||||||
spRuntimeOp{},
|
|
||||||
spTmpdirOp{},
|
spTmpdirOp{},
|
||||||
spAccountOp{},
|
spAccountOp{},
|
||||||
|
|
||||||
@@ -281,7 +282,8 @@ func (state *outcomeStateSys) toSystem() error {
|
|||||||
&spPulseOp{},
|
&spPulseOp{},
|
||||||
&spDBusOp{},
|
&spDBusOp{},
|
||||||
|
|
||||||
spFinalOp{},
|
// must run last
|
||||||
|
&spFilesystemOp{},
|
||||||
}
|
}
|
||||||
|
|
||||||
state.Shim.Ops = make([]outcomeOp, 0, len(ops))
|
state.Shim.Ops = make([]outcomeOp, 0, len(ops))
|
||||||
|
|||||||
@@ -23,14 +23,11 @@ import (
|
|||||||
//#include "shim-signal.h"
|
//#include "shim-signal.h"
|
||||||
import "C"
|
import "C"
|
||||||
|
|
||||||
const (
|
// shimEnv is the name of the environment variable storing decimal representation of
|
||||||
// setup pipe fd for [container.Receive]
|
// setup pipe fd for [container.Receive].
|
||||||
shimEnv = "HAKUREI_SHIM"
|
const shimEnv = "HAKUREI_SHIM"
|
||||||
|
|
||||||
// only used for a nil configured env map
|
|
||||||
envAllocSize = 1 << 6
|
|
||||||
)
|
|
||||||
|
|
||||||
|
// shimParams is embedded in outcomeState and transmitted from priv side to shim.
|
||||||
type shimParams struct {
|
type shimParams struct {
|
||||||
// Priv side pid, checked against ppid in signal handler for the syscall.SIGCONT hack.
|
// Priv side pid, checked against ppid in signal handler for the syscall.SIGCONT hack.
|
||||||
PrivPID int
|
PrivPID int
|
||||||
@@ -39,7 +36,7 @@ type shimParams struct {
|
|||||||
// Limits are enforced on the priv side.
|
// Limits are enforced on the priv side.
|
||||||
WaitDelay time.Duration
|
WaitDelay time.Duration
|
||||||
|
|
||||||
// Verbosity pass through from [container.Msg].
|
// Verbosity pass through from [message.Msg].
|
||||||
Verbose bool
|
Verbose bool
|
||||||
|
|
||||||
// Outcome setup ops, contains setup state. Populated by outcome.finalise.
|
// Outcome setup ops, contains setup state. Populated by outcome.finalise.
|
||||||
@@ -131,12 +128,12 @@ func ShimMain() {
|
|||||||
|
|
||||||
// setup has not completed, terminate immediately
|
// setup has not completed, terminate immediately
|
||||||
msg.Resume()
|
msg.Resume()
|
||||||
os.Exit(hst.ShimExitRequest)
|
os.Exit(hst.ExitRequest)
|
||||||
return
|
return
|
||||||
|
|
||||||
case 1: // got SIGCONT after adoption: monitor died before delivering signal
|
case 1: // got SIGCONT after adoption: monitor died before delivering signal
|
||||||
msg.BeforeExit()
|
msg.BeforeExit()
|
||||||
os.Exit(hst.ShimExitOrphan)
|
os.Exit(hst.ExitOrphan)
|
||||||
return
|
return
|
||||||
|
|
||||||
case 2: // unreachable
|
case 2: // unreachable
|
||||||
@@ -172,7 +169,7 @@ func ShimMain() {
|
|||||||
|
|
||||||
if err := z.Start(); err != nil {
|
if err := z.Start(); err != nil {
|
||||||
printMessageError("cannot start container:", err)
|
printMessageError("cannot start container:", err)
|
||||||
os.Exit(1)
|
os.Exit(hst.ExitFailure)
|
||||||
}
|
}
|
||||||
if err := z.Serve(); err != nil {
|
if err := z.Serve(); err != nil {
|
||||||
printMessageError("cannot configure container:", err)
|
printMessageError("cannot configure container:", err)
|
||||||
@@ -189,7 +186,7 @@ func ShimMain() {
|
|||||||
var exitError *exec.ExitError
|
var exitError *exec.ExitError
|
||||||
if !errors.As(err, &exitError) {
|
if !errors.As(err, &exitError) {
|
||||||
if errors.Is(err, context.Canceled) {
|
if errors.Is(err, context.Canceled) {
|
||||||
os.Exit(2)
|
os.Exit(hst.ExitCancel)
|
||||||
}
|
}
|
||||||
log.Printf("wait: %v", err)
|
log.Printf("wait: %v", err)
|
||||||
os.Exit(127)
|
os.Exit(127)
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package app
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"maps"
|
|
||||||
"os"
|
"os"
|
||||||
"syscall"
|
"syscall"
|
||||||
"testing"
|
"testing"
|
||||||
@@ -42,48 +41,32 @@ func TestSpAccountOp(t *testing.T) {
|
|||||||
return c
|
return c
|
||||||
}, nil, []stub.Call{
|
}, nil, []stub.Call{
|
||||||
// this op performs basic validation and does not make calls during toSystem
|
// this op performs basic validation and does not make calls during toSystem
|
||||||
}, newI(), nil, nil, func(state *outcomeStateParams) {
|
}, newI(), nil, nil, insertsOps(nil), []stub.Call{
|
||||||
state.params.Ops = new(container.Ops)
|
|
||||||
}, []stub.Call{
|
|
||||||
// this op configures the container state and does not make calls during toContainer
|
// this op configures the container state and does not make calls during toContainer
|
||||||
}, &container.Params{
|
}, &container.Params{
|
||||||
Dir: config.Container.Home,
|
Dir: config.Container.Home,
|
||||||
Ops: new(container.Ops).
|
Ops: new(container.Ops).
|
||||||
Place(m("/etc/passwd"), []byte("chronos:x:1000:100:Hakurei:/data/data/org.chromium.Chromium:/run/current-system/sw/bin/zsh\n")).
|
Place(m("/etc/passwd"), []byte("chronos:x:1000:100:Hakurei:/data/data/org.chromium.Chromium:/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")),
|
||||||
}, func(t *testing.T, state *outcomeStateParams) {
|
}, paramsWantEnv(config, map[string]string{
|
||||||
wantEnv := map[string]string{
|
|
||||||
"HOME": config.Container.Home.String(),
|
"HOME": config.Container.Home.String(),
|
||||||
"USER": config.Container.Username,
|
"USER": config.Container.Username,
|
||||||
"SHELL": config.Container.Shell.String(),
|
"SHELL": config.Container.Shell.String(),
|
||||||
}
|
}, nil), nil},
|
||||||
maps.Copy(wantEnv, config.Container.Env)
|
|
||||||
if !maps.Equal(state.env, wantEnv) {
|
|
||||||
t.Errorf("toContainer: env = %#v, want %#v", state.env, wantEnv)
|
|
||||||
}
|
|
||||||
}, nil},
|
|
||||||
|
|
||||||
{"success", func(bool, bool) outcomeOp { return spAccountOp{} }, hst.Template, nil, []stub.Call{
|
{"success", func(bool, bool) outcomeOp { return spAccountOp{} }, hst.Template, nil, []stub.Call{
|
||||||
// this op performs basic validation and does not make calls during toSystem
|
// this op performs basic validation and does not make calls during toSystem
|
||||||
}, newI(), nil, nil, func(state *outcomeStateParams) {
|
}, newI(), nil, nil, insertsOps(nil), []stub.Call{
|
||||||
state.params.Ops = new(container.Ops)
|
|
||||||
}, []stub.Call{
|
|
||||||
// this op configures the container state and does not make calls during toContainer
|
// this op configures the container state and does not make calls during toContainer
|
||||||
}, &container.Params{
|
}, &container.Params{
|
||||||
Dir: config.Container.Home,
|
Dir: config.Container.Home,
|
||||||
Ops: new(container.Ops).
|
Ops: new(container.Ops).
|
||||||
Place(m("/etc/passwd"), []byte("chronos:x:1000:100:Hakurei:/data/data/org.chromium.Chromium:/run/current-system/sw/bin/zsh\n")).
|
Place(m("/etc/passwd"), []byte("chronos:x:1000:100:Hakurei:/data/data/org.chromium.Chromium:/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")),
|
||||||
}, func(t *testing.T, state *outcomeStateParams) {
|
}, paramsWantEnv(config, map[string]string{
|
||||||
wantEnv := map[string]string{
|
|
||||||
"HOME": config.Container.Home.String(),
|
"HOME": config.Container.Home.String(),
|
||||||
"USER": config.Container.Username,
|
"USER": config.Container.Username,
|
||||||
"SHELL": config.Container.Shell.String(),
|
"SHELL": config.Container.Shell.String(),
|
||||||
}
|
}, nil), nil},
|
||||||
maps.Copy(wantEnv, config.Container.Env)
|
|
||||||
if !maps.Equal(state.env, wantEnv) {
|
|
||||||
t.Errorf("toContainer: env = %#v, want %#v", state.env, wantEnv)
|
|
||||||
}
|
|
||||||
}, nil},
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"io/fs"
|
"io/fs"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
|
"slices"
|
||||||
"strconv"
|
"strconv"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
@@ -16,6 +17,8 @@ import (
|
|||||||
"hakurei.app/container/seccomp"
|
"hakurei.app/container/seccomp"
|
||||||
"hakurei.app/hst"
|
"hakurei.app/hst"
|
||||||
"hakurei.app/message"
|
"hakurei.app/message"
|
||||||
|
"hakurei.app/system"
|
||||||
|
"hakurei.app/system/acl"
|
||||||
"hakurei.app/system/dbus"
|
"hakurei.app/system/dbus"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -120,6 +123,7 @@ func (s *spParamsOp) toContainer(state *outcomeStateParams) error {
|
|||||||
func init() { gob.Register(new(spFilesystemOp)) }
|
func init() { gob.Register(new(spFilesystemOp)) }
|
||||||
|
|
||||||
// spFilesystemOp applies configured filesystems to [container.Params], excluding the optional root filesystem.
|
// spFilesystemOp applies configured filesystems to [container.Params], excluding the optional root filesystem.
|
||||||
|
// This outcomeOp is hardcoded to always run last.
|
||||||
type spFilesystemOp struct {
|
type spFilesystemOp struct {
|
||||||
// Matched paths to cover. Stored during toSystem.
|
// Matched paths to cover. Stored during toSystem.
|
||||||
HidePaths []*check.Absolute
|
HidePaths []*check.Absolute
|
||||||
@@ -259,6 +263,8 @@ func (s *spFilesystemOp) toSystem(state *outcomeStateSys) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// append ExtraPerms last
|
||||||
|
flattenExtraPerms(state.sys, state.extraPerms)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -278,6 +284,15 @@ func (s *spFilesystemOp) toContainer(state *outcomeStateParams) error {
|
|||||||
if state.Container.Flags&hst.FDevice == 0 {
|
if state.Container.Flags&hst.FDevice == 0 {
|
||||||
state.params.Remount(fhs.AbsDev, syscall.MS_RDONLY)
|
state.params.Remount(fhs.AbsDev, syscall.MS_RDONLY)
|
||||||
}
|
}
|
||||||
|
state.params.Remount(fhs.AbsRoot, syscall.MS_RDONLY)
|
||||||
|
|
||||||
|
state.params.Env = make([]string, 0, len(state.env))
|
||||||
|
for key, value := range state.env {
|
||||||
|
// key validated early via hst
|
||||||
|
state.params.Env = append(state.params.Env, key+"="+value)
|
||||||
|
}
|
||||||
|
slices.Sort(state.params.Env)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -313,6 +328,32 @@ func evalSymlinks(msg message.Msg, k syscallDispatcher, v *string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// flattenExtraPerms expands a slice of [hst.ExtraPermConfig] into [system.I].
|
||||||
|
func flattenExtraPerms(sys *system.I, extraPerms []hst.ExtraPermConfig) {
|
||||||
|
for i := range extraPerms {
|
||||||
|
p := &extraPerms[i]
|
||||||
|
if p.Path == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.Ensure {
|
||||||
|
sys.Ensure(p.Path, 0700)
|
||||||
|
}
|
||||||
|
|
||||||
|
perms := make(acl.Perms, 0, 3)
|
||||||
|
if p.Read {
|
||||||
|
perms = append(perms, acl.Read)
|
||||||
|
}
|
||||||
|
if p.Write {
|
||||||
|
perms = append(perms, acl.Write)
|
||||||
|
}
|
||||||
|
if p.Execute {
|
||||||
|
perms = append(perms, acl.Execute)
|
||||||
|
}
|
||||||
|
sys.UpdatePermType(system.User, p.Path, perms...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// opsAdapter implements [hst.Ops] on [container.Ops].
|
// opsAdapter implements [hst.Ops] on [container.Ops].
|
||||||
type opsAdapter struct{ *container.Ops }
|
type opsAdapter struct{ *container.Ops }
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package app
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"maps"
|
|
||||||
"os"
|
"os"
|
||||||
"reflect"
|
"reflect"
|
||||||
"syscall"
|
"syscall"
|
||||||
@@ -15,6 +14,8 @@ import (
|
|||||||
"hakurei.app/container/seccomp"
|
"hakurei.app/container/seccomp"
|
||||||
"hakurei.app/container/stub"
|
"hakurei.app/container/stub"
|
||||||
"hakurei.app/hst"
|
"hakurei.app/hst"
|
||||||
|
"hakurei.app/system"
|
||||||
|
"hakurei.app/system/acl"
|
||||||
"hakurei.app/system/dbus"
|
"hakurei.app/system/dbus"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -72,16 +73,9 @@ func TestSpParamsOp(t *testing.T) {
|
|||||||
Proc(fhs.AbsProc).Tmpfs(hst.AbsPrivateTmp, 1<<12, 0755).
|
Proc(fhs.AbsProc).Tmpfs(hst.AbsPrivateTmp, 1<<12, 0755).
|
||||||
DevWritable(fhs.AbsDev, true).
|
DevWritable(fhs.AbsDev, true).
|
||||||
Tmpfs(fhs.AbsDev.Append("shm"), 0, 01777),
|
Tmpfs(fhs.AbsDev.Append("shm"), 0, 01777),
|
||||||
}, func(t *testing.T, state *outcomeStateParams) {
|
}, paramsWantEnv(config, map[string]string{
|
||||||
wantEnv := map[string]string{
|
|
||||||
"TERM": "xterm",
|
"TERM": "xterm",
|
||||||
}
|
}, func(t *testing.T, state *outcomeStateParams) {
|
||||||
maps.Copy(wantEnv, config.Container.Env)
|
|
||||||
if !maps.Equal(state.env, wantEnv) {
|
|
||||||
t.Errorf("toContainer: env = %#v, want %#v", state.env, wantEnv)
|
|
||||||
}
|
|
||||||
|
|
||||||
const wantAutoEtcPrefix = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
|
||||||
if state.as.AutoEtcPrefix != wantAutoEtcPrefix {
|
if state.as.AutoEtcPrefix != wantAutoEtcPrefix {
|
||||||
t.Errorf("toContainer: as.AutoEtcPrefix = %q, want %q", state.as.AutoEtcPrefix, wantAutoEtcPrefix)
|
t.Errorf("toContainer: as.AutoEtcPrefix = %q, want %q", state.as.AutoEtcPrefix, wantAutoEtcPrefix)
|
||||||
}
|
}
|
||||||
@@ -90,7 +84,7 @@ func TestSpParamsOp(t *testing.T) {
|
|||||||
if !reflect.DeepEqual(state.filesystem, wantFilesystems) {
|
if !reflect.DeepEqual(state.filesystem, wantFilesystems) {
|
||||||
t.Errorf("toContainer: filesystem = %#v, want %#v", state.filesystem, wantFilesystems)
|
t.Errorf("toContainer: filesystem = %#v, want %#v", state.filesystem, wantFilesystems)
|
||||||
}
|
}
|
||||||
}, nil},
|
}), nil},
|
||||||
|
|
||||||
{"success", func(isShim, _ bool) outcomeOp {
|
{"success", func(isShim, _ bool) outcomeOp {
|
||||||
if !isShim {
|
if !isShim {
|
||||||
@@ -117,15 +111,9 @@ func TestSpParamsOp(t *testing.T) {
|
|||||||
Proc(fhs.AbsProc).Tmpfs(hst.AbsPrivateTmp, 1<<12, 0755).
|
Proc(fhs.AbsProc).Tmpfs(hst.AbsPrivateTmp, 1<<12, 0755).
|
||||||
Bind(fhs.AbsDev, fhs.AbsDev, bits.BindWritable|bits.BindDevice).
|
Bind(fhs.AbsDev, fhs.AbsDev, bits.BindWritable|bits.BindDevice).
|
||||||
Tmpfs(fhs.AbsDev.Append("shm"), 0, 01777),
|
Tmpfs(fhs.AbsDev.Append("shm"), 0, 01777),
|
||||||
}, func(t *testing.T, state *outcomeStateParams) {
|
}, paramsWantEnv(config, map[string]string{
|
||||||
wantEnv := map[string]string{
|
|
||||||
"TERM": "xterm",
|
"TERM": "xterm",
|
||||||
}
|
}, func(t *testing.T, state *outcomeStateParams) {
|
||||||
maps.Copy(wantEnv, config.Container.Env)
|
|
||||||
if !maps.Equal(state.env, wantEnv) {
|
|
||||||
t.Errorf("toContainer: env = %#v, want %#v", state.env, wantEnv)
|
|
||||||
}
|
|
||||||
|
|
||||||
if state.as.AutoEtcPrefix != wantAutoEtcPrefix {
|
if state.as.AutoEtcPrefix != wantAutoEtcPrefix {
|
||||||
t.Errorf("toContainer: as.AutoEtcPrefix = %q, want %q", state.as.AutoEtcPrefix, wantAutoEtcPrefix)
|
t.Errorf("toContainer: as.AutoEtcPrefix = %q, want %q", state.as.AutoEtcPrefix, wantAutoEtcPrefix)
|
||||||
}
|
}
|
||||||
@@ -134,7 +122,7 @@ func TestSpParamsOp(t *testing.T) {
|
|||||||
if !reflect.DeepEqual(state.filesystem, wantFilesystems) {
|
if !reflect.DeepEqual(state.filesystem, wantFilesystems) {
|
||||||
t.Errorf("toContainer: filesystem = %#v, want %#v", state.filesystem, wantFilesystems)
|
t.Errorf("toContainer: filesystem = %#v, want %#v", state.filesystem, wantFilesystems)
|
||||||
}
|
}
|
||||||
}, nil},
|
}), nil},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -159,6 +147,16 @@ func TestSpFilesystemOp(t *testing.T) {
|
|||||||
}
|
}
|
||||||
configSmall := newConfigSmall()
|
configSmall := newConfigSmall()
|
||||||
|
|
||||||
|
needsApplyState := func(next pStateContainerFunc) pStateContainerFunc {
|
||||||
|
return func(state *outcomeStateParams) {
|
||||||
|
state.as = hst.ApplyState{AutoEtcPrefix: wantAutoEtcPrefix, Ops: opsAdapter{state.params.Ops}}
|
||||||
|
|
||||||
|
if next != nil {
|
||||||
|
next(state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
checkOpBehaviour(t, []opBehaviourTestCase{
|
checkOpBehaviour(t, []opBehaviourTestCase{
|
||||||
{"readdir", func(bool, bool) outcomeOp {
|
{"readdir", func(bool, bool) outcomeOp {
|
||||||
return new(spFilesystemOp)
|
return new(spFilesystemOp)
|
||||||
@@ -310,12 +308,14 @@ func TestSpFilesystemOp(t *testing.T) {
|
|||||||
call("evalSymlinks", stub.ExpectArgs{"/var/lib/hakurei/base/org.nixos/.ro-store"}, nePrefix+"/var/lib/hakurei/base/org.nixos/.ro-store", nil),
|
call("evalSymlinks", stub.ExpectArgs{"/var/lib/hakurei/base/org.nixos/.ro-store"}, nePrefix+"/var/lib/hakurei/base/org.nixos/.ro-store", nil),
|
||||||
call("evalSymlinks", stub.ExpectArgs{"/var/lib/hakurei/base/org.nixos/org.chromium.Chromium"}, nePrefix+"/var/lib/hakurei/base/org.nixos/org.chromium.Chromium", nil),
|
call("evalSymlinks", stub.ExpectArgs{"/var/lib/hakurei/base/org.nixos/org.chromium.Chromium"}, nePrefix+"/var/lib/hakurei/base/org.nixos/org.chromium.Chromium", nil),
|
||||||
call("verbosef", stub.ExpectArgs{"hiding path %q from %q", []any{"/proc/nonexistent/eval/etc/dbus", "/etc/"}}, nil, nil),
|
call("verbosef", stub.ExpectArgs{"hiding path %q from %q", []any{"/proc/nonexistent/eval/etc/dbus", "/etc/"}}, nil, nil),
|
||||||
}, newI(), nil, nil, func(state *outcomeStateParams) {
|
}, newI().
|
||||||
state.filesystem = configSmall.Container.Filesystem
|
Ensure(m("/var/lib/hakurei/u0"), 0700).
|
||||||
state.params.Ops = new(container.Ops)
|
UpdatePermType(system.User, m("/var/lib/hakurei/u0"),
|
||||||
state.as = hst.ApplyState{AutoEtcPrefix: wantAutoEtcPrefix, Ops: opsAdapter{state.params.Ops}}
|
acl.Execute).
|
||||||
state.filesystem = append(state.filesystem, hst.FilesystemConfigJSON{})
|
UpdatePermType(system.User, m("/var/lib/hakurei/u0/org.chromium.Chromium"),
|
||||||
}, []stub.Call{
|
acl.Read, acl.Write, acl.Execute), nil, nil, insertsOps(needsApplyState(func(state *outcomeStateParams) {
|
||||||
|
state.filesystem = append(configSmall.Container.Filesystem, hst.FilesystemConfigJSON{})
|
||||||
|
})), []stub.Call{
|
||||||
// this op configures the container state and does not make calls during toContainer
|
// this op configures the container state and does not make calls during toContainer
|
||||||
}, nil, nil, &hst.AppError{
|
}, nil, nil, &hst.AppError{
|
||||||
Step: "finalise",
|
Step: "finalise",
|
||||||
@@ -341,13 +341,22 @@ func TestSpFilesystemOp(t *testing.T) {
|
|||||||
call("evalSymlinks", stub.ExpectArgs{"/var/lib/hakurei/base/org.nixos/.ro-store"}, nePrefix+"/var/lib/hakurei/base/org.nixos/.ro-store", nil),
|
call("evalSymlinks", stub.ExpectArgs{"/var/lib/hakurei/base/org.nixos/.ro-store"}, nePrefix+"/var/lib/hakurei/base/org.nixos/.ro-store", nil),
|
||||||
call("evalSymlinks", stub.ExpectArgs{"/var/lib/hakurei/base/org.nixos/org.chromium.Chromium"}, nePrefix+"/var/lib/hakurei/base/org.nixos/org.chromium.Chromium", nil),
|
call("evalSymlinks", stub.ExpectArgs{"/var/lib/hakurei/base/org.nixos/org.chromium.Chromium"}, nePrefix+"/var/lib/hakurei/base/org.nixos/org.chromium.Chromium", nil),
|
||||||
call("verbosef", stub.ExpectArgs{"hiding path %q from %q", []any{"/proc/nonexistent/eval/etc/dbus", "/etc/"}}, nil, nil),
|
call("verbosef", stub.ExpectArgs{"hiding path %q from %q", []any{"/proc/nonexistent/eval/etc/dbus", "/etc/"}}, nil, nil),
|
||||||
}, newI(), nil, nil, func(state *outcomeStateParams) {
|
}, newI().
|
||||||
|
Ensure(m("/var/lib/hakurei/u0"), 0700).
|
||||||
|
UpdatePermType(system.User, m("/var/lib/hakurei/u0"),
|
||||||
|
acl.Execute).
|
||||||
|
UpdatePermType(system.User, m("/var/lib/hakurei/u0/org.chromium.Chromium"),
|
||||||
|
acl.Read, acl.Write, acl.Execute), nil, nil, insertsOps(needsApplyState(func(state *outcomeStateParams) {
|
||||||
state.filesystem = configSmall.Container.Filesystem
|
state.filesystem = configSmall.Container.Filesystem
|
||||||
state.params.Ops = new(container.Ops)
|
})), []stub.Call{
|
||||||
state.as = hst.ApplyState{AutoEtcPrefix: wantAutoEtcPrefix, Ops: opsAdapter{state.params.Ops}}
|
|
||||||
}, []stub.Call{
|
|
||||||
// this op configures the container state and does not make calls during toContainer
|
// this op configures the container state and does not make calls during toContainer
|
||||||
}, &container.Params{
|
}, &container.Params{
|
||||||
|
Env: []string{
|
||||||
|
"GOOGLE_API_KEY=AIzaSyBHDrl33hwRp4rMQY0ziRbj8K9LPA6vUCY",
|
||||||
|
"GOOGLE_DEFAULT_CLIENT_ID=77185425430.apps.googleusercontent.com",
|
||||||
|
"GOOGLE_DEFAULT_CLIENT_SECRET=OTJgUOQcT7lO7GsGZq2G4IlT",
|
||||||
|
},
|
||||||
|
|
||||||
Ops: new(container.Ops).
|
Ops: new(container.Ops).
|
||||||
Etc(fhs.AbsEtc, wantAutoEtcPrefix).
|
Etc(fhs.AbsEtc, wantAutoEtcPrefix).
|
||||||
OverlayReadonly(
|
OverlayReadonly(
|
||||||
@@ -356,7 +365,8 @@ func TestSpFilesystemOp(t *testing.T) {
|
|||||||
fhs.AbsVarLib.Append("hakurei/base/org.nixos/org.chromium.Chromium")).
|
fhs.AbsVarLib.Append("hakurei/base/org.nixos/org.chromium.Chromium")).
|
||||||
Readonly(hst.AbsPrivateTmp, 0755).
|
Readonly(hst.AbsPrivateTmp, 0755).
|
||||||
Tmpfs(m("/proc/nonexistent/eval/etc/dbus"), 1<<13, 0755).
|
Tmpfs(m("/proc/nonexistent/eval/etc/dbus"), 1<<13, 0755).
|
||||||
Remount(fhs.AbsDev, syscall.MS_RDONLY),
|
Remount(fhs.AbsDev, syscall.MS_RDONLY).
|
||||||
|
Remount(fhs.AbsRoot, syscall.MS_RDONLY),
|
||||||
}, nil, nil},
|
}, nil, nil},
|
||||||
|
|
||||||
{"success", func(bool, bool) outcomeOp {
|
{"success", func(bool, bool) outcomeOp {
|
||||||
@@ -386,13 +396,22 @@ func TestSpFilesystemOp(t *testing.T) {
|
|||||||
call("evalSymlinks", stub.ExpectArgs{"/var/lib/hakurei/base/org.debian/sys"}, nePrefix+"/var/lib/hakurei/base/org.debian/sys", nil),
|
call("evalSymlinks", stub.ExpectArgs{"/var/lib/hakurei/base/org.debian/sys"}, nePrefix+"/var/lib/hakurei/base/org.debian/sys", nil),
|
||||||
call("evalSymlinks", stub.ExpectArgs{"/var/lib/hakurei/base/org.debian/usr"}, nePrefix+"/var/lib/hakurei/base/org.debian/usr", nil),
|
call("evalSymlinks", stub.ExpectArgs{"/var/lib/hakurei/base/org.debian/usr"}, nePrefix+"/var/lib/hakurei/base/org.debian/usr", nil),
|
||||||
call("evalSymlinks", stub.ExpectArgs{"/var/lib/hakurei/base/org.debian/var"}, nePrefix+"/var/lib/hakurei/base/org.debian/var", nil),
|
call("evalSymlinks", stub.ExpectArgs{"/var/lib/hakurei/base/org.debian/var"}, nePrefix+"/var/lib/hakurei/base/org.debian/var", nil),
|
||||||
}, newI(), nil, nil, func(state *outcomeStateParams) {
|
}, newI().
|
||||||
|
Ensure(m("/var/lib/hakurei/u0"), 0700).
|
||||||
|
UpdatePermType(system.User, m("/var/lib/hakurei/u0"),
|
||||||
|
acl.Execute).
|
||||||
|
UpdatePermType(system.User, m("/var/lib/hakurei/u0/org.chromium.Chromium"),
|
||||||
|
acl.Read, acl.Write, acl.Execute), nil, nil, insertsOps(needsApplyState(func(state *outcomeStateParams) {
|
||||||
state.filesystem = config.Container.Filesystem[1:]
|
state.filesystem = config.Container.Filesystem[1:]
|
||||||
state.params.Ops = new(container.Ops)
|
})), []stub.Call{
|
||||||
state.as = hst.ApplyState{AutoEtcPrefix: wantAutoEtcPrefix, Ops: opsAdapter{state.params.Ops}}
|
|
||||||
}, []stub.Call{
|
|
||||||
// this op configures the container state and does not make calls during toContainer
|
// this op configures the container state and does not make calls during toContainer
|
||||||
}, &container.Params{
|
}, &container.Params{
|
||||||
|
Env: []string{
|
||||||
|
"GOOGLE_API_KEY=AIzaSyBHDrl33hwRp4rMQY0ziRbj8K9LPA6vUCY",
|
||||||
|
"GOOGLE_DEFAULT_CLIENT_ID=77185425430.apps.googleusercontent.com",
|
||||||
|
"GOOGLE_DEFAULT_CLIENT_SECRET=OTJgUOQcT7lO7GsGZq2G4IlT",
|
||||||
|
},
|
||||||
|
|
||||||
Ops: new(container.Ops).
|
Ops: new(container.Ops).
|
||||||
Etc(fhs.AbsEtc, wantAutoEtcPrefix).
|
Etc(fhs.AbsEtc, wantAutoEtcPrefix).
|
||||||
Tmpfs(fhs.AbsTmp, 0, 0755).
|
Tmpfs(fhs.AbsTmp, 0, 0755).
|
||||||
@@ -407,11 +426,47 @@ func TestSpFilesystemOp(t *testing.T) {
|
|||||||
fhs.AbsVarLib.Append("hakurei/u0/org.chromium.Chromium"),
|
fhs.AbsVarLib.Append("hakurei/u0/org.chromium.Chromium"),
|
||||||
check.MustAbs("/data/data/org.chromium.Chromium"),
|
check.MustAbs("/data/data/org.chromium.Chromium"),
|
||||||
bits.BindWritable|bits.BindEnsure).
|
bits.BindWritable|bits.BindEnsure).
|
||||||
Bind(fhs.AbsDev.Append("dri"), fhs.AbsDev.Append("dri"), bits.BindDevice|bits.BindWritable|bits.BindOptional),
|
Bind(fhs.AbsDev.Append("dri"), fhs.AbsDev.Append("dri"), bits.BindDevice|bits.BindWritable|bits.BindOptional).
|
||||||
|
Remount(fhs.AbsRoot, syscall.MS_RDONLY),
|
||||||
}, nil, nil},
|
}, nil, nil},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestFlattenExtraPerms(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
perms []hst.ExtraPermConfig
|
||||||
|
want *system.I
|
||||||
|
}{
|
||||||
|
{"path nil check", append(hst.Template().ExtraPerms, hst.ExtraPermConfig{}), newI().
|
||||||
|
Ensure(m("/var/lib/hakurei/u0"), 0700).
|
||||||
|
UpdatePermType(system.User, m("/var/lib/hakurei/u0"),
|
||||||
|
acl.Execute).
|
||||||
|
UpdatePermType(system.User, m("/var/lib/hakurei/u0/org.chromium.Chromium"),
|
||||||
|
acl.Read, acl.Write, acl.Execute)},
|
||||||
|
|
||||||
|
{"template", hst.Template().ExtraPerms, newI().
|
||||||
|
Ensure(m("/var/lib/hakurei/u0"), 0700).
|
||||||
|
UpdatePermType(system.User, m("/var/lib/hakurei/u0"),
|
||||||
|
acl.Execute).
|
||||||
|
UpdatePermType(system.User, m("/var/lib/hakurei/u0/org.chromium.Chromium"),
|
||||||
|
acl.Read, acl.Write, acl.Execute)},
|
||||||
|
}
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
got := newI()
|
||||||
|
flattenExtraPerms(got, tc.perms)
|
||||||
|
if !reflect.DeepEqual(got, tc.want) {
|
||||||
|
t.Errorf("flattenExtraPerms: sys = %#v, want %#v", got, tc.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// invalidFSHost implements the Host method of [hst.FilesystemConfig] with an invalid response.
|
// invalidFSHost implements the Host method of [hst.FilesystemConfig] with an invalid response.
|
||||||
type invalidFSHost bool
|
type invalidFSHost bool
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import (
|
|||||||
func init() { gob.Register(new(spDBusOp)) }
|
func init() { gob.Register(new(spDBusOp)) }
|
||||||
|
|
||||||
// spDBusOp maintains an xdg-dbus-proxy instance for the container.
|
// spDBusOp maintains an xdg-dbus-proxy instance for the container.
|
||||||
|
// Runs after spRuntimeOp.
|
||||||
type spDBusOp struct {
|
type spDBusOp struct {
|
||||||
// Whether to bind the system bus socket. Populated during toSystem.
|
// Whether to bind the system bus socket. Populated during toSystem.
|
||||||
ProxySystem bool
|
ProxySystem bool
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
package app
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"maps"
|
|
||||||
"reflect"
|
|
||||||
"syscall"
|
"syscall"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
@@ -18,7 +16,6 @@ import (
|
|||||||
|
|
||||||
func TestSpDBusOp(t *testing.T) {
|
func TestSpDBusOp(t *testing.T) {
|
||||||
config := hst.Template()
|
config := hst.Template()
|
||||||
const instancePrefix = container.Nonexistent + "/tmp/hakurei.0/" + wantAutoEtcPrefix
|
|
||||||
|
|
||||||
checkOpBehaviour(t, []opBehaviourTestCase{
|
checkOpBehaviour(t, []opBehaviourTestCase{
|
||||||
{"not enabled", func(bool, bool) outcomeOp {
|
{"not enabled", func(bool, bool) outcomeOp {
|
||||||
@@ -41,11 +38,7 @@ func TestSpDBusOp(t *testing.T) {
|
|||||||
"unix:path=/run/user/1000/bus",
|
"unix:path=/run/user/1000/bus",
|
||||||
"unix:path=/var/run/dbus/system_bus_socket",
|
"unix:path=/var/run/dbus/system_bus_socket",
|
||||||
}, nil),
|
}, nil),
|
||||||
}, nil, func(t *testing.T, state *outcomeStateSys) {
|
}, nil, sysUsesInstance(nil), &system.OpError{
|
||||||
if want := m(instancePrefix); !reflect.DeepEqual(state.sharePath, want) {
|
|
||||||
t.Errorf("outcomeStateSys: sharePath = %v, want %v", state.sharePath, want)
|
|
||||||
}
|
|
||||||
}, &system.OpError{
|
|
||||||
Op: "dbus",
|
Op: "dbus",
|
||||||
Err: syscall.EINVAL,
|
Err: syscall.EINVAL,
|
||||||
Msg: "message bus proxy configuration contains NUL byte",
|
Msg: "message bus proxy configuration contains NUL byte",
|
||||||
@@ -66,7 +59,7 @@ func TestSpDBusOp(t *testing.T) {
|
|||||||
call("isVerbose", stub.ExpectArgs{}, true, nil),
|
call("isVerbose", stub.ExpectArgs{}, true, nil),
|
||||||
call("verbose", stub.ExpectArgs{[]any{"session bus proxy:", []string{
|
call("verbose", stub.ExpectArgs{[]any{"session bus proxy:", []string{
|
||||||
"unix:path=/run/user/1000/bus",
|
"unix:path=/run/user/1000/bus",
|
||||||
instancePrefix + "/bus",
|
wantInstancePrefix + "/bus",
|
||||||
"--filter",
|
"--filter",
|
||||||
"--talk=org.freedesktop.DBus",
|
"--talk=org.freedesktop.DBus",
|
||||||
"--talk=org.freedesktop.Notifications",
|
"--talk=org.freedesktop.Notifications",
|
||||||
@@ -77,7 +70,7 @@ func TestSpDBusOp(t *testing.T) {
|
|||||||
}}}, nil, nil),
|
}}}, nil, nil),
|
||||||
call("verbose", stub.ExpectArgs{[]any{"message bus proxy final args:", helper.MustNewCheckedArgs(
|
call("verbose", stub.ExpectArgs{[]any{"message bus proxy final args:", helper.MustNewCheckedArgs(
|
||||||
"unix:path=/run/user/1000/bus",
|
"unix:path=/run/user/1000/bus",
|
||||||
instancePrefix+"/bus",
|
wantInstancePrefix+"/bus",
|
||||||
"--filter",
|
"--filter",
|
||||||
"--talk=org.freedesktop.DBus",
|
"--talk=org.freedesktop.DBus",
|
||||||
"--talk=org.freedesktop.Notifications",
|
"--talk=org.freedesktop.Notifications",
|
||||||
@@ -88,40 +81,25 @@ func TestSpDBusOp(t *testing.T) {
|
|||||||
)}}, nil, nil),
|
)}}, nil, nil),
|
||||||
}, func() *system.I {
|
}, func() *system.I {
|
||||||
sys := system.New(panicMsgContext{}, message.NewMsg(nil), checkExpectUid)
|
sys := system.New(panicMsgContext{}, message.NewMsg(nil), checkExpectUid)
|
||||||
sys.Ephemeral(system.Process, m(instancePrefix), 0711)
|
sys.Ephemeral(system.Process, m(wantInstancePrefix), 0711)
|
||||||
if err := sys.ProxyDBus(
|
if err := sys.ProxyDBus(
|
||||||
dbus.NewConfig(config.ID, true, true), nil,
|
dbus.NewConfig(config.ID, true, true), nil,
|
||||||
dbus.ProxyPair{"unix:path=/run/user/1000/bus", instancePrefix + "/bus"},
|
dbus.ProxyPair{"unix:path=/run/user/1000/bus", wantInstancePrefix + "/bus"},
|
||||||
dbus.ProxyPair{"unix:path=/var/run/dbus/system_bus_socket", instancePrefix + "/system_bus_socket"},
|
dbus.ProxyPair{"unix:path=/var/run/dbus/system_bus_socket", wantInstancePrefix + "/system_bus_socket"},
|
||||||
); err != nil {
|
); err != nil {
|
||||||
t.Fatalf("cannot prepare sys: %v", err)
|
t.Fatalf("cannot prepare sys: %v", err)
|
||||||
}
|
}
|
||||||
sys.UpdatePerm(m(instancePrefix+"/bus"), acl.Read, acl.Write)
|
sys.UpdatePerm(m(wantInstancePrefix+"/bus"), acl.Read, acl.Write)
|
||||||
return sys
|
return sys
|
||||||
}(), func(t *testing.T, state *outcomeStateSys) {
|
}(), sysUsesInstance(nil), nil, insertsOps(afterSpRuntimeOp(nil)), []stub.Call{
|
||||||
if want := m(instancePrefix); !reflect.DeepEqual(state.sharePath, want) {
|
|
||||||
t.Errorf("outcomeStateSys: sharePath = %v, want %v", state.sharePath, want)
|
|
||||||
}
|
|
||||||
}, nil, func(state *outcomeStateParams) {
|
|
||||||
state.params.Ops = new(container.Ops)
|
|
||||||
|
|
||||||
// emulates spRuntimeOp
|
|
||||||
state.runtimeDir = m("/run/user/1000")
|
|
||||||
}, []stub.Call{
|
|
||||||
// this op configures the container state and does not make calls during toContainer
|
// this op configures the container state and does not make calls during toContainer
|
||||||
}, &container.Params{
|
}, &container.Params{
|
||||||
Ops: new(container.Ops).
|
Ops: new(container.Ops).
|
||||||
Bind(m(instancePrefix+"/bus"),
|
Bind(m(wantInstancePrefix+"/bus"),
|
||||||
m("/run/user/1000/bus"), 0),
|
m("/run/user/1000/bus"), 0),
|
||||||
}, func(t *testing.T, state *outcomeStateParams) {
|
}, paramsWantEnv(config, map[string]string{
|
||||||
wantEnv := map[string]string{
|
|
||||||
"DBUS_SESSION_BUS_ADDRESS": "unix:path=/run/user/1000/bus",
|
"DBUS_SESSION_BUS_ADDRESS": "unix:path=/run/user/1000/bus",
|
||||||
}
|
}, nil), nil},
|
||||||
maps.Copy(wantEnv, config.Container.Env)
|
|
||||||
if !maps.Equal(state.env, wantEnv) {
|
|
||||||
t.Errorf("toContainer: env = %#v, want %#v", state.env, wantEnv)
|
|
||||||
}
|
|
||||||
}, nil},
|
|
||||||
|
|
||||||
{"success", func(isShim, _ bool) outcomeOp {
|
{"success", func(isShim, _ bool) outcomeOp {
|
||||||
if !isShim {
|
if !isShim {
|
||||||
@@ -136,7 +114,7 @@ func TestSpDBusOp(t *testing.T) {
|
|||||||
call("isVerbose", stub.ExpectArgs{}, true, nil),
|
call("isVerbose", stub.ExpectArgs{}, true, nil),
|
||||||
call("verbose", stub.ExpectArgs{[]any{"session bus proxy:", []string{
|
call("verbose", stub.ExpectArgs{[]any{"session bus proxy:", []string{
|
||||||
"unix:path=/run/user/1000/bus",
|
"unix:path=/run/user/1000/bus",
|
||||||
instancePrefix + "/bus",
|
wantInstancePrefix + "/bus",
|
||||||
"--filter",
|
"--filter",
|
||||||
"--talk=org.freedesktop.Notifications",
|
"--talk=org.freedesktop.Notifications",
|
||||||
"--talk=org.freedesktop.FileManager1",
|
"--talk=org.freedesktop.FileManager1",
|
||||||
@@ -153,7 +131,7 @@ func TestSpDBusOp(t *testing.T) {
|
|||||||
}}}, nil, nil),
|
}}}, nil, nil),
|
||||||
call("verbose", stub.ExpectArgs{[]any{"system bus proxy:", []string{
|
call("verbose", stub.ExpectArgs{[]any{"system bus proxy:", []string{
|
||||||
"unix:path=/var/run/dbus/system_bus_socket",
|
"unix:path=/var/run/dbus/system_bus_socket",
|
||||||
instancePrefix + "/system_bus_socket",
|
wantInstancePrefix + "/system_bus_socket",
|
||||||
"--filter",
|
"--filter",
|
||||||
"--talk=org.bluez",
|
"--talk=org.bluez",
|
||||||
"--talk=org.freedesktop.Avahi",
|
"--talk=org.freedesktop.Avahi",
|
||||||
@@ -161,7 +139,7 @@ func TestSpDBusOp(t *testing.T) {
|
|||||||
}}}, nil, nil),
|
}}}, nil, nil),
|
||||||
call("verbose", stub.ExpectArgs{[]any{"message bus proxy final args:", helper.MustNewCheckedArgs(
|
call("verbose", stub.ExpectArgs{[]any{"message bus proxy final args:", helper.MustNewCheckedArgs(
|
||||||
"unix:path=/run/user/1000/bus",
|
"unix:path=/run/user/1000/bus",
|
||||||
instancePrefix+"/bus",
|
wantInstancePrefix+"/bus",
|
||||||
"--filter",
|
"--filter",
|
||||||
"--talk=org.freedesktop.Notifications",
|
"--talk=org.freedesktop.Notifications",
|
||||||
"--talk=org.freedesktop.FileManager1",
|
"--talk=org.freedesktop.FileManager1",
|
||||||
@@ -177,7 +155,7 @@ func TestSpDBusOp(t *testing.T) {
|
|||||||
"--broadcast=org.freedesktop.portal.*=@/org/freedesktop/portal/*",
|
"--broadcast=org.freedesktop.portal.*=@/org/freedesktop/portal/*",
|
||||||
|
|
||||||
"unix:path=/var/run/dbus/system_bus_socket",
|
"unix:path=/var/run/dbus/system_bus_socket",
|
||||||
instancePrefix+"/system_bus_socket",
|
wantInstancePrefix+"/system_bus_socket",
|
||||||
"--filter",
|
"--filter",
|
||||||
"--talk=org.bluez",
|
"--talk=org.bluez",
|
||||||
"--talk=org.freedesktop.Avahi",
|
"--talk=org.freedesktop.Avahi",
|
||||||
@@ -185,43 +163,28 @@ func TestSpDBusOp(t *testing.T) {
|
|||||||
)}}, nil, nil),
|
)}}, nil, nil),
|
||||||
}, func() *system.I {
|
}, func() *system.I {
|
||||||
sys := system.New(panicMsgContext{}, message.NewMsg(nil), checkExpectUid)
|
sys := system.New(panicMsgContext{}, message.NewMsg(nil), checkExpectUid)
|
||||||
sys.Ephemeral(system.Process, m(instancePrefix), 0711)
|
sys.Ephemeral(system.Process, m(wantInstancePrefix), 0711)
|
||||||
if err := sys.ProxyDBus(
|
if err := sys.ProxyDBus(
|
||||||
config.SessionBus, config.SystemBus,
|
config.SessionBus, config.SystemBus,
|
||||||
dbus.ProxyPair{"unix:path=/run/user/1000/bus", instancePrefix + "/bus"},
|
dbus.ProxyPair{"unix:path=/run/user/1000/bus", wantInstancePrefix + "/bus"},
|
||||||
dbus.ProxyPair{"unix:path=/var/run/dbus/system_bus_socket", instancePrefix + "/system_bus_socket"},
|
dbus.ProxyPair{"unix:path=/var/run/dbus/system_bus_socket", wantInstancePrefix + "/system_bus_socket"},
|
||||||
); err != nil {
|
); err != nil {
|
||||||
t.Fatalf("cannot prepare sys: %v", err)
|
t.Fatalf("cannot prepare sys: %v", err)
|
||||||
}
|
}
|
||||||
sys.UpdatePerm(m(instancePrefix+"/bus"), acl.Read, acl.Write).
|
sys.UpdatePerm(m(wantInstancePrefix+"/bus"), acl.Read, acl.Write).
|
||||||
UpdatePerm(m(instancePrefix+"/system_bus_socket"), acl.Read, acl.Write)
|
UpdatePerm(m(wantInstancePrefix+"/system_bus_socket"), acl.Read, acl.Write)
|
||||||
return sys
|
return sys
|
||||||
}(), func(t *testing.T, state *outcomeStateSys) {
|
}(), sysUsesInstance(nil), nil, insertsOps(afterSpRuntimeOp(nil)), []stub.Call{
|
||||||
if want := m(instancePrefix); !reflect.DeepEqual(state.sharePath, want) {
|
|
||||||
t.Errorf("outcomeStateSys: sharePath = %v, want %v", state.sharePath, want)
|
|
||||||
}
|
|
||||||
}, nil, func(state *outcomeStateParams) {
|
|
||||||
state.params.Ops = new(container.Ops)
|
|
||||||
|
|
||||||
// emulates spRuntimeOp
|
|
||||||
state.runtimeDir = m("/run/user/1000")
|
|
||||||
}, []stub.Call{
|
|
||||||
// this op configures the container state and does not make calls during toContainer
|
// this op configures the container state and does not make calls during toContainer
|
||||||
}, &container.Params{
|
}, &container.Params{
|
||||||
Ops: new(container.Ops).
|
Ops: new(container.Ops).
|
||||||
Bind(m(instancePrefix+"/bus"),
|
Bind(m(wantInstancePrefix+"/bus"),
|
||||||
m("/run/user/1000/bus"), 0).
|
m("/run/user/1000/bus"), 0).
|
||||||
Bind(m(instancePrefix+"/system_bus_socket"),
|
Bind(m(wantInstancePrefix+"/system_bus_socket"),
|
||||||
m("/var/run/dbus/system_bus_socket"), 0),
|
m("/var/run/dbus/system_bus_socket"), 0),
|
||||||
}, func(t *testing.T, state *outcomeStateParams) {
|
}, paramsWantEnv(config, map[string]string{
|
||||||
wantEnv := map[string]string{
|
|
||||||
"DBUS_SESSION_BUS_ADDRESS": "unix:path=/run/user/1000/bus",
|
"DBUS_SESSION_BUS_ADDRESS": "unix:path=/run/user/1000/bus",
|
||||||
"DBUS_SYSTEM_BUS_ADDRESS": "unix:path=/var/run/dbus/system_bus_socket",
|
"DBUS_SYSTEM_BUS_ADDRESS": "unix:path=/var/run/dbus/system_bus_socket",
|
||||||
}
|
}, nil), nil},
|
||||||
maps.Copy(wantEnv, config.Container.Env)
|
|
||||||
if !maps.Equal(state.env, wantEnv) {
|
|
||||||
t.Errorf("toContainer: env = %#v, want %#v", state.env, wantEnv)
|
|
||||||
}
|
|
||||||
}, nil},
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,67 +0,0 @@
|
|||||||
package app
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/gob"
|
|
||||||
"fmt"
|
|
||||||
"slices"
|
|
||||||
"strings"
|
|
||||||
"syscall"
|
|
||||||
|
|
||||||
"hakurei.app/container/fhs"
|
|
||||||
"hakurei.app/hst"
|
|
||||||
"hakurei.app/system"
|
|
||||||
"hakurei.app/system/acl"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() { gob.Register(spFinalOp{}) }
|
|
||||||
|
|
||||||
// spFinalOp is a transitional op destined for removal after #3, #8, #9 has been resolved.
|
|
||||||
// It exists to avoid reordering the expected entries in test cases.
|
|
||||||
type spFinalOp struct{}
|
|
||||||
|
|
||||||
func (s spFinalOp) toSystem(state *outcomeStateSys) error {
|
|
||||||
// append ExtraPerms last
|
|
||||||
for i := range state.extraPerms {
|
|
||||||
p := &state.extraPerms[i]
|
|
||||||
if p.Path == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if p.Ensure {
|
|
||||||
state.sys.Ensure(p.Path, 0700)
|
|
||||||
}
|
|
||||||
|
|
||||||
perms := make(acl.Perms, 0, 3)
|
|
||||||
if p.Read {
|
|
||||||
perms = append(perms, acl.Read)
|
|
||||||
}
|
|
||||||
if p.Write {
|
|
||||||
perms = append(perms, acl.Write)
|
|
||||||
}
|
|
||||||
if p.Execute {
|
|
||||||
perms = append(perms, acl.Execute)
|
|
||||||
}
|
|
||||||
state.sys.UpdatePermType(system.User, p.Path, perms...)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s spFinalOp) toContainer(state *outcomeStateParams) error {
|
|
||||||
// TODO(ophestra): move this to spFilesystemOp after #8 and #9
|
|
||||||
|
|
||||||
// mount root read-only as the final setup Op
|
|
||||||
state.params.Remount(fhs.AbsRoot, syscall.MS_RDONLY)
|
|
||||||
|
|
||||||
state.params.Env = make([]string, 0, len(state.env))
|
|
||||||
for key, value := range state.env {
|
|
||||||
if strings.IndexByte(key, '=') != -1 {
|
|
||||||
return &hst.AppError{Step: "flatten environment", Err: syscall.EINVAL,
|
|
||||||
Msg: fmt.Sprintf("invalid environment variable %s", key)}
|
|
||||||
}
|
|
||||||
state.params.Env = append(state.params.Env, key+"="+value)
|
|
||||||
}
|
|
||||||
// range over map has randomised order
|
|
||||||
slices.Sort(state.params.Env)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -7,10 +7,12 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"os"
|
"os"
|
||||||
|
"strconv"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"hakurei.app/container/check"
|
"hakurei.app/container/check"
|
||||||
"hakurei.app/hst"
|
"hakurei.app/hst"
|
||||||
|
"hakurei.app/message"
|
||||||
)
|
)
|
||||||
|
|
||||||
const pulseCookieSizeMax = 1 << 8
|
const pulseCookieSizeMax = 1 << 8
|
||||||
@@ -18,9 +20,12 @@ const pulseCookieSizeMax = 1 << 8
|
|||||||
func init() { gob.Register(new(spPulseOp)) }
|
func init() { gob.Register(new(spPulseOp)) }
|
||||||
|
|
||||||
// spPulseOp exports the PulseAudio server to the container.
|
// spPulseOp exports the PulseAudio server to the container.
|
||||||
|
// Runs after spRuntimeOp.
|
||||||
type spPulseOp struct {
|
type spPulseOp struct {
|
||||||
// PulseAudio cookie data, populated during toSystem if a cookie is present.
|
// PulseAudio cookie data, populated during toSystem if a cookie is present.
|
||||||
Cookie *[pulseCookieSizeMax]byte
|
Cookie *[pulseCookieSizeMax]byte
|
||||||
|
// PulseAudio cookie size, populated during toSystem if a cookie is present.
|
||||||
|
CookieSize int
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *spPulseOp) toSystem(state *outcomeStateSys) error {
|
func (s *spPulseOp) toSystem(state *outcomeStateSys) error {
|
||||||
@@ -34,115 +39,31 @@ func (s *spPulseOp) toSystem(state *outcomeStateSys) error {
|
|||||||
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 newWithMessageError(fmt.Sprintf("PulseAudio directory %q not found", pulseRuntimeDir), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if fi, err := state.k.stat(pulseSocket.String()); err != nil {
|
if fi, err := state.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}
|
||||||
}
|
}
|
||||||
return newWithMessage(fmt.Sprintf("PulseAudio directory %q found but socket does not exist", pulseRuntimeDir))
|
return newWithMessageError(fmt.Sprintf("PulseAudio directory %q found but socket does not exist", pulseRuntimeDir), err)
|
||||||
} else {
|
} else {
|
||||||
if m := fi.Mode(); m&0o006 != 0o006 {
|
if m := fi.Mode(); m&0o006 != 0o006 {
|
||||||
return newWithMessage(fmt.Sprintf("unexpected permissions on %q: %s", pulseSocket, m))
|
return newWithMessage(fmt.Sprintf("unexpected permissions on %q: %s", pulseSocket, m))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// hard link pulse socket into target-executable share
|
// pulse socket is world writable and its parent directory DAC permissions prevents access;
|
||||||
|
// hard link to target-executable share directory to grant access
|
||||||
state.sys.Link(pulseSocket, state.runtime().Append("pulse"))
|
state.sys.Link(pulseSocket, state.runtime().Append("pulse"))
|
||||||
|
|
||||||
// publish current user's pulse cookie for target user
|
// load up to pulseCookieSizeMax bytes of pulse cookie for transmission to shim
|
||||||
var paCookiePath *check.Absolute
|
if a, err := discoverPulseCookie(state.k); err != nil {
|
||||||
{
|
return err
|
||||||
const paLocateStep = "locate PulseAudio cookie"
|
} else if a != nil {
|
||||||
|
|
||||||
// from environment
|
|
||||||
if p, ok := state.k.lookupEnv("PULSE_COOKIE"); ok {
|
|
||||||
if a, err := check.NewAbs(p); err != nil {
|
|
||||||
return &hst.AppError{Step: paLocateStep, Err: err}
|
|
||||||
} else {
|
|
||||||
// this takes precedence, do not verify whether the file is accessible
|
|
||||||
paCookiePath = a
|
|
||||||
goto out
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// $HOME/.pulse-cookie
|
|
||||||
if p, ok := state.k.lookupEnv("HOME"); ok {
|
|
||||||
if a, err := check.NewAbs(p); err != nil {
|
|
||||||
return &hst.AppError{Step: paLocateStep, Err: err}
|
|
||||||
} else {
|
|
||||||
paCookiePath = a.Append(".pulse-cookie")
|
|
||||||
}
|
|
||||||
|
|
||||||
if fi, err := state.k.stat(paCookiePath.String()); err != nil {
|
|
||||||
paCookiePath = nil
|
|
||||||
if !errors.Is(err, fs.ErrNotExist) {
|
|
||||||
return &hst.AppError{Step: "access PulseAudio cookie", Err: err}
|
|
||||||
}
|
|
||||||
// fallthrough
|
|
||||||
} else if fi.IsDir() {
|
|
||||||
paCookiePath = nil
|
|
||||||
} else {
|
|
||||||
goto out
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// $XDG_CONFIG_HOME/pulse/cookie
|
|
||||||
if p, ok := state.k.lookupEnv("XDG_CONFIG_HOME"); ok {
|
|
||||||
if a, err := check.NewAbs(p); err != nil {
|
|
||||||
return &hst.AppError{Step: paLocateStep, Err: err}
|
|
||||||
} else {
|
|
||||||
paCookiePath = a.Append("pulse", "cookie")
|
|
||||||
}
|
|
||||||
if fi, err := state.k.stat(paCookiePath.String()); err != nil {
|
|
||||||
paCookiePath = nil
|
|
||||||
if !errors.Is(err, fs.ErrNotExist) {
|
|
||||||
return &hst.AppError{Step: "access PulseAudio cookie", Err: err}
|
|
||||||
}
|
|
||||||
// fallthrough
|
|
||||||
} else if fi.IsDir() {
|
|
||||||
paCookiePath = nil
|
|
||||||
} else {
|
|
||||||
goto out
|
|
||||||
}
|
|
||||||
}
|
|
||||||
out:
|
|
||||||
}
|
|
||||||
|
|
||||||
if paCookiePath != nil {
|
|
||||||
if b, err := state.k.stat(paCookiePath.String()); err != nil {
|
|
||||||
return &hst.AppError{Step: "access PulseAudio cookie", Err: err}
|
|
||||||
} else {
|
|
||||||
if b.IsDir() {
|
|
||||||
return &hst.AppError{Step: "read PulseAudio cookie", Err: &os.PathError{Op: "stat", Path: paCookiePath.String(), Err: syscall.EISDIR}}
|
|
||||||
}
|
|
||||||
if b.Size() > pulseCookieSizeMax {
|
|
||||||
return newWithMessageError(
|
|
||||||
fmt.Sprintf("PulseAudio cookie at %q exceeds maximum expected size", paCookiePath),
|
|
||||||
&os.PathError{Op: "stat", Path: paCookiePath.String(), Err: syscall.ENOMEM},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var r io.ReadCloser
|
|
||||||
if f, err := state.k.open(paCookiePath.String()); err != nil {
|
|
||||||
return &hst.AppError{Step: "open PulseAudio cookie", Err: err}
|
|
||||||
} else {
|
|
||||||
r = f
|
|
||||||
}
|
|
||||||
|
|
||||||
s.Cookie = new([pulseCookieSizeMax]byte)
|
s.Cookie = new([pulseCookieSizeMax]byte)
|
||||||
if n, err := r.Read(s.Cookie[:]); err != nil {
|
if s.CookieSize, err = loadFile(state.msg, state.k, "PulseAudio cookie", a.String(), s.Cookie[:]); err != nil {
|
||||||
if !errors.Is(err, io.EOF) {
|
return err
|
||||||
_ = r.Close()
|
|
||||||
return &hst.AppError{Step: "read PulseAudio cookie", Err: err}
|
|
||||||
}
|
|
||||||
state.msg.Verbosef("copied %d bytes from %q", n, paCookiePath)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := r.Close(); err != nil {
|
|
||||||
return &hst.AppError{Step: "close PulseAudio cookie", Err: err}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
state.msg.Verbose("cannot locate PulseAudio cookie (tried " +
|
state.msg.Verbose("cannot locate PulseAudio cookie (tried " +
|
||||||
@@ -161,8 +82,12 @@ func (s *spPulseOp) toContainer(state *outcomeStateParams) error {
|
|||||||
|
|
||||||
if s.Cookie != nil {
|
if s.Cookie != nil {
|
||||||
innerDst := hst.AbsPrivateTmp.Append("/pulse-cookie")
|
innerDst := hst.AbsPrivateTmp.Append("/pulse-cookie")
|
||||||
|
|
||||||
|
if s.CookieSize < 0 || s.CookieSize > pulseCookieSizeMax {
|
||||||
|
return newWithMessage("unexpected PulseAudio cookie size")
|
||||||
|
}
|
||||||
state.env["PULSE_COOKIE"] = innerDst.String()
|
state.env["PULSE_COOKIE"] = innerDst.String()
|
||||||
state.params.Place(innerDst, s.Cookie[:])
|
state.params.Place(innerDst, s.Cookie[:s.CookieSize])
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -175,3 +100,111 @@ func (s *spPulseOp) commonPaths(state *outcomeState) (pulseRuntimeDir, pulseSock
|
|||||||
pulseSocket = pulseRuntimeDir.Append("native")
|
pulseSocket = pulseRuntimeDir.Append("native")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// discoverPulseCookie attempts to discover the pathname of the PulseAudio cookie of the current user.
|
||||||
|
// If both returned pathname and error are nil, the cookie is likely unavailable and can be silently skipped.
|
||||||
|
func discoverPulseCookie(k syscallDispatcher) (*check.Absolute, error) {
|
||||||
|
const paLocateStep = "locate PulseAudio cookie"
|
||||||
|
|
||||||
|
// from environment
|
||||||
|
if p, ok := k.lookupEnv("PULSE_COOKIE"); ok {
|
||||||
|
if a, err := check.NewAbs(p); err != nil {
|
||||||
|
return nil, &hst.AppError{Step: paLocateStep, Err: err}
|
||||||
|
} else {
|
||||||
|
// this takes precedence, do not verify whether the file is accessible
|
||||||
|
return a, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// $HOME/.pulse-cookie
|
||||||
|
if p, ok := k.lookupEnv("HOME"); ok {
|
||||||
|
var pulseCookiePath *check.Absolute
|
||||||
|
if a, err := check.NewAbs(p); err != nil {
|
||||||
|
return nil, &hst.AppError{Step: paLocateStep, Err: err}
|
||||||
|
} else {
|
||||||
|
pulseCookiePath = a.Append(".pulse-cookie")
|
||||||
|
}
|
||||||
|
|
||||||
|
if fi, err := k.stat(pulseCookiePath.String()); err != nil {
|
||||||
|
if !errors.Is(err, fs.ErrNotExist) {
|
||||||
|
return nil, &hst.AppError{Step: "access PulseAudio cookie", Err: err}
|
||||||
|
}
|
||||||
|
// fallthrough
|
||||||
|
} else if fi.IsDir() {
|
||||||
|
// fallthrough
|
||||||
|
} else {
|
||||||
|
return pulseCookiePath, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// $XDG_CONFIG_HOME/pulse/cookie
|
||||||
|
if p, ok := k.lookupEnv("XDG_CONFIG_HOME"); ok {
|
||||||
|
var pulseCookiePath *check.Absolute
|
||||||
|
if a, err := check.NewAbs(p); err != nil {
|
||||||
|
return nil, &hst.AppError{Step: paLocateStep, Err: err}
|
||||||
|
} else {
|
||||||
|
pulseCookiePath = a.Append("pulse", "cookie")
|
||||||
|
}
|
||||||
|
|
||||||
|
if fi, err := k.stat(pulseCookiePath.String()); err != nil {
|
||||||
|
if !errors.Is(err, fs.ErrNotExist) {
|
||||||
|
return nil, &hst.AppError{Step: "access PulseAudio cookie", Err: err}
|
||||||
|
}
|
||||||
|
// fallthrough
|
||||||
|
} else if fi.IsDir() {
|
||||||
|
// fallthrough
|
||||||
|
} else {
|
||||||
|
return pulseCookiePath, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// cookie not present
|
||||||
|
// not fatal: authentication is disabled
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// loadFile reads up to len(buf) bytes from the file at pathname.
|
||||||
|
func loadFile(
|
||||||
|
msg message.Msg, k syscallDispatcher,
|
||||||
|
description, pathname string, buf []byte,
|
||||||
|
) (int, error) {
|
||||||
|
n := len(buf)
|
||||||
|
if n == 0 {
|
||||||
|
return -1, errors.New("invalid buffer")
|
||||||
|
}
|
||||||
|
|
||||||
|
if fi, err := k.stat(pathname); err != nil {
|
||||||
|
return -1, &hst.AppError{Step: "access " + description, Err: err}
|
||||||
|
} else {
|
||||||
|
if fi.IsDir() {
|
||||||
|
return -1, &hst.AppError{Step: "read " + description,
|
||||||
|
Err: &os.PathError{Op: "stat", Path: pathname, Err: syscall.EISDIR}}
|
||||||
|
}
|
||||||
|
if s := fi.Size(); s > int64(n) {
|
||||||
|
return -1, newWithMessageError(
|
||||||
|
description+" at "+strconv.Quote(pathname)+" exceeds expected size",
|
||||||
|
&os.PathError{Op: "stat", Path: pathname, Err: syscall.ENOMEM},
|
||||||
|
)
|
||||||
|
} else if s < int64(n) {
|
||||||
|
msg.Verbosef("%s at %q is %d bytes shorter than expected", description, pathname, int64(n)-s)
|
||||||
|
} else {
|
||||||
|
msg.Verbosef("loading %d bytes from %q", n, pathname)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if f, err := k.open(pathname); err != nil {
|
||||||
|
return -1, &hst.AppError{Step: "open " + description, Err: err}
|
||||||
|
} else {
|
||||||
|
if n, err = f.Read(buf); err != nil {
|
||||||
|
if !errors.Is(err, io.EOF) {
|
||||||
|
_ = f.Close()
|
||||||
|
return n, &hst.AppError{Step: "read " + description, Err: err}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = f.Close(); err != nil {
|
||||||
|
return n, &hst.AppError{Step: "close " + description, Err: err}
|
||||||
|
}
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
460
internal/app/sppulse_test.go
Normal file
460
internal/app/sppulse_test.go
Normal file
@@ -0,0 +1,460 @@
|
|||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"os"
|
||||||
|
"syscall"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"hakurei.app/container"
|
||||||
|
"hakurei.app/container/check"
|
||||||
|
"hakurei.app/container/stub"
|
||||||
|
"hakurei.app/hst"
|
||||||
|
"hakurei.app/system"
|
||||||
|
"hakurei.app/system/acl"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSpPulseOp(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
config := hst.Template()
|
||||||
|
sampleCookie := bytes.Repeat([]byte{0xfc}, pulseCookieSizeMax)
|
||||||
|
|
||||||
|
checkOpBehaviour(t, []opBehaviourTestCase{
|
||||||
|
{"not enabled", func(bool, bool) outcomeOp {
|
||||||
|
return new(spPulseOp)
|
||||||
|
}, func() *hst.Config {
|
||||||
|
c := hst.Template()
|
||||||
|
*c.Enablements = 0
|
||||||
|
return c
|
||||||
|
}, nil, nil, nil, nil, errNotEnabled, nil, nil, nil, nil, nil},
|
||||||
|
|
||||||
|
{"socketDir stat", func(isShim, _ bool) outcomeOp {
|
||||||
|
if !isShim {
|
||||||
|
return new(spPulseOp)
|
||||||
|
}
|
||||||
|
return &spPulseOp{Cookie: (*[256]byte)(sampleCookie)}
|
||||||
|
}, hst.Template, nil, []stub.Call{
|
||||||
|
call("stat", stub.ExpectArgs{wantRuntimePath + "/pulse"}, (*stubFi)(nil), stub.UniqueError(2)),
|
||||||
|
}, nil, nil, &hst.AppError{
|
||||||
|
Step: `access PulseAudio directory "/proc/nonexistent/xdg_runtime_dir/pulse"`,
|
||||||
|
Err: stub.UniqueError(2),
|
||||||
|
}, nil, nil, nil, nil, nil},
|
||||||
|
|
||||||
|
{"socketDir nonexistent", func(bool, bool) outcomeOp {
|
||||||
|
return new(spPulseOp)
|
||||||
|
}, hst.Template, nil, []stub.Call{
|
||||||
|
call("stat", stub.ExpectArgs{wantRuntimePath + "/pulse"}, (*stubFi)(nil), os.ErrNotExist),
|
||||||
|
}, nil, nil, &hst.AppError{
|
||||||
|
Step: "finalise",
|
||||||
|
Err: os.ErrNotExist,
|
||||||
|
Msg: `PulseAudio directory "/proc/nonexistent/xdg_runtime_dir/pulse" not found`,
|
||||||
|
}, nil, nil, nil, nil, nil},
|
||||||
|
|
||||||
|
{"socket stat", func(bool, bool) outcomeOp {
|
||||||
|
return new(spPulseOp)
|
||||||
|
}, hst.Template, nil, []stub.Call{
|
||||||
|
call("stat", stub.ExpectArgs{wantRuntimePath + "/pulse"}, (*stubFi)(nil), nil),
|
||||||
|
call("stat", stub.ExpectArgs{wantRuntimePath + "/pulse/native"}, (*stubFi)(nil), stub.UniqueError(1)),
|
||||||
|
}, nil, nil, &hst.AppError{
|
||||||
|
Step: `access PulseAudio socket "/proc/nonexistent/xdg_runtime_dir/pulse/native"`,
|
||||||
|
Err: stub.UniqueError(1),
|
||||||
|
}, nil, nil, nil, nil, nil},
|
||||||
|
|
||||||
|
{"socket nonexistent", func(bool, bool) outcomeOp {
|
||||||
|
return new(spPulseOp)
|
||||||
|
}, hst.Template, nil, []stub.Call{
|
||||||
|
call("stat", stub.ExpectArgs{wantRuntimePath + "/pulse"}, (*stubFi)(nil), nil),
|
||||||
|
call("stat", stub.ExpectArgs{wantRuntimePath + "/pulse/native"}, (*stubFi)(nil), os.ErrNotExist),
|
||||||
|
}, nil, nil, &hst.AppError{
|
||||||
|
Step: "finalise",
|
||||||
|
Err: os.ErrNotExist,
|
||||||
|
Msg: `PulseAudio directory "/proc/nonexistent/xdg_runtime_dir/pulse" found but socket does not exist`,
|
||||||
|
}, nil, nil, nil, nil, nil},
|
||||||
|
|
||||||
|
{"socket mode", func(bool, bool) outcomeOp {
|
||||||
|
return new(spPulseOp)
|
||||||
|
}, hst.Template, nil, []stub.Call{
|
||||||
|
call("stat", stub.ExpectArgs{wantRuntimePath + "/pulse"}, (*stubFi)(nil), nil),
|
||||||
|
call("stat", stub.ExpectArgs{wantRuntimePath + "/pulse/native"}, &stubFi{mode: 0660}, nil),
|
||||||
|
}, nil, nil, &hst.AppError{
|
||||||
|
Step: "finalise",
|
||||||
|
Err: os.ErrInvalid,
|
||||||
|
Msg: `unexpected permissions on "/proc/nonexistent/xdg_runtime_dir/pulse/native": -rw-rw----`,
|
||||||
|
}, nil, nil, nil, nil, nil},
|
||||||
|
|
||||||
|
{"cookie notAbs", func(bool, bool) outcomeOp {
|
||||||
|
return new(spPulseOp)
|
||||||
|
}, hst.Template, nil, []stub.Call{
|
||||||
|
call("stat", stub.ExpectArgs{wantRuntimePath + "/pulse"}, (*stubFi)(nil), nil),
|
||||||
|
call("stat", stub.ExpectArgs{wantRuntimePath + "/pulse/native"}, &stubFi{mode: 0666}, nil),
|
||||||
|
call("lookupEnv", stub.ExpectArgs{"PULSE_COOKIE"}, "proc/nonexistent/cookie", nil),
|
||||||
|
}, nil, nil, &hst.AppError{
|
||||||
|
Step: "locate PulseAudio cookie",
|
||||||
|
Err: &check.AbsoluteError{Pathname: "proc/nonexistent/cookie"},
|
||||||
|
}, nil, nil, nil, nil, nil},
|
||||||
|
|
||||||
|
{"cookie loadFile", func(bool, bool) outcomeOp {
|
||||||
|
return new(spPulseOp)
|
||||||
|
}, hst.Template, nil, []stub.Call{
|
||||||
|
call("stat", stub.ExpectArgs{wantRuntimePath + "/pulse"}, (*stubFi)(nil), nil),
|
||||||
|
call("stat", stub.ExpectArgs{wantRuntimePath + "/pulse/native"}, &stubFi{mode: 0666}, nil),
|
||||||
|
call("lookupEnv", stub.ExpectArgs{"PULSE_COOKIE"}, "/proc/nonexistent/cookie", nil),
|
||||||
|
call("stat", stub.ExpectArgs{"/proc/nonexistent/cookie"}, &stubFi{isDir: false, size: 1 << 8}, nil),
|
||||||
|
call("verbosef", stub.ExpectArgs{"loading %d bytes from %q", []any{1 << 8, "/proc/nonexistent/cookie"}}, nil, nil),
|
||||||
|
call("open", stub.ExpectArgs{"/proc/nonexistent/cookie"}, (*stubOsFile)(nil), stub.UniqueError(0)),
|
||||||
|
}, nil, nil, &hst.AppError{
|
||||||
|
Step: "open PulseAudio cookie",
|
||||||
|
Err: stub.UniqueError(0),
|
||||||
|
}, nil, nil, nil, nil, nil},
|
||||||
|
|
||||||
|
{"cookie bad shim size", func(isShim, clearUnexported bool) outcomeOp {
|
||||||
|
if !isShim {
|
||||||
|
return new(spPulseOp)
|
||||||
|
}
|
||||||
|
op := &spPulseOp{Cookie: (*[pulseCookieSizeMax]byte)(sampleCookie), CookieSize: pulseCookieSizeMax}
|
||||||
|
if clearUnexported {
|
||||||
|
op.CookieSize += +0xfd
|
||||||
|
}
|
||||||
|
return op
|
||||||
|
}, hst.Template, nil, []stub.Call{
|
||||||
|
call("stat", stub.ExpectArgs{wantRuntimePath + "/pulse"}, (*stubFi)(nil), nil),
|
||||||
|
call("stat", stub.ExpectArgs{wantRuntimePath + "/pulse/native"}, &stubFi{mode: 0666}, nil),
|
||||||
|
call("lookupEnv", stub.ExpectArgs{"PULSE_COOKIE"}, "/proc/nonexistent/cookie", nil),
|
||||||
|
call("stat", stub.ExpectArgs{"/proc/nonexistent/cookie"}, &stubFi{isDir: false, size: 1 << 8}, nil),
|
||||||
|
call("verbosef", stub.ExpectArgs{"loading %d bytes from %q", []any{1 << 8, "/proc/nonexistent/cookie"}}, nil, nil),
|
||||||
|
call("open", stub.ExpectArgs{"/proc/nonexistent/cookie"}, &stubOsFile{Reader: bytes.NewReader(sampleCookie)}, nil),
|
||||||
|
}, newI().
|
||||||
|
// state.ensureRuntimeDir
|
||||||
|
Ensure(m(wantRunDirPath), 0700).
|
||||||
|
UpdatePermType(system.User, m(wantRunDirPath), acl.Execute).
|
||||||
|
Ensure(m(wantRuntimePath), 0700).
|
||||||
|
UpdatePermType(system.User, m(wantRuntimePath), acl.Execute).
|
||||||
|
// state.runtime
|
||||||
|
Ephemeral(system.Process, m(wantRuntimeSharePath), 0700).
|
||||||
|
UpdatePerm(m(wantRuntimeSharePath), acl.Execute).
|
||||||
|
// toSystem
|
||||||
|
Link(m(wantRuntimePath+"/pulse/native"), m(wantRuntimeSharePath+"/pulse")), sysUsesRuntime(nil), nil, insertsOps(afterSpRuntimeOp(nil)), []stub.Call{
|
||||||
|
// this op configures the container state and does not make calls during toContainer
|
||||||
|
}, nil, nil, &hst.AppError{
|
||||||
|
Step: "finalise",
|
||||||
|
Err: os.ErrInvalid,
|
||||||
|
Msg: "unexpected PulseAudio cookie size",
|
||||||
|
}},
|
||||||
|
|
||||||
|
{"success cookie short", func(isShim, _ bool) outcomeOp {
|
||||||
|
if !isShim {
|
||||||
|
return new(spPulseOp)
|
||||||
|
}
|
||||||
|
sampleCookieTrunc := make([]byte, pulseCookieSizeMax)
|
||||||
|
copy(sampleCookieTrunc, sampleCookie[:len(sampleCookie)-0xe])
|
||||||
|
return &spPulseOp{Cookie: (*[pulseCookieSizeMax]byte)(sampleCookieTrunc), CookieSize: pulseCookieSizeMax - 0xe}
|
||||||
|
}, hst.Template, nil, []stub.Call{
|
||||||
|
call("stat", stub.ExpectArgs{wantRuntimePath + "/pulse"}, (*stubFi)(nil), nil),
|
||||||
|
call("stat", stub.ExpectArgs{wantRuntimePath + "/pulse/native"}, &stubFi{mode: 0666}, nil),
|
||||||
|
call("lookupEnv", stub.ExpectArgs{"PULSE_COOKIE"}, "/proc/nonexistent/cookie", nil),
|
||||||
|
call("stat", stub.ExpectArgs{"/proc/nonexistent/cookie"}, &stubFi{isDir: false, size: pulseCookieSizeMax - 0xe}, nil),
|
||||||
|
call("verbosef", stub.ExpectArgs{"%s at %q is %d bytes shorter than expected", []any{"PulseAudio cookie", "/proc/nonexistent/cookie", int64(0xe)}}, nil, nil),
|
||||||
|
call("open", stub.ExpectArgs{"/proc/nonexistent/cookie"}, &stubOsFile{Reader: bytes.NewReader(sampleCookie[:len(sampleCookie)-0xe])}, nil),
|
||||||
|
}, newI().
|
||||||
|
// state.ensureRuntimeDir
|
||||||
|
Ensure(m(wantRunDirPath), 0700).
|
||||||
|
UpdatePermType(system.User, m(wantRunDirPath), acl.Execute).
|
||||||
|
Ensure(m(wantRuntimePath), 0700).
|
||||||
|
UpdatePermType(system.User, m(wantRuntimePath), acl.Execute).
|
||||||
|
// state.runtime
|
||||||
|
Ephemeral(system.Process, m(wantRuntimeSharePath), 0700).
|
||||||
|
UpdatePerm(m(wantRuntimeSharePath), acl.Execute).
|
||||||
|
// toSystem
|
||||||
|
Link(m(wantRuntimePath+"/pulse/native"), m(wantRuntimeSharePath+"/pulse")), sysUsesRuntime(nil), nil, insertsOps(afterSpRuntimeOp(nil)), []stub.Call{
|
||||||
|
// this op configures the container state and does not make calls during toContainer
|
||||||
|
}, &container.Params{
|
||||||
|
Ops: new(container.Ops).
|
||||||
|
Bind(m(wantRuntimeSharePath+"/pulse"), m("/run/user/1000/pulse/native"), 0).
|
||||||
|
Place(m("/.hakurei/pulse-cookie"), sampleCookie[:len(sampleCookie)-0xe]),
|
||||||
|
}, paramsWantEnv(config, map[string]string{
|
||||||
|
"PULSE_SERVER": "unix:/run/user/1000/pulse/native",
|
||||||
|
"PULSE_COOKIE": "/.hakurei/pulse-cookie",
|
||||||
|
}, nil), nil},
|
||||||
|
|
||||||
|
{"success cookie", func(isShim, _ bool) outcomeOp {
|
||||||
|
if !isShim {
|
||||||
|
return new(spPulseOp)
|
||||||
|
}
|
||||||
|
return &spPulseOp{Cookie: (*[pulseCookieSizeMax]byte)(sampleCookie), CookieSize: pulseCookieSizeMax}
|
||||||
|
}, hst.Template, nil, []stub.Call{
|
||||||
|
call("stat", stub.ExpectArgs{wantRuntimePath + "/pulse"}, (*stubFi)(nil), nil),
|
||||||
|
call("stat", stub.ExpectArgs{wantRuntimePath + "/pulse/native"}, &stubFi{mode: 0666}, nil),
|
||||||
|
call("lookupEnv", stub.ExpectArgs{"PULSE_COOKIE"}, "/proc/nonexistent/cookie", nil),
|
||||||
|
call("stat", stub.ExpectArgs{"/proc/nonexistent/cookie"}, &stubFi{isDir: false, size: 1 << 8}, nil),
|
||||||
|
call("verbosef", stub.ExpectArgs{"loading %d bytes from %q", []any{1 << 8, "/proc/nonexistent/cookie"}}, nil, nil),
|
||||||
|
call("open", stub.ExpectArgs{"/proc/nonexistent/cookie"}, &stubOsFile{Reader: bytes.NewReader(sampleCookie)}, nil),
|
||||||
|
}, newI().
|
||||||
|
// state.ensureRuntimeDir
|
||||||
|
Ensure(m(wantRunDirPath), 0700).
|
||||||
|
UpdatePermType(system.User, m(wantRunDirPath), acl.Execute).
|
||||||
|
Ensure(m(wantRuntimePath), 0700).
|
||||||
|
UpdatePermType(system.User, m(wantRuntimePath), acl.Execute).
|
||||||
|
// state.runtime
|
||||||
|
Ephemeral(system.Process, m(wantRuntimeSharePath), 0700).
|
||||||
|
UpdatePerm(m(wantRuntimeSharePath), acl.Execute).
|
||||||
|
// toSystem
|
||||||
|
Link(m(wantRuntimePath+"/pulse/native"), m(wantRuntimeSharePath+"/pulse")), sysUsesRuntime(nil), nil, insertsOps(afterSpRuntimeOp(nil)), []stub.Call{
|
||||||
|
// this op configures the container state and does not make calls during toContainer
|
||||||
|
}, &container.Params{
|
||||||
|
Ops: new(container.Ops).
|
||||||
|
Bind(m(wantRuntimeSharePath+"/pulse"), m("/run/user/1000/pulse/native"), 0).
|
||||||
|
Place(m("/.hakurei/pulse-cookie"), sampleCookie),
|
||||||
|
}, paramsWantEnv(config, map[string]string{
|
||||||
|
"PULSE_SERVER": "unix:/run/user/1000/pulse/native",
|
||||||
|
"PULSE_COOKIE": "/.hakurei/pulse-cookie",
|
||||||
|
}, nil), nil},
|
||||||
|
|
||||||
|
{"success", func(bool, bool) outcomeOp {
|
||||||
|
return new(spPulseOp)
|
||||||
|
}, hst.Template, nil, []stub.Call{
|
||||||
|
call("stat", stub.ExpectArgs{wantRuntimePath + "/pulse"}, (*stubFi)(nil), nil),
|
||||||
|
call("stat", stub.ExpectArgs{wantRuntimePath + "/pulse/native"}, &stubFi{mode: 0666}, nil),
|
||||||
|
call("lookupEnv", stub.ExpectArgs{"PULSE_COOKIE"}, nil, nil),
|
||||||
|
call("lookupEnv", stub.ExpectArgs{"HOME"}, nil, nil),
|
||||||
|
call("lookupEnv", stub.ExpectArgs{"XDG_CONFIG_HOME"}, nil, nil),
|
||||||
|
call("verbose", stub.ExpectArgs{[]any{"cannot locate PulseAudio cookie (tried $PULSE_COOKIE, $XDG_CONFIG_HOME/pulse/cookie, $HOME/.pulse-cookie)"}}, nil, nil),
|
||||||
|
}, newI().
|
||||||
|
// state.ensureRuntimeDir
|
||||||
|
Ensure(m(wantRunDirPath), 0700).
|
||||||
|
UpdatePermType(system.User, m(wantRunDirPath), acl.Execute).
|
||||||
|
Ensure(m(wantRuntimePath), 0700).
|
||||||
|
UpdatePermType(system.User, m(wantRuntimePath), acl.Execute).
|
||||||
|
// state.runtime
|
||||||
|
Ephemeral(system.Process, m(wantRuntimeSharePath), 0700).
|
||||||
|
UpdatePerm(m(wantRuntimeSharePath), acl.Execute).
|
||||||
|
// toSystem
|
||||||
|
Link(m(wantRuntimePath+"/pulse/native"), m(wantRuntimeSharePath+"/pulse")), sysUsesRuntime(nil), nil, insertsOps(afterSpRuntimeOp(nil)), []stub.Call{
|
||||||
|
// this op configures the container state and does not make calls during toContainer
|
||||||
|
}, &container.Params{
|
||||||
|
Ops: new(container.Ops).
|
||||||
|
Bind(m(wantRuntimeSharePath+"/pulse"), m("/run/user/1000/pulse/native"), 0),
|
||||||
|
}, paramsWantEnv(config, map[string]string{
|
||||||
|
"PULSE_SERVER": "unix:/run/user/1000/pulse/native",
|
||||||
|
}, nil), nil},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDiscoverPulseCookie(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
fCheckPathname := func(k *kstub) error {
|
||||||
|
a, err := discoverPulseCookie(k)
|
||||||
|
k.Verbose(a)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
checkSimple(t, "discoverPulseCookie", []simpleTestCase{
|
||||||
|
{"override notAbs", fCheckPathname, stub.Expect{Calls: []stub.Call{
|
||||||
|
call("lookupEnv", stub.ExpectArgs{"PULSE_COOKIE"}, "proc/nonexistent/pulse-cookie", nil),
|
||||||
|
call("verbose", stub.ExpectArgs{[]any{(*check.Absolute)(nil)}}, nil, nil),
|
||||||
|
}}, &hst.AppError{
|
||||||
|
Step: "locate PulseAudio cookie",
|
||||||
|
Err: &check.AbsoluteError{Pathname: "proc/nonexistent/pulse-cookie"},
|
||||||
|
}},
|
||||||
|
|
||||||
|
{"success override", fCheckPathname, stub.Expect{Calls: []stub.Call{
|
||||||
|
call("lookupEnv", stub.ExpectArgs{"PULSE_COOKIE"}, "/proc/nonexistent/pulse-cookie", nil),
|
||||||
|
call("verbose", stub.ExpectArgs{[]any{m("/proc/nonexistent/pulse-cookie")}}, nil, nil),
|
||||||
|
}}, nil},
|
||||||
|
|
||||||
|
{"home notAbs", fCheckPathname, stub.Expect{Calls: []stub.Call{
|
||||||
|
call("lookupEnv", stub.ExpectArgs{"PULSE_COOKIE"}, nil, nil),
|
||||||
|
call("lookupEnv", stub.ExpectArgs{"HOME"}, "proc/nonexistent/home", nil),
|
||||||
|
call("verbose", stub.ExpectArgs{[]any{(*check.Absolute)(nil)}}, nil, nil),
|
||||||
|
}}, &hst.AppError{
|
||||||
|
Step: "locate PulseAudio cookie",
|
||||||
|
Err: &check.AbsoluteError{Pathname: "proc/nonexistent/home"},
|
||||||
|
}},
|
||||||
|
|
||||||
|
{"home stat", fCheckPathname, stub.Expect{Calls: []stub.Call{
|
||||||
|
call("lookupEnv", stub.ExpectArgs{"PULSE_COOKIE"}, nil, nil),
|
||||||
|
call("lookupEnv", stub.ExpectArgs{"HOME"}, "/proc/nonexistent/home", nil),
|
||||||
|
call("stat", stub.ExpectArgs{"/proc/nonexistent/home/.pulse-cookie"}, (*stubFi)(nil), stub.UniqueError(1)),
|
||||||
|
call("verbose", stub.ExpectArgs{[]any{(*check.Absolute)(nil)}}, nil, nil),
|
||||||
|
}}, &hst.AppError{
|
||||||
|
Step: "access PulseAudio cookie",
|
||||||
|
Err: stub.UniqueError(1),
|
||||||
|
}},
|
||||||
|
|
||||||
|
{"home nonexistent", fCheckPathname, stub.Expect{Calls: []stub.Call{
|
||||||
|
call("lookupEnv", stub.ExpectArgs{"PULSE_COOKIE"}, nil, nil),
|
||||||
|
call("lookupEnv", stub.ExpectArgs{"HOME"}, "/proc/nonexistent/home", nil),
|
||||||
|
call("stat", stub.ExpectArgs{"/proc/nonexistent/home/.pulse-cookie"}, (*stubFi)(nil), os.ErrNotExist),
|
||||||
|
call("lookupEnv", stub.ExpectArgs{"XDG_CONFIG_HOME"}, nil, nil),
|
||||||
|
call("verbose", stub.ExpectArgs{[]any{(*check.Absolute)(nil)}}, nil, nil),
|
||||||
|
}}, nil},
|
||||||
|
|
||||||
|
{"success home", fCheckPathname, stub.Expect{Calls: []stub.Call{
|
||||||
|
call("lookupEnv", stub.ExpectArgs{"PULSE_COOKIE"}, nil, nil),
|
||||||
|
call("lookupEnv", stub.ExpectArgs{"HOME"}, "/proc/nonexistent/home", nil),
|
||||||
|
call("stat", stub.ExpectArgs{"/proc/nonexistent/home/.pulse-cookie"}, &stubFi{}, nil),
|
||||||
|
call("verbose", stub.ExpectArgs{[]any{m("/proc/nonexistent/home/.pulse-cookie")}}, nil, nil),
|
||||||
|
}}, nil},
|
||||||
|
|
||||||
|
{"xdg notAbs", fCheckPathname, stub.Expect{Calls: []stub.Call{
|
||||||
|
call("lookupEnv", stub.ExpectArgs{"PULSE_COOKIE"}, nil, nil),
|
||||||
|
call("lookupEnv", stub.ExpectArgs{"HOME"}, nil, nil),
|
||||||
|
call("lookupEnv", stub.ExpectArgs{"XDG_CONFIG_HOME"}, "proc/nonexistent/xdg", nil),
|
||||||
|
call("verbose", stub.ExpectArgs{[]any{(*check.Absolute)(nil)}}, nil, nil),
|
||||||
|
}}, &hst.AppError{
|
||||||
|
Step: "locate PulseAudio cookie",
|
||||||
|
Err: &check.AbsoluteError{Pathname: "proc/nonexistent/xdg"},
|
||||||
|
}},
|
||||||
|
|
||||||
|
{"xdg stat", fCheckPathname, stub.Expect{Calls: []stub.Call{
|
||||||
|
call("lookupEnv", stub.ExpectArgs{"PULSE_COOKIE"}, nil, nil),
|
||||||
|
call("lookupEnv", stub.ExpectArgs{"HOME"}, nil, nil),
|
||||||
|
call("lookupEnv", stub.ExpectArgs{"XDG_CONFIG_HOME"}, "/proc/nonexistent/xdg", nil),
|
||||||
|
call("stat", stub.ExpectArgs{"/proc/nonexistent/xdg/pulse/cookie"}, (*stubFi)(nil), stub.UniqueError(0)),
|
||||||
|
call("verbose", stub.ExpectArgs{[]any{(*check.Absolute)(nil)}}, nil, nil),
|
||||||
|
}}, &hst.AppError{
|
||||||
|
Step: "access PulseAudio cookie",
|
||||||
|
Err: stub.UniqueError(0),
|
||||||
|
}},
|
||||||
|
|
||||||
|
{"xdg dir", fCheckPathname, stub.Expect{Calls: []stub.Call{
|
||||||
|
call("lookupEnv", stub.ExpectArgs{"PULSE_COOKIE"}, nil, nil),
|
||||||
|
call("lookupEnv", stub.ExpectArgs{"HOME"}, nil, nil),
|
||||||
|
call("lookupEnv", stub.ExpectArgs{"XDG_CONFIG_HOME"}, "/proc/nonexistent/xdg", nil),
|
||||||
|
call("stat", stub.ExpectArgs{"/proc/nonexistent/xdg/pulse/cookie"}, &stubFi{isDir: true}, nil),
|
||||||
|
call("verbose", stub.ExpectArgs{[]any{(*check.Absolute)(nil)}}, nil, nil),
|
||||||
|
}}, nil},
|
||||||
|
|
||||||
|
{"success home dir xdg nonexistent", fCheckPathname, stub.Expect{Calls: []stub.Call{
|
||||||
|
call("lookupEnv", stub.ExpectArgs{"PULSE_COOKIE"}, nil, nil),
|
||||||
|
call("lookupEnv", stub.ExpectArgs{"HOME"}, "/proc/nonexistent/home", nil),
|
||||||
|
call("stat", stub.ExpectArgs{"/proc/nonexistent/home/.pulse-cookie"}, &stubFi{isDir: true}, nil),
|
||||||
|
call("lookupEnv", stub.ExpectArgs{"XDG_CONFIG_HOME"}, "/proc/nonexistent/xdg", nil),
|
||||||
|
call("stat", stub.ExpectArgs{"/proc/nonexistent/xdg/pulse/cookie"}, (*stubFi)(nil), os.ErrNotExist),
|
||||||
|
call("verbose", stub.ExpectArgs{[]any{(*check.Absolute)(nil)}}, nil, nil),
|
||||||
|
}}, nil},
|
||||||
|
|
||||||
|
{"success home nonexistent xdg", fCheckPathname, stub.Expect{Calls: []stub.Call{
|
||||||
|
call("lookupEnv", stub.ExpectArgs{"PULSE_COOKIE"}, nil, nil),
|
||||||
|
call("lookupEnv", stub.ExpectArgs{"HOME"}, "/proc/nonexistent/home", nil),
|
||||||
|
call("stat", stub.ExpectArgs{"/proc/nonexistent/home/.pulse-cookie"}, (*stubFi)(nil), os.ErrNotExist),
|
||||||
|
call("lookupEnv", stub.ExpectArgs{"XDG_CONFIG_HOME"}, "/proc/nonexistent/xdg", nil),
|
||||||
|
call("stat", stub.ExpectArgs{"/proc/nonexistent/xdg/pulse/cookie"}, &stubFi{}, nil),
|
||||||
|
call("verbose", stub.ExpectArgs{[]any{m("/proc/nonexistent/xdg/pulse/cookie")}}, nil, nil),
|
||||||
|
}}, nil},
|
||||||
|
|
||||||
|
{"success empty environ", fCheckPathname, stub.Expect{Calls: []stub.Call{
|
||||||
|
call("lookupEnv", stub.ExpectArgs{"PULSE_COOKIE"}, nil, nil),
|
||||||
|
call("lookupEnv", stub.ExpectArgs{"HOME"}, nil, nil),
|
||||||
|
call("lookupEnv", stub.ExpectArgs{"XDG_CONFIG_HOME"}, nil, nil),
|
||||||
|
call("verbose", stub.ExpectArgs{[]any{(*check.Absolute)(nil)}}, nil, nil),
|
||||||
|
}}, nil},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLoadFile(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
fAfterWriteExact := func(k *kstub) error {
|
||||||
|
buf := make([]byte, 1<<8)
|
||||||
|
n, err := loadFile(k, k,
|
||||||
|
"simulated PulseAudio cookie",
|
||||||
|
"/home/ophestra/xdg/config/pulse/cookie",
|
||||||
|
buf)
|
||||||
|
k.Verbose(buf[:n])
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
fAfterWrite := func(k *kstub) error {
|
||||||
|
buf := make([]byte, 1<<8+0xfd)
|
||||||
|
n, err := loadFile(k, k,
|
||||||
|
"simulated PulseAudio cookie",
|
||||||
|
"/home/ophestra/xdg/config/pulse/cookie",
|
||||||
|
buf)
|
||||||
|
k.Verbose(buf[:n])
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
fBeforeWrite := func(k *kstub) error {
|
||||||
|
buf := make([]byte, 1<<8+0xfd)
|
||||||
|
n, err := loadFile(k, k,
|
||||||
|
"simulated PulseAudio cookie",
|
||||||
|
"/home/ophestra/xdg/config/pulse/cookie",
|
||||||
|
buf)
|
||||||
|
k.Verbose(n)
|
||||||
|
|
||||||
|
if !bytes.Equal(buf, make([]byte, len(buf))) {
|
||||||
|
t.Errorf("loadFile: buf = %#v", buf)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
sampleCookie := bytes.Repeat([]byte{0xfc}, pulseCookieSizeMax)
|
||||||
|
checkSimple(t, "loadFile", []simpleTestCase{
|
||||||
|
{"buf", func(k *kstub) error {
|
||||||
|
n, err := loadFile(k, k,
|
||||||
|
"simulated PulseAudio cookie",
|
||||||
|
"/home/ophestra/xdg/config/pulse/cookie",
|
||||||
|
nil)
|
||||||
|
k.Verbose(n)
|
||||||
|
return err
|
||||||
|
}, stub.Expect{Calls: []stub.Call{
|
||||||
|
call("verbose", stub.ExpectArgs{[]any{-1}}, nil, nil),
|
||||||
|
}}, errors.New("invalid buffer")},
|
||||||
|
|
||||||
|
{"stat", fBeforeWrite, stub.Expect{Calls: []stub.Call{
|
||||||
|
call("stat", stub.ExpectArgs{"/home/ophestra/xdg/config/pulse/cookie"}, (*stubFi)(nil), stub.UniqueError(3)),
|
||||||
|
call("verbose", stub.ExpectArgs{[]any{-1}}, nil, nil),
|
||||||
|
}}, &hst.AppError{
|
||||||
|
Step: "access simulated PulseAudio cookie",
|
||||||
|
Err: stub.UniqueError(3),
|
||||||
|
}},
|
||||||
|
|
||||||
|
{"dir", fBeforeWrite, stub.Expect{Calls: []stub.Call{
|
||||||
|
call("stat", stub.ExpectArgs{"/home/ophestra/xdg/config/pulse/cookie"}, &stubFi{isDir: true}, nil),
|
||||||
|
call("verbose", stub.ExpectArgs{[]any{-1}}, nil, nil),
|
||||||
|
}}, &hst.AppError{
|
||||||
|
Step: "read simulated PulseAudio cookie",
|
||||||
|
Err: &os.PathError{Op: "stat", Path: "/home/ophestra/xdg/config/pulse/cookie", Err: syscall.EISDIR},
|
||||||
|
}},
|
||||||
|
|
||||||
|
{"oob", fBeforeWrite, stub.Expect{Calls: []stub.Call{
|
||||||
|
call("stat", stub.ExpectArgs{"/home/ophestra/xdg/config/pulse/cookie"}, &stubFi{size: 1<<8 + 0xff}, nil),
|
||||||
|
call("verbose", stub.ExpectArgs{[]any{-1}}, nil, nil),
|
||||||
|
}}, &hst.AppError{
|
||||||
|
Step: "finalise",
|
||||||
|
Err: &os.PathError{Op: "stat", Path: "/home/ophestra/xdg/config/pulse/cookie", Err: syscall.ENOMEM},
|
||||||
|
Msg: `simulated PulseAudio cookie at "/home/ophestra/xdg/config/pulse/cookie" exceeds expected size`,
|
||||||
|
}},
|
||||||
|
|
||||||
|
{"open", fBeforeWrite, stub.Expect{Calls: []stub.Call{
|
||||||
|
call("stat", stub.ExpectArgs{"/home/ophestra/xdg/config/pulse/cookie"}, &stubFi{size: 1 << 8}, nil),
|
||||||
|
call("verbosef", stub.ExpectArgs{"%s at %q is %d bytes shorter than expected", []any{"simulated PulseAudio cookie", "/home/ophestra/xdg/config/pulse/cookie", int64(0xfd)}}, nil, nil),
|
||||||
|
call("open", stub.ExpectArgs{"/home/ophestra/xdg/config/pulse/cookie"}, (*stubOsFile)(nil), stub.UniqueError(2)),
|
||||||
|
call("verbose", stub.ExpectArgs{[]any{-1}}, nil, nil),
|
||||||
|
}}, &hst.AppError{Step: "open simulated PulseAudio cookie", Err: stub.UniqueError(2)}},
|
||||||
|
|
||||||
|
{"read", fBeforeWrite, stub.Expect{Calls: []stub.Call{
|
||||||
|
call("stat", stub.ExpectArgs{"/home/ophestra/xdg/config/pulse/cookie"}, &stubFi{size: 1 << 8}, nil),
|
||||||
|
call("verbosef", stub.ExpectArgs{"%s at %q is %d bytes shorter than expected", []any{"simulated PulseAudio cookie", "/home/ophestra/xdg/config/pulse/cookie", int64(0xfd)}}, nil, nil),
|
||||||
|
call("open", stub.ExpectArgs{"/home/ophestra/xdg/config/pulse/cookie"}, &stubOsFile{Reader: errorReader{stub.UniqueError(1)}}, nil),
|
||||||
|
call("verbose", stub.ExpectArgs{[]any{-1}}, nil, nil),
|
||||||
|
}}, &hst.AppError{Step: "read simulated PulseAudio cookie", Err: stub.UniqueError(1)}},
|
||||||
|
|
||||||
|
{"short close", fAfterWrite, stub.Expect{Calls: []stub.Call{
|
||||||
|
call("stat", stub.ExpectArgs{"/home/ophestra/xdg/config/pulse/cookie"}, &stubFi{size: 1 << 8}, nil),
|
||||||
|
call("verbosef", stub.ExpectArgs{"%s at %q is %d bytes shorter than expected", []any{"simulated PulseAudio cookie", "/home/ophestra/xdg/config/pulse/cookie", int64(0xfd)}}, nil, nil),
|
||||||
|
call("open", stub.ExpectArgs{"/home/ophestra/xdg/config/pulse/cookie"}, &stubOsFile{closeErr: stub.UniqueError(0), Reader: bytes.NewReader(sampleCookie)}, nil),
|
||||||
|
call("verbose", stub.ExpectArgs{[]any{sampleCookie}}, nil, nil),
|
||||||
|
}}, &hst.AppError{Step: "close simulated PulseAudio cookie", Err: stub.UniqueError(0)}},
|
||||||
|
|
||||||
|
{"success", fAfterWriteExact, stub.Expect{Calls: []stub.Call{
|
||||||
|
call("stat", stub.ExpectArgs{"/home/ophestra/xdg/config/pulse/cookie"}, &stubFi{size: 1 << 8}, nil),
|
||||||
|
call("verbosef", stub.ExpectArgs{"loading %d bytes from %q", []any{1 << 8, "/home/ophestra/xdg/config/pulse/cookie"}}, nil, nil),
|
||||||
|
call("open", stub.ExpectArgs{"/home/ophestra/xdg/config/pulse/cookie"}, &stubOsFile{Reader: bytes.NewReader(sampleCookie)}, nil),
|
||||||
|
call("verbose", stub.ExpectArgs{[]any{sampleCookie}}, nil, nil),
|
||||||
|
}}, nil},
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -6,43 +6,114 @@ import (
|
|||||||
"hakurei.app/container/bits"
|
"hakurei.app/container/bits"
|
||||||
"hakurei.app/container/check"
|
"hakurei.app/container/check"
|
||||||
"hakurei.app/container/fhs"
|
"hakurei.app/container/fhs"
|
||||||
|
"hakurei.app/hst"
|
||||||
"hakurei.app/system"
|
"hakurei.app/system"
|
||||||
"hakurei.app/system/acl"
|
"hakurei.app/system/acl"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() { gob.Register(spRuntimeOp{}) }
|
const (
|
||||||
|
/*
|
||||||
|
Path to a user-private user-writable directory that is bound
|
||||||
|
to the user login time on the machine. It is automatically
|
||||||
|
created the first time a user logs in and removed on the
|
||||||
|
user's final logout. If a user logs in twice at the same time,
|
||||||
|
both sessions will see the same $XDG_RUNTIME_DIR and the same
|
||||||
|
contents. If a user logs in once, then logs out again, and
|
||||||
|
logs in again, the directory contents will have been lost in
|
||||||
|
between, but applications should not rely on this behavior and
|
||||||
|
must be able to deal with stale files. To store
|
||||||
|
session-private data in this directory, the user should
|
||||||
|
include the value of $XDG_SESSION_ID in the filename. This
|
||||||
|
directory shall be used for runtime file system objects such
|
||||||
|
as AF_UNIX sockets, FIFOs, PID files and similar. It is
|
||||||
|
guaranteed that this directory is local and offers the
|
||||||
|
greatest possible file system feature set the operating system
|
||||||
|
provides. For further details, see the XDG Base Directory
|
||||||
|
Specification[3]. $XDG_RUNTIME_DIR is not set if the current
|
||||||
|
user is not the original user of the session.
|
||||||
|
*/
|
||||||
|
envXDGRuntimeDir = "XDG_RUNTIME_DIR"
|
||||||
|
|
||||||
|
/*
|
||||||
|
The session class. This may be used instead of class= on the
|
||||||
|
module parameter line, and is usually preferred.
|
||||||
|
*/
|
||||||
|
envXDGSessionClass = "XDG_SESSION_CLASS"
|
||||||
|
|
||||||
|
/*
|
||||||
|
A regular interactive user session. This is the default class
|
||||||
|
for sessions for which a TTY or X display is known at session
|
||||||
|
registration time.
|
||||||
|
*/
|
||||||
|
xdgSessionClassUser = "user"
|
||||||
|
|
||||||
|
/*
|
||||||
|
The session type. This may be used instead of type= on the
|
||||||
|
module parameter line, and is usually preferred.
|
||||||
|
|
||||||
|
One of "unspecified", "tty", "x11", "wayland", "mir", or "web".
|
||||||
|
*/
|
||||||
|
envXDGSessionType = "XDG_SESSION_TYPE"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() { gob.Register(new(spRuntimeOp)) }
|
||||||
|
|
||||||
|
const (
|
||||||
|
sessionTypeUnspec = iota
|
||||||
|
sessionTypeTTY
|
||||||
|
sessionTypeX11
|
||||||
|
sessionTypeWayland
|
||||||
|
)
|
||||||
|
|
||||||
// spRuntimeOp sets up XDG_RUNTIME_DIR inside the container.
|
// spRuntimeOp sets up XDG_RUNTIME_DIR inside the container.
|
||||||
type spRuntimeOp struct{}
|
type spRuntimeOp struct {
|
||||||
|
// SessionType determines the value of envXDGSessionType. Populated during toSystem.
|
||||||
|
SessionType uintptr
|
||||||
|
}
|
||||||
|
|
||||||
func (s spRuntimeOp) toSystem(state *outcomeStateSys) error {
|
func (s *spRuntimeOp) toSystem(state *outcomeStateSys) error {
|
||||||
runtimeDir, runtimeDirInst := s.commonPaths(state.outcomeState)
|
runtimeDir, runtimeDirInst := s.commonPaths(state.outcomeState)
|
||||||
state.sys.Ensure(runtimeDir, 0700)
|
state.sys.Ensure(runtimeDir, 0700)
|
||||||
state.sys.UpdatePermType(system.User, runtimeDir, acl.Execute)
|
state.sys.UpdatePermType(system.User, runtimeDir, acl.Execute)
|
||||||
state.sys.Ensure(runtimeDirInst, 0700)
|
state.sys.Ensure(runtimeDirInst, 0700)
|
||||||
state.sys.UpdatePermType(system.User, runtimeDirInst, acl.Read, acl.Write, acl.Execute)
|
state.sys.UpdatePermType(system.User, runtimeDirInst, acl.Read, acl.Write, acl.Execute)
|
||||||
|
|
||||||
|
if state.et&hst.EWayland != 0 {
|
||||||
|
s.SessionType = sessionTypeWayland
|
||||||
|
} else if state.et&hst.EX11 != 0 {
|
||||||
|
s.SessionType = sessionTypeX11
|
||||||
|
} else {
|
||||||
|
s.SessionType = sessionTypeTTY
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s spRuntimeOp) toContainer(state *outcomeStateParams) error {
|
func (s *spRuntimeOp) toContainer(state *outcomeStateParams) error {
|
||||||
const (
|
|
||||||
xdgRuntimeDir = "XDG_RUNTIME_DIR"
|
|
||||||
xdgSessionClass = "XDG_SESSION_CLASS"
|
|
||||||
xdgSessionType = "XDG_SESSION_TYPE"
|
|
||||||
)
|
|
||||||
|
|
||||||
state.runtimeDir = fhs.AbsRunUser.Append(state.mapuid.String())
|
state.runtimeDir = fhs.AbsRunUser.Append(state.mapuid.String())
|
||||||
state.env[xdgRuntimeDir] = state.runtimeDir.String()
|
state.env[envXDGRuntimeDir] = state.runtimeDir.String()
|
||||||
state.env[xdgSessionClass] = "user"
|
state.env[envXDGSessionClass] = xdgSessionClassUser
|
||||||
state.env[xdgSessionType] = "tty"
|
|
||||||
|
switch s.SessionType {
|
||||||
|
case sessionTypeUnspec:
|
||||||
|
state.env[envXDGSessionType] = "unspecified"
|
||||||
|
case sessionTypeTTY:
|
||||||
|
state.env[envXDGSessionType] = "tty"
|
||||||
|
case sessionTypeX11:
|
||||||
|
state.env[envXDGSessionType] = "x11"
|
||||||
|
case sessionTypeWayland:
|
||||||
|
state.env[envXDGSessionType] = "wayland"
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
_, runtimeDirInst := s.commonPaths(state.outcomeState)
|
_, runtimeDirInst := s.commonPaths(state.outcomeState)
|
||||||
state.params.Tmpfs(fhs.AbsRunUser, 1<<12, 0755)
|
state.params.
|
||||||
state.params.Bind(runtimeDirInst, state.runtimeDir, bits.BindWritable)
|
Tmpfs(fhs.AbsRunUser, 1<<12, 0755).
|
||||||
|
Bind(runtimeDirInst, state.runtimeDir, bits.BindWritable)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s spRuntimeOp) commonPaths(state *outcomeState) (runtimeDir, runtimeDirInst *check.Absolute) {
|
func (s *spRuntimeOp) commonPaths(state *outcomeState) (runtimeDir, runtimeDirInst *check.Absolute) {
|
||||||
runtimeDir = state.sc.SharePath.Append("runtime")
|
runtimeDir = state.sc.SharePath.Append("runtime")
|
||||||
runtimeDirInst = runtimeDir.Append(state.identity.String())
|
runtimeDirInst = runtimeDir.Append(state.identity.String())
|
||||||
return
|
return
|
||||||
|
|||||||
128
internal/app/spruntime_test.go
Normal file
128
internal/app/spruntime_test.go
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"hakurei.app/container"
|
||||||
|
"hakurei.app/container/bits"
|
||||||
|
"hakurei.app/container/fhs"
|
||||||
|
"hakurei.app/container/stub"
|
||||||
|
"hakurei.app/hst"
|
||||||
|
"hakurei.app/system"
|
||||||
|
"hakurei.app/system/acl"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSpRuntimeOp(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
config := hst.Template()
|
||||||
|
|
||||||
|
checkOpBehaviour(t, []opBehaviourTestCase{
|
||||||
|
{"success zero", func(isShim bool, clearUnexported bool) outcomeOp {
|
||||||
|
if !isShim {
|
||||||
|
return new(spRuntimeOp)
|
||||||
|
}
|
||||||
|
op := &spRuntimeOp{sessionTypeTTY}
|
||||||
|
if clearUnexported {
|
||||||
|
op.SessionType = sessionTypeUnspec
|
||||||
|
}
|
||||||
|
return op
|
||||||
|
}, func() *hst.Config {
|
||||||
|
c := hst.Template()
|
||||||
|
*c.Enablements = 0
|
||||||
|
return c
|
||||||
|
}, nil, []stub.Call{
|
||||||
|
// this op configures the system state and does not make calls during toSystem
|
||||||
|
}, newI().
|
||||||
|
Ensure(m("/proc/nonexistent/tmp/hakurei.0/runtime"), 0700).
|
||||||
|
UpdatePermType(system.User, m("/proc/nonexistent/tmp/hakurei.0/runtime"), acl.Execute).
|
||||||
|
Ensure(m("/proc/nonexistent/tmp/hakurei.0/runtime/9"), 0700).
|
||||||
|
UpdatePermType(system.User, m("/proc/nonexistent/tmp/hakurei.0/runtime/9"), acl.Read, acl.Write, acl.Execute), nil, nil, insertsOps(nil), []stub.Call{
|
||||||
|
// this op configures the container state and does not make calls during toContainer
|
||||||
|
}, &container.Params{
|
||||||
|
Ops: new(container.Ops).
|
||||||
|
Tmpfs(fhs.AbsRunUser, 1<<12, 0755).
|
||||||
|
Bind(m("/proc/nonexistent/tmp/hakurei.0/runtime/9"), m("/run/user/1000"), bits.BindWritable),
|
||||||
|
}, paramsWantEnv(config, map[string]string{
|
||||||
|
"XDG_RUNTIME_DIR": "/run/user/1000",
|
||||||
|
"XDG_SESSION_CLASS": "user",
|
||||||
|
"XDG_SESSION_TYPE": "unspecified",
|
||||||
|
}, nil), nil},
|
||||||
|
|
||||||
|
{"success tty", func(isShim, _ bool) outcomeOp {
|
||||||
|
if !isShim {
|
||||||
|
return new(spRuntimeOp)
|
||||||
|
}
|
||||||
|
return &spRuntimeOp{sessionTypeTTY}
|
||||||
|
}, func() *hst.Config {
|
||||||
|
c := hst.Template()
|
||||||
|
*c.Enablements = 0
|
||||||
|
return c
|
||||||
|
}, nil, []stub.Call{
|
||||||
|
// this op configures the system state and does not make calls during toSystem
|
||||||
|
}, newI().
|
||||||
|
Ensure(m("/proc/nonexistent/tmp/hakurei.0/runtime"), 0700).
|
||||||
|
UpdatePermType(system.User, m("/proc/nonexistent/tmp/hakurei.0/runtime"), acl.Execute).
|
||||||
|
Ensure(m("/proc/nonexistent/tmp/hakurei.0/runtime/9"), 0700).
|
||||||
|
UpdatePermType(system.User, m("/proc/nonexistent/tmp/hakurei.0/runtime/9"), acl.Read, acl.Write, acl.Execute), nil, nil, insertsOps(nil), []stub.Call{
|
||||||
|
// this op configures the container state and does not make calls during toContainer
|
||||||
|
}, &container.Params{
|
||||||
|
Ops: new(container.Ops).
|
||||||
|
Tmpfs(fhs.AbsRunUser, 1<<12, 0755).
|
||||||
|
Bind(m("/proc/nonexistent/tmp/hakurei.0/runtime/9"), m("/run/user/1000"), bits.BindWritable),
|
||||||
|
}, paramsWantEnv(config, map[string]string{
|
||||||
|
"XDG_RUNTIME_DIR": "/run/user/1000",
|
||||||
|
"XDG_SESSION_CLASS": "user",
|
||||||
|
"XDG_SESSION_TYPE": "tty",
|
||||||
|
}, nil), nil},
|
||||||
|
|
||||||
|
{"success x11", func(isShim, _ bool) outcomeOp {
|
||||||
|
if !isShim {
|
||||||
|
return new(spRuntimeOp)
|
||||||
|
}
|
||||||
|
return &spRuntimeOp{sessionTypeX11}
|
||||||
|
}, func() *hst.Config {
|
||||||
|
c := hst.Template()
|
||||||
|
*c.Enablements = hst.Enablements(hst.EX11)
|
||||||
|
return c
|
||||||
|
}, nil, []stub.Call{
|
||||||
|
// this op configures the system state and does not make calls during toSystem
|
||||||
|
}, newI().
|
||||||
|
Ensure(m("/proc/nonexistent/tmp/hakurei.0/runtime"), 0700).
|
||||||
|
UpdatePermType(system.User, m("/proc/nonexistent/tmp/hakurei.0/runtime"), acl.Execute).
|
||||||
|
Ensure(m("/proc/nonexistent/tmp/hakurei.0/runtime/9"), 0700).
|
||||||
|
UpdatePermType(system.User, m("/proc/nonexistent/tmp/hakurei.0/runtime/9"), acl.Read, acl.Write, acl.Execute), nil, nil, insertsOps(nil), []stub.Call{
|
||||||
|
// this op configures the container state and does not make calls during toContainer
|
||||||
|
}, &container.Params{
|
||||||
|
Ops: new(container.Ops).
|
||||||
|
Tmpfs(fhs.AbsRunUser, 1<<12, 0755).
|
||||||
|
Bind(m("/proc/nonexistent/tmp/hakurei.0/runtime/9"), m("/run/user/1000"), bits.BindWritable),
|
||||||
|
}, paramsWantEnv(config, map[string]string{
|
||||||
|
"XDG_RUNTIME_DIR": "/run/user/1000",
|
||||||
|
"XDG_SESSION_CLASS": "user",
|
||||||
|
"XDG_SESSION_TYPE": "x11",
|
||||||
|
}, nil), nil},
|
||||||
|
|
||||||
|
{"success", func(isShim, _ bool) outcomeOp {
|
||||||
|
if !isShim {
|
||||||
|
return new(spRuntimeOp)
|
||||||
|
}
|
||||||
|
return &spRuntimeOp{sessionTypeWayland}
|
||||||
|
}, hst.Template, nil, []stub.Call{
|
||||||
|
// this op configures the system state and does not make calls during toSystem
|
||||||
|
}, newI().
|
||||||
|
Ensure(m("/proc/nonexistent/tmp/hakurei.0/runtime"), 0700).
|
||||||
|
UpdatePermType(system.User, m("/proc/nonexistent/tmp/hakurei.0/runtime"), acl.Execute).
|
||||||
|
Ensure(m("/proc/nonexistent/tmp/hakurei.0/runtime/9"), 0700).
|
||||||
|
UpdatePermType(system.User, m("/proc/nonexistent/tmp/hakurei.0/runtime/9"), acl.Read, acl.Write, acl.Execute), nil, nil, insertsOps(nil), []stub.Call{
|
||||||
|
// this op configures the container state and does not make calls during toContainer
|
||||||
|
}, &container.Params{
|
||||||
|
Ops: new(container.Ops).
|
||||||
|
Tmpfs(fhs.AbsRunUser, 1<<12, 0755).
|
||||||
|
Bind(m("/proc/nonexistent/tmp/hakurei.0/runtime/9"), m("/run/user/1000"), bits.BindWritable),
|
||||||
|
}, paramsWantEnv(config, map[string]string{
|
||||||
|
"XDG_RUNTIME_DIR": "/run/user/1000",
|
||||||
|
"XDG_SESSION_CLASS": "user",
|
||||||
|
"XDG_SESSION_TYPE": "wayland",
|
||||||
|
}, nil), nil},
|
||||||
|
})
|
||||||
|
}
|
||||||
34
internal/app/sptmpdir_test.go
Normal file
34
internal/app/sptmpdir_test.go
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"hakurei.app/container"
|
||||||
|
"hakurei.app/container/bits"
|
||||||
|
"hakurei.app/container/fhs"
|
||||||
|
"hakurei.app/container/stub"
|
||||||
|
"hakurei.app/hst"
|
||||||
|
"hakurei.app/system"
|
||||||
|
"hakurei.app/system/acl"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSpTmpdirOp(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
checkOpBehaviour(t, []opBehaviourTestCase{
|
||||||
|
{"success", func(bool, bool) outcomeOp {
|
||||||
|
return spTmpdirOp{}
|
||||||
|
}, hst.Template, nil, []stub.Call{
|
||||||
|
// this op configures the system state and does not make calls during toSystem
|
||||||
|
}, newI().
|
||||||
|
Ensure(m("/proc/nonexistent/tmp/hakurei.0/tmpdir"), 0700).
|
||||||
|
UpdatePermType(system.User, m("/proc/nonexistent/tmp/hakurei.0/tmpdir"), acl.Execute).
|
||||||
|
Ensure(m("/proc/nonexistent/tmp/hakurei.0/tmpdir/9"), 01700).
|
||||||
|
UpdatePermType(system.User, m("/proc/nonexistent/tmp/hakurei.0/tmpdir/9"), acl.Read, acl.Write, acl.Execute), nil, nil, insertsOps(nil), []stub.Call{
|
||||||
|
// this op configures the container state and does not make calls during toContainer
|
||||||
|
}, &container.Params{
|
||||||
|
Ops: new(container.Ops).
|
||||||
|
Bind(m("/proc/nonexistent/tmp/hakurei.0/tmpdir/9"), fhs.AbsTmp, bits.BindWritable),
|
||||||
|
}, nil, nil},
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -12,6 +12,7 @@ import (
|
|||||||
func init() { gob.Register(new(spWaylandOp)) }
|
func init() { gob.Register(new(spWaylandOp)) }
|
||||||
|
|
||||||
// spWaylandOp exports the Wayland display server to the container.
|
// spWaylandOp exports the Wayland display server to the container.
|
||||||
|
// Runs after spRuntimeOp.
|
||||||
type spWaylandOp struct {
|
type spWaylandOp struct {
|
||||||
// Path to host wayland socket. Populated during toSystem if DirectWayland is true.
|
// Path to host wayland socket. Populated during toSystem if DirectWayland is true.
|
||||||
SocketPath *check.Absolute
|
SocketPath *check.Absolute
|
||||||
|
|||||||
104
internal/app/spwayland_test.go
Normal file
104
internal/app/spwayland_test.go
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"hakurei.app/container"
|
||||||
|
"hakurei.app/container/stub"
|
||||||
|
"hakurei.app/hst"
|
||||||
|
"hakurei.app/system"
|
||||||
|
"hakurei.app/system/acl"
|
||||||
|
"hakurei.app/system/wayland"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSpWaylandOp(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
config := hst.Template()
|
||||||
|
|
||||||
|
checkOpBehaviour(t, []opBehaviourTestCase{
|
||||||
|
{"not enabled", func(bool, bool) outcomeOp {
|
||||||
|
return new(spWaylandOp)
|
||||||
|
}, func() *hst.Config {
|
||||||
|
c := hst.Template()
|
||||||
|
*c.Enablements = 0
|
||||||
|
return c
|
||||||
|
}, nil, nil, nil, nil, errNotEnabled, nil, nil, nil, nil, nil},
|
||||||
|
|
||||||
|
{"success notAbs defaultAppId", func(bool, bool) outcomeOp {
|
||||||
|
return new(spWaylandOp)
|
||||||
|
}, func() *hst.Config {
|
||||||
|
c := hst.Template()
|
||||||
|
c.ID = ""
|
||||||
|
return c
|
||||||
|
}, nil, []stub.Call{
|
||||||
|
call("lookupEnv", stub.ExpectArgs{"WAYLAND_DISPLAY"}, "wayland-1", nil),
|
||||||
|
}, newI().
|
||||||
|
// state.instance
|
||||||
|
Ephemeral(system.Process, m(wantInstancePrefix), 0711).
|
||||||
|
// toSystem
|
||||||
|
Wayland(
|
||||||
|
m(wantInstancePrefix+"/wayland"),
|
||||||
|
m(wantRuntimePath+"/wayland-1"),
|
||||||
|
"app.hakurei."+wantAutoEtcPrefix,
|
||||||
|
wantAutoEtcPrefix,
|
||||||
|
), sysUsesInstance(nil), nil, insertsOps(afterSpRuntimeOp(nil)), []stub.Call{
|
||||||
|
// this op configures the container state and does not make calls during toContainer
|
||||||
|
}, &container.Params{
|
||||||
|
Ops: new(container.Ops).
|
||||||
|
Bind(m(wantInstancePrefix+"/wayland"), m("/run/user/1000/wayland-0"), 0),
|
||||||
|
}, paramsWantEnv(config, map[string]string{
|
||||||
|
wayland.WaylandDisplay: wayland.FallbackName,
|
||||||
|
}, nil), nil},
|
||||||
|
|
||||||
|
{"success direct", func(isShim, _ bool) outcomeOp {
|
||||||
|
if !isShim {
|
||||||
|
return new(spWaylandOp)
|
||||||
|
}
|
||||||
|
return &spWaylandOp{SocketPath: m("/proc/nonexistent/wayland")}
|
||||||
|
}, func() *hst.Config {
|
||||||
|
c := hst.Template()
|
||||||
|
c.DirectWayland = true
|
||||||
|
return c
|
||||||
|
}, nil, []stub.Call{
|
||||||
|
call("lookupEnv", stub.ExpectArgs{"WAYLAND_DISPLAY"}, "/proc/nonexistent/wayland", nil),
|
||||||
|
call("verbose", stub.ExpectArgs{[]any{"direct wayland access, PROCEED WITH CAUTION"}}, nil, nil),
|
||||||
|
}, newI().
|
||||||
|
// state.ensureRuntimeDir
|
||||||
|
Ensure(m(wantRunDirPath), 0700).
|
||||||
|
UpdatePermType(system.User, m(wantRunDirPath), acl.Execute).
|
||||||
|
Ensure(m(wantRuntimePath), 0700).
|
||||||
|
UpdatePermType(system.User, m(wantRuntimePath), acl.Execute).
|
||||||
|
// toSystem
|
||||||
|
UpdatePermType(hst.EWayland, m("/proc/nonexistent/wayland"), acl.Read, acl.Write, acl.Execute), nil, nil, insertsOps(afterSpRuntimeOp(nil)), []stub.Call{
|
||||||
|
// this op configures the container state and does not make calls during toContainer
|
||||||
|
}, &container.Params{
|
||||||
|
Ops: new(container.Ops).
|
||||||
|
Bind(m("/proc/nonexistent/wayland"), m("/run/user/1000/wayland-0"), 0),
|
||||||
|
}, paramsWantEnv(config, map[string]string{
|
||||||
|
wayland.WaylandDisplay: wayland.FallbackName,
|
||||||
|
}, nil), nil},
|
||||||
|
|
||||||
|
{"success", func(bool, bool) outcomeOp {
|
||||||
|
return new(spWaylandOp)
|
||||||
|
}, hst.Template, nil, []stub.Call{
|
||||||
|
call("lookupEnv", stub.ExpectArgs{"WAYLAND_DISPLAY"}, nil, nil),
|
||||||
|
call("verbose", stub.ExpectArgs{[]any{"WAYLAND_DISPLAY is not set, assuming wayland-0"}}, nil, nil),
|
||||||
|
}, newI().
|
||||||
|
// state.instance
|
||||||
|
Ephemeral(system.Process, m(wantInstancePrefix), 0711).
|
||||||
|
// toSystem
|
||||||
|
Wayland(
|
||||||
|
m(wantInstancePrefix+"/wayland"),
|
||||||
|
m(wantRuntimePath+"/"+wayland.FallbackName),
|
||||||
|
"org.chromium.Chromium",
|
||||||
|
wantAutoEtcPrefix,
|
||||||
|
), sysUsesInstance(nil), nil, insertsOps(afterSpRuntimeOp(nil)), []stub.Call{
|
||||||
|
// this op configures the container state and does not make calls during toContainer
|
||||||
|
}, &container.Params{
|
||||||
|
Ops: new(container.Ops).
|
||||||
|
Bind(m(wantInstancePrefix+"/wayland"), m("/run/user/1000/wayland-0"), 0),
|
||||||
|
}, paramsWantEnv(config, map[string]string{
|
||||||
|
wayland.WaylandDisplay: wayland.FallbackName,
|
||||||
|
}, nil), nil},
|
||||||
|
})
|
||||||
|
}
|
||||||
119
internal/app/spx11_test.go
Normal file
119
internal/app/spx11_test.go
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"hakurei.app/container"
|
||||||
|
"hakurei.app/container/stub"
|
||||||
|
"hakurei.app/hst"
|
||||||
|
"hakurei.app/system/acl"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSpX11Op(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
config := hst.Template()
|
||||||
|
|
||||||
|
checkOpBehaviour(t, []opBehaviourTestCase{
|
||||||
|
{"not enabled", func(bool, bool) outcomeOp {
|
||||||
|
return new(spX11Op)
|
||||||
|
}, hst.Template, nil, nil, nil, nil, errNotEnabled, nil, nil, nil, nil, nil},
|
||||||
|
|
||||||
|
{"lookupEnv", func(bool, bool) outcomeOp {
|
||||||
|
return new(spX11Op)
|
||||||
|
}, func() *hst.Config {
|
||||||
|
c := hst.Template()
|
||||||
|
*c.Enablements |= hst.Enablements(hst.EX11)
|
||||||
|
return c
|
||||||
|
}, nil, []stub.Call{
|
||||||
|
call("lookupEnv", stub.ExpectArgs{"DISPLAY"}, nil, nil),
|
||||||
|
}, nil, nil, &hst.AppError{
|
||||||
|
Step: "finalise",
|
||||||
|
Err: os.ErrInvalid,
|
||||||
|
Msg: "DISPLAY is not set",
|
||||||
|
}, nil, nil, nil, nil, nil},
|
||||||
|
|
||||||
|
{"abs stat", func(bool, bool) outcomeOp {
|
||||||
|
return new(spX11Op)
|
||||||
|
}, func() *hst.Config {
|
||||||
|
c := hst.Template()
|
||||||
|
*c.Enablements |= hst.Enablements(hst.EX11)
|
||||||
|
return c
|
||||||
|
}, nil, []stub.Call{
|
||||||
|
call("lookupEnv", stub.ExpectArgs{"DISPLAY"}, "unix:/tmp/.X11-unix/X0", nil),
|
||||||
|
call("stat", stub.ExpectArgs{"/tmp/.X11-unix/X0"}, (*stubFi)(nil), stub.UniqueError(0)),
|
||||||
|
}, nil, nil, &hst.AppError{
|
||||||
|
Step: `access X11 socket "/tmp/.X11-unix/X0"`,
|
||||||
|
Err: stub.UniqueError(0),
|
||||||
|
}, nil, nil, nil, nil, nil},
|
||||||
|
|
||||||
|
{"success abs nonexistent", func(isShim, _ bool) outcomeOp {
|
||||||
|
if !isShim {
|
||||||
|
return new(spX11Op)
|
||||||
|
}
|
||||||
|
return &spX11Op{Display: "unix:/tmp/.X11-unix/X0"}
|
||||||
|
}, func() *hst.Config {
|
||||||
|
c := hst.Template()
|
||||||
|
*c.Enablements |= hst.Enablements(hst.EX11)
|
||||||
|
return c
|
||||||
|
}, nil, []stub.Call{
|
||||||
|
call("lookupEnv", stub.ExpectArgs{"DISPLAY"}, "unix:/tmp/.X11-unix/X0", nil),
|
||||||
|
call("stat", stub.ExpectArgs{"/tmp/.X11-unix/X0"}, (*stubFi)(nil), os.ErrNotExist),
|
||||||
|
}, newI().
|
||||||
|
ChangeHosts("#1000009"), nil, nil, insertsOps(nil), []stub.Call{
|
||||||
|
// this op configures the container state and does not make calls during toContainer
|
||||||
|
}, &container.Params{
|
||||||
|
Ops: new(container.Ops).
|
||||||
|
Bind(absX11SocketDir, absX11SocketDir, 0),
|
||||||
|
}, paramsWantEnv(config, map[string]string{
|
||||||
|
"DISPLAY": "unix:/tmp/.X11-unix/X0",
|
||||||
|
}, nil), nil},
|
||||||
|
|
||||||
|
{"success abs abstract", func(isShim, _ bool) outcomeOp {
|
||||||
|
if !isShim {
|
||||||
|
return new(spX11Op)
|
||||||
|
}
|
||||||
|
return &spX11Op{Display: "unix:/tmp/.X11-unix/X0"}
|
||||||
|
}, func() *hst.Config {
|
||||||
|
c := hst.Template()
|
||||||
|
*c.Enablements |= hst.Enablements(hst.EX11)
|
||||||
|
c.Container.Flags &= ^hst.FHostAbstract
|
||||||
|
return c
|
||||||
|
}, nil, []stub.Call{
|
||||||
|
call("lookupEnv", stub.ExpectArgs{"DISPLAY"}, "unix:/tmp/.X11-unix/X0", nil),
|
||||||
|
call("stat", stub.ExpectArgs{"/tmp/.X11-unix/X0"}, (*stubFi)(nil), nil),
|
||||||
|
}, newI().
|
||||||
|
UpdatePermType(hst.EX11, m("/tmp/.X11-unix/X0"), acl.Read, acl.Write, acl.Execute).
|
||||||
|
ChangeHosts("#1000009"), nil, nil, insertsOps(nil), []stub.Call{
|
||||||
|
// this op configures the container state and does not make calls during toContainer
|
||||||
|
}, &container.Params{
|
||||||
|
Ops: new(container.Ops).
|
||||||
|
Bind(absX11SocketDir, absX11SocketDir, 0),
|
||||||
|
}, paramsWantEnv(config, map[string]string{
|
||||||
|
"DISPLAY": "unix:/tmp/.X11-unix/X0",
|
||||||
|
}, nil), nil},
|
||||||
|
|
||||||
|
{"success", func(isShim, _ bool) outcomeOp {
|
||||||
|
if !isShim {
|
||||||
|
return new(spX11Op)
|
||||||
|
}
|
||||||
|
return &spX11Op{Display: ":0"}
|
||||||
|
}, func() *hst.Config {
|
||||||
|
c := hst.Template()
|
||||||
|
*c.Enablements |= hst.Enablements(hst.EX11)
|
||||||
|
return c
|
||||||
|
}, nil, []stub.Call{
|
||||||
|
call("lookupEnv", stub.ExpectArgs{"DISPLAY"}, ":0", nil),
|
||||||
|
call("stat", stub.ExpectArgs{"/tmp/.X11-unix/X0"}, (*stubFi)(nil), nil),
|
||||||
|
}, newI().
|
||||||
|
UpdatePermType(hst.EX11, m("/tmp/.X11-unix/X0"), acl.Read, acl.Write, acl.Execute).
|
||||||
|
ChangeHosts("#1000009"), nil, nil, insertsOps(nil), []stub.Call{
|
||||||
|
// this op configures the container state and does not make calls during toContainer
|
||||||
|
}, &container.Params{
|
||||||
|
Ops: new(container.Ops).
|
||||||
|
Bind(absX11SocketDir, absX11SocketDir, 0),
|
||||||
|
}, paramsWantEnv(config, map[string]string{
|
||||||
|
"DISPLAY": ":0",
|
||||||
|
}, nil), nil},
|
||||||
|
})
|
||||||
|
}
|
||||||
10
ldd/exec.go
10
ldd/exec.go
@@ -16,17 +16,17 @@ import (
|
|||||||
"hakurei.app/message"
|
"hakurei.app/message"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
|
||||||
lddName = "ldd"
|
|
||||||
lddTimeout = 2 * time.Second
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
msgStatic = []byte("Not a valid dynamic program")
|
msgStatic = []byte("Not a valid dynamic program")
|
||||||
msgStaticGlibc = []byte("not a dynamic executable")
|
msgStaticGlibc = []byte("not a dynamic executable")
|
||||||
)
|
)
|
||||||
|
|
||||||
func Exec(ctx context.Context, msg message.Msg, p string) ([]*Entry, error) {
|
func Exec(ctx context.Context, msg message.Msg, p string) ([]*Entry, error) {
|
||||||
|
const (
|
||||||
|
lddName = "ldd"
|
||||||
|
lddTimeout = 4 * time.Second
|
||||||
|
)
|
||||||
|
|
||||||
c, cancel := context.WithTimeout(ctx, lddTimeout)
|
c, cancel := context.WithTimeout(ctx, lddTimeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ in
|
|||||||
"WAYLAND_DISPLAY=wayland-0"
|
"WAYLAND_DISPLAY=wayland-0"
|
||||||
"XDG_RUNTIME_DIR=/run/user/65534"
|
"XDG_RUNTIME_DIR=/run/user/65534"
|
||||||
"XDG_SESSION_CLASS=user"
|
"XDG_SESSION_CLASS=user"
|
||||||
"XDG_SESSION_TYPE=tty"
|
"XDG_SESSION_TYPE=wayland"
|
||||||
];
|
];
|
||||||
|
|
||||||
fs = fs "dead" {
|
fs = fs "dead" {
|
||||||
@@ -218,6 +218,15 @@ in
|
|||||||
(ent "/" ignore ignore ignore ignore ignore)
|
(ent "/" ignore ignore ignore ignore ignore)
|
||||||
(ent "/" ignore ignore ignore ignore ignore)
|
(ent "/" ignore ignore ignore ignore ignore)
|
||||||
(ent "/" "/dev/shm" "rw,nosuid,nodev,relatime" "tmpfs" "ephemeral" "rw,uid=1000004,gid=1000004")
|
(ent "/" "/dev/shm" "rw,nosuid,nodev,relatime" "tmpfs" "ephemeral" "rw,uid=1000004,gid=1000004")
|
||||||
|
(ent "/" "/run/user" "rw,nosuid,nodev,relatime" "tmpfs" "ephemeral" "rw,size=4k,mode=755,uid=1000004,gid=1000004")
|
||||||
|
(ent "/tmp/hakurei.0/runtime/4" "/run/user/65534" "rw,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
||||||
|
(ent "/tmp/hakurei.0/tmpdir/4" "/tmp" "rw,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
||||||
|
(ent ignore "/etc/passwd" "ro,nosuid,nodev,relatime" "tmpfs" "rootfs" "rw,uid=1000004,gid=1000004")
|
||||||
|
(ent ignore "/etc/group" "ro,nosuid,nodev,relatime" "tmpfs" "rootfs" "rw,uid=1000004,gid=1000004")
|
||||||
|
(ent ignore "/run/user/65534/wayland-0" "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
||||||
|
(ent "/tmp/.X11-unix" "/tmp/.X11-unix" "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
||||||
|
(ent ignore "/run/user/65534/pulse/native" "ro,nosuid,nodev,relatime" "tmpfs" "tmpfs" ignore)
|
||||||
|
(ent ignore "/run/user/65534/bus" "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
||||||
(ent "/bin" "/bin" "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
(ent "/bin" "/bin" "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
||||||
(ent "/usr/bin" "/usr/bin" "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
(ent "/usr/bin" "/usr/bin" "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
||||||
(ent "/" "/nix/store" "ro,nosuid,nodev,relatime" "overlay" "overlay" "rw,lowerdir=/mnt-root/nix/.ro-store,upperdir=/mnt-root/nix/.rw-store/upper,workdir=/mnt-root/nix/.rw-store/work,uuid=on")
|
(ent "/" "/nix/store" "ro,nosuid,nodev,relatime" "overlay" "overlay" "rw,lowerdir=/mnt-root/nix/.ro-store,upperdir=/mnt-root/nix/.rw-store/upper,workdir=/mnt-root/nix/.rw-store/work,uuid=on")
|
||||||
@@ -233,15 +242,6 @@ in
|
|||||||
(ent "/" "/.hakurei/store" "rw,relatime" "overlay" "overlay" "rw,lowerdir=/host/nix/.ro-store:/host/nix/.rw-store/upper,upperdir=/host/tmp/.hakurei-store-rw/upper,workdir=/host/tmp/.hakurei-store-rw/work,redirect_dir=nofollow,userxattr")
|
(ent "/" "/.hakurei/store" "rw,relatime" "overlay" "overlay" "rw,lowerdir=/host/nix/.ro-store:/host/nix/.rw-store/upper,upperdir=/host/tmp/.hakurei-store-rw/upper,workdir=/host/tmp/.hakurei-store-rw/work,redirect_dir=nofollow,userxattr")
|
||||||
(ent "/etc" ignore "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
(ent "/etc" ignore "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
||||||
(ent "/var/lib/hakurei/u0/a4" "/var/lib/hakurei/u0/a4" "rw,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
(ent "/var/lib/hakurei/u0/a4" "/var/lib/hakurei/u0/a4" "rw,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
||||||
(ent "/" "/run/user" "rw,nosuid,nodev,relatime" "tmpfs" "ephemeral" "rw,size=4k,mode=755,uid=1000004,gid=1000004")
|
|
||||||
(ent "/tmp/hakurei.0/runtime/4" "/run/user/65534" "rw,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
|
||||||
(ent "/tmp/hakurei.0/tmpdir/4" "/tmp" "rw,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
|
||||||
(ent ignore "/etc/passwd" "ro,nosuid,nodev,relatime" "tmpfs" "rootfs" "rw,uid=1000004,gid=1000004")
|
|
||||||
(ent ignore "/etc/group" "ro,nosuid,nodev,relatime" "tmpfs" "rootfs" "rw,uid=1000004,gid=1000004")
|
|
||||||
(ent ignore "/run/user/65534/wayland-0" "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
|
||||||
(ent "/tmp/.X11-unix" "/tmp/.X11-unix" "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
|
||||||
(ent ignore "/run/user/65534/pulse/native" "ro,nosuid,nodev,relatime" "tmpfs" "tmpfs" ignore)
|
|
||||||
(ent ignore "/run/user/65534/bus" "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
|
||||||
];
|
];
|
||||||
|
|
||||||
seccomp = true;
|
seccomp = true;
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ in
|
|||||||
"WAYLAND_DISPLAY=wayland-0"
|
"WAYLAND_DISPLAY=wayland-0"
|
||||||
"XDG_RUNTIME_DIR=/run/user/1000"
|
"XDG_RUNTIME_DIR=/run/user/1000"
|
||||||
"XDG_SESSION_CLASS=user"
|
"XDG_SESSION_CLASS=user"
|
||||||
"XDG_SESSION_TYPE=tty"
|
"XDG_SESSION_TYPE=wayland"
|
||||||
];
|
];
|
||||||
|
|
||||||
fs = fs "dead" {
|
fs = fs "dead" {
|
||||||
@@ -245,6 +245,14 @@ in
|
|||||||
(ent "/" "/dev/pts" "rw,nosuid,noexec,relatime" "devpts" "devpts" "rw,mode=620,ptmxmode=666")
|
(ent "/" "/dev/pts" "rw,nosuid,noexec,relatime" "devpts" "devpts" "rw,mode=620,ptmxmode=666")
|
||||||
(ent "/" "/dev/mqueue" "rw,nosuid,nodev,noexec,relatime" "mqueue" "mqueue" "rw")
|
(ent "/" "/dev/mqueue" "rw,nosuid,nodev,noexec,relatime" "mqueue" "mqueue" "rw")
|
||||||
(ent "/" "/dev/shm" "rw,nosuid,nodev,relatime" "tmpfs" "ephemeral" "rw,uid=1000003,gid=1000003")
|
(ent "/" "/dev/shm" "rw,nosuid,nodev,relatime" "tmpfs" "ephemeral" "rw,uid=1000003,gid=1000003")
|
||||||
|
(ent "/" "/run/user" "rw,nosuid,nodev,relatime" "tmpfs" "ephemeral" "rw,size=4k,mode=755,uid=1000003,gid=1000003")
|
||||||
|
(ent "/tmp/hakurei.0/runtime/3" "/run/user/1000" "rw,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
||||||
|
(ent "/tmp/hakurei.0/tmpdir/3" "/tmp" "rw,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
||||||
|
(ent ignore "/etc/passwd" "ro,nosuid,nodev,relatime" "tmpfs" "rootfs" "rw,uid=1000003,gid=1000003")
|
||||||
|
(ent ignore "/etc/group" "ro,nosuid,nodev,relatime" "tmpfs" "rootfs" "rw,uid=1000003,gid=1000003")
|
||||||
|
(ent ignore "/run/user/1000/wayland-0" "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
||||||
|
(ent ignore "/run/user/1000/pulse/native" "ro,nosuid,nodev,relatime" "tmpfs" "tmpfs" ignore)
|
||||||
|
(ent ignore "/run/user/1000/bus" "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
||||||
(ent "/bin" "/bin" "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
(ent "/bin" "/bin" "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
||||||
(ent "/usr/bin" "/usr/bin" "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
(ent "/usr/bin" "/usr/bin" "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
||||||
(ent "/" "/nix/store" "ro,nosuid,nodev,relatime" "overlay" "overlay" "rw,lowerdir=/mnt-root/nix/.ro-store,upperdir=/mnt-root/nix/.rw-store/upper,workdir=/mnt-root/nix/.rw-store/work,uuid=on")
|
(ent "/" "/nix/store" "ro,nosuid,nodev,relatime" "overlay" "overlay" "rw,lowerdir=/mnt-root/nix/.ro-store,upperdir=/mnt-root/nix/.rw-store/upper,workdir=/mnt-root/nix/.rw-store/work,uuid=on")
|
||||||
@@ -260,14 +268,6 @@ in
|
|||||||
(ent "/" "/.hakurei/store" "rw,relatime" "overlay" "overlay" "rw,lowerdir=/host/nix/.ro-store:/host/nix/.rw-store/upper,upperdir=/host/tmp/.hakurei-store-rw/upper,workdir=/host/tmp/.hakurei-store-rw/work,redirect_dir=nofollow,userxattr")
|
(ent "/" "/.hakurei/store" "rw,relatime" "overlay" "overlay" "rw,lowerdir=/host/nix/.ro-store:/host/nix/.rw-store/upper,upperdir=/host/tmp/.hakurei-store-rw/upper,workdir=/host/tmp/.hakurei-store-rw/work,redirect_dir=nofollow,userxattr")
|
||||||
(ent "/etc" ignore "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
(ent "/etc" ignore "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
||||||
(ent "/var/lib/hakurei/u0/a3" "/var/lib/hakurei/u0/a3" "rw,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
(ent "/var/lib/hakurei/u0/a3" "/var/lib/hakurei/u0/a3" "rw,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
||||||
(ent "/" "/run/user" "rw,nosuid,nodev,relatime" "tmpfs" "ephemeral" "rw,size=4k,mode=755,uid=1000003,gid=1000003")
|
|
||||||
(ent "/tmp/hakurei.0/runtime/3" "/run/user/1000" "rw,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
|
||||||
(ent "/tmp/hakurei.0/tmpdir/3" "/tmp" "rw,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
|
||||||
(ent ignore "/etc/passwd" "ro,nosuid,nodev,relatime" "tmpfs" "rootfs" "rw,uid=1000003,gid=1000003")
|
|
||||||
(ent ignore "/etc/group" "ro,nosuid,nodev,relatime" "tmpfs" "rootfs" "rw,uid=1000003,gid=1000003")
|
|
||||||
(ent ignore "/run/user/1000/wayland-0" "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
|
||||||
(ent ignore "/run/user/1000/pulse/native" "ro,nosuid,nodev,relatime" "tmpfs" "tmpfs" ignore)
|
|
||||||
(ent ignore "/run/user/1000/bus" "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
|
||||||
];
|
];
|
||||||
|
|
||||||
seccomp = true;
|
seccomp = true;
|
||||||
|
|||||||
@@ -161,7 +161,7 @@
|
|||||||
(ent "/../../.." "/sys/fs/cgroup" "rw,nosuid,nodev,noexec,relatime" "cgroup2" "cgroup2" "rw,nsdelegate,memory_recursiveprot")
|
(ent "/../../.." "/sys/fs/cgroup" "rw,nosuid,nodev,noexec,relatime" "cgroup2" "cgroup2" "rw,nsdelegate,memory_recursiveprot")
|
||||||
(ent "/" "/sys/fs/pstore" "rw,nosuid,nodev,noexec,relatime" "pstore" "pstore" "rw")
|
(ent "/" "/sys/fs/pstore" "rw,nosuid,nodev,noexec,relatime" "pstore" "pstore" "rw")
|
||||||
(ent "/" "/sys/fs/bpf" "rw,nosuid,nodev,noexec,relatime" "bpf" "bpf" "rw,mode=700")
|
(ent "/" "/sys/fs/bpf" "rw,nosuid,nodev,noexec,relatime" "bpf" "bpf" "rw,mode=700")
|
||||||
# systemd race: tracefs debugfs configfs fusectl
|
# systemd nondeterminism: tracefs debugfs configfs fusectl
|
||||||
(ent "/" ignore "rw,nosuid,nodev,noexec,relatime" ignore ignore "rw")
|
(ent "/" ignore "rw,nosuid,nodev,noexec,relatime" ignore ignore "rw")
|
||||||
(ent "/" ignore "rw,nosuid,nodev,noexec,relatime" ignore ignore "rw")
|
(ent "/" ignore "rw,nosuid,nodev,noexec,relatime" ignore ignore "rw")
|
||||||
(ent "/" ignore "rw,nosuid,nodev,noexec,relatime" ignore ignore "rw")
|
(ent "/" ignore "rw,nosuid,nodev,noexec,relatime" ignore ignore "rw")
|
||||||
@@ -181,16 +181,16 @@
|
|||||||
(ent ignore "/dev/console" "rw,nosuid,noexec,relatime" "devpts" "devpts" "rw,gid=3,mode=620,ptmxmode=666")
|
(ent ignore "/dev/console" "rw,nosuid,noexec,relatime" "devpts" "devpts" "rw,gid=3,mode=620,ptmxmode=666")
|
||||||
(ent "/" "/dev/mqueue" "rw,nosuid,nodev,noexec,relatime" "mqueue" "mqueue" "rw")
|
(ent "/" "/dev/mqueue" "rw,nosuid,nodev,noexec,relatime" "mqueue" "mqueue" "rw")
|
||||||
(ent "/" "/dev/shm" "rw,nosuid,nodev,relatime" "tmpfs" "ephemeral" "rw,uid=1000000,gid=1000000")
|
(ent "/" "/dev/shm" "rw,nosuid,nodev,relatime" "tmpfs" "ephemeral" "rw,uid=1000000,gid=1000000")
|
||||||
(ent "/kvm" "/dev/kvm" "rw,nosuid" "devtmpfs" "devtmpfs" ignore)
|
|
||||||
(ent "/etc" ignore "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
|
||||||
(ent "/" "/run/user/1000" "rw,nosuid,nodev,relatime" "tmpfs" "ephemeral" "rw,size=8k,mode=755,uid=1000000,gid=1000000")
|
|
||||||
(ent "/" "/run/nscd" "rw,nosuid,nodev,relatime" "tmpfs" "ephemeral" "rw,size=8k,mode=755,uid=1000000,gid=1000000")
|
|
||||||
(ent "/" "/run/dbus" "rw,nosuid,nodev,relatime" "tmpfs" "ephemeral" "rw,size=8k,mode=755,uid=1000000,gid=1000000")
|
|
||||||
(ent "/" "/run/user" "rw,nosuid,nodev,relatime" "tmpfs" "ephemeral" "rw,size=4k,mode=755,uid=1000000,gid=1000000")
|
(ent "/" "/run/user" "rw,nosuid,nodev,relatime" "tmpfs" "ephemeral" "rw,size=4k,mode=755,uid=1000000,gid=1000000")
|
||||||
(ent "/tmp/hakurei.0/runtime/0" "/run/user/65534" "rw,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
(ent "/tmp/hakurei.0/runtime/0" "/run/user/65534" "rw,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
||||||
(ent "/tmp/hakurei.0/tmpdir/0" "/tmp" "rw,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
(ent "/tmp/hakurei.0/tmpdir/0" "/tmp" "rw,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
||||||
(ent ignore "/etc/passwd" "ro,nosuid,nodev,relatime" "tmpfs" "rootfs" "rw,uid=1000000,gid=1000000")
|
(ent ignore "/etc/passwd" "ro,nosuid,nodev,relatime" "tmpfs" "rootfs" "rw,uid=1000000,gid=1000000")
|
||||||
(ent ignore "/etc/group" "ro,nosuid,nodev,relatime" "tmpfs" "rootfs" "rw,uid=1000000,gid=1000000")
|
(ent ignore "/etc/group" "ro,nosuid,nodev,relatime" "tmpfs" "rootfs" "rw,uid=1000000,gid=1000000")
|
||||||
|
(ent "/kvm" "/dev/kvm" "rw,nosuid" "devtmpfs" "devtmpfs" ignore)
|
||||||
|
(ent "/etc" ignore "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
||||||
|
(ent "/" "/run/user/1000" "rw,nosuid,nodev,relatime" "tmpfs" "ephemeral" "rw,size=8k,mode=755,uid=1000000,gid=1000000")
|
||||||
|
(ent "/" "/run/nscd" "rw,nosuid,nodev,relatime" "tmpfs" "ephemeral" "rw,size=8k,mode=755,uid=1000000,gid=1000000")
|
||||||
|
(ent "/" "/run/dbus" "rw,nosuid,nodev,relatime" "tmpfs" "ephemeral" "rw,size=8k,mode=755,uid=1000000,gid=1000000")
|
||||||
];
|
];
|
||||||
|
|
||||||
seccomp = true;
|
seccomp = true;
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ in
|
|||||||
"WAYLAND_DISPLAY=wayland-0"
|
"WAYLAND_DISPLAY=wayland-0"
|
||||||
"XDG_RUNTIME_DIR=/run/user/65534"
|
"XDG_RUNTIME_DIR=/run/user/65534"
|
||||||
"XDG_SESSION_CLASS=user"
|
"XDG_SESSION_CLASS=user"
|
||||||
"XDG_SESSION_TYPE=tty"
|
"XDG_SESSION_TYPE=wayland"
|
||||||
];
|
];
|
||||||
|
|
||||||
fs = fs "dead" {
|
fs = fs "dead" {
|
||||||
@@ -243,6 +243,14 @@ in
|
|||||||
(ent ignore "/dev/console" "rw,nosuid,noexec,relatime" "devpts" "devpts" "rw,gid=3,mode=620,ptmxmode=666")
|
(ent ignore "/dev/console" "rw,nosuid,noexec,relatime" "devpts" "devpts" "rw,gid=3,mode=620,ptmxmode=666")
|
||||||
(ent "/" "/dev/mqueue" "rw,nosuid,nodev,noexec,relatime" "mqueue" "mqueue" "rw")
|
(ent "/" "/dev/mqueue" "rw,nosuid,nodev,noexec,relatime" "mqueue" "mqueue" "rw")
|
||||||
(ent "/" "/dev/shm" "rw,nosuid,nodev,relatime" "tmpfs" "ephemeral" "rw,uid=1000005,gid=1000005")
|
(ent "/" "/dev/shm" "rw,nosuid,nodev,relatime" "tmpfs" "ephemeral" "rw,uid=1000005,gid=1000005")
|
||||||
|
(ent "/" "/run/user" "rw,nosuid,nodev,relatime" "tmpfs" "ephemeral" "rw,size=4k,mode=755,uid=1000005,gid=1000005")
|
||||||
|
(ent "/tmp/hakurei.0/runtime/5" "/run/user/65534" "rw,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
||||||
|
(ent "/tmp/hakurei.0/tmpdir/5" "/tmp" "rw,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
||||||
|
(ent ignore "/etc/passwd" "ro,nosuid,nodev,relatime" "tmpfs" "rootfs" "rw,uid=1000005,gid=1000005")
|
||||||
|
(ent ignore "/etc/group" "ro,nosuid,nodev,relatime" "tmpfs" "rootfs" "rw,uid=1000005,gid=1000005")
|
||||||
|
(ent ignore "/run/user/65534/wayland-0" "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
||||||
|
(ent ignore "/run/user/65534/pulse/native" "ro,nosuid,nodev,relatime" "tmpfs" "tmpfs" ignore)
|
||||||
|
(ent ignore "/run/user/65534/bus" "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
||||||
(ent "/bin" "/bin" "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
(ent "/bin" "/bin" "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
||||||
(ent "/usr/bin" "/usr/bin" "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
(ent "/usr/bin" "/usr/bin" "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
||||||
(ent "/" "/nix/store" "ro,nosuid,nodev,relatime" "overlay" "overlay" "rw,lowerdir=/mnt-root/nix/.ro-store,upperdir=/mnt-root/nix/.rw-store/upper,workdir=/mnt-root/nix/.rw-store/work,uuid=on")
|
(ent "/" "/nix/store" "ro,nosuid,nodev,relatime" "overlay" "overlay" "rw,lowerdir=/mnt-root/nix/.ro-store,upperdir=/mnt-root/nix/.rw-store/upper,workdir=/mnt-root/nix/.rw-store/work,uuid=on")
|
||||||
@@ -255,14 +263,6 @@ in
|
|||||||
(ent "/var/tmp" "/var/tmp" "rw,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
(ent "/var/tmp" "/var/tmp" "rw,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
||||||
(ent "/etc" ignore "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
(ent "/etc" ignore "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
||||||
(ent "/var/lib/hakurei/u0/a5" "/var/lib/hakurei/u0/a5" "rw,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
(ent "/var/lib/hakurei/u0/a5" "/var/lib/hakurei/u0/a5" "rw,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
||||||
(ent "/" "/run/user" "rw,nosuid,nodev,relatime" "tmpfs" "ephemeral" "rw,size=4k,mode=755,uid=1000005,gid=1000005")
|
|
||||||
(ent "/tmp/hakurei.0/runtime/5" "/run/user/65534" "rw,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
|
||||||
(ent "/tmp/hakurei.0/tmpdir/5" "/tmp" "rw,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
|
||||||
(ent ignore "/etc/passwd" "ro,nosuid,nodev,relatime" "tmpfs" "rootfs" "rw,uid=1000005,gid=1000005")
|
|
||||||
(ent ignore "/etc/group" "ro,nosuid,nodev,relatime" "tmpfs" "rootfs" "rw,uid=1000005,gid=1000005")
|
|
||||||
(ent ignore "/run/user/65534/wayland-0" "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
|
||||||
(ent ignore "/run/user/65534/pulse/native" "ro,nosuid,nodev,relatime" "tmpfs" "tmpfs" ignore)
|
|
||||||
(ent ignore "/run/user/65534/bus" "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
|
||||||
];
|
];
|
||||||
|
|
||||||
seccomp = true;
|
seccomp = true;
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ in
|
|||||||
"WAYLAND_DISPLAY=wayland-0"
|
"WAYLAND_DISPLAY=wayland-0"
|
||||||
"XDG_RUNTIME_DIR=/run/user/65534"
|
"XDG_RUNTIME_DIR=/run/user/65534"
|
||||||
"XDG_SESSION_CLASS=user"
|
"XDG_SESSION_CLASS=user"
|
||||||
"XDG_SESSION_TYPE=tty"
|
"XDG_SESSION_TYPE=wayland"
|
||||||
];
|
];
|
||||||
|
|
||||||
fs = fs "dead" {
|
fs = fs "dead" {
|
||||||
@@ -241,6 +241,14 @@ in
|
|||||||
(ent "/" "/dev/pts" "rw,nosuid,noexec,relatime" "devpts" "devpts" "rw,mode=620,ptmxmode=666")
|
(ent "/" "/dev/pts" "rw,nosuid,noexec,relatime" "devpts" "devpts" "rw,mode=620,ptmxmode=666")
|
||||||
(ent "/" "/dev/mqueue" "rw,nosuid,nodev,noexec,relatime" "mqueue" "mqueue" "rw")
|
(ent "/" "/dev/mqueue" "rw,nosuid,nodev,noexec,relatime" "mqueue" "mqueue" "rw")
|
||||||
(ent "/" "/dev/shm" "rw,nosuid,nodev,relatime" "tmpfs" "ephemeral" "rw,uid=1000001,gid=1000001")
|
(ent "/" "/dev/shm" "rw,nosuid,nodev,relatime" "tmpfs" "ephemeral" "rw,uid=1000001,gid=1000001")
|
||||||
|
(ent "/" "/run/user" "rw,nosuid,nodev,relatime" "tmpfs" "ephemeral" "rw,size=4k,mode=755,uid=1000001,gid=1000001")
|
||||||
|
(ent "/tmp/hakurei.0/runtime/1" "/run/user/65534" "rw,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
||||||
|
(ent "/tmp/hakurei.0/tmpdir/1" "/tmp" "rw,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
||||||
|
(ent ignore "/etc/passwd" "ro,nosuid,nodev,relatime" "tmpfs" "rootfs" "rw,uid=1000001,gid=1000001")
|
||||||
|
(ent ignore "/etc/group" "ro,nosuid,nodev,relatime" "tmpfs" "rootfs" "rw,uid=1000001,gid=1000001")
|
||||||
|
(ent ignore "/run/user/65534/wayland-0" "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
||||||
|
(ent ignore "/run/user/65534/pulse/native" "ro,nosuid,nodev,relatime" "tmpfs" "tmpfs" ignore)
|
||||||
|
(ent ignore "/run/user/65534/bus" "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
||||||
(ent "/bin" "/bin" "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
(ent "/bin" "/bin" "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
||||||
(ent "/usr/bin" "/usr/bin" "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
(ent "/usr/bin" "/usr/bin" "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
||||||
(ent "/" "/nix/store" "ro,nosuid,nodev,relatime" "overlay" "overlay" "rw,lowerdir=/mnt-root/nix/.ro-store,upperdir=/mnt-root/nix/.rw-store/upper,workdir=/mnt-root/nix/.rw-store/work,uuid=on")
|
(ent "/" "/nix/store" "ro,nosuid,nodev,relatime" "overlay" "overlay" "rw,lowerdir=/mnt-root/nix/.ro-store,upperdir=/mnt-root/nix/.rw-store/upper,workdir=/mnt-root/nix/.rw-store/work,uuid=on")
|
||||||
@@ -253,14 +261,6 @@ in
|
|||||||
(ent "/var/tmp" "/var/tmp" "rw,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
(ent "/var/tmp" "/var/tmp" "rw,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
||||||
(ent "/etc" ignore "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
(ent "/etc" ignore "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
||||||
(ent "/var/lib/hakurei/u0/a1" "/var/lib/hakurei/u0/a1" "rw,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
(ent "/var/lib/hakurei/u0/a1" "/var/lib/hakurei/u0/a1" "rw,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
||||||
(ent "/" "/run/user" "rw,nosuid,nodev,relatime" "tmpfs" "ephemeral" "rw,size=4k,mode=755,uid=1000001,gid=1000001")
|
|
||||||
(ent "/tmp/hakurei.0/runtime/1" "/run/user/65534" "rw,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
|
||||||
(ent "/tmp/hakurei.0/tmpdir/1" "/tmp" "rw,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
|
||||||
(ent ignore "/etc/passwd" "ro,nosuid,nodev,relatime" "tmpfs" "rootfs" "rw,uid=1000001,gid=1000001")
|
|
||||||
(ent ignore "/etc/group" "ro,nosuid,nodev,relatime" "tmpfs" "rootfs" "rw,uid=1000001,gid=1000001")
|
|
||||||
(ent ignore "/run/user/65534/wayland-0" "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
|
||||||
(ent ignore "/run/user/65534/pulse/native" "ro,nosuid,nodev,relatime" "tmpfs" "tmpfs" ignore)
|
|
||||||
(ent ignore "/run/user/65534/bus" "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
|
||||||
];
|
];
|
||||||
|
|
||||||
seccomp = true;
|
seccomp = true;
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ in
|
|||||||
"WAYLAND_DISPLAY=wayland-0"
|
"WAYLAND_DISPLAY=wayland-0"
|
||||||
"XDG_RUNTIME_DIR=/run/user/65534"
|
"XDG_RUNTIME_DIR=/run/user/65534"
|
||||||
"XDG_SESSION_CLASS=user"
|
"XDG_SESSION_CLASS=user"
|
||||||
"XDG_SESSION_TYPE=tty"
|
"XDG_SESSION_TYPE=wayland"
|
||||||
];
|
];
|
||||||
|
|
||||||
fs = fs "dead" {
|
fs = fs "dead" {
|
||||||
@@ -250,6 +250,15 @@ in
|
|||||||
(ent ignore "/dev/console" "rw,nosuid,noexec,relatime" "devpts" "devpts" "rw,gid=3,mode=620,ptmxmode=666")
|
(ent ignore "/dev/console" "rw,nosuid,noexec,relatime" "devpts" "devpts" "rw,gid=3,mode=620,ptmxmode=666")
|
||||||
(ent "/" "/dev/mqueue" "rw,nosuid,nodev,noexec,relatime" "mqueue" "mqueue" "rw")
|
(ent "/" "/dev/mqueue" "rw,nosuid,nodev,noexec,relatime" "mqueue" "mqueue" "rw")
|
||||||
(ent "/" "/dev/shm" "rw,nosuid,nodev,relatime" "tmpfs" "ephemeral" "rw,uid=1000002,gid=1000002")
|
(ent "/" "/dev/shm" "rw,nosuid,nodev,relatime" "tmpfs" "ephemeral" "rw,uid=1000002,gid=1000002")
|
||||||
|
(ent "/" "/run/user" "rw,nosuid,nodev,relatime" "tmpfs" "ephemeral" "rw,size=4k,mode=755,uid=1000002,gid=1000002")
|
||||||
|
(ent "/tmp/hakurei.0/runtime/2" "/run/user/65534" "rw,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
||||||
|
(ent "/tmp/hakurei.0/tmpdir/2" "/tmp" "rw,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
||||||
|
(ent ignore "/etc/passwd" "ro,nosuid,nodev,relatime" "tmpfs" "rootfs" "rw,uid=1000002,gid=1000002")
|
||||||
|
(ent ignore "/etc/group" "ro,nosuid,nodev,relatime" "tmpfs" "rootfs" "rw,uid=1000002,gid=1000002")
|
||||||
|
(ent ignore "/run/user/65534/wayland-0" "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
||||||
|
(ent "/tmp/.X11-unix" "/tmp/.X11-unix" "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
||||||
|
(ent ignore "/run/user/65534/pulse/native" "ro,nosuid,nodev,relatime" "tmpfs" "tmpfs" ignore)
|
||||||
|
(ent ignore "/run/user/65534/bus" "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
||||||
(ent "/bin" "/bin" "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
(ent "/bin" "/bin" "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
||||||
(ent "/usr/bin" "/usr/bin" "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
(ent "/usr/bin" "/usr/bin" "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
||||||
(ent "/" "/nix/store" "ro,nosuid,nodev,relatime" "overlay" "overlay" "rw,lowerdir=/mnt-root/nix/.ro-store,upperdir=/mnt-root/nix/.rw-store/upper,workdir=/mnt-root/nix/.rw-store/work,uuid=on")
|
(ent "/" "/nix/store" "ro,nosuid,nodev,relatime" "overlay" "overlay" "rw,lowerdir=/mnt-root/nix/.ro-store,upperdir=/mnt-root/nix/.rw-store/upper,workdir=/mnt-root/nix/.rw-store/work,uuid=on")
|
||||||
@@ -265,15 +274,6 @@ in
|
|||||||
(ent "/" "/.hakurei/store" "rw,relatime" "overlay" "overlay" "rw,lowerdir=/host/nix/.ro-store:/host/nix/.rw-store/upper,upperdir=/host/tmp/.hakurei-store-rw/upper,workdir=/host/tmp/.hakurei-store-rw/work,redirect_dir=nofollow,uuid=on,userxattr")
|
(ent "/" "/.hakurei/store" "rw,relatime" "overlay" "overlay" "rw,lowerdir=/host/nix/.ro-store:/host/nix/.rw-store/upper,upperdir=/host/tmp/.hakurei-store-rw/upper,workdir=/host/tmp/.hakurei-store-rw/work,redirect_dir=nofollow,uuid=on,userxattr")
|
||||||
(ent "/etc" ignore "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
(ent "/etc" ignore "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
||||||
(ent "/var/lib/hakurei/u0/a2" "/var/lib/hakurei/u0/a2" "rw,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
(ent "/var/lib/hakurei/u0/a2" "/var/lib/hakurei/u0/a2" "rw,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
||||||
(ent "/" "/run/user" "rw,nosuid,nodev,relatime" "tmpfs" "ephemeral" "rw,size=4k,mode=755,uid=1000002,gid=1000002")
|
|
||||||
(ent "/tmp/hakurei.0/runtime/2" "/run/user/65534" "rw,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
|
||||||
(ent "/tmp/hakurei.0/tmpdir/2" "/tmp" "rw,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
|
||||||
(ent ignore "/etc/passwd" "ro,nosuid,nodev,relatime" "tmpfs" "rootfs" "rw,uid=1000002,gid=1000002")
|
|
||||||
(ent ignore "/etc/group" "ro,nosuid,nodev,relatime" "tmpfs" "rootfs" "rw,uid=1000002,gid=1000002")
|
|
||||||
(ent ignore "/run/user/65534/wayland-0" "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
|
||||||
(ent "/tmp/.X11-unix" "/tmp/.X11-unix" "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
|
||||||
(ent ignore "/run/user/65534/pulse/native" "ro,nosuid,nodev,relatime" "tmpfs" "tmpfs" ignore)
|
|
||||||
(ent ignore "/run/user/65534/bus" "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
|
||||||
];
|
];
|
||||||
|
|
||||||
seccomp = true;
|
seccomp = true;
|
||||||
|
|||||||
Reference in New Issue
Block a user