From b74a08dda968a161e066c92c1bf5b4489e5a4fbd Mon Sep 17 00:00:00 2001 From: Ophestra Date: Tue, 18 Mar 2025 02:17:46 +0900 Subject: [PATCH] sandbox: prepare ops early Some setup code needs to run in host root. This change allows that to happen. Signed-off-by: Ophestra --- sandbox/container.go | 1 + sandbox/init.go | 20 ++++++++++++++---- sandbox/mount.go | 39 +++++++++++++++------------------- sandbox/path.go | 19 ----------------- sandbox/sequential.go | 49 ++++++++++++++++++++++++++++++++++++++----- 5 files changed, 78 insertions(+), 50 deletions(-) diff --git a/sandbox/container.go b/sandbox/container.go index f45e231..31da929 100644 --- a/sandbox/container.go +++ b/sandbox/container.go @@ -101,6 +101,7 @@ type ( Ops []Op Op interface { + early(params *Params) error apply(params *Params) error prefix() string diff --git a/sandbox/init.go b/sandbox/init.go index 05d6623..cf7fd1b 100644 --- a/sandbox/init.go +++ b/sandbox/init.go @@ -98,6 +98,7 @@ func Init(prepare func(prefix string), setVerbose func(verbose bool)) { log.Fatalf("cannot set SUID_DUMP_DISABLE: %s", err) } + oldmask := syscall.Umask(0) if params.Hostname != "" { if err := syscall.Sethostname([]byte(params.Hostname)); err != nil { log.Fatalf("cannot set hostname: %v", err) @@ -114,6 +115,19 @@ func Init(prepare func(prefix string), setVerbose func(verbose bool)) { log.Fatalf("cannot make / rslave: %v", err) } + for i, op := range *params.Ops { + if op == nil { + log.Fatalf("invalid op %d", i) + } + + if err := op.early(¶ms.Params); err != nil { + msg.PrintBaseErr(err, + fmt.Sprintf("cannot prepare op %d:", i)) + msg.BeforeExit() + os.Exit(1) + } + } + if err := syscall.Mount("rootfs", basePath, "tmpfs", syscall.MS_NODEV|syscall.MS_NOSUID, ""); err != nil { @@ -143,10 +157,7 @@ func Init(prepare func(prefix string), setVerbose func(verbose bool)) { } for i, op := range *params.Ops { - if op == nil { - log.Fatalf("invalid op %d", i) - } - + // ops already checked during early setup msg.Verbosef("%s %s", op.prefix(), op) if err := op.apply(¶ms.Params); err != nil { msg.PrintBaseErr(err, @@ -220,6 +231,7 @@ func Init(prepare func(prefix string), setVerbose func(verbose bool)) { for i := range extraFiles { extraFiles[i] = os.NewFile(uintptr(offsetSetup+i), "extra file "+strconv.Itoa(i)) } + syscall.Umask(oldmask) /* prepare initial process diff --git a/sandbox/mount.go b/sandbox/mount.go index 216a585..dfe9f03 100644 --- a/sandbox/mount.go +++ b/sandbox/mount.go @@ -14,6 +14,7 @@ const ( BindWritable BindDevice + bindResolved bindAbsolute bindRecursive ) @@ -22,26 +23,21 @@ func bindMount(src, dest string, flags int) error { target := toSysroot(dest) var source string - if flags&BindSource == 0 { - // this is what bwrap does, so the behaviour is kept for now, - // however recursively resolving links might improve user experience - if rp, err := realpathHost(src); err != nil { - if os.IsNotExist(err) { - if flags&BindOptional != 0 { - return nil - } else { - return msg.WrapErr(err, - fmt.Sprintf("path %q does not exist", src)) - } - } - return msg.WrapErr(err, err.Error()) - } else { - source = toHost(rp) + 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") } - } else if flags&BindOptional != 0 { - return msg.WrapErr(syscall.EINVAL, - "flag source excludes optional") - } else if flags&bindAbsolute != 0 { source = src } else { source = toHost(src) @@ -51,8 +47,7 @@ func bindMount(src, dest string, flags int) error { return msg.WrapErr(err, err.Error()) } else if fi.IsDir() { if err = os.MkdirAll(target, 0755); err != nil { - return wrapErrSuffix(err, - fmt.Sprintf("cannot create directory %q:", dest)) + return msg.WrapErr(err, err.Error()) } } else if err = ensureFile(target, 0444); err != nil { if errors.Is(err, syscall.EISDIR) { @@ -87,7 +82,7 @@ func bindMount(src, dest string, flags int) error { func mountTmpfs(fsname, name string, size int, perm os.FileMode) error { target := toSysroot(name) if err := os.MkdirAll(target, perm); err != nil { - return err + return msg.WrapErr(err, err.Error()) } opt := fmt.Sprintf("mode=%#o", perm) if size > 0 { diff --git a/sandbox/path.go b/sandbox/path.go index 6c5a6e2..4057748 100644 --- a/sandbox/path.go +++ b/sandbox/path.go @@ -26,25 +26,6 @@ func toHost(name string) string { return path.Join(hostPath, name) } -func realpathHost(name string) (string, error) { - source := toHost(name) - rp, err := os.Readlink(source) - - if err != nil { - if errors.Is(err, syscall.EINVAL) { - // not a symlink - return name, nil - } - return "", err - } - - if !path.IsAbs(rp) { - return name, nil - } - msg.Verbosef("path %q resolves to %q", name, rp) - return rp, nil -} - func createFile(name string, perm os.FileMode, content []byte) error { if err := os.MkdirAll(path.Dir(name), 0755); err != nil { return err diff --git a/sandbox/sequential.go b/sandbox/sequential.go index 153de0a..bfcbb06 100644 --- a/sandbox/sequential.go +++ b/sandbox/sequential.go @@ -6,6 +6,7 @@ import ( "math" "os" "path" + "path/filepath" "slices" "syscall" "unsafe" @@ -15,17 +16,48 @@ func init() { gob.Register(new(BindMount)) } // BindMount bind mounts host path Source on container path Target. type BindMount struct { - Source, Target string + Source, SourceFinal, Target string Flags int } +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" + return nil + } + return msg.WrapErr(err, err.Error()) + } else { + b.SourceFinal = v + b.Flags |= bindResolved + return nil + } +} + func (b *BindMount) apply(*Params) error { - if !path.IsAbs(b.Source) || !path.IsAbs(b.Target) { + if b.SourceFinal == "\x00" { + if b.Flags&BindOptional == 0 { + // unreachable + return syscall.EBADE + } + return nil + } + + if !path.IsAbs(b.SourceFinal) || !path.IsAbs(b.Target) { return msg.WrapErr(syscall.EBADE, "path is not absolute") } - return bindMount(b.Source, b.Target, b.Flags) + return bindMount(b.SourceFinal, b.Target, b.Flags) } func (b *BindMount) Is(op Op) bool { vb, ok := op.(*BindMount); return ok && *b == *vb } @@ -37,7 +69,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 | bindRecursive}) return f } @@ -46,6 +78,7 @@ func init() { gob.Register(new(MountProc)) } // MountProc mounts a private instance of proc. type MountProc string +func (p MountProc) early(*Params) error { return nil } func (p MountProc) apply(*Params) error { v := string(p) @@ -76,6 +109,7 @@ func init() { gob.Register(new(MountDev)) } // MountDev mounts part of host dev. type MountDev string +func (d MountDev) early(*Params) error { return nil } func (d MountDev) apply(params *Params) error { v := string(d) @@ -135,7 +169,7 @@ 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"), BindDevice); err != nil { + if err := bindMount("/proc/self/fd/1", path.Join(v, "console"), BindSource|BindDevice); err != nil { return err } } @@ -157,6 +191,7 @@ func init() { gob.Register(new(MountMqueue)) } // MountMqueue mounts a private mqueue instance on container Path. type MountMqueue string +func (m MountMqueue) early(*Params) error { return nil } func (m MountMqueue) apply(*Params) error { v := string(m) @@ -191,6 +226,7 @@ type MountTmpfs struct { Perm os.FileMode } +func (t *MountTmpfs) early(*Params) error { return nil } func (t *MountTmpfs) apply(*Params) error { if !path.IsAbs(t.Path) { return msg.WrapErr(syscall.EBADE, @@ -216,6 +252,7 @@ func init() { gob.Register(new(Symlink)) } // Symlink creates a symlink in the container filesystem. type Symlink [2]string +func (l *Symlink) early(*Params) error { return nil } func (l *Symlink) apply(*Params) error { // symlink target is an arbitrary path value, so only validate link name here if !path.IsAbs(l[1]) { @@ -241,6 +278,7 @@ func init() { gob.Register(new(Mkdir)) } // Mkdir creates a directory in the container filesystem. type Mkdir string +func (m Mkdir) early(*Params) error { return nil } func (m Mkdir) apply(*Params) error { v := string(m) @@ -272,6 +310,7 @@ type Tmpfile struct { Data []byte } +func (t *Tmpfile) early(*Params) error { return nil } func (t *Tmpfile) apply(*Params) error { if !path.IsAbs(t.Path) { return msg.WrapErr(syscall.EBADE,