aboutsummaryrefslogtreecommitdiffhomepage
path: root/internal/app/spcontainer.go
diff options
context:
space:
mode:
authorOphestra <cat@gensokyo.uk>2025-10-29 04:32:43 +0900
committerOphestra <cat@gensokyo.uk>2025-10-29 04:33:13 +0900
commita0b4e47acc228e48165966d8e139277b8f6f450e (patch)
treed363a6781536ce946aa445271ca1d3df587e6031 /internal/app/spcontainer.go
parenta52f7038e5a607dd0901244abb791c96deaf9c2d (diff)
internal/outcome: rename from app
This is less ambiguous, and more accurately describes the purpose of the package. Signed-off-by: Ophestra <cat@gensokyo.uk>
Diffstat (limited to 'internal/app/spcontainer.go')
-rw-r--r--internal/app/spcontainer.go391
1 files changed, 0 insertions, 391 deletions
diff --git a/internal/app/spcontainer.go b/internal/app/spcontainer.go
deleted file mode 100644
index d411cb9f..00000000
--- a/internal/app/spcontainer.go
+++ /dev/null
@@ -1,391 +0,0 @@
-package app
-
-import (
- "encoding/gob"
- "errors"
- "io/fs"
- "os"
- "path"
- "slices"
- "strconv"
- "syscall"
-
- "hakurei.app/container"
- "hakurei.app/container/check"
- "hakurei.app/container/comp"
- "hakurei.app/container/fhs"
- "hakurei.app/container/seccomp"
- "hakurei.app/hst"
- "hakurei.app/internal/validate"
- "hakurei.app/message"
- "hakurei.app/system"
- "hakurei.app/system/acl"
- "hakurei.app/system/dbus"
-)
-
-const varRunNscd = fhs.Var + "run/nscd"
-
-func init() { gob.Register(new(spParamsOp)) }
-
-// spParamsOp initialises unordered fields of [container.Params] and the optional root filesystem.
-// This outcomeOp is hardcoded to always run first.
-type spParamsOp struct {
- // Value of $TERM, stored during toSystem.
- Term string
- // Whether $TERM is set, stored during toSystem.
- TermSet bool
-}
-
-func (s *spParamsOp) toSystem(state *outcomeStateSys) error {
- s.Term, s.TermSet = state.k.lookupEnv("TERM")
- state.sys.Ensure(state.sc.SharePath, 0711)
- return nil
-}
-
-func (s *spParamsOp) toContainer(state *outcomeStateParams) error {
- // pass $TERM for proper terminal I/O in initial process
- if s.TermSet {
- state.env["TERM"] = s.Term
- }
-
- // in practice there should be less than 30 system mount points
- const preallocateOpsCount = 1 << 5
-
- state.params.Hostname = state.Container.Hostname
- state.params.RetainSession = state.Container.Flags&hst.FTty != 0
- state.params.HostNet = state.Container.Flags&hst.FHostNet != 0
- state.params.HostAbstract = state.Container.Flags&hst.FHostAbstract != 0
-
- if state.Container.Path == nil {
- return newWithMessage("invalid program path")
- }
- state.params.Path = state.Container.Path
-
- if len(state.Container.Args) == 0 {
- state.params.Args = []string{state.Container.Path.String()}
- } else {
- state.params.Args = state.Container.Args
- }
-
- // the container is canceled when shim is requested to exit or receives an interrupt or termination signal;
- // this behaviour is implemented in the shim
- state.params.ForwardCancel = state.Shim.WaitDelay > 0
-
- if state.Container.Flags&hst.FMultiarch != 0 {
- state.params.SeccompFlags |= seccomp.AllowMultiarch
- }
-
- if state.Container.Flags&hst.FSeccompCompat == 0 {
- state.params.SeccompPresets |= comp.PresetExt
- }
- if state.Container.Flags&hst.FDevel == 0 {
- state.params.SeccompPresets |= comp.PresetDenyDevel
- }
- if state.Container.Flags&hst.FUserns == 0 {
- state.params.SeccompPresets |= comp.PresetDenyNS
- }
- if state.Container.Flags&hst.FTty == 0 {
- state.params.SeccompPresets |= comp.PresetDenyTTY
- }
-
- if state.Container.Flags&hst.FMapRealUID != 0 {
- state.params.Uid = state.Mapuid
- state.params.Gid = state.Mapgid
- }
-
- {
- state.as.AutoEtcPrefix = state.id.String()
- ops := make(container.Ops, 0, preallocateOpsCount+len(state.Container.Filesystem))
- state.params.Ops = &ops
- state.as.Ops = opsAdapter{&ops}
- }
-
- rootfs, filesystem, _ := resolveRoot(state.Container)
- state.filesystem = filesystem
- if rootfs != nil {
- rootfs.Apply(&state.as)
- }
-
- // early mount points
- state.params.
- Proc(fhs.AbsProc).
- Tmpfs(hst.AbsPrivateTmp, 1<<12, 0755)
- if state.Container.Flags&hst.FDevice == 0 {
- state.params.DevWritable(fhs.AbsDev, true)
- } else {
- state.params.Bind(fhs.AbsDev, fhs.AbsDev, comp.BindWritable|comp.BindDevice)
- }
- // /dev is mounted readonly later on, this prevents /dev/shm from going readonly with it
- state.params.Tmpfs(fhs.AbsDev.Append("shm"), 0, 01777)
-
- return nil
-}
-
-func init() { gob.Register(new(spFilesystemOp)) }
-
-// spFilesystemOp applies configured filesystems to [container.Params], excluding the optional root filesystem.
-// This outcomeOp is hardcoded to always run last.
-type spFilesystemOp struct {
- // Matched paths to cover. Stored during toSystem.
- HidePaths []*check.Absolute
-}
-
-func (s *spFilesystemOp) toSystem(state *outcomeStateSys) error {
- /* 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 */
- hidePaths := []string{
- state.sc.RuntimePath.String(),
- state.sc.SharePath.String(),
-
- // this causes emulated passwd database to be bypassed on some /etc/ setups
- varRunNscd,
- }
-
- // dbus.Address does not go through syscallDispatcher
- systemBusAddr := dbus.FallbackSystemBusAddress
- if addr, ok := state.k.lookupEnv(dbus.SystemBusAddress); ok {
- systemBusAddr = addr
- }
-
- if entries, err := dbus.Parse([]byte(systemBusAddr)); err != nil {
- return &hst.AppError{Step: "parse dbus address", Err: 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 == fhs.Root {
- state.msg.Verbosef("dbus socket %q is in an unusual location", pair[1])
- }
- hidePaths = append(hidePaths, dir)
- } else {
- state.msg.Verbosef("dbus socket %q is not absolute", pair[1])
- }
- }
- }
- }
- }
- hidePathMatch := make([]bool, len(hidePaths))
- for i := range hidePaths {
- if err := evalSymlinks(state.msg, state.k, &hidePaths[i]); err != nil {
- return &hst.AppError{Step: "evaluate path hiding target", Err: err}
- }
- }
-
- _, filesystem, autoroot := resolveRoot(state.Container)
-
- var hidePathSourceCount int
- for i, c := range filesystem {
- if !c.Valid() {
- return newWithMessage("invalid filesystem at index " + strconv.Itoa(i))
- }
-
- // fs counter
- hidePathSourceCount += len(c.Host())
- }
-
- // AutoRootOp is a collection of many BindMountOp internally
- var autoRootEntries []fs.DirEntry
- if autoroot != nil {
- if d, err := state.k.readdir(autoroot.Source.String()); err != nil {
- return &hst.AppError{Step: "access autoroot source", Err: err}
- } else {
- // autoroot counter
- hidePathSourceCount += len(d)
- autoRootEntries = d
- }
- }
-
- hidePathSource := make([]*check.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(state.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 newWithMessage("impossible path hiding state reached")
- }
-
- hidePathSourceEval[i] = [2]string{a.String(), a.String()}
- if err := evalSymlinks(state.msg, state.k, &hidePathSourceEval[i][0]); err != nil {
- return &hst.AppError{Step: "evaluate path hiding source", Err: err}
- }
- }
-
- for _, p := range hidePathSourceEval {
- for i := range hidePaths {
- // skip matched entries
- if hidePathMatch[i] {
- continue
- }
-
- if ok, err := validate.DeepContainsH(p[0], hidePaths[i]); err != nil {
- return &hst.AppError{Step: "determine path hiding outcome", Err: err}
- } else if ok {
- hidePathMatch[i] = true
- state.msg.Verbosef("hiding path %q from %q", hidePaths[i], p[1])
- }
- }
- }
-
- // copy matched paths for shim
- for i, ok := range hidePathMatch {
- if ok {
- if a, err := check.NewAbs(hidePaths[i]); err != nil {
- return newWithMessage("invalid path hiding candidate " + strconv.Quote(hidePaths[i]))
- } else {
- s.HidePaths = append(s.HidePaths, a)
- }
- }
- }
-
- // append ExtraPerms last
- flattenExtraPerms(state.sys, state.extraPerms)
- return nil
-}
-
-func (s *spFilesystemOp) toContainer(state *outcomeStateParams) error {
- for i, c := range state.filesystem {
- if !c.Valid() {
- return newWithMessage("invalid filesystem at index " + strconv.Itoa(i))
- }
- c.Apply(&state.as)
- }
-
- for _, a := range s.HidePaths {
- state.params.Tmpfs(a, 1<<13, 0755)
- }
-
- // no more configured paths beyond this point
- if state.Container.Flags&hst.FDevice == 0 {
- 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
-}
-
-// resolveRoot handles the root filesystem special case for [hst.FilesystemConfig] and additionally resolves autoroot
-// as it requires special handling during path hiding.
-func resolveRoot(c *hst.ContainerConfig) (rootfs hst.FilesystemConfig, filesystem []hst.FilesystemConfigJSON, autoroot *hst.FSBind) {
- // root filesystem special case
- filesystem = c.Filesystem
- // valid happens late, so root gets it here
- if len(filesystem) > 0 && filesystem[0].Valid() && filesystem[0].Path().String() == fhs.Root {
- // if the first element targets /, it is inserted early and excluded from path hiding
- rootfs = filesystem[0].FilesystemConfig
- filesystem = filesystem[1:]
-
- // autoroot requires special handling during path hiding
- if b, ok := rootfs.(*hst.FSBind); ok && b.IsAutoRoot() {
- autoroot = b
- }
- }
- return
-}
-
-// evalSymlinks calls syscallDispatcher.evalSymlinks but discards errors unwrapping to [fs.ErrNotExist].
-func evalSymlinks(msg message.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
-}
-
-// 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].
-type opsAdapter struct{ *container.Ops }
-
-func (p opsAdapter) Tmpfs(target *check.Absolute, size int, perm os.FileMode) hst.Ops {
- return opsAdapter{p.Ops.Tmpfs(target, size, perm)}
-}
-
-func (p opsAdapter) Readonly(target *check.Absolute, perm os.FileMode) hst.Ops {
- return opsAdapter{p.Ops.Readonly(target, perm)}
-}
-
-func (p opsAdapter) Bind(source, target *check.Absolute, flags int) hst.Ops {
- return opsAdapter{p.Ops.Bind(source, target, flags)}
-}
-
-func (p opsAdapter) Overlay(target, state, work *check.Absolute, layers ...*check.Absolute) hst.Ops {
- return opsAdapter{p.Ops.Overlay(target, state, work, layers...)}
-}
-
-func (p opsAdapter) OverlayReadonly(target *check.Absolute, layers ...*check.Absolute) hst.Ops {
- return opsAdapter{p.Ops.OverlayReadonly(target, layers...)}
-}
-
-func (p opsAdapter) Link(target *check.Absolute, linkName string, dereference bool) hst.Ops {
- return opsAdapter{p.Ops.Link(target, linkName, dereference)}
-}
-
-func (p opsAdapter) Root(host *check.Absolute, flags int) hst.Ops {
- return opsAdapter{p.Ops.Root(host, flags)}
-}
-
-func (p opsAdapter) Etc(host *check.Absolute, prefix string) hst.Ops {
- return opsAdapter{p.Ops.Etc(host, prefix)}
-}