diff --git a/pkg/sentry/fsimpl/overlay/filesystem.go b/pkg/sentry/fsimpl/overlay/filesystem.go index 496b0ca1b..14b32d2cf 100644 --- a/pkg/sentry/fsimpl/overlay/filesystem.go +++ b/pkg/sentry/fsimpl/overlay/filesystem.go @@ -295,6 +295,13 @@ func (fs *filesystem) lookupLocked(ctx context.Context, parent *dentry, name str return false } + // Directories use the lowest layer inode and device numbers to generate a + // filesystem local inode number. This way the inode number does not change + // after copy ups. + child.devMajor = atomicbitops.FromUint32(stat.DevMajor) + child.devMinor = atomicbitops.FromUint32(stat.DevMinor) + child.ino = atomicbitops.FromUint64(stat.Ino) + // Directories are merged with directories from lower layers if they // are not explicitly opaque. opaqueVal, err := vfsObj.GetXattrAt(ctx, fs.creds, &vfs.PathOperation{ @@ -316,9 +323,10 @@ func (fs *filesystem) lookupLocked(ctx context.Context, parent *dentry, name str return nil, topLookupLayer, linuxerr.ENOENT } - // Device and inode numbers were copied from the topmost layer above; - // override them if necessary. We can use RacyLoad() because child is still - // being initialized. + // Device and inode numbers were copied from the topmost layer above for + // non-directories. They were copied from the bottommost layer for + // directories. Override them if necessary. We can use RacyLoad() because + // child is still being initialized. if child.isDir() { child.ino.Store(fs.newDirIno(child.devMajor.RacyLoad(), child.devMinor.RacyLoad(), child.ino.RacyLoad())) child.devMajor = atomicbitops.FromUint32(linux.UNNAMED_MAJOR) diff --git a/pkg/sentry/fsimpl/overlay/overlay.go b/pkg/sentry/fsimpl/overlay/overlay.go index 462cf51f8..eb955c09c 100644 --- a/pkg/sentry/fsimpl/overlay/overlay.go +++ b/pkg/sentry/fsimpl/overlay/overlay.go @@ -117,7 +117,7 @@ type filesystem struct { renameMu renameRWMutex `state:"nosave"` // dirInoCache caches overlay-private directory inode numbers by mapped - // topmost device numbers and inode number. dirInoCache is protected by + // bottommost device numbers and inode number. dirInoCache is protected by // dirInoCacheMu. dirInoCacheMu dirInoCacheMutex `state:"nosave"` dirInoCache map[layerDevNoAndIno]uint64 @@ -310,6 +310,8 @@ func (fstype FilesystemType) GetFilesystem(ctx context.Context, vfsObj *vfs.Virt if rootStat.Mode&linux.S_IFMT == linux.S_IFDIR { root.devMajor = atomicbitops.FromUint32(linux.UNNAMED_MAJOR) root.devMinor = atomicbitops.FromUint32(fs.dirDevMinor) + // For root dir, it is okay to use top most level's stat to compute inode + // number because we don't allow copy ups on root dentries. root.ino.Store(fs.newDirIno(rootStat.DevMajor, rootStat.DevMinor, rootStat.Ino)) } else if !root.upperVD.Ok() { root.devMajor = atomicbitops.FromUint32(linux.UNNAMED_MAJOR)