aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorOphestra <cat@gensokyo.uk>2025-08-01 04:10:33 +0900
committerOphestra <cat@gensokyo.uk>2025-08-01 04:21:54 +0900
commit387b86bcdd7798a149ba2349cdf2a699f05cefe5 (patch)
treeede8a378bb00664ba38ce4619e03f72507d7b415
parent4e856438656ea0a1b30d52975f6b5415568df9a8 (diff)
app: integrate container autoroot
Doing this instead of mounting directly on / because it's impossible to ensure a parent is available for every path hakurei wants to mount to. This situation is similar to autoetc hence the similar name, however a symlink mirror will not work in this case. Signed-off-by: Ophestra <cat@gensokyo.uk>
-rw-r--r--cmd/hakurei/print_test.go6
-rw-r--r--hst/container.go7
-rw-r--r--hst/template.go11
-rw-r--r--hst/template_test.go2
-rw-r--r--internal/app/app_pd_linux_test.go26
-rw-r--r--internal/app/container_linux.go79
-rw-r--r--internal/app/seal_linux.go35
-rw-r--r--test/sandbox/case/pd.nix24
8 files changed, 101 insertions, 89 deletions
diff --git a/cmd/hakurei/print_test.go b/cmd/hakurei/print_test.go
index efe6ffb8..fc8348fb 100644
--- a/cmd/hakurei/print_test.go
+++ b/cmd/hakurei/print_test.go
@@ -302,6 +302,8 @@ App
"/run/user/150"
]
],
+ "auto_root": "/var/lib/hakurei/base/org.debian",
+ "root_flags": 2,
"etc": "/etc",
"auto_etc": true,
"cover": [
@@ -430,6 +432,8 @@ App
"/run/user/150"
]
],
+ "auto_root": "/var/lib/hakurei/base/org.debian",
+ "root_flags": 2,
"etc": "/etc",
"auto_etc": true,
"cover": [
@@ -612,6 +616,8 @@ func Test_printPs(t *testing.T) {
"/run/user/150"
]
],
+ "auto_root": "/var/lib/hakurei/base/org.debian",
+ "root_flags": 2,
"etc": "/etc",
"auto_etc": true,
"cover": [
diff --git a/hst/container.go b/hst/container.go
index c385344c..56229929 100644
--- a/hst/container.go
+++ b/hst/container.go
@@ -45,10 +45,17 @@ type (
// create symlinks inside container filesystem
Link [][2]string `json:"symlink"`
+ // automatically bind mount top-level directories to container root;
+ // the zero value disables this behaviour
+ AutoRoot string `json:"auto_root,omitempty"`
+ // extra flags for AutoRoot
+ RootFlags int `json:"root_flags,omitempty"`
+
// read-only /etc directory
Etc string `json:"etc,omitempty"`
// automatically set up /etc symlinks
AutoEtc bool `json:"auto_etc"`
+
// cover these paths or create them if they do not already exist
Cover []string `json:"cover"`
}
diff --git a/hst/template.go b/hst/template.go
index 3ab9c277..391e58e2 100644
--- a/hst/template.go
+++ b/hst/template.go
@@ -1,6 +1,7 @@
package hst
import (
+ "hakurei.app/container"
"hakurei.app/container/seccomp"
"hakurei.app/system"
"hakurei.app/system/dbus"
@@ -85,10 +86,12 @@ func Template() *Config {
Dst: "/data/data/org.chromium.Chromium", Write: true, Must: true},
{Src: "/dev/dri", Device: true},
},
- Link: [][2]string{{"/run/user/65534", "/run/user/150"}},
- Etc: "/etc",
- AutoEtc: true,
- Cover: []string{"/var/run/nscd"},
+ Link: [][2]string{{"/run/user/65534", "/run/user/150"}},
+ AutoRoot: "/var/lib/hakurei/base/org.debian",
+ RootFlags: container.BindWritable,
+ Etc: "/etc",
+ AutoEtc: true,
+ Cover: []string{"/var/run/nscd"},
},
}
}
diff --git a/hst/template_test.go b/hst/template_test.go
index 8761b7d4..7f0caa7d 100644
--- a/hst/template_test.go
+++ b/hst/template_test.go
@@ -126,6 +126,8 @@ func TestTemplate(t *testing.T) {
"/run/user/150"
]
],
+ "auto_root": "/var/lib/hakurei/base/org.debian",
+ "root_flags": 2,
"etc": "/etc",
"auto_etc": true,
"cover": [
diff --git a/internal/app/app_pd_linux_test.go b/internal/app/app_pd_linux_test.go
index 5712a42a..aea59d1c 100644
--- a/internal/app/app_pd_linux_test.go
+++ b/internal/app/app_pd_linux_test.go
@@ -42,21 +42,10 @@ var testCasesPd = []sealTestCase{
"XDG_SESSION_TYPE=tty",
},
Ops: new(container.Ops).
+ Root("/", "4a450b6596d7bc15bd01780eb9a607ac", container.BindWritable).
Proc("/proc").
Tmpfs(hst.Tmp, 4096, 0755).
Dev("/dev").Mqueue("/dev/mqueue").
- Bind("/bin", "/bin", container.BindWritable).
- Bind("/boot", "/boot", container.BindWritable).
- Bind("/home", "/home", container.BindWritable).
- Bind("/lib", "/lib", container.BindWritable).
- Bind("/lib64", "/lib64", container.BindWritable).
- Bind("/nix", "/nix", container.BindWritable).
- Bind("/root", "/root", container.BindWritable).
- Bind("/run", "/run", container.BindWritable).
- Bind("/srv", "/srv", container.BindWritable).
- Bind("/sys", "/sys", container.BindWritable).
- Bind("/usr", "/usr", container.BindWritable).
- Bind("/var", "/var", container.BindWritable).
Bind("/dev/kvm", "/dev/kvm", container.BindWritable|container.BindDevice|container.BindOptional).
Tmpfs("/run/user/1971", 8192, 0755).
Tmpfs("/run/dbus", 8192, 0755).
@@ -186,21 +175,10 @@ var testCasesPd = []sealTestCase{
"XDG_SESSION_TYPE=tty",
},
Ops: new(container.Ops).
+ Root("/", "ebf083d1b175911782d413369b64ce7c", container.BindWritable).
Proc("/proc").
Tmpfs(hst.Tmp, 4096, 0755).
Dev("/dev").Mqueue("/dev/mqueue").
- Bind("/bin", "/bin", container.BindWritable).
- Bind("/boot", "/boot", container.BindWritable).
- Bind("/home", "/home", container.BindWritable).
- Bind("/lib", "/lib", container.BindWritable).
- Bind("/lib64", "/lib64", container.BindWritable).
- Bind("/nix", "/nix", container.BindWritable).
- Bind("/root", "/root", container.BindWritable).
- Bind("/run", "/run", container.BindWritable).
- Bind("/srv", "/srv", container.BindWritable).
- Bind("/sys", "/sys", container.BindWritable).
- Bind("/usr", "/usr", container.BindWritable).
- Bind("/var", "/var", container.BindWritable).
Bind("/dev/dri", "/dev/dri", container.BindWritable|container.BindDevice|container.BindOptional).
Bind("/dev/kvm", "/dev/kvm", container.BindWritable|container.BindDevice|container.BindOptional).
Tmpfs("/run/user/1971", 8192, 0755).
diff --git a/internal/app/container_linux.go b/internal/app/container_linux.go
index 5efd3de7..cd9448a8 100644
--- a/internal/app/container_linux.go
+++ b/internal/app/container_linux.go
@@ -6,6 +6,7 @@ import (
"io/fs"
"maps"
"path"
+ "slices"
"syscall"
"hakurei.app/container"
@@ -21,7 +22,7 @@ const preallocateOpsCount = 1 << 5
// newContainer initialises [container.Params] via [hst.ContainerConfig].
// Note that remaining container setup must be queued by the caller.
-func newContainer(s *hst.ContainerConfig, os sys.State, uid, gid *int) (*container.Params, map[string]string, error) {
+func newContainer(s *hst.ContainerConfig, os sys.State, prefix string, uid, gid *int) (*container.Params, map[string]string, error) {
if s == nil {
return nil, nil, syscall.EBADE
}
@@ -72,6 +73,13 @@ func newContainer(s *hst.ContainerConfig, os sys.State, uid, gid *int) (*contain
*gid = container.OverflowGid()
}
+ if s.AutoRoot != "" {
+ if !path.IsAbs(s.AutoRoot) {
+ return nil, nil, fmt.Errorf("auto root target %q not absolute", s.AutoRoot)
+ }
+ params.Root(s.AutoRoot, prefix, s.RootFlags)
+ }
+
params.
Proc("/proc").
Tmpfs(hst.Tmp, 1<<12, 0755)
@@ -120,6 +128,28 @@ func newContainer(s *hst.ContainerConfig, os sys.State, uid, gid *int) (*contain
return nil, nil, err
}
}
+ // evaluated path, input path
+ hidePathSource := make([][2]string, 0, len(s.Filesystem))
+
+ // AutoRoot is a collection of many BindMountOp internally
+ if s.AutoRoot != "" {
+ if d, err := os.ReadDir(s.AutoRoot); err != nil {
+ return nil, nil, err
+ } else {
+ hidePathSource = slices.Grow(hidePathSource, len(d))
+ for _, ent := range d {
+ name := ent.Name()
+ if container.IsAutoRootBindable(name) {
+ name = path.Join(s.AutoRoot, name)
+ srcP := [2]string{name, name}
+ if err = evalSymlinks(os, &srcP[0]); err != nil {
+ return nil, nil, err
+ }
+ hidePathSource = append(hidePathSource, srcP)
+ }
+ }
+ }
+ }
for _, c := range s.Filesystem {
if c == nil {
@@ -137,24 +167,11 @@ func newContainer(s *hst.ContainerConfig, os sys.State, uid, gid *int) (*contain
return nil, nil, fmt.Errorf("dst path %q is not absolute", dest)
}
- srcH := c.Src
- if err := evalSymlinks(os, &srcH); err != nil {
+ p := [2]string{c.Src, c.Src}
+ if err := evalSymlinks(os, &p[0]); err != nil {
return nil, nil, err
}
-
- for i := range hidePaths {
- // skip matched entries
- if hidePathMatch[i] {
- continue
- }
-
- if ok, err := deepContainsH(srcH, hidePaths[i]); err != nil {
- return nil, nil, err
- } else if ok {
- hidePathMatch[i] = true
- os.Printf("hiding paths from %q", c.Src)
- }
- }
+ hidePathSource = append(hidePathSource, p)
var flags int
if c.Write {
@@ -169,6 +186,22 @@ func newContainer(s *hst.ContainerConfig, os sys.State, uid, gid *int) (*contain
params.Bind(c.Src, dest, flags)
}
+ for _, p := range hidePathSource {
+ for i := range hidePaths {
+ // skip matched entries
+ if hidePathMatch[i] {
+ continue
+ }
+
+ if ok, err := deepContainsH(p[0], hidePaths[i]); err != nil {
+ return nil, nil, err
+ } else if ok {
+ hidePathMatch[i] = true
+ os.Printf("hiding path %q from %q", hidePaths[i], p[1])
+ }
+ }
+ }
+
// cover matched paths
for i, ok := range hidePathMatch {
if ok {
@@ -180,6 +213,18 @@ func newContainer(s *hst.ContainerConfig, os sys.State, uid, gid *int) (*contain
params.Link(l[0], l[1])
}
+ if !s.AutoEtc {
+ if s.Etc != "" {
+ params.Bind(s.Etc, "/etc", 0)
+ }
+ } else {
+ etcPath := s.Etc
+ if etcPath == "" {
+ etcPath = "/etc"
+ }
+ params.Etc(etcPath, prefix)
+ }
+
return params, maps.Clone(s.Env), nil
}
diff --git a/internal/app/seal_linux.go b/internal/app/seal_linux.go
index 13257211..d020697e 100644
--- a/internal/app/seal_linux.go
+++ b/internal/app/seal_linux.go
@@ -241,26 +241,9 @@ func (seal *outcome) finalise(ctx context.Context, sys sys.State, config *hst.Co
Net: true,
Tty: true,
AutoEtc: true,
- }
- // bind entries in /
- if d, err := sys.ReadDir("/"); err != nil {
- return err
- } else {
- b := make([]*hst.FilesystemConfig, 0, len(d))
- for _, ent := range d {
- p := "/" + ent.Name()
- switch p {
- case "/proc":
- case "/dev":
- case "/tmp":
- case "/mnt":
- case "/etc":
- default:
- b = append(b, &hst.FilesystemConfig{Src: p, Write: true, Must: true})
- }
- }
- conf.Filesystem = append(conf.Filesystem, b...)
+ AutoRoot: "/",
+ RootFlags: container.BindWritable,
}
// hide nscd from sandbox if present
@@ -282,7 +265,7 @@ func (seal *outcome) finalise(ctx context.Context, sys sys.State, config *hst.Co
{
var uid, gid int
var err error
- seal.container, seal.env, err = newContainer(config.Container, sys, &uid, &gid)
+ seal.container, seal.env, err = newContainer(config.Container, sys, seal.id.String(), &uid, &gid)
seal.waitDelay = config.Container.WaitDelay
if err != nil {
return hlog.WrapErrSuffix(err,
@@ -305,18 +288,6 @@ func (seal *outcome) finalise(ctx context.Context, sys sys.State, config *hst.Co
}
}
- if !config.Container.AutoEtc {
- if config.Container.Etc != "" {
- seal.container.Bind(config.Container.Etc, "/etc", 0)
- }
- } else {
- etcPath := config.Container.Etc
- if etcPath == "" {
- etcPath = "/etc"
- }
- seal.container.Etc(etcPath, seal.id.String())
- }
-
// inner XDG_RUNTIME_DIR default formatting of `/run/user/%d` as mapped uid
innerRuntimeDir := path.Join("/run/user", mapuid.String())
seal.env[xdgRuntimeDir] = innerRuntimeDir
diff --git a/test/sandbox/case/pd.nix b/test/sandbox/case/pd.nix
index 6f2ca85f..9842eb15 100644
--- a/test/sandbox/case/pd.nix
+++ b/test/sandbox/case/pd.nix
@@ -139,18 +139,6 @@
mount = [
(ent "/sysroot" "/" "rw,nosuid,nodev,relatime" "tmpfs" "rootfs" "rw,uid=1000000,gid=1000000")
- (ent "/" "/proc" "rw,nosuid,nodev,noexec,relatime" "proc" "proc" "rw")
- (ent "/" "/.hakurei" "rw,nosuid,nodev,relatime" "tmpfs" "tmpfs" "rw,size=4k,mode=755,uid=1000000,gid=1000000")
- (ent "/" "/dev" "rw,nosuid,nodev,relatime" "tmpfs" "devtmpfs" "rw,mode=755,uid=1000000,gid=1000000")
- (ent "/null" "/dev/null" "rw,nosuid" "devtmpfs" "devtmpfs" ignore)
- (ent "/zero" "/dev/zero" "rw,nosuid" "devtmpfs" "devtmpfs" ignore)
- (ent "/full" "/dev/full" "rw,nosuid" "devtmpfs" "devtmpfs" ignore)
- (ent "/random" "/dev/random" "rw,nosuid" "devtmpfs" "devtmpfs" ignore)
- (ent "/urandom" "/dev/urandom" "rw,nosuid" "devtmpfs" "devtmpfs" ignore)
- (ent "/tty" "/dev/tty" "rw,nosuid" "devtmpfs" "devtmpfs" ignore)
- (ent "/" "/dev/pts" "rw,nosuid,noexec,relatime" "devpts" "devpts" "rw,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 "/bin" "/bin" "rw,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
(ent "/home" "/home" "rw,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
(ent "/lib64" "/lib64" "rw,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
@@ -180,6 +168,18 @@
(ent "/" ignore "rw,nosuid,nodev,noexec,relatime" ignore ignore "rw")
(ent "/usr" "/usr" "rw,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
(ent "/var" "/var" "rw,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
+ (ent "/" "/proc" "rw,nosuid,nodev,noexec,relatime" "proc" "proc" "rw")
+ (ent "/" "/.hakurei" "rw,nosuid,nodev,relatime" "tmpfs" "tmpfs" "rw,size=4k,mode=755,uid=1000000,gid=1000000")
+ (ent "/" "/dev" "rw,nosuid,nodev,relatime" "tmpfs" "devtmpfs" "rw,mode=755,uid=1000000,gid=1000000")
+ (ent "/null" "/dev/null" "rw,nosuid" "devtmpfs" "devtmpfs" ignore)
+ (ent "/zero" "/dev/zero" "rw,nosuid" "devtmpfs" "devtmpfs" ignore)
+ (ent "/full" "/dev/full" "rw,nosuid" "devtmpfs" "devtmpfs" ignore)
+ (ent "/random" "/dev/random" "rw,nosuid" "devtmpfs" "devtmpfs" ignore)
+ (ent "/urandom" "/dev/urandom" "rw,nosuid" "devtmpfs" "devtmpfs" ignore)
+ (ent "/tty" "/dev/tty" "rw,nosuid" "devtmpfs" "devtmpfs" ignore)
+ (ent "/" "/dev/pts" "rw,nosuid,noexec,relatime" "devpts" "devpts" "rw,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 "/kvm" "/dev/kvm" "rw,nosuid" "devtmpfs" "devtmpfs" ignore)
(ent "/" "/run/user/1000" "rw,nosuid,nodev,relatime" "tmpfs" "tmpfs" "rw,size=8k,mode=755,uid=1000000,gid=1000000")
(ent "/" "/run/dbus" "rw,nosuid,nodev,relatime" "tmpfs" "tmpfs" "rw,size=8k,mode=755,uid=1000000,gid=1000000")