diff --git a/Makefile b/Makefile index 15a2940e2..c972f9cea 100644 --- a/Makefile +++ b/Makefile @@ -270,6 +270,7 @@ docker-tests: load-basic $(RUNTIME_BIN) @$(call install_runtime,$(RUNTIME)-fdlimit,--fdlimit=2000) # Used by TestRlimitNoFile. @$(call install_runtime,$(RUNTIME)-dcache,--fdlimit=2000 --dcache=100) # Used by TestDentryCacheLimit. @$(call install_runtime,$(RUNTIME)-host-uds,--host-uds=all) # Used by TestHostSocketConnect. + @$(call install_runtime,$(RUNTIME)-overlay,--overlay2=root:/tmp) # Used by TestOverlay*. @$(call test_runtime,$(RUNTIME),$(INTEGRATION_TARGETS) //test/e2e:integration_runtime_test) .PHONY: docker-tests diff --git a/pkg/sentry/fsimpl/overlay/filesystem.go b/pkg/sentry/fsimpl/overlay/filesystem.go index f62d46029..ee7899b3a 100644 --- a/pkg/sentry/fsimpl/overlay/filesystem.go +++ b/pkg/sentry/fsimpl/overlay/filesystem.go @@ -161,6 +161,9 @@ afterSymlink: rp.Advance() return d.parent, d.parent.topLookupLayer(), nil } + if uint64(len(name)) > fs.maxFilenameLen { + return nil, lookupLayerNone, linuxerr.ENAMETOOLONG + } child, topLookupLayer, err := fs.getChildLocked(ctx, d, name, ds) if err != nil { return nil, topLookupLayer, err @@ -504,6 +507,9 @@ func (fs *filesystem) doCreateAt(ctx context.Context, rp *vfs.ResolvingPath, ct if name == "." || name == ".." { return linuxerr.EEXIST } + if uint64(len(name)) > fs.maxFilenameLen { + return linuxerr.ENAMETOOLONG + } if parent.vfsd.IsDead() { return linuxerr.ENOENT } @@ -1090,6 +1096,9 @@ func (fs *filesystem) RenameAt(ctx context.Context, rp *vfs.ResolvingPath, oldPa } return linuxerr.EBUSY } + if uint64(len(newName)) > fs.maxFilenameLen { + return linuxerr.ENAMETOOLONG + } // Do not check for newName length, since different filesystem // implementations impose different name limits. upperfs.RenameAt() will fail // appropriately if it has to. diff --git a/pkg/sentry/fsimpl/overlay/overlay.go b/pkg/sentry/fsimpl/overlay/overlay.go index 6d1a5b3af..c2e3e783c 100644 --- a/pkg/sentry/fsimpl/overlay/overlay.go +++ b/pkg/sentry/fsimpl/overlay/overlay.go @@ -125,6 +125,9 @@ type filesystem struct { // lastDirIno is the last inode number assigned to a directory. lastDirIno // is protected by dirInoCacheMu. lastDirIno uint64 + + // MaxFilenameLen is the maximum filename length allowed by the overlayfs. + maxFilenameLen uint64 } // +stateify savable @@ -264,9 +267,23 @@ func (fstype FilesystemType) GetFilesystem(ctx context.Context, vfsObj *vfs.Virt dirDevMinor: dirDevMinor, lowerDevMinors: make(map[layerDevNumber]uint32), dirInoCache: make(map[layerDevNoAndIno]uint64), + maxFilenameLen: linux.NAME_MAX, } fs.vfsfs.Init(vfsObj, &fstype, fs) + // Configure max filename length. Similar to what Linux does in + // fs/overlayfs/super.c:ovl_fill_super() -> ... -> ovl_check_namelen(). + if fsopts.UpperRoot.Ok() { + if err := fs.updateMaxNameLen(ctx, creds, vfsObj, fs.opts.UpperRoot); err != nil { + ctx.Debugf("overlay.FilesystemType.GetFilesystem: failed to StatFSAt on upper layer root: %v", err) + } + } + for _, lowerRoot := range fsopts.LowerRoots { + if err := fs.updateMaxNameLen(ctx, creds, vfsObj, lowerRoot); err != nil { + ctx.Debugf("overlay.FilesystemType.GetFilesystem: failed to StatFSAt on lower layer root: %v", err) + } + } + // Construct the root dentry. root := fs.newDentry() root.refs = atomicbitops.FromInt64(1) @@ -368,6 +385,21 @@ func (fs *filesystem) Release(ctx context.Context) { } } +// updateMaxNameLen is analogous to fs/overlayfs/super.c:ovl_check_namelen(). +func (fs *filesystem) updateMaxNameLen(ctx context.Context, creds *auth.Credentials, vfsObj *vfs.VirtualFilesystem, vd vfs.VirtualDentry) error { + statfs, err := vfsObj.StatFSAt(ctx, creds, &vfs.PathOperation{ + Root: vd, + Start: vd, + }) + if err != nil { + return err + } + if statfs.NameLength > fs.maxFilenameLen { + fs.maxFilenameLen = statfs.NameLength + } + return nil +} + func (fs *filesystem) statFS(ctx context.Context) (linux.Statfs, error) { // Always statfs the root of the topmost layer. Compare Linux's // fs/overlayfs/super.c:ovl_statfs(). diff --git a/test/e2e/integration_runtime_test.go b/test/e2e/integration_runtime_test.go index 34e74562e..add04aeea 100644 --- a/test/e2e/integration_runtime_test.go +++ b/test/e2e/integration_runtime_test.go @@ -24,6 +24,7 @@ package integration import ( "context" "flag" + "fmt" "io/ioutil" "net" "os" @@ -178,3 +179,20 @@ func TestHostSocketConnect(t *testing.T) { } wg.Wait() } + +func TestOverlayNameTooLong(t *testing.T) { + ctx := context.Background() + d := dockerutil.MakeContainerWithRuntime(ctx, t, "-overlay") + defer d.CleanUp(ctx) + + opts := dockerutil.RunOpts{ + Image: "basic/integrationtest", + WorkDir: "/root", + } + longName := strings.Repeat("a", unix.NAME_MAX+1) + if got, err := d.Run(ctx, opts, "bash", "-c", fmt.Sprintf("stat %s || true", longName)); err != nil { + t.Fatalf("docker run failed: %v", err) + } else if want := "File name too long"; !strings.Contains(got, want) { + t.Errorf("container output %q does not contain %q", got, want) + } +}