From a70daf22504f80c4e3dfbc2f34cf95db4dbb5879 Mon Sep 17 00:00:00 2001 From: Ophestra Date: Fri, 21 Mar 2025 12:58:38 +0900 Subject: [PATCH] sandbox: resolve inverted flags in op Signed-off-by: Ophestra --- sandbox/const.go | 6 ---- sandbox/mount.go | 76 +++++-------------------------------------- sandbox/path.go | 27 +++++++++++++-- sandbox/sequential.go | 69 ++++++++++++++++++++++++++++++++------- sandbox/syscall.go | 6 ++++ 5 files changed, 95 insertions(+), 89 deletions(-) delete mode 100644 sandbox/const.go diff --git a/sandbox/const.go b/sandbox/const.go deleted file mode 100644 index afced96..0000000 --- a/sandbox/const.go +++ /dev/null @@ -1,6 +0,0 @@ -package sandbox - -const ( - PR_SET_NO_NEW_PRIVS = 0x26 - CAP_SYS_ADMIN = 0x15 -) diff --git a/sandbox/mount.go b/sandbox/mount.go index dfe9f03..bb5d613 100644 --- a/sandbox/mount.go +++ b/sandbox/mount.go @@ -1,82 +1,22 @@ package sandbox import ( - "errors" "fmt" "os" - "strings" "syscall" ) -const ( - BindOptional = 1 << iota - BindSource - BindWritable - BindDevice - - bindResolved - bindAbsolute - bindRecursive -) - -func bindMount(src, dest string, flags int) error { - target := toSysroot(dest) - var source string - - if flags&BindSource != 0 { - if flags&BindOptional != 0 { - return msg.WrapErr(syscall.EINVAL, - "flag source excludes optional") - } - } else if flags&bindResolved == 0 { - return msg.WrapErr(syscall.EBADE, - "flag source must be set on direct bind call") - } - - if flags&bindAbsolute != 0 { - if flags&BindSource == 0 { - return msg.WrapErr(syscall.EINVAL, - "flag absolute implies source") - } - source = src - } else { - source = toHost(src) - } - - if fi, err := os.Stat(source); err != nil { - return msg.WrapErr(err, err.Error()) - } else if fi.IsDir() { - if err = os.MkdirAll(target, 0755); err != nil { - return msg.WrapErr(err, err.Error()) - } - } else if err = ensureFile(target, 0444); err != nil { - if errors.Is(err, syscall.EISDIR) { - return msg.WrapErr(err, - fmt.Sprintf("path %q is a directory", dest)) - } - return wrapErrSuffix(err, - fmt.Sprintf("cannot create %q:", dest)) - } - +func (p *procPaths) bindMount(source, target string, flags uintptr, eq bool) error { var mf uintptr = syscall.MS_SILENT | syscall.MS_BIND - if flags&bindRecursive != 0 { - mf |= syscall.MS_REC - } - if flags&BindWritable == 0 { - mf |= syscall.MS_RDONLY - } - if flags&BindDevice == 0 { - mf |= syscall.MS_NODEV - } - if msg.IsVerbose() { - if strings.TrimPrefix(source, hostPath) == strings.TrimPrefix(target, sysrootPath) { - msg.Verbosef("resolved %q flags %#x", target, mf) - } else { - msg.Verbosef("resolved %q on %q flags %#x", source, target, mf) - } + mf |= flags & syscall.MS_REC + if eq { + msg.Verbosef("resolved %q flags %#x", target, mf) + } else { + msg.Verbosef("resolved %q on %q flags %#x", source, target, mf) } + return wrapErrSuffix(syscall.Mount(source, target, "", mf, ""), - fmt.Sprintf("cannot bind %q on %q:", src, dest)) + fmt.Sprintf("cannot mount %q on %q:", source, target)) } func mountTmpfs(fsname, name string, size int, perm os.FileMode) error { diff --git a/sandbox/path.go b/sandbox/path.go index 4057748..5a28bdf 100644 --- a/sandbox/path.go +++ b/sandbox/path.go @@ -2,9 +2,11 @@ package sandbox import ( "errors" + "fmt" "io/fs" "os" "path" + "strconv" "strings" "syscall" ) @@ -28,14 +30,17 @@ func toHost(name string) string { func createFile(name string, perm os.FileMode, content []byte) error { if err := os.MkdirAll(path.Dir(name), 0755); err != nil { - return err + return msg.WrapErr(err, err.Error()) } f, err := os.OpenFile(name, syscall.O_CREAT|syscall.O_EXCL|syscall.O_WRONLY, perm) if err != nil { - return err + return msg.WrapErr(err, err.Error()) } if content != nil { _, err = f.Write(content) + if err != nil { + err = msg.WrapErr(err, err.Error()) + } } return errors.Join(f.Close(), err) } @@ -50,7 +55,23 @@ func ensureFile(name string, perm os.FileMode) error { } if mode := fi.Mode(); mode&fs.ModeDir != 0 || mode&fs.ModeSymlink != 0 { - err = syscall.EISDIR + err = msg.WrapErr(syscall.EISDIR, + fmt.Sprintf("path %q is a directory", name)) } return err } + +var hostProc = newProcPats(hostPath) + +func newProcPats(prefix string) *procPaths { + return &procPaths{prefix, prefix + "/self", prefix + "/self/mountinfo"} +} + +type procPaths struct { + prefix string + self string + mountinfo 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) } diff --git a/sandbox/sequential.go b/sandbox/sequential.go index bfcbb06..4af38d0 100644 --- a/sandbox/sequential.go +++ b/sandbox/sequential.go @@ -21,16 +21,18 @@ type BindMount struct { Flags int } +const ( + BindOptional = 1 << iota + BindWritable + BindDevice +) + func (b *BindMount) early(*Params) error { if !path.IsAbs(b.Source) { return msg.WrapErr(syscall.EBADE, fmt.Sprintf("path %q is not absolute", b.Source)) } - if b.Flags&BindSource != 0 { - b.SourceFinal = b.Source - return nil - } if v, err := filepath.EvalSymlinks(b.Source); err != nil { if os.IsNotExist(err) && b.Flags&BindOptional != 0 { b.SourceFinal = "\x00" @@ -39,7 +41,6 @@ func (b *BindMount) early(*Params) error { return msg.WrapErr(err, err.Error()) } else { b.SourceFinal = v - b.Flags |= bindResolved return nil } } @@ -57,7 +58,28 @@ func (b *BindMount) apply(*Params) error { return msg.WrapErr(syscall.EBADE, "path is not absolute") } - return bindMount(b.SourceFinal, b.Target, b.Flags) + + source := toHost(b.SourceFinal) + target := toSysroot(b.Target) + if fi, err := os.Stat(source); err != nil { + return msg.WrapErr(err, err.Error()) + } else if fi.IsDir() { + if err = os.MkdirAll(target, 0755); err != nil { + return msg.WrapErr(err, err.Error()) + } + } else if err = ensureFile(target, 0444); err != nil { + return err + } + + var flags uintptr = syscall.MS_REC + if b.Flags&BindWritable == 0 { + flags |= syscall.MS_RDONLY + } + if b.Flags&BindDevice == 0 { + flags |= syscall.MS_NODEV + } + + return hostProc.bindMount(source, target, flags, b.SourceFinal == b.Target) } func (b *BindMount) Is(op Op) bool { vb, ok := op.(*BindMount); return ok && *b == *vb } @@ -69,7 +91,7 @@ func (b *BindMount) String() string { 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}) + *f = append(*f, &BindMount{source, "", target, flags}) return f } @@ -124,9 +146,15 @@ func (d MountDev) apply(params *Params) error { } for _, name := range []string{"null", "zero", "full", "random", "urandom", "tty"} { - if err := bindMount( - "/dev/"+name, path.Join(v, name), - BindSource|BindDevice, + targetPath := toSysroot(path.Join(v, name)) + if err := ensureFile(targetPath, 0444); err != nil { + return err + } + if err := hostProc.bindMount( + toHost("/dev/"+name), + targetPath, + syscall.MS_RDONLY, + true, ); err != nil { return err } @@ -169,7 +197,16 @@ func (d MountDev) apply(params *Params) error { syscall.SYS_IOCTL, 1, syscall.TIOCGWINSZ, uintptr(unsafe.Pointer(&buf[0])), ); errno == 0 { - if err := bindMount("/proc/self/fd/1", path.Join(v, "console"), BindSource|BindDevice); err != nil { + consolePath := toSysroot(path.Join(v, "console")) + if err := ensureFile(consolePath, 0444); err != nil { + return err + } + if err := hostProc.bindMount( + hostProc.stdout(), + consolePath, + syscall.MS_RDONLY, + false, + ); err != nil { return err } } @@ -330,7 +367,15 @@ func (t *Tmpfile) apply(*Params) error { tmpPath = f.Name() } - if err := bindMount(tmpPath, t.Path, BindSource|bindAbsolute); err != nil { + target := toSysroot(t.Path) + if err := ensureFile(target, 0444); err != nil { + return err + } else if err = hostProc.bindMount( + tmpPath, + target, + syscall.MS_RDONLY|syscall.MS_NODEV, + false, + ); err != nil { return err } else if err = os.Remove(tmpPath); err != nil { return msg.WrapErr(err, err.Error()) diff --git a/sandbox/syscall.go b/sandbox/syscall.go index a17928f..dd1679f 100644 --- a/sandbox/syscall.go +++ b/sandbox/syscall.go @@ -2,6 +2,12 @@ package sandbox import "syscall" +const ( + O_PATH = 0x200000 + PR_SET_NO_NEW_PRIVS = 0x26 + CAP_SYS_ADMIN = 0x15 +) + const ( SUID_DUMP_DISABLE = iota SUID_DUMP_USER