sandbox: apply vfs options to bind mounts
All checks were successful
Test / Create distribution (push) Successful in 25s
Test / Fortify (push) Successful in 2m41s
Test / Fpkg (push) Successful in 3m45s
Test / Data race detector (push) Successful in 4m11s
Test / Flake checks (push) Successful in 56s

Signed-off-by: Ophestra <cat@gensokyo.uk>
This commit is contained in:
Ophestra 2025-03-23 05:27:57 +09:00
parent 8c3a817881
commit c638193268
Signed by: cat
SSH Key Fingerprint: SHA256:gQ67O0enBZ7UdZypgtspB2FDM1g3GVw8nX0XSdcFw8Q
3 changed files with 108 additions and 11 deletions

View File

@ -111,7 +111,7 @@ func TestContainer(t *testing.T) {
mnt = append(mnt,
&check.Mntent{FSName: "tmpfs", Dir: "/tmp", Type: "tmpfs", Opts: "host_passthrough"},
&check.Mntent{FSName: "\x00", Dir: os.Args[0], Type: "\x00", Opts: "\x00"},
&check.Mntent{FSName: "rootfs", Dir: "/etc/hostname", Type: "tmpfs", Opts: "host_passthrough"},
&check.Mntent{FSName: "rootfs", Dir: "/etc/hostname", Type: "tmpfs", Opts: "\x00"},
)
for _, name := range libPaths {
mnt = append(mnt, &check.Mntent{FSName: "\x00", Dir: name, Type: "\x00", Opts: "\x00", Freq: -1, Passno: -1})

View File

@ -1,22 +1,102 @@
package sandbox
import (
"errors"
"fmt"
"os"
"path/filepath"
"syscall"
"git.gensokyo.uk/security/fortify/sandbox/vfs"
)
func (p *procPaths) bindMount(source, target string, flags uintptr, eq bool) error {
var mf uintptr = syscall.MS_SILENT | syscall.MS_BIND
mf |= flags & syscall.MS_REC
if eq {
msg.Verbosef("resolved %q flags %#x", target, mf)
msg.Verbosef("resolved %q flags %#x", target, flags)
} else {
msg.Verbosef("resolved %q on %q flags %#x", source, target, mf)
msg.Verbosef("resolved %q on %q flags %#x", source, target, flags)
}
return wrapErrSuffix(syscall.Mount(source, target, "", mf, ""),
fmt.Sprintf("cannot mount %q on %q:", source, target))
if err := syscall.Mount(source, target, "",
syscall.MS_SILENT|syscall.MS_BIND|flags&syscall.MS_REC, ""); err != nil {
return wrapErrSuffix(err,
fmt.Sprintf("cannot mount %q on %q:", source, target))
}
var targetFinal string
if v, err := filepath.EvalSymlinks(target); err != nil {
return msg.WrapErr(err, err.Error())
} else {
targetFinal = v
if targetFinal != target {
msg.Verbosef("target resolves to %q", targetFinal)
}
}
// final target path according to the kernel through proc
var targetKFinal string
{
var destFd int
if err := IgnoringEINTR(func() (err error) {
destFd, err = syscall.Open(targetFinal, O_PATH|syscall.O_CLOEXEC, 0)
return
}); err != nil {
return wrapErrSuffix(err,
fmt.Sprintf("cannot open %q:", targetFinal))
}
if v, err := os.Readlink(p.fd(destFd)); err != nil {
return msg.WrapErr(err, err.Error())
} else if err = syscall.Close(destFd); err != nil {
return wrapErrSuffix(err,
fmt.Sprintf("cannot close %q:", targetFinal))
} else {
targetKFinal = v
}
}
mf := syscall.MS_NOSUID | flags&syscall.MS_NODEV | flags&syscall.MS_RDONLY
return hostProc.mountinfo(func(d *vfs.MountInfoDecoder) error {
n, err := d.Unfold(targetKFinal)
if err != nil {
if errors.Is(err, syscall.ESTALE) {
return msg.WrapErr(err,
fmt.Sprintf("mount point %q never appeared in mountinfo", targetKFinal))
}
return wrapErrSuffix(err,
"cannot unfold mount hierarchy:")
}
if err = remountWithFlags(n, mf); err != nil {
return err
}
if flags&syscall.MS_REC == 0 {
return nil
}
for cur := range n.Collective() {
err = remountWithFlags(cur, mf)
if err != nil && !errors.Is(err, syscall.EACCES) {
return err
}
}
return nil
})
}
func remountWithFlags(n *vfs.MountInfoNode, mf uintptr) error {
kf, unmatched := n.Flags()
if len(unmatched) != 0 {
msg.Verbosef("unmatched vfs options: %q", unmatched)
}
if kf&mf != mf {
return wrapErrSuffix(syscall.Mount("none", n.Clean, "",
syscall.MS_SILENT|syscall.MS_BIND|syscall.MS_REMOUNT|kf|mf,
""),
fmt.Sprintf("cannot remount %q:", n.Clean))
}
return nil
}
func mountTmpfs(fsname, name string, size int, perm os.FileMode) error {

View File

@ -9,6 +9,8 @@ import (
"strconv"
"strings"
"syscall"
"git.gensokyo.uk/security/fortify/sandbox/vfs"
)
const (
@ -64,14 +66,29 @@ func ensureFile(name string, perm os.FileMode) error {
var hostProc = newProcPats(hostPath)
func newProcPats(prefix string) *procPaths {
return &procPaths{prefix, prefix + "/self", prefix + "/self/mountinfo"}
return &procPaths{prefix + "/proc", prefix + "/proc/self"}
}
type procPaths struct {
prefix string
self string
mountinfo string
prefix string
self string
}
func (p *procPaths) stdout() string { return p.self + "/fd/1" }
func (p *procPaths) fd(fd int) string { return p.self + "/fd/" + strconv.Itoa(fd) }
func (p *procPaths) mountinfo(f func(d *vfs.MountInfoDecoder) error) error {
if r, err := os.Open(p.self + "/mountinfo"); err != nil {
return msg.WrapErr(err, err.Error())
} else {
d := vfs.NewMountInfoDecoder(r)
err0 := f(d)
if err = r.Close(); err != nil {
return wrapErrSuffix(err,
"cannot close mountinfo:")
} else if err = d.Err(); err != nil {
return wrapErrSuffix(err,
"cannot parse mountinfo:")
}
return err0
}
}