package app import ( "errors" "fmt" "io/fs" "maps" "path" "syscall" "hakurei.app/container" "hakurei.app/container/seccomp" "hakurei.app/hst" "hakurei.app/internal/hlog" "hakurei.app/internal/sys" "hakurei.app/system/dbus" ) // in practice there should be less than 30 entries added by the runtime; // allocating slightly more as a margin for future expansion 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, prefix string, uid, gid *int) (*container.Params, map[string]string, error) { if s == nil { return nil, nil, hlog.WrapErr(syscall.EBADE, "invalid container configuration") } params := &container.Params{ Hostname: s.Hostname, SeccompFlags: s.SeccompFlags, SeccompPresets: s.SeccompPresets, RetainSession: s.Tty, HostNet: s.Net, // 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, } { ops := make(container.Ops, 0, preallocateOpsCount+len(s.Filesystem)+len(s.Link)) params.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 { /* some programs fail to connect to dbus session running as a different uid so this workaround is introduced to map priv-side caller uid in container */ params.Uid = os.Getuid() *uid = params.Uid params.Gid = os.Getgid() *gid = params.Gid } else { *uid = container.OverflowUid() *gid = container.OverflowGid() } if s.AutoRoot != nil { params.Root(s.AutoRoot, prefix, s.RootFlags) } 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) } /* 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 sc := os.Paths() 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 { os.Printf("dbus socket %q is in an unusual location", pair[1]) } hidePaths = append(hidePaths, dir) } else { os.Printf("dbus socket %q is not absolute", pair[1]) } } } } } hidePathMatch := make([]bool, len(hidePaths)) for i := range hidePaths { if err := evalSymlinks(os, &hidePaths[i]); err != nil { return nil, nil, err } } var hidePathSourceCount int for i, c := range s.Filesystem { if !c.Valid() { return nil, nil, fmt.Errorf("invalid filesystem at index %d", i) } c.Apply(params.Ops) // fs counter hidePathSourceCount += len(c.Host()) } // AutoRoot is a collection of many BindMountOp internally var autoRootEntries []fs.DirEntry if s.AutoRoot != nil { if d, err := os.ReadDir(s.AutoRoot.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 s.Filesystem { // all entries already checked above hidePathSource = append(hidePathSource, c.Host()...) } // autoroot append if s.AutoRoot != nil { for _, ent := range autoRootEntries { name := ent.Name() if container.IsAutoRootBindable(name) { hidePathSource = append(hidePathSource, s.AutoRoot.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(os, &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 os.Printf("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) } } } for i, l := range s.Link { if l.Target == nil || l.Linkname == "" { return nil, nil, fmt.Errorf("invalid link at index %d", i) } linkname := l.Linkname var dereference bool if linkname[0] == '*' && path.IsAbs(linkname[1:]) { linkname = linkname[1:] dereference = true } params.Link(l.Target, linkname, dereference) } if !s.AutoEtc { if s.Etc != nil { params.Bind(s.Etc, container.AbsFHSEtc, 0) } } else { if s.Etc == nil { params.Etc(container.AbsFHSEtc, prefix) } else { params.Etc(s.Etc, prefix) } } // no more ContainerConfig paths beyond this point if !s.Device { params.Remount(container.AbsFHSDev, syscall.MS_RDONLY) } return params, maps.Clone(s.Env), nil } func evalSymlinks(os sys.State, v *string) error { if p, err := os.EvalSymlinks(*v); err != nil { if !errors.Is(err, fs.ErrNotExist) { return err } os.Printf("path %q does not yet exist", *v) } else { *v = p } return nil }