From a81a4402a265aec6715172cd3502ee7eebbf64aa Mon Sep 17 00:00:00 2001 From: Fabricio Voznika Date: Fri, 24 Aug 2018 10:16:38 -0700 Subject: Add option to panic gofer if writes are attempted over RO mounts This is used when '--overlay=true' to guarantee writes are not sent to gofer. PiperOrigin-RevId: 210116288 Change-Id: I7616008c4c0e8d3668e07a205207f46e2144bf30 --- runsc/cmd/gofer.go | 7 ++++++- runsc/fsgofer/fsgofer.go | 24 ++++++++++++++++++++++++ runsc/fsgofer/fsgofer_test.go | 25 +++++++++++++++++++++++++ runsc/sandbox/sandbox.go | 4 ++++ 4 files changed, 59 insertions(+), 1 deletion(-) diff --git a/runsc/cmd/gofer.go b/runsc/cmd/gofer.go index ed4b1d29c..e23f64d12 100644 --- a/runsc/cmd/gofer.go +++ b/runsc/cmd/gofer.go @@ -38,6 +38,8 @@ type Gofer struct { // controllerFD is the file descriptor of a stream socket for the // control server that is donated to this process. controllerFD int + + panicOnWrite bool } // Name implements subcommands.Command. @@ -61,6 +63,7 @@ func (g *Gofer) SetFlags(f *flag.FlagSet) { f.Var(&g.ioFDs, "io-fds", "list of FDs to connect 9P servers. They must follow this order: root first, then mounts as defined in the spec") f.BoolVar(&g.applyCaps, "apply-caps", true, "if true, apply capabilities to restrict what the Gofer process can do") f.IntVar(&g.controllerFD, "controller-fd", -1, "required FD of a stream socket for the control server that must be donated to this process") + f.BoolVar(&g.panicOnWrite, "panic-on-write", false, "if true, panics on attempts to write to RO mounts. RW mounts are unnaffected") } // Execute implements subcommands.Command. @@ -110,7 +113,8 @@ func (g *Gofer) Execute(_ context.Context, f *flag.FlagSet, args ...interface{}) ats := make([]p9.Attacher, 0, len(spec.Mounts)+1) p := absPath(g.bundleDir, spec.Root.Path) ats = append(ats, fsgofer.NewAttachPoint(p, fsgofer.Config{ - ROMount: spec.Root.Readonly, + ROMount: spec.Root.Readonly, + PanicOnWrite: g.panicOnWrite, // Docker uses overlay2 by default for the root mount, and overlay2 does a copy-up when // each file is opened as writable. Thus, we open files lazily to avoid copy-up. LazyOpenForWrite: true, @@ -123,6 +127,7 @@ func (g *Gofer) Execute(_ context.Context, f *flag.FlagSet, args ...interface{}) p = absPath(g.bundleDir, m.Source) ats = append(ats, fsgofer.NewAttachPoint(p, fsgofer.Config{ ROMount: isReadonlyMount(m.Options), + PanicOnWrite: g.panicOnWrite, LazyOpenForWrite: false, })) diff --git a/runsc/fsgofer/fsgofer.go b/runsc/fsgofer/fsgofer.go index 38263896a..1316dc618 100644 --- a/runsc/fsgofer/fsgofer.go +++ b/runsc/fsgofer/fsgofer.go @@ -76,6 +76,9 @@ type Config struct { // ROMount is set to true if this is a readonly mount. ROMount bool + // PanicOnWrite panics on attempts to write to RO mounts. + PanicOnWrite bool + // LazyOpenForWrite makes the underlying file to be opened in RDONLY // mode initially and be reopened in case write access is desired. // This is done to workaround the behavior in 'overlay2' that @@ -375,6 +378,9 @@ func (l *localFile) Open(mode p9.OpenFlags) (*fd.FD, p9.QID, uint32, error) { // Create implements p9.File. func (l *localFile) Create(name string, mode p9.OpenFlags, perm p9.FileMode, uid p9.UID, gid p9.GID) (*fd.FD, p9.File, p9.QID, uint32, error) { if l.conf.ROMount { + if l.conf.PanicOnWrite { + panic("attempt to write to RO mount") + } return nil, nil, p9.QID{}, 0, syscall.EBADF } if !isNameValid(name) { @@ -429,6 +435,9 @@ func (l *localFile) Create(name string, mode p9.OpenFlags, perm p9.FileMode, uid // Mkdir implements p9.File. func (l *localFile) Mkdir(name string, perm p9.FileMode, uid p9.UID, gid p9.GID) (p9.QID, error) { if l.conf.ROMount { + if l.conf.PanicOnWrite { + panic("attempt to write to RO mount") + } return p9.QID{}, syscall.EBADF } @@ -585,6 +594,9 @@ func (l *localFile) GetAttr(_ p9.AttrMask) (p9.QID, p9.AttrMask, p9.Attr, error) // an error happens. func (l *localFile) SetAttr(valid p9.SetAttrMask, attr p9.SetAttr) error { if l.conf.ROMount { + if l.conf.PanicOnWrite { + panic("attempt to write to RO mount") + } return syscall.EBADF } @@ -722,6 +734,9 @@ func (*localFile) Remove() error { // Rename implements p9.File. func (l *localFile) Rename(directory p9.File, name string) error { if l.conf.ROMount { + if l.conf.PanicOnWrite { + panic("attempt to write to RO mount") + } return syscall.EBADF } if !isNameValid(name) { @@ -789,6 +804,9 @@ func (l *localFile) WriteAt(p []byte, offset uint64) (int, error) { // Symlink implements p9.File. func (l *localFile) Symlink(target, newName string, uid p9.UID, gid p9.GID) (p9.QID, error) { if l.conf.ROMount { + if l.conf.PanicOnWrite { + panic("attempt to write to RO mount") + } return p9.QID{}, syscall.EBADF } if !isNameValid(newName) { @@ -819,6 +837,9 @@ func (l *localFile) Symlink(target, newName string, uid p9.UID, gid p9.GID) (p9. // Link implements p9.File. func (l *localFile) Link(target p9.File, newName string) error { if l.conf.ROMount { + if l.conf.PanicOnWrite { + panic("attempt to write to RO mount") + } return syscall.EBADF } if !isNameValid(newName) { @@ -842,6 +863,9 @@ func (*localFile) Mknod(_ string, _ p9.FileMode, _ uint32, _ uint32, _ p9.UID, _ // UnlinkAt implements p9.File. func (l *localFile) UnlinkAt(name string, flags uint32) error { if l.conf.ROMount { + if l.conf.PanicOnWrite { + panic("attempt to write to RO mount") + } return syscall.EBADF } if !isNameValid(name) { diff --git a/runsc/fsgofer/fsgofer_test.go b/runsc/fsgofer/fsgofer_test.go index 8d038eaf6..fcece4e83 100644 --- a/runsc/fsgofer/fsgofer_test.go +++ b/runsc/fsgofer/fsgofer_test.go @@ -34,6 +34,15 @@ func init() { allConfs = append(allConfs, roConfs...) } +func assertPanic(t *testing.T, f func()) { + defer func() { + if r := recover(); r == nil { + t.Errorf("function did not panic") + } + }() + f() +} + var ( allTypes = []fileType{regular, directory, symlink} @@ -434,6 +443,22 @@ func TestROMountChecks(t *testing.T) { }) } +func TestROMountPanics(t *testing.T) { + conf := Config{ROMount: true, PanicOnWrite: true} + runCustom(t, allTypes, []Config{conf}, func(t *testing.T, s state) { + assertPanic(t, func() { s.file.Create("..", p9.ReadWrite, 0777, p9.UID(os.Getuid()), p9.GID(os.Getgid())) }) + assertPanic(t, func() { s.file.Mkdir("..", 0777, p9.UID(os.Getuid()), p9.GID(os.Getgid())) }) + assertPanic(t, func() { s.file.Rename(s.file, "..") }) + assertPanic(t, func() { s.file.Symlink("some_place", "..", p9.UID(os.Getuid()), p9.GID(os.Getgid())) }) + assertPanic(t, func() { s.file.UnlinkAt("..", 0) }) + assertPanic(t, func() { s.file.Link(s.file, "..") }) + + valid := p9.SetAttrMask{Size: true} + attr := p9.SetAttr{Size: 0} + assertPanic(t, func() { s.file.SetAttr(valid, attr) }) + }) +} + func TestInvalidName(t *testing.T) { runCustom(t, []fileType{regular}, rwConfs, func(t *testing.T, s state) { if _, _, _, _, err := s.file.Create("..", p9.ReadWrite, 0777, p9.UID(os.Getuid()), p9.GID(os.Getgid())); err != syscall.EINVAL { diff --git a/runsc/sandbox/sandbox.go b/runsc/sandbox/sandbox.go index e5d1f791d..7789608f8 100644 --- a/runsc/sandbox/sandbox.go +++ b/runsc/sandbox/sandbox.go @@ -345,6 +345,10 @@ func (s *Sandbox) createGoferProcess(spec *specs.Spec, conf *boot.Config, bundle setUIDGIDMappings(cmd, spec) nss := filterNS([]specs.LinuxNamespaceType{specs.UserNamespace}, spec) + if conf.Overlay { + args = append(args, "--panic-on-write=true") + } + // Start the gofer in the given namespace. log.Debugf("Starting gofer: %s %v", binPath, args) if err := startInNS(cmd, nss); err != nil { -- cgit v1.2.3