diff --git a/cmd/hakurei/print_test.go b/cmd/hakurei/print_test.go index efe6ffb..fc8348f 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 c385344..5622992 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 3ab9c27..391e58e 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 8761b7d..7f0caa7 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 5712a42..aea59d1 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 5efd3de..cd9448a 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 1325721..d020697 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 6f2ca85..9842eb1 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")