diff options
| author | Ophestra <cat@gensokyo.uk> | 2025-03-13 20:59:03 +0900 |
|---|---|---|
| committer | Ophestra <cat@gensokyo.uk> | 2025-03-13 21:36:26 +0900 |
| commit | 9b1a60b5c9f7a92abb632639117d61c5cbaf74f7 (patch) | |
| tree | 0fa6b8dae30a9a687f0e465c7d0fc5e0f24b48b1 /internal/sandbox/sequential.go | |
| parent | beb3918809f0743e2275f6a3f358f622cea73d6b (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.go | 169 |
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 +} |
