Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
153 changes: 96 additions & 57 deletions pkg/sentry/fsimpl/gofer/filesystem.go
Original file line number Diff line number Diff line change
Expand Up @@ -1441,6 +1441,7 @@ func (fs *filesystem) RenameAt(ctx context.Context, rp *vfs.ResolvingPath, oldPa
// users.
return linuxerr.EINVAL
}
exchange := opts.Flags&linux.RENAME_EXCHANGE != 0

newName := rp.Component()
if newName == "." || newName == ".." {
Expand Down Expand Up @@ -1501,7 +1502,7 @@ func (fs *filesystem) RenameAt(ctx context.Context, rp *vfs.ResolvingPath, oldPa
}
}
} else {
if opts.MustBeDir || rp.MustBeDir() {
if !exchange && (opts.MustBeDir || rp.MustBeDir()) {
return linuxerr.ENOTDIR
}
}
Expand Down Expand Up @@ -1529,7 +1530,26 @@ func (fs *filesystem) RenameAt(ctx context.Context, rp *vfs.ResolvingPath, oldPa
return err
}
replacedVFSD = &replaced.vfsd
if replaced.isDir() {
if exchange {
// The exchanged files may differ in type, and a directory being
// exchanged may be non-empty; but exchanging a file with an
// ancestor directory would disconnect the latter from the tree.
if genericIsAncestorDentry(fs, replaced, renamed) {
return linuxerr.EINVAL
}
if rp.MustBeDir() && !replaced.isDir() {
return linuxerr.ENOTDIR
}
if opts.MustBeDir && !renamed.isDir() {
return linuxerr.ENOTDIR
}
if oldParent != newParent && replaced.isDir() {
// Writability is needed to change replaced's "..".
if err := replaced.checkPermissions(creds, vfs.MayWrite); err != nil {
return err
}
}
} else if replaced.isDir() {
if !renamed.isDir() {
return linuxerr.EISDIR
}
Expand All @@ -1542,7 +1562,7 @@ func (fs *filesystem) RenameAt(ctx context.Context, rp *vfs.ResolvingPath, oldPa
}
}
} else { // replaced == nil
if opts.Flags&linux.RENAME_EXCHANGE != 0 {
if exchange {
// RENAME_EXCHANGE requires that the target file exist.
return linuxerr.ENOENT
}
Expand All @@ -1564,7 +1584,7 @@ func (fs *filesystem) RenameAt(ctx context.Context, rp *vfs.ResolvingPath, oldPa
vfsObj.AbortRenameDentry(&handle, &renamed.vfsd, replacedVFSD)
return err
}
} else if replaced != nil && !replaced.inode.isSynthetic() && opts.Flags&linux.RENAME_EXCHANGE == 0 {
} else if replaced != nil && !replaced.inode.isSynthetic() && !exchange {
// We are replacing an existing real file with a synthetic one, so we
// need to unlink the former.
flags := uint32(0)
Expand All @@ -1586,7 +1606,7 @@ func (fs *filesystem) RenameAt(ctx context.Context, rp *vfs.ResolvingPath, oldPa
}

vfsObj.RenameBegin(&handle)
if opts.Flags&linux.RENAME_EXCHANGE != 0 {
if exchange {
if oldParent != newParent {
switch {
case replaced.inode.isSynthetic() && !renamed.inode.isSynthetic():
Expand Down Expand Up @@ -1616,66 +1636,85 @@ func (fs *filesystem) RenameAt(ctx context.Context, rp *vfs.ResolvingPath, oldPa
oldParent.clearDirentsLocked()
oldParent.touchCMtime()
}
if oldParent != newParent && newParent.inode.cachedMetadataAuthoritative() {
newParent.clearDirentsLocked()
newParent.touchCMtime()
if oldParent != newParent {
if newParent.inode.cachedMetadataAuthoritative() {
newParent.clearDirentsLocked()
newParent.touchCMtime()
}
// If exactly one of the exchanged files is a directory, its ".."
// entry moves from one parent directory to the other.
if renamed.isDir() && !replaced.isDir() {
if oldParent.inode.cachedMetadataAuthoritative() {
oldParent.decLinks()
}
if newParent.inode.cachedMetadataAuthoritative() {
newParent.incLinks()
}
} else if !renamed.isDir() && replaced.isDir() {
if newParent.inode.cachedMetadataAuthoritative() {
newParent.decLinks()
}
if oldParent.inode.cachedMetadataAuthoritative() {
oldParent.incLinks()
}
}
}
// Sends notifications for both the renamed and replaced dentries.
vfs.InotifyRename(ctx, &renamed.inode.watches, &oldParent.inode.watches, &newParent.inode.watches, oldName, newName, renamed.isDir())
vfs.InotifyRename(ctx, &replaced.inode.watches, &newParent.inode.watches, &oldParent.inode.watches, newName, oldName, replaced.isDir())
} else {
if replaced != nil {
replaced.setDeleted()
// If an extra reference is held on replaced as described by the
// comment for dentry.refs, drop that reference now. We can't race with
// fs.unlinkAt() or invalidation since fs.renameMu has been locked for
// writing since before we obtained replaced.
if replaced.inode.isSynthetic() {
newParent.syntheticChildren--
replaced.decRefNoCaching()
} else if replaced.inode.endpoint != nil {
replaced.decRefNoCaching()
}
ds = appendDentry(ds, replaced)
// Remove the replaced entry from its parent's cache.
delete(newParent.children, newName)
}
oldParent.cacheNegativeLookupLocked(oldName) // +checklocksforce: oldParent.childrenMu is held if oldParent != newParent.
if renamed.inode.isSynthetic() {
oldParent.syntheticChildren--
newParent.syntheticChildren++
}
// We have d.opMu for writing, so no need to check for existence of a
// child with the given name. We could not have raced.
newParent.cacheNewChildLocked(renamed, newName)
oldParent.decRefNoCaching()
if oldParent != newParent {
ds = appendDentry(ds, newParent)
ds = appendDentry(ds, oldParent)
}
toDecRef = vfsObj.CommitRenameReplaceDentry(ctx, &handle, &renamed.vfsd, replacedVFSD)
return nil
}
if replaced != nil {
replaced.setDeleted()
// If an extra reference is held on replaced as described by the
// comment for dentry.refs, drop that reference now. We can't race with
// fs.unlinkAt() or invalidation since fs.renameMu has been locked for
// writing since before we obtained replaced.
if replaced.inode.isSynthetic() {
newParent.syntheticChildren--
replaced.decRefNoCaching()
} else if replaced.inode.endpoint != nil {
replaced.decRefNoCaching()
}
ds = appendDentry(ds, replaced)
// Remove the replaced entry from its parent's cache.
delete(newParent.children, newName)
}
oldParent.cacheNegativeLookupLocked(oldName) // +checklocksforce: oldParent.childrenMu is held if oldParent != newParent.
if renamed.inode.isSynthetic() {
oldParent.syntheticChildren--
newParent.syntheticChildren++
}
// We have d.opMu for writing, so no need to check for existence of a
// child with the given name. We could not have raced.
newParent.cacheNewChildLocked(renamed, newName)
oldParent.decRefNoCaching()
if oldParent != newParent {
ds = appendDentry(ds, newParent)
ds = appendDentry(ds, oldParent)
}
toDecRef = vfsObj.CommitRenameReplaceDentry(ctx, &handle, &renamed.vfsd, replacedVFSD)

// Update metadata.
if renamed.inode.cachedMetadataAuthoritative() {
renamed.touchCtime()
}
if oldParent.inode.cachedMetadataAuthoritative() {
oldParent.clearDirentsLocked()
oldParent.touchCMtime()
if renamed.isDir() {
oldParent.decLinks()
}
// Update metadata.
if renamed.inode.cachedMetadataAuthoritative() {
renamed.touchCtime()
}
if oldParent.inode.cachedMetadataAuthoritative() {
oldParent.clearDirentsLocked()
oldParent.touchCMtime()
if renamed.isDir() {
oldParent.decLinks()
}
if newParent.inode.cachedMetadataAuthoritative() {
newParent.clearDirentsLocked()
newParent.touchCMtime()
if renamed.isDir() && (replaced == nil || !replaced.isDir()) {
// Increase the link count if we did not replace another directory.
newParent.incLinks()
}
}
if newParent.inode.cachedMetadataAuthoritative() {
newParent.clearDirentsLocked()
newParent.touchCMtime()
if renamed.isDir() && (replaced == nil || !replaced.isDir()) {
// Increase the link count if we did not replace another directory.
newParent.incLinks()
}
vfs.InotifyRename(ctx, &renamed.inode.watches, &oldParent.inode.watches, &newParent.inode.watches, oldName, newName, renamed.isDir())
}
vfs.InotifyRename(ctx, &renamed.inode.watches, &oldParent.inode.watches, &newParent.inode.watches, oldName, newName, renamed.isDir())
return nil
}

Expand Down
95 changes: 87 additions & 8 deletions pkg/sentry/fsimpl/overlay/filesystem.go
Original file line number Diff line number Diff line change
Expand Up @@ -1090,9 +1090,13 @@ func (fs *filesystem) RenameAt(ctx context.Context, rp *vfs.ResolvingPath, oldPa
return err
}

if opts.Flags&^linux.RENAME_NOREPLACE != 0 {
if opts.Flags&^(linux.RENAME_NOREPLACE|linux.RENAME_EXCHANGE) != 0 {
return linuxerr.EINVAL
}
if opts.Flags&(linux.RENAME_NOREPLACE|linux.RENAME_EXCHANGE) == linux.RENAME_NOREPLACE|linux.RENAME_EXCHANGE {
return linuxerr.EINVAL
}
exchange := opts.Flags&linux.RENAME_EXCHANGE != 0

newName := rp.Component()
if newName == "." || newName == ".." {
Expand Down Expand Up @@ -1142,7 +1146,7 @@ func (fs *filesystem) RenameAt(ctx context.Context, rp *vfs.ResolvingPath, oldPa
}
}
} else {
if opts.MustBeDir || rp.MustBeDir() {
if !exchange && (opts.MustBeDir || rp.MustBeDir()) {
return linuxerr.ENOTDIR
}
}
Expand Down Expand Up @@ -1175,7 +1179,26 @@ func (fs *filesystem) RenameAt(ctx context.Context, rp *vfs.ResolvingPath, oldPa
return err
}
replacedVFSD = &replaced.vfsd
if replaced.isDir() {
if exchange {
// The exchanged files may differ in type, and a directory being
// exchanged may be non-empty; but exchanging a file with an
// ancestor directory would disconnect the latter from the tree.
if genericIsAncestorDentry(fs, replaced, renamed) {
return linuxerr.EINVAL
}
if rp.MustBeDir() && !replaced.isDir() {
return linuxerr.ENOTDIR
}
if opts.MustBeDir && !renamed.isDir() {
return linuxerr.ENOTDIR
}
if oldParent != newParent && replaced.isDir() {
// Writability is needed to change replaced's "..".
if err := replaced.checkPermissions(creds, vfs.MayWrite); err != nil {
return err
}
}
} else if replaced.isDir() {
if !renamed.isDir() {
return linuxerr.EISDIR
}
Expand All @@ -1193,6 +1216,9 @@ func (fs *filesystem) RenameAt(ctx context.Context, rp *vfs.ResolvingPath, oldPa
return linuxerr.ENOTDIR
}
}
} else if exchange {
// RENAME_EXCHANGE requires that the target file exist.
return linuxerr.ENOENT
}

if oldParent == newParent && oldName == newName {
Expand All @@ -1216,10 +1242,24 @@ func (fs *filesystem) RenameAt(ctx context.Context, rp *vfs.ResolvingPath, oldPa
if err := newParent.copyUpLocked(ctx); err != nil {
return err
}
// If replaced exists, it doesn't need to be copied-up, but we do need to
// serialize with copy-up. Holding renameMu for writing should be
// sufficient, but out of an abundance of caution...
if replaced != nil {
if exchange {
// replaced is also renamed on the upper layer, so it (and all of its
// descendants if it's a directory) must be copied-up too.
if err := replaced.copyUpLocked(ctx); err != nil {
return err
}
if replaced.isDir() {
replaced.dirMu.NestedLock(dirLockReplaced)
err := replaced.copyUpDescendantsLocked(ctx, &ds)
replaced.dirMu.NestedUnlock(dirLockReplaced)
if err != nil {
return err
}
}
} else if replaced != nil {
// replaced doesn't need to be copied-up, but we do need to serialize
// with copy-up. Holding renameMu for writing should be sufficient, but
// out of an abundance of caution...
replaced.copyMu.RLock()
defer replaced.copyMu.RUnlock()
}
Expand Down Expand Up @@ -1256,7 +1296,7 @@ func (fs *filesystem) RenameAt(ctx context.Context, rp *vfs.ResolvingPath, oldPa
}
}
}
if renamed.isDir() {
if !exchange && renamed.isDir() {
if replacedLayer == lookupLayerUpper {
// Remove whiteouts from the directory being replaced.
needRecreateWhiteouts = true
Expand Down Expand Up @@ -1304,6 +1344,45 @@ func (fs *filesystem) RenameAt(ctx context.Context, rp *vfs.ResolvingPath, oldPa
return err
}

if exchange {
// Below this point, renamed is at newpop and replaced is at oldpop.
// Commit the exchange, update the overlay filesystem tree, and abandon
// attempts to recover from errors.
vfsObj.RenameBegin(&handle)
genericSetParentAndName(fs, renamed, newParent, newName)
genericSetParentAndName(fs, replaced, oldParent, oldName)
// References held by renamed and replaced on their parents are
// exchanged as well; the counts on each parent are unchanged.
oldParent.children[oldName] = replaced
newParent.children[newName] = renamed
oldParent.dirents = nil
newParent.dirents = nil
vfsObj.CommitRenameExchangeDentry(&handle, &renamed.vfsd, replacedVFSD)

// An exchanged directory's contents can no longer be merged with
// lower layer directories at its new location.
if renamed.isDir() {
if err := vfsObj.SetXattrAt(ctx, fs.creds, &newpop, &vfs.SetXattrOptions{
Name: fs.xattrOpaque,
Value: "y",
}); err != nil {
panic(fmt.Sprintf("unrecoverable overlayfs inconsistency: failed to make exchanged directory opaque: %v", err))
}
}
if replaced.isDir() {
if err := vfsObj.SetXattrAt(ctx, fs.creds, &oldpop, &vfs.SetXattrOptions{
Name: fs.xattrOpaque,
Value: "y",
}); err != nil {
panic(fmt.Sprintf("unrecoverable overlayfs inconsistency: failed to make exchanged directory opaque: %v", err))
}
}

vfs.InotifyRename(ctx, &renamed.watches, &oldParent.watches, &newParent.watches, oldName, newName, renamed.isDir())
vfs.InotifyRename(ctx, &replaced.watches, &newParent.watches, &oldParent.watches, newName, oldName, replaced.isDir())
return nil
}

// Below this point, the renamed dentry is now at newpop, and anything we
// replaced is gone forever. Commit the rename, update the overlay
// filesystem tree, and abandon attempts to recover from errors.
Expand Down
Loading
Loading