aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorOphestra <cat@gensokyo.uk>2025-03-23 05:27:57 +0900
committerOphestra <cat@gensokyo.uk>2025-03-23 05:27:57 +0900
commitc6381932684a45aff6c187d53e5568bf52444457 (patch)
treeb837c39d86d8b65973a6b088987cbb507028b9fd
parent8c3a81788188bbafbd824abbd1efe91e79a5e21e (diff)
sandbox: apply vfs options to bind mounts
Signed-off-by: Ophestra <cat@gensokyo.uk>
-rw-r--r--sandbox/container_test.go2
-rw-r--r--sandbox/mount.go92
-rw-r--r--sandbox/path.go25
3 files changed, 108 insertions, 11 deletions
diff --git a/sandbox/container_test.go b/sandbox/container_test.go
index c8bdcd0e..13063600 100644
--- a/sandbox/container_test.go
+++ b/sandbox/container_test.go
@@ -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})
diff --git a/sandbox/mount.go b/sandbox/mount.go
index bb5d6135..6683aaec 100644
--- a/sandbox/mount.go
+++ b/sandbox/mount.go
@@ -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)
+ }
+
+ 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
+ }
}
- return wrapErrSuffix(syscall.Mount(source, target, "", mf, ""),
- fmt.Sprintf("cannot mount %q on %q:", source, target))
+ 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 {
diff --git a/sandbox/path.go b/sandbox/path.go
index 5a28bdf9..78c5a995 100644
--- a/sandbox/path.go
+++ b/sandbox/path.go
@@ -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
+ }
+}