fortify/internal/app/instance/common/container.go
Ophestra 31b7ddd122
All checks were successful
Test / Create distribution (push) Successful in 26s
Test / Sandbox (push) Successful in 1m50s
Test / Fortify (push) Successful in 2m46s
Test / Sandbox (race detector) (push) Successful in 2m59s
Test / Fortify (race detector) (push) Successful in 4m23s
Test / Fpkg (push) Successful in 5m25s
Test / Flake checks (push) Successful in 1m1s
fst: improve config
The config struct more or less "grew" to what it is today. This change moves things around to make more sense and fixes nonsensical comments describing obsolete behaviour.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2025-04-13 03:30:19 +09:00

188 lines
4.4 KiB
Go

package common
import (
"errors"
"fmt"
"io/fs"
"maps"
"path"
"slices"
"syscall"
"git.gensokyo.uk/security/fortify/dbus"
"git.gensokyo.uk/security/fortify/fst"
"git.gensokyo.uk/security/fortify/internal/sys"
"git.gensokyo.uk/security/fortify/sandbox"
"git.gensokyo.uk/security/fortify/sandbox/seccomp"
)
// NewContainer initialises [sandbox.Params] via [fst.ContainerConfig].
// Note that remaining container setup must be queued by the caller.
func NewContainer(s *fst.ContainerConfig, os sys.State, uid, gid *int) (*sandbox.Params, map[string]string, error) {
if s == nil {
return nil, nil, syscall.EBADE
}
container := &sandbox.Params{
Hostname: s.Hostname,
Ops: new(sandbox.Ops),
Seccomp: s.Seccomp,
}
if s.Multiarch {
container.Seccomp |= seccomp.FilterMultiarch
}
/* this is only 4 KiB of memory on a 64-bit system,
permissive defaults on NixOS results in around 100 entries
so this capacity should eliminate copies for most setups */
*container.Ops = slices.Grow(*container.Ops, 1<<8)
if s.Devel {
container.Flags |= sandbox.FAllowDevel
}
if s.Userns {
container.Flags |= sandbox.FAllowUserns
}
if s.Net {
container.Flags |= sandbox.FAllowNet
}
if s.Tty {
container.Flags |= sandbox.FAllowTTY
}
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 */
container.Uid = os.Getuid()
*uid = container.Uid
container.Gid = os.Getgid()
*gid = container.Gid
} else {
*uid = sandbox.OverflowUid()
*gid = sandbox.OverflowGid()
}
container.
Proc("/proc").
Tmpfs(fst.Tmp, 1<<12, 0755)
if !s.Device {
container.Dev("/dev").Mqueue("/dev/mqueue")
} else {
container.Bind("/dev", "/dev", sandbox.BindWritable|sandbox.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, sc.SharePath)
_, 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 == "/" {
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
}
}
for _, c := range s.Filesystem {
if c == nil {
continue
}
if !path.IsAbs(c.Src) {
return nil, nil, fmt.Errorf("src path %q is not absolute", c.Src)
}
dest := c.Dst
if c.Dst == "" {
dest = c.Src
} else if !path.IsAbs(dest) {
return nil, nil, fmt.Errorf("dst path %q is not absolute", dest)
}
srcH := c.Src
if err := evalSymlinks(os, &srcH); 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)
}
}
var flags int
if c.Write {
flags |= sandbox.BindWritable
}
if c.Device {
flags |= sandbox.BindDevice | sandbox.BindWritable
}
if !c.Must {
flags |= sandbox.BindOptional
}
container.Bind(c.Src, dest, flags)
}
// cover matched paths
for i, ok := range hidePathMatch {
if ok {
container.Tmpfs(hidePaths[i], 1<<13, 0755)
}
}
for _, l := range s.Link {
container.Link(l[0], l[1])
}
return container, 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
}