aboutsummaryrefslogtreecommitdiffhomepage
path: root/internal/sandbox/sequential.go
diff options
context:
space:
mode:
authorOphestra <cat@gensokyo.uk>2025-03-13 20:59:03 +0900
committerOphestra <cat@gensokyo.uk>2025-03-13 21:36:26 +0900
commit9b1a60b5c9f7a92abb632639117d61c5cbaf74f7 (patch)
tree0fa6b8dae30a9a687f0e465c7d0fc5e0f24b48b1 /internal/sandbox/sequential.go
parentbeb3918809f0743e2275f6a3f358f622cea73d6b (diff)
sandbox: native container tooling
This should eventually replace bwrap. Signed-off-by: Ophestra <cat@gensokyo.uk>
Diffstat (limited to 'internal/sandbox/sequential.go')
-rw-r--r--internal/sandbox/sequential.go169
1 files changed, 169 insertions, 0 deletions
diff --git a/internal/sandbox/sequential.go b/internal/sandbox/sequential.go
new file mode 100644
index 00000000..603d59e0
--- /dev/null
+++ b/internal/sandbox/sequential.go
@@ -0,0 +1,169 @@
+package sandbox
+
+import (
+ "encoding/gob"
+ "errors"
+ "fmt"
+ "math"
+ "os"
+ "path"
+ "strings"
+ "syscall"
+
+ "git.gensokyo.uk/security/fortify/internal/fmsg"
+)
+
+func init() { gob.Register(new(BindMount)) }
+
+const (
+ BindOptional = 1 << iota
+ BindRecursive
+ BindWritable
+ BindDevices
+)
+
+// BindMount bind mounts host path Source on container path Target.
+type BindMount struct {
+ Source, Target string
+
+ Flags int
+}
+
+func (b *BindMount) apply() error {
+ if !path.IsAbs(b.Source) || !path.IsAbs(b.Target) {
+ return syscall.EBADE
+ }
+ target := toSysroot(b.Target)
+ var source string
+
+ // this is what bwrap does, so the behaviour is kept for now,
+ // however recursively resolving links might improve user experience
+ if rp, err := realpathHost(b.Source); err != nil {
+ if os.IsNotExist(err) {
+ if b.Flags&BindOptional != 0 {
+ return nil
+ } else {
+ return fmsg.WrapError(err,
+ fmt.Sprintf("path %q does not exist", b.Source))
+ }
+ }
+ return fmsg.WrapError(err, err.Error())
+ } else {
+ source = toHost(rp)
+ }
+
+ if fi, err := os.Stat(source); err != nil {
+ return fmsg.WrapError(err, err.Error())
+ } else if fi.IsDir() {
+ if err = os.MkdirAll(target, 0755); err != nil {
+ return fmsg.WrapErrorSuffix(err,
+ fmt.Sprintf("cannot create directory %q:", b.Target))
+ }
+ } else if err = ensureFile(target, 0444); err != nil {
+ if errors.Is(err, syscall.EISDIR) {
+ return fmsg.WrapError(err,
+ fmt.Sprintf("path %q is a directory", b.Target))
+ }
+ return fmsg.WrapErrorSuffix(err,
+ fmt.Sprintf("cannot create %q:", b.Target))
+ }
+
+ var flags uintptr = syscall.MS_SILENT | syscall.MS_BIND
+ if b.Flags&BindRecursive != 0 {
+ flags |= syscall.MS_REC
+ }
+ if b.Flags&BindWritable == 0 {
+ flags |= syscall.MS_RDONLY
+ }
+ if b.Flags&BindDevices == 0 {
+ flags |= syscall.MS_NODEV
+ }
+ if fmsg.Load() {
+ if strings.TrimPrefix(source, hostPath) == strings.TrimPrefix(target, sysrootPath) {
+ fmsg.Verbosef("resolved %q flags %#x", target, flags)
+ } else {
+ fmsg.Verbosef("resolved %q on %q flags %#x", source, target, flags)
+ }
+ }
+ return fmsg.WrapErrorSuffix(syscall.Mount(source, target, "", flags, ""),
+ fmt.Sprintf("cannot bind %q on %q:", b.Source, b.Target))
+}
+
+func (b *BindMount) Is(op Op) bool { vb, ok := op.(*BindMount); return ok && *b == *vb }
+func (b *BindMount) String() string {
+ if b.Source == b.Target {
+ return fmt.Sprintf("%q flags %#x", b.Source, b.Flags)
+ }
+ return fmt.Sprintf("%q on %q flags %#x", b.Source, b.Target, b.Flags&BindWritable)
+}
+func (f *Ops) Bind(source, target string, flags int) *Ops {
+ *f = append(*f, &BindMount{source, target, flags | BindRecursive})
+ return f
+}
+
+func init() { gob.Register(new(MountProc)) }
+
+// MountProc mounts a private proc instance on container Path.
+type MountProc struct {
+ Path string
+}
+
+func (p *MountProc) apply() error {
+ if !path.IsAbs(p.Path) {
+ return fmsg.WrapError(syscall.EBADE,
+ fmt.Sprintf("path %q is not absolute", p.Path))
+ }
+
+ target := toSysroot(p.Path)
+ if err := os.MkdirAll(target, 0755); err != nil {
+ return fmsg.WrapError(err, err.Error())
+ }
+ return fmsg.WrapErrorSuffix(syscall.Mount("proc", target, "proc",
+ syscall.MS_NOSUID|syscall.MS_NOEXEC|syscall.MS_NODEV, ""),
+ fmt.Sprintf("cannot mount proc on %q:", p.Path))
+}
+
+func (p *MountProc) Is(op Op) bool { vp, ok := op.(*MountProc); return ok && *p == *vp }
+func (p *MountProc) String() string { return fmt.Sprintf("proc on %q", p.Path) }
+func (f *Ops) Proc(dest string) *Ops {
+ *f = append(*f, &MountProc{dest})
+ return f
+}
+
+func init() { gob.Register(new(MountTmpfs)) }
+
+// MountTmpfs mounts tmpfs on container Path.
+type MountTmpfs struct {
+ Path string
+ Size int
+ Mode os.FileMode
+}
+
+func (t *MountTmpfs) apply() error {
+ if !path.IsAbs(t.Path) {
+ return fmsg.WrapError(syscall.EBADE,
+ fmt.Sprintf("path %q is not absolute", t.Path))
+ }
+ if t.Size < 0 || t.Size > math.MaxUint>>1 {
+ return fmsg.WrapError(syscall.EBADE,
+ fmt.Sprintf("size %d out of bounds", t.Size))
+ }
+ target := toSysroot(t.Path)
+ if err := os.MkdirAll(target, 0755); err != nil {
+ return err
+ }
+ opt := fmt.Sprintf("mode=%#o", t.Mode)
+ if t.Size > 0 {
+ opt += fmt.Sprintf(",size=%d", t.Mode)
+ }
+ return fmsg.WrapErrorSuffix(syscall.Mount("tmpfs", target, "tmpfs",
+ syscall.MS_NOSUID|syscall.MS_NODEV, opt),
+ fmt.Sprintf("cannot mount tmpfs on %q:", t.Path))
+}
+
+func (t *MountTmpfs) Is(op Op) bool { vt, ok := op.(*MountTmpfs); return ok && *t == *vt }
+func (t *MountTmpfs) String() string { return fmt.Sprintf("tmpfs on %q size %d", t.Path, t.Size) }
+func (f *Ops) Tmpfs(dest string, size int, mode os.FileMode) *Ops {
+ *f = append(*f, &MountTmpfs{dest, size, mode})
+ return f
+}