diff options
| author | Ophestra <cat@gensokyo.uk> | 2025-10-05 02:42:41 +0900 |
|---|---|---|
| committer | Ophestra <cat@gensokyo.uk> | 2025-10-05 02:52:50 +0900 |
| commit | eb5ee4fece0327b1183ae823d68e4cf4e3e1ced0 (patch) | |
| tree | 64e1c411e9d0a29637982519b9bd3a5ff6642d1a /internal/app/container.go | |
| parent | 9462af08f38fefe40d985383d8825258a6d1f0a8 (diff) | |
internal/app: modularise outcome finalise
This is the initial effort of splitting up host and container side of finalisation for params to shim. The new layout also enables much finer grained unit testing of each step, as well as partition access to per-app state for each step.
Signed-off-by: Ophestra <cat@gensokyo.uk>
Diffstat (limited to 'internal/app/container.go')
| -rw-r--r-- | internal/app/container.go | 253 |
1 files changed, 0 insertions, 253 deletions
diff --git a/internal/app/container.go b/internal/app/container.go deleted file mode 100644 index eef4e226..00000000 --- a/internal/app/container.go +++ /dev/null @@ -1,253 +0,0 @@ -package app - -import ( - "errors" - "fmt" - "io/fs" - "maps" - "path" - "syscall" - - "hakurei.app/container" - "hakurei.app/container/seccomp" - "hakurei.app/hst" - "hakurei.app/system/dbus" -) - -// in practice there should be less than 30 system mount points -const preallocateOpsCount = 1 << 5 - -// newContainer initialises [container.Params] via [hst.ContainerConfig]. -// Note that remaining container setup must be queued by the caller. -func newContainer( - msg container.Msg, - k syscallDispatcher, - s *hst.ContainerConfig, - prefix string, - sc *hst.Paths, - uid, gid *int, -) (*container.Params, map[string]string, error) { - if s == nil { - return nil, nil, newWithMessage("invalid container configuration") - } - - params := &container.Params{ - Hostname: s.Hostname, - RetainSession: s.Tty, - HostNet: s.HostNet, - HostAbstract: s.HostAbstract, - - // the container is canceled when shim is requested to exit or receives an interrupt or termination signal; - // this behaviour is implemented in the shim - ForwardCancel: s.WaitDelay >= 0, - } - - as := &hst.ApplyState{AutoEtcPrefix: prefix} - { - ops := make(container.Ops, 0, preallocateOpsCount+len(s.Filesystem)) - params.Ops = &ops - as.Ops = &ops - } - - if s.Multiarch { - params.SeccompFlags |= seccomp.AllowMultiarch - } - - if !s.SeccompCompat { - params.SeccompPresets |= seccomp.PresetExt - } - if !s.Devel { - params.SeccompPresets |= seccomp.PresetDenyDevel - } - if !s.Userns { - params.SeccompPresets |= seccomp.PresetDenyNS - } - if !s.Tty { - params.SeccompPresets |= seccomp.PresetDenyTTY - } - - if s.MapRealUID { - params.Uid = k.getuid() - *uid = params.Uid - params.Gid = k.getgid() - *gid = params.Gid - } else { - *uid = k.overflowUid(msg) - *gid = k.overflowGid(msg) - } - - filesystem := s.Filesystem - var autoroot *hst.FSBind - // valid happens late, so root mount gets it here - if len(filesystem) > 0 && filesystem[0].Valid() && filesystem[0].Path().String() == container.FHSRoot { - // if the first element targets /, it is inserted early and excluded from path hiding - rootfs := filesystem[0].FilesystemConfig - filesystem = filesystem[1:] - rootfs.Apply(as) - - // autoroot requires special handling during path hiding - if b, ok := rootfs.(*hst.FSBind); ok && b.IsAutoRoot() { - autoroot = b - } - } - - params. - Proc(container.AbsFHSProc). - Tmpfs(hst.AbsTmp, 1<<12, 0755) - - if !s.Device { - params.DevWritable(container.AbsFHSDev, true) - } else { - params.Bind(container.AbsFHSDev, container.AbsFHSDev, container.BindWritable|container.BindDevice) - } - // /dev is mounted readonly later on, this prevents /dev/shm from going readonly with it - params.Tmpfs(container.AbsFHSDev.Append("shm"), 0, 01777) - - /* retrieve paths and hide them if they're made available in the sandbox; - - this feature tries to improve user experience of permissive defaults, and - to warn about issues in custom configuration; it is NOT a security feature - and should not be treated as such, ALWAYS be careful with what you bind */ - var hidePaths []string - hidePaths = append(hidePaths, sc.RuntimePath.String(), sc.SharePath.String()) - _, systemBusAddr := dbus.Address() - if entries, err := dbus.Parse([]byte(systemBusAddr)); err != nil { - return nil, nil, err - } else { - // there is usually only one, do not preallocate - for _, entry := range entries { - if entry.Method != "unix" { - continue - } - for _, pair := range entry.Values { - if pair[0] == "path" { - if path.IsAbs(pair[1]) { - // get parent dir of socket - dir := path.Dir(pair[1]) - if dir == "." || dir == container.FHSRoot { - msg.Verbosef("dbus socket %q is in an unusual location", pair[1]) - } - hidePaths = append(hidePaths, dir) - } else { - msg.Verbosef("dbus socket %q is not absolute", pair[1]) - } - } - } - } - } - hidePathMatch := make([]bool, len(hidePaths)) - for i := range hidePaths { - if err := evalSymlinks(msg, k, &hidePaths[i]); err != nil { - return nil, nil, err - } - } - - var hidePathSourceCount int - for i, c := range filesystem { - if !c.Valid() { - return nil, nil, fmt.Errorf("invalid filesystem at index %d", i) - } - c.Apply(as) - - // fs counter - hidePathSourceCount += len(c.Host()) - } - - // AutoRootOp is a collection of many BindMountOp internally - var autoRootEntries []fs.DirEntry - if autoroot != nil { - if d, err := k.readdir(autoroot.Source.String()); err != nil { - return nil, nil, err - } else { - // autoroot counter - hidePathSourceCount += len(d) - autoRootEntries = d - } - } - - hidePathSource := make([]*container.Absolute, 0, hidePathSourceCount) - - // fs append - for _, c := range filesystem { - // all entries already checked above - hidePathSource = append(hidePathSource, c.Host()...) - } - - // autoroot append - if autoroot != nil { - for _, ent := range autoRootEntries { - name := ent.Name() - if container.IsAutoRootBindable(msg, name) { - hidePathSource = append(hidePathSource, autoroot.Source.Append(name)) - } - } - } - - // evaluated path, input path - hidePathSourceEval := make([][2]string, len(hidePathSource)) - for i, a := range hidePathSource { - if a == nil { - // unreachable - return nil, nil, syscall.ENOTRECOVERABLE - } - - hidePathSourceEval[i] = [2]string{a.String(), a.String()} - if err := evalSymlinks(msg, k, &hidePathSourceEval[i][0]); err != nil { - return nil, nil, err - } - } - - for _, p := range hidePathSourceEval { - 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 - msg.Verbosef("hiding path %q from %q", hidePaths[i], p[1]) - } - } - } - - // cover matched paths - for i, ok := range hidePathMatch { - if ok { - if a, err := container.NewAbs(hidePaths[i]); err != nil { - var absoluteError *container.AbsoluteError - if !errors.As(err, &absoluteError) { - return nil, nil, err - } - if absoluteError == nil { - return nil, nil, syscall.ENOTRECOVERABLE - } - return nil, nil, fmt.Errorf("invalid path hiding candidate %q", absoluteError.Pathname) - } else { - params.Tmpfs(a, 1<<13, 0755) - } - } - } - - // no more ContainerConfig paths beyond this point - if !s.Device { - params.Remount(container.AbsFHSDev, syscall.MS_RDONLY) - } - - return params, maps.Clone(s.Env), nil -} - -// evalSymlinks calls syscallDispatcher.evalSymlinks but discards errors unwrapping to [fs.ErrNotExist]. -func evalSymlinks(msg container.Msg, k syscallDispatcher, v *string) error { - if p, err := k.evalSymlinks(*v); err != nil { - if !errors.Is(err, fs.ErrNotExist) { - return err - } - msg.Verbosef("path %q does not yet exist", *v) - } else { - *v = p - } - return nil -} |
