From c73e9536447c8fe4bb6db1b27ac175ff27fa5aa6 Mon Sep 17 00:00:00 2001 From: Ayush Ranjan Date: Mon, 20 Jun 2022 18:33:55 -0700 Subject: [PATCH] Preserve directory inode number after copy up in overlayfs. Earlier we were using the topmost inode and device number to compute the directory inode number. But the topmost layer changes after a copy up which causes a new inode number to be generated after a copy up. This confuses some applications like GNU's FTS library. See fts_safe_changedir() for example which errors out with ENOENT if it observes that the dirctory inode or device number has changed. Now we use the bottommost layer's inode and device numbers. Linux and VFS1 also preserve the directory's inode number after copy up. PiperOrigin-RevId: 456147019 --- pkg/sentry/fsimpl/overlay/filesystem.go | 14 +++++++++++--- pkg/sentry/fsimpl/overlay/overlay.go | 4 +++- 2 files changed, 14 insertions(+), 4 deletions(-) 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)