fsnotify: invalidate dcache before IN_DELETE event
commita37d9a17f0
upstream. Apparently, there are some applications that use IN_DELETE event as an invalidation mechanism and expect that if they try to open a file with the name reported with the delete event, that it should not contain the content of the deleted file. Commit49246466a9
("fsnotify: move fsnotify_nameremove() hook out of d_delete()") moved the fsnotify delete hook before d_delete() so fsnotify will have access to a positive dentry. This allowed a race where opening the deleted file via cached dentry is now possible after receiving the IN_DELETE event. To fix the regression, create a new hook fsnotify_delete() that takes the unlinked inode as an argument and use a helper d_delete_notify() to pin the inode, so we can pass it to fsnotify_delete() after d_delete(). Backporting hint: this regression is from v5.3. Although patch will apply with only trivial conflicts to v5.4 and v5.10, it won't build, because fsnotify_delete() implementation is different in each of those versions (see fsnotify_link()). A follow up patch will fix the fsnotify_unlink/rmdir() calls in pseudo filesystem that do not need to call d_delete(). Link: https://lore.kernel.org/r/20220120215305.282577-1-amir73il@gmail.com Reported-by: Ivan Delalande <colona@arista.com> Link: https://lore.kernel.org/linux-fsdevel/YeNyzoDM5hP5LtGW@visor/ Fixes:49246466a9
("fsnotify: move fsnotify_nameremove() hook out of d_delete()") Cc: stable@vger.kernel.org # v5.3+ Signed-off-by: Amir Goldstein <amir73il@gmail.com> Signed-off-by: Jan Kara <jack@suse.cz> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> Signed-off-by: Alex Shi <alexsshi@tencent.com>
This commit is contained in:
parent
75e3d56004
commit
8c16d4b7aa
|
@ -3027,10 +3027,8 @@ static noinline int btrfs_ioctl_snap_destroy(struct file *file,
|
||||||
inode_lock(inode);
|
inode_lock(inode);
|
||||||
err = btrfs_delete_subvolume(dir, dentry);
|
err = btrfs_delete_subvolume(dir, dentry);
|
||||||
inode_unlock(inode);
|
inode_unlock(inode);
|
||||||
if (!err) {
|
if (!err)
|
||||||
fsnotify_rmdir(dir, dentry);
|
d_delete_notify(dir, dentry);
|
||||||
d_delete(dentry);
|
|
||||||
}
|
|
||||||
|
|
||||||
out_dput:
|
out_dput:
|
||||||
dput(dentry);
|
dput(dentry);
|
||||||
|
|
10
fs/namei.c
10
fs/namei.c
|
@ -3878,13 +3878,12 @@ int vfs_rmdir(struct inode *dir, struct dentry *dentry)
|
||||||
dentry->d_inode->i_flags |= S_DEAD;
|
dentry->d_inode->i_flags |= S_DEAD;
|
||||||
dont_mount(dentry);
|
dont_mount(dentry);
|
||||||
detach_mounts(dentry);
|
detach_mounts(dentry);
|
||||||
fsnotify_rmdir(dir, dentry);
|
|
||||||
|
|
||||||
out:
|
out:
|
||||||
inode_unlock(dentry->d_inode);
|
inode_unlock(dentry->d_inode);
|
||||||
dput(dentry);
|
dput(dentry);
|
||||||
if (!error)
|
if (!error)
|
||||||
d_delete(dentry);
|
d_delete_notify(dir, dentry);
|
||||||
return error;
|
return error;
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL(vfs_rmdir);
|
EXPORT_SYMBOL(vfs_rmdir);
|
||||||
|
@ -3995,7 +3994,6 @@ int vfs_unlink(struct inode *dir, struct dentry *dentry, struct inode **delegate
|
||||||
if (!error) {
|
if (!error) {
|
||||||
dont_mount(dentry);
|
dont_mount(dentry);
|
||||||
detach_mounts(dentry);
|
detach_mounts(dentry);
|
||||||
fsnotify_unlink(dir, dentry);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4003,9 +4001,11 @@ out:
|
||||||
inode_unlock(target);
|
inode_unlock(target);
|
||||||
|
|
||||||
/* We don't d_delete() NFS sillyrenamed files--they still exist. */
|
/* We don't d_delete() NFS sillyrenamed files--they still exist. */
|
||||||
if (!error && !(dentry->d_flags & DCACHE_NFSFS_RENAMED)) {
|
if (!error && dentry->d_flags & DCACHE_NFSFS_RENAMED) {
|
||||||
|
fsnotify_unlink(dir, dentry);
|
||||||
|
} else if (!error) {
|
||||||
fsnotify_link_count(target);
|
fsnotify_link_count(target);
|
||||||
d_delete(dentry);
|
d_delete_notify(dir, dentry);
|
||||||
}
|
}
|
||||||
|
|
||||||
return error;
|
return error;
|
||||||
|
|
|
@ -188,6 +188,42 @@ static inline void fsnotify_link(struct inode *dir, struct inode *inode, struct
|
||||||
fsnotify(dir, FS_CREATE, inode, FSNOTIFY_EVENT_INODE, &new_dentry->d_name, 0);
|
fsnotify(dir, FS_CREATE, inode, FSNOTIFY_EVENT_INODE, &new_dentry->d_name, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* fsnotify_delete - @dentry was unlinked and unhashed
|
||||||
|
*
|
||||||
|
* Caller must make sure that dentry->d_name is stable.
|
||||||
|
*
|
||||||
|
* Note: unlike fsnotify_unlink(), we have to pass also the unlinked inode
|
||||||
|
* as this may be called after d_delete() and old_dentry may be negative.
|
||||||
|
*/
|
||||||
|
static inline void fsnotify_delete(struct inode *dir, struct inode *inode,
|
||||||
|
struct dentry *dentry)
|
||||||
|
{
|
||||||
|
__u32 mask = FS_DELETE;
|
||||||
|
|
||||||
|
if (S_ISDIR(inode->i_mode))
|
||||||
|
mask |= FS_ISDIR;
|
||||||
|
|
||||||
|
fsnotify(dir, mask, inode, FSNOTIFY_EVENT_INODE, &dentry->d_name, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* d_delete_notify - delete a dentry and call fsnotify_delete()
|
||||||
|
* @dentry: The dentry to delete
|
||||||
|
*
|
||||||
|
* This helper is used to guaranty that the unlinked inode cannot be found
|
||||||
|
* by lookup of this name after fsnotify_delete() event has been delivered.
|
||||||
|
*/
|
||||||
|
static inline void d_delete_notify(struct inode *dir, struct dentry *dentry)
|
||||||
|
{
|
||||||
|
struct inode *inode = d_inode(dentry);
|
||||||
|
|
||||||
|
ihold(inode);
|
||||||
|
d_delete(dentry);
|
||||||
|
fsnotify_delete(dir, inode, dentry);
|
||||||
|
iput(inode);
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* fsnotify_unlink - 'name' was unlinked
|
* fsnotify_unlink - 'name' was unlinked
|
||||||
*
|
*
|
||||||
|
@ -195,10 +231,10 @@ static inline void fsnotify_link(struct inode *dir, struct inode *inode, struct
|
||||||
*/
|
*/
|
||||||
static inline void fsnotify_unlink(struct inode *dir, struct dentry *dentry)
|
static inline void fsnotify_unlink(struct inode *dir, struct dentry *dentry)
|
||||||
{
|
{
|
||||||
/* Expected to be called before d_delete() */
|
if (WARN_ON_ONCE(d_is_negative(dentry)))
|
||||||
WARN_ON_ONCE(d_is_negative(dentry));
|
return;
|
||||||
|
|
||||||
fsnotify_dirent(dir, dentry, FS_DELETE);
|
fsnotify_delete(dir, d_inode(dentry), dentry);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -218,10 +254,10 @@ static inline void fsnotify_mkdir(struct inode *inode, struct dentry *dentry)
|
||||||
*/
|
*/
|
||||||
static inline void fsnotify_rmdir(struct inode *dir, struct dentry *dentry)
|
static inline void fsnotify_rmdir(struct inode *dir, struct dentry *dentry)
|
||||||
{
|
{
|
||||||
/* Expected to be called before d_delete() */
|
if (WARN_ON_ONCE(d_is_negative(dentry)))
|
||||||
WARN_ON_ONCE(d_is_negative(dentry));
|
return;
|
||||||
|
|
||||||
fsnotify_dirent(dir, dentry, FS_DELETE | FS_ISDIR);
|
fsnotify_delete(dir, d_inode(dentry), dentry);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
Loading…
Reference in New Issue