fsnotify: invalidate dcache before IN_DELETE event
Subsystems (7)
Amir Goldstein
reviewerPATCH v2 1/2 fsnotify: invalidate dcache before IN_DELETE event
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.
Commit 49246466a989 ("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().
---
Changes since v1:
- Split patch for pseudo filesystem (Jan)
- Fix logic change for DCACHE_NFSFS_RENAMED (Jan)
- btrfs also needs d_delete_notify()
- Make fsnotify_unlink/rmdir() wrappers of fsnotify_delete()
- FS_DELETE event always uses FSNOTIFY_EVENT_INODE data_type
fs/btrfs/ioctl.c | 6 ++--- fs/namei.c | 10 ++++---- include/linux/fsnotify.h | 49 +++++++++++++++++++++++++++++++++++----- 3 files changed, 50 insertions(+), 15 deletions(-)
fs/btrfs/ioctl.c +2 -4
@@ -3060,10 +3060,8 @@ static noinline int btrfs_ioctl_snap_destroy(struct file *file,btrfs_inode_lock (inode,0 ); err =btrfs_delete_subvolume (dir, dentry);btrfs_inode_unlock (inode,0 ); -if (!err) { -fsnotify_rmdir (dir, dentry); -d_delete (dentry); - } +if (!err) +d_delete_notify (dir, dentry); out_dput:dput (dentry);
fs/namei.c +5 -5
@@ -3973,13 +3973,12 @@ int vfs_rmdir(struct user_namespace *mnt_userns, struct inode *dir, dentry->d_inode->i_flags |=S_DEAD ;dont_mount (dentry);detach_mounts (dentry); -fsnotify_rmdir (dir, dentry); out:inode_unlock (dentry->d_inode);dput (dentry);if (!error) -d_delete (dentry); +d_delete_notify (dir, dentry);return error; }EXPORT_SYMBOL (vfs_rmdir);
@@ -4101,7 +4100,6 @@ int vfs_unlink(struct user_namespace *mnt_userns, struct inode *dir,if (!error) {dont_mount (dentry);detach_mounts (dentry); -fsnotify_unlink (dir, dentry); } } }
@@ -4109,9 +4107,11 @@ int vfs_unlink(struct user_namespace *mnt_userns, struct inode *dir,inode_unlock (target);/* 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); -d_delete (dentry); +d_delete_notify (dir, dentry); }return error;
include/linux/fsnotify.h +43 -6
69 lines not on this page · raw message
Signature · 1 line
2.34.1
Amir Goldstein
reviewerPATCH v2 2/2 fsnotify: fix fsnotify hooks in pseudo filesystems
Commit 49246466a989 ("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 in pseudo filesystems, convert d_delete() calls to d_drop() (see commit 46c46f8df9aa ("devpts_pty_kill(): don't bother with d_delete()") and move the fsnotify hook after d_drop().
Add a missing fsnotify_unlink() hook in nfsdfs that was found during the audit of fsnotify hooks in pseudo filesystems.
Note that the fsnotify hooks in simple_recursive_removal() follow d_invalidate(), so they require no change.
--- fs/configfs/dir.c | 6 +++--- fs/devpts/inode.c | 2 +- fs/nfsd/nfsctl.c | 5 +++-- net/sunrpc/rpc_pipe.c | 4 ++-- 4 files changed, 9 insertions(+), 8 deletions(-)
fs/configfs/dir.c +3 -3
@@ -1780,8 +1780,8 @@ void configfs_unregister_group(struct config_group *group)configfs_detach_group (&group->cg_item);d_inode (dentry)->i_flags |=S_DEAD ;dont_mount (dentry); +d_drop (dentry);fsnotify_rmdir (d_inode (parent), dentry); -d_delete (dentry);inode_unlock (d_inode (parent));dput (dentry);
@@ -1922,10 +1922,10 @@ void configfs_unregister_subsystem(struct configfs_subsystem *subsys)configfs_detach_group (&group->cg_item);d_inode (dentry)->i_flags |=S_DEAD ;dont_mount (dentry); -fsnotify_rmdir (d_inode (root), dentry);inode_unlock (d_inode (dentry)); -d_delete (dentry); +d_drop (dentry); +fsnotify_rmdir (d_inode (root), dentry);inode_unlock (d_inode (root));
fs/devpts/inode.c +1 -1
@@ -621,8 +621,8 @@ void devpts_pty_kill(struct dentry *dentry) dentry->d_fsdata =NULL ;drop_nlink (dentry->d_inode); -fsnotify_unlink (d_inode (dentry->d_parent), dentry);d_drop (dentry); +fsnotify_unlink (d_inode (dentry->d_parent), dentry);dput (dentry);/* d_alloc_name() in devpts_pty_new() */ }
fs/nfsd/nfsctl.c +3 -2
@@ -1249,7 +1249,8 @@ static void nfsdfs_remove_file(struct inode *dir, struct dentry *dentry)clear_ncl (d_inode (dentry));dget (dentry); ret =simple_unlink (dir, dentry); -d_delete (dentry); +d_drop (dentry); +fsnotify_unlink (dir, dentry);dput (dentry);WARN_ON_ONCE (ret); }
@@ -1340,8 +1341,8 @@ void nfsd_client_rmdir(struct dentry *dentry)dget (dentry); ret =simple_rmdir (dir, dentry);WARN_ON_ONCE (ret); +d_drop (dentry);fsnotify_rmdir (dir, dentry); -d_delete (dentry);dput (dentry);inode_unlock (dir); }
net/sunrpc/rpc_pipe.c +2 -2
@@ -600,9 +600,9 @@ static int __rpc_rmdir(struct inode *dir, struct dentry *dentry)dget (dentry); ret =simple_rmdir (dir, dentry); +d_drop (dentry);if (!ret)fsnotify_rmdir (dir, dentry); -d_delete (dentry);dput (dentry);return ret; }
@@ -613,9 +613,9 @@ static int __rpc_unlink(struct inode *dir, struct dentry *dentry)dget (dentry); ret =simple_unlink (dir, dentry); +d_drop (dentry);if (!ret)fsnotify_unlink (dir, dentry); -d_delete (dentry);dput (dentry);return ret; }
Signature · 1 line
2.34.1
Jan Kara
maintainerQuoted 21 lines · Amir Goldstein
Apparently, there are some applications that use
IN_DELETEevent 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.Commit
49246466a989("fsnotify: movefsnotify_nameremove()hook out ofd_delete()") moved the fsnotify delete hook befored_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_DELETEevent.To fix the regression, create a new hook
fsnotify_delete()that takes the unlinked inode as an argument and use a helperd_delete_notify()to pin the inode, so we can pass it tofsnotify_delete()afterd_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 (seefsnotify_link()).
Quoted 8 lines
A follow up patch will fix the fsnotify_unlink/rmdir() calls in pseudo filesystem that do not need to call
d_delete().Reported-by Ivan Delalande <[email protected]>Fixes 49246466a989 ("fsnotify: move fsnotify_nameremove() hook out of d_delete()")Cc [email protected] # v5.3+Signed-off-by Amir Goldstein <[email protected]>
Thanks. Both patches look good to me now, I've merged them into my tree and will push them to Linus later this week.
Honza
Quoted 152 lines
---
Changes since v1:
- Split patch for pseudo filesystem (Jan)
- Fix logic change forDCACHE_NFSFS_RENAMED(Jan)
- btrfs also needsd_delete_notify()
- Make fsnotify_unlink/rmdir() wrappers offsnotify_delete()
-FS_DELETEevent always usesFSNOTIFY_EVENT_INODEdata_typefs/btrfs/ioctl.c | 6 ++--- fs/namei.c | 10 ++++---- include/linux/fsnotify.h | 49 +++++++++++++++++++++++++++++++++++----- 3 files changed, 50 insertions(+), 15 deletions(-)fs/btrfs/ioctl.c +2
-4@@ -3060,10 +3060,8 @@ static noinline int btrfs_ioctl_snap_destroy(struct file *file,btrfs_inode_lock (inode,0 ); err =btrfs_delete_subvolume (dir, dentry);btrfs_inode_unlock (inode,0 ); -if (!err) { -fsnotify_rmdir (dir, dentry); -d_delete (dentry); - } +if (!err) +d_delete_notify (dir, dentry); out_dput:dput (dentry);fs/namei.c +5
-5@@ -3973,13 +3973,12 @@ int vfs_rmdir(struct user_namespace *mnt_userns, struct inode *dir, dentry->d_inode->i_flags |=S_DEAD ;dont_mount (dentry);detach_mounts (dentry); -fsnotify_rmdir (dir, dentry); out:inode_unlock (dentry->d_inode);dput (dentry);if (!error) -d_delete (dentry); +d_delete_notify (dir, dentry);return error; }EXPORT_SYMBOL (vfs_rmdir);@@ -4101,7 +4100,6 @@ int vfs_unlink(struct user_namespace *mnt_userns, struct inode *dir,if (!error) {dont_mount (dentry);detach_mounts (dentry); -fsnotify_unlink (dir, dentry); } } }@@ -4109,9 +4107,11 @@ int vfs_unlink(struct user_namespace *mnt_userns, struct inode *dir,inode_unlock (target);/* 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); -d_delete (dentry); +d_delete_notify (dir, dentry); }return error;include/linux/fsnotify.h +43
-6@@ -224,6 +224,43 @@ static inline void fsnotify_link(struct inode *dir, struct inode *inode, dir, &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_name (mask, inode,FSNOTIFY_EVENT_INODE , dir, &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 * @@ -231,10 +268,10 @@ static inline void fsnotify_link(struct inode *dir, struct inode *inode,*/ static inline void fsnotify_unlink (struct inode *dir,struct dentry *dentry) { -/* Expected to be called before d_delete() */ -WARN_ON_ONCE (d_is_negative (dentry)); +if (WARN_ON_ONCE (d_is_negative (dentry))) +return ; -fsnotify_dirent (dir, dentry,FS_DELETE ); +fsnotify_delete (dir,d_inode (dentry), dentry); }/* @@ -258,10 +295,10 @@ static inline void fsnotify_mkdir(struct inode *dir, struct dentry *dentry)*/ static inline void fsnotify_rmdir (struct inode *dir,struct dentry *dentry) { -/* Expected to be called before d_delete() */ -WARN_ON_ONCE (d_is_negative (dentry)); +if (WARN_ON_ONCE (d_is_negative (dentry))) +return ; -fsnotify_dirent (dir, dentry,FS_DELETE |FS_ISDIR ); +fsnotify_delete (dir,d_inode (dentry), dentry); }/* Signature · 1 line
2.34.1
Signature · 2 lines
Jan Kara <[email protected]> SUSE Labs, CR
Amir Goldstein
reviewerQuoted 26 lines · Jan Kara
Quoted 24 lines · Amir Goldstein
Apparently, there are some applications that use
IN_DELETEevent 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.Commit
49246466a989("fsnotify: movefsnotify_nameremove()hook out ofd_delete()") moved the fsnotify delete hook befored_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_DELETEevent.To fix the regression, create a new hook
fsnotify_delete()that takes the unlinked inode as an argument and use a helperd_delete_notify()to pin the inode, so we can pass it tofsnotify_delete()afterd_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 (seefsnotify_link()).A follow up patch will fix the fsnotify_unlink/rmdir() calls in pseudo filesystem that do not need to call
d_delete().
Quoted 8 lines
Quoted 5 lines
Reported-by Ivan Delalande <[email protected]>Fixes 49246466a989 ("fsnotify: move fsnotify_nameremove() hook out of d_delete()")Cc [email protected] # v5.3+Signed-off-by Amir Goldstein <[email protected]>Thanks. Both patches look good to me now, I've merged them into my tree and will push them to Linus later this week.
LTP test:
https://github.com/amir73il/ltp/commits/in_delete
I'll post it later.
Thanks,
Amir.