Confine package ownership changes to private mounts
This commit is contained in:
parent
2c420012bd
commit
8334fd379f
1 changed files with 402 additions and 25 deletions
167
patches/gvisor-9f8f6c917-private-ownership.patch
Normal file
167
patches/gvisor-9f8f6c917-private-ownership.patch
Normal file
|
|
@ -0,0 +1,167 @@
|
|||
diff --git a/runsc/cmd/gofer.go b/runsc/cmd/gofer.go
|
||||
index 2cfbae6..8d7bc35 100644
|
||||
--- a/runsc/cmd/gofer.go
|
||||
+++ b/runsc/cmd/gofer.go
|
||||
@@ -16,6 +16,7 @@ package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
+ "encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@@ -42,6 +43,8 @@ import (
|
||||
"gvisor.dev/gvisor/runsc/specutils"
|
||||
)
|
||||
|
||||
+const ownerChangeMountsAnnotation = "dev.lazaret.runsc.owner-change-mounts"
|
||||
+
|
||||
var caps = []string{
|
||||
"CAP_CHOWN",
|
||||
"CAP_DAC_OVERRIDE",
|
||||
@@ -398,6 +401,30 @@ func (g *Gofer) serve(spec *specs.Spec, conf *config.Config, root string, ruid i
|
||||
log.Infof("Serving /dev mapped on FD %d (ro: false)", g.devIoFD)
|
||||
}
|
||||
|
||||
+ servedMounts := make(map[string]bool, len(cfgs))
|
||||
+ for _, cfg := range cfgs {
|
||||
+ servedMounts[cfg.mountPath] = true
|
||||
+ }
|
||||
+ ownerChangeMounts := make(map[string]bool)
|
||||
+ if encoded, ok := spec.Annotations[ownerChangeMountsAnnotation]; ok {
|
||||
+ var destinations []string
|
||||
+ if err := json.Unmarshal([]byte(encoded), &destinations); err != nil {
|
||||
+ util.Fatalf("parsing %s annotation: %v", ownerChangeMountsAnnotation, err)
|
||||
+ }
|
||||
+ for _, destination := range destinations {
|
||||
+ if !filepath.IsAbs(destination) || filepath.Clean(destination) != destination {
|
||||
+ util.Fatalf("invalid owner-change mount destination: %q", destination)
|
||||
+ }
|
||||
+ if !servedMounts[destination] {
|
||||
+ util.Fatalf("owner-change destination is not a gofer mount: %q", destination)
|
||||
+ }
|
||||
+ if ownerChangeMounts[destination] {
|
||||
+ util.Fatalf("duplicate owner-change mount destination: %q", destination)
|
||||
+ }
|
||||
+ ownerChangeMounts[destination] = true
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
// These are global fsgofer configurations.
|
||||
fsgoferConf := &fsgofer.Config{
|
||||
HostUDS: conf.GetHostUDS(),
|
||||
@@ -430,7 +457,10 @@ func (g *Gofer) serve(spec *specs.Spec, conf *config.Config, root string, ruid i
|
||||
}
|
||||
}
|
||||
if connImpl == nil {
|
||||
- connImpl = fsgofer.NewConnectionImpl(fsgoferConf)
|
||||
+ mountConf := *fsgoferConf
|
||||
+ mountConf.DenyChown = !ownerChangeMounts[cfg.mountPath]
|
||||
+ mountConf.DonateMountPointFD = mountConf.DonateMountPointFD && ownerChangeMounts[cfg.mountPath]
|
||||
+ connImpl = fsgofer.NewConnectionImpl(&mountConf)
|
||||
connOpts = fsgofer.ConnectionOpts(cfg.readonly)
|
||||
}
|
||||
conn, err := server.CreateConnection(cfg.sock, cfg.mountPath, connOpts, connImpl)
|
||||
diff --git a/runsc/fsgofer/lisafs.go b/runsc/fsgofer/lisafs.go
|
||||
index ff6aeff..e5829b2 100644
|
||||
--- a/runsc/fsgofer/lisafs.go
|
||||
+++ b/runsc/fsgofer/lisafs.go
|
||||
@@ -61,6 +61,9 @@ type Config struct {
|
||||
// be donated to the client on Mount RPC.
|
||||
DonateMountPointFD bool
|
||||
|
||||
+ // DenyChown rejects owner changes before applying any requested attributes.
|
||||
+ DenyChown bool
|
||||
+
|
||||
// Gofer process's RUID.
|
||||
RUID int
|
||||
|
||||
@@ -148,6 +151,7 @@ func (i *connectionImpl) Mount(c *lisafs.Connection, mountNode *lisafs.Node) (*l
|
||||
hostFD: rootHostFD,
|
||||
writableHostFD: atomicbitops.FromInt32(-1),
|
||||
isMountPoint: true,
|
||||
+ denyChown: i.config.DenyChown,
|
||||
}
|
||||
mountNode.IncRef() // Ref is transferred to ControlFD.
|
||||
rootFD.ControlFD.Init(c, mountNode, linux.FileMode(stat.Mode), rootFD)
|
||||
@@ -201,6 +205,7 @@ func (i *connectionImpl) SupportedMessages() []lisafs.MID {
|
||||
// controlFDLisa implements lisafs.ControlFDImpl.
|
||||
type controlFDLisa struct {
|
||||
lisafs.ControlFD
|
||||
+ denyChown bool
|
||||
|
||||
// hostFD is the file descriptor which can be used to make host syscalls.
|
||||
hostFD int
|
||||
@@ -245,6 +250,7 @@ func newControlFDLisa(hostFD int, parent *controlFDLisa, name string, mode linux
|
||||
})
|
||||
childFD.hostFD = hostFD
|
||||
childFD.writableHostFD = atomicbitops.FromInt32(-1)
|
||||
+ childFD.denyChown = parent.denyChown
|
||||
childFD.ControlFD.Init(parent.Conn(), childNode, mode, childFD)
|
||||
return childFD
|
||||
}
|
||||
@@ -304,6 +310,9 @@ func (fd *controlFDLisa) Stat() (lisafs.Statx, error) {
|
||||
|
||||
// SetStat implements lisafs.ControlFDImpl.SetStat.
|
||||
func (fd *controlFDLisa) SetStat(stat lisafs.SetStatReq) (failureMask uint32, failureErr error) {
|
||||
+ if fd.denyChown && stat.Mask&(unix.STATX_UID|unix.STATX_GID) != 0 {
|
||||
+ return stat.Mask, unix.EPERM
|
||||
+ }
|
||||
if stat.Mask&unix.STATX_MODE != 0 {
|
||||
switch fd.FileType() {
|
||||
case unix.S_IFLNK:
|
||||
@@ -705,9 +719,11 @@ func (fd *controlFDLisa) Symlink(name string, target string, uid lisafs.UID, gid
|
||||
if err != nil {
|
||||
return nil, lisafs.Statx{}, err
|
||||
}
|
||||
- if err := fchown(symlinkFD, uid, gid); err != nil {
|
||||
- unix.Close(symlinkFD)
|
||||
- return nil, lisafs.Statx{}, err
|
||||
+ if !fd.denyChown {
|
||||
+ if err := fchown(symlinkFD, uid, gid); err != nil {
|
||||
+ unix.Close(symlinkFD)
|
||||
+ return nil, lisafs.Statx{}, err
|
||||
+ }
|
||||
}
|
||||
|
||||
symlinkStat, err := fstatTo(symlinkFD)
|
||||
@@ -1072,4 +1083,2 @@ func (fd *controlFDLisa) ListXattr(size uint64) (lisafs.StringArray, error) {
|
||||
- if size > linux.XATTR_LIST_MAX || size == 0 {
|
||||
- size = linux.XATTR_LIST_MAX
|
||||
- }
|
||||
+ size = linux.XATTR_LIST_MAX
|
||||
bPtr := listXattrBufPool.Get().(*[]byte)
|
||||
diff --git a/runsc/fsgofer/lisafs_test.go b/runsc/fsgofer/lisafs_test.go
|
||||
index b10eefc..676960c 100644
|
||||
--- a/runsc/fsgofer/lisafs_test.go
|
||||
+++ b/runsc/fsgofer/lisafs_test.go
|
||||
@@ -61,3 +61,19 @@ func (tester) BindSupported() bool {
|
||||
func TestFSGofer(t *testing.T) {
|
||||
testsuite.RunAllLocalFSTests(t, tester{})
|
||||
}
|
||||
+
|
||||
+type denyChownTester struct {
|
||||
+ tester
|
||||
+}
|
||||
+
|
||||
+func (denyChownTester) NewConnImpl(t *testing.T) lisafs.ConnectionImpl {
|
||||
+ return fsgofer.NewConnectionImpl(&fsgofer.Config{DenyChown: true})
|
||||
+}
|
||||
+
|
||||
+func (denyChownTester) SetUserGroupIDSupported() bool {
|
||||
+ return false
|
||||
+}
|
||||
+
|
||||
+func TestFSGoferDenyChown(t *testing.T) {
|
||||
+ testsuite.RunAllLocalFSTests(t, denyChownTester{})
|
||||
+}
|
||||
diff --git a/pkg/sentry/fsimpl/gofer/directfs_inode.go b/pkg/sentry/fsimpl/gofer/directfs_inode.go
|
||||
index a7c1ab1..d3a0d96 100644
|
||||
--- a/pkg/sentry/fsimpl/gofer/directfs_inode.go
|
||||
+++ b/pkg/sentry/fsimpl/gofer/directfs_inode.go
|
||||
@@ -612,4 +612,2 @@ func (i *directfsInode) listXattr(ctx context.Context, size uint64, d *dentry) ([]string, error) {
|
||||
- if size > linux.XATTR_LIST_MAX || size == 0 {
|
||||
- size = linux.XATTR_LIST_MAX
|
||||
- }
|
||||
+ size = linux.XATTR_LIST_MAX
|
||||
bPtr := xattrBufPool.Get().(*[]byte)
|
||||
Loading…
Reference in a new issue