Skip to content

Commit a37d9a1

Browse files
amir73iljankara
authored andcommitted
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 4924646 ("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/[email protected] Reported-by: Ivan Delalande <[email protected]> Link: https://lore.kernel.org/linux-fsdevel/YeNyzoDM5hP5LtGW@visor/ Fixes: 4924646 ("fsnotify: move fsnotify_nameremove() hook out of d_delete()") Cc: [email protected] # v5.3+ Signed-off-by: Amir Goldstein <[email protected]> Signed-off-by: Jan Kara <[email protected]>
1 parent 217663f commit a37d9a1

File tree

3 files changed

+50
-15
lines changed

3 files changed

+50
-15
lines changed

fs/btrfs/ioctl.c

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3086,10 +3086,8 @@ static noinline int btrfs_ioctl_snap_destroy(struct file *file,
30863086
btrfs_inode_lock(inode, 0);
30873087
err = btrfs_delete_subvolume(dir, dentry);
30883088
btrfs_inode_unlock(inode, 0);
3089-
if (!err) {
3090-
fsnotify_rmdir(dir, dentry);
3091-
d_delete(dentry);
3092-
}
3089+
if (!err)
3090+
d_delete_notify(dir, dentry);
30933091

30943092
out_dput:
30953093
dput(dentry);

fs/namei.c

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3974,13 +3974,12 @@ int vfs_rmdir(struct user_namespace *mnt_userns, struct inode *dir,
39743974
dentry->d_inode->i_flags |= S_DEAD;
39753975
dont_mount(dentry);
39763976
detach_mounts(dentry);
3977-
fsnotify_rmdir(dir, dentry);
39783977

39793978
out:
39803979
inode_unlock(dentry->d_inode);
39813980
dput(dentry);
39823981
if (!error)
3983-
d_delete(dentry);
3982+
d_delete_notify(dir, dentry);
39843983
return error;
39853984
}
39863985
EXPORT_SYMBOL(vfs_rmdir);
@@ -4102,17 +4101,18 @@ int vfs_unlink(struct user_namespace *mnt_userns, struct inode *dir,
41024101
if (!error) {
41034102
dont_mount(dentry);
41044103
detach_mounts(dentry);
4105-
fsnotify_unlink(dir, dentry);
41064104
}
41074105
}
41084106
}
41094107
out:
41104108
inode_unlock(target);
41114109

41124110
/* We don't d_delete() NFS sillyrenamed files--they still exist. */
4113-
if (!error && !(dentry->d_flags & DCACHE_NFSFS_RENAMED)) {
4111+
if (!error && dentry->d_flags & DCACHE_NFSFS_RENAMED) {
4112+
fsnotify_unlink(dir, dentry);
4113+
} else if (!error) {
41144114
fsnotify_link_count(target);
4115-
d_delete(dentry);
4115+
d_delete_notify(dir, dentry);
41164116
}
41174117

41184118
return error;

include/linux/fsnotify.h

Lines changed: 43 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -224,17 +224,54 @@ static inline void fsnotify_link(struct inode *dir, struct inode *inode,
224224
dir, &new_dentry->d_name, 0);
225225
}
226226

227+
/*
228+
* fsnotify_delete - @dentry was unlinked and unhashed
229+
*
230+
* Caller must make sure that dentry->d_name is stable.
231+
*
232+
* Note: unlike fsnotify_unlink(), we have to pass also the unlinked inode
233+
* as this may be called after d_delete() and old_dentry may be negative.
234+
*/
235+
static inline void fsnotify_delete(struct inode *dir, struct inode *inode,
236+
struct dentry *dentry)
237+
{
238+
__u32 mask = FS_DELETE;
239+
240+
if (S_ISDIR(inode->i_mode))
241+
mask |= FS_ISDIR;
242+
243+
fsnotify_name(mask, inode, FSNOTIFY_EVENT_INODE, dir, &dentry->d_name,
244+
0);
245+
}
246+
247+
/**
248+
* d_delete_notify - delete a dentry and call fsnotify_delete()
249+
* @dentry: The dentry to delete
250+
*
251+
* This helper is used to guaranty that the unlinked inode cannot be found
252+
* by lookup of this name after fsnotify_delete() event has been delivered.
253+
*/
254+
static inline void d_delete_notify(struct inode *dir, struct dentry *dentry)
255+
{
256+
struct inode *inode = d_inode(dentry);
257+
258+
ihold(inode);
259+
d_delete(dentry);
260+
fsnotify_delete(dir, inode, dentry);
261+
iput(inode);
262+
}
263+
227264
/*
228265
* fsnotify_unlink - 'name' was unlinked
229266
*
230267
* Caller must make sure that dentry->d_name is stable.
231268
*/
232269
static inline void fsnotify_unlink(struct inode *dir, struct dentry *dentry)
233270
{
234-
/* Expected to be called before d_delete() */
235-
WARN_ON_ONCE(d_is_negative(dentry));
271+
if (WARN_ON_ONCE(d_is_negative(dentry)))
272+
return;
236273

237-
fsnotify_dirent(dir, dentry, FS_DELETE);
274+
fsnotify_delete(dir, d_inode(dentry), dentry);
238275
}
239276

240277
/*
@@ -258,10 +295,10 @@ static inline void fsnotify_mkdir(struct inode *dir, struct dentry *dentry)
258295
*/
259296
static inline void fsnotify_rmdir(struct inode *dir, struct dentry *dentry)
260297
{
261-
/* Expected to be called before d_delete() */
262-
WARN_ON_ONCE(d_is_negative(dentry));
298+
if (WARN_ON_ONCE(d_is_negative(dentry)))
299+
return;
263300

264-
fsnotify_dirent(dir, dentry, FS_DELETE | FS_ISDIR);
301+
fsnotify_delete(dir, d_inode(dentry), dentry);
265302
}
266303

267304
/*

0 commit comments

Comments
 (0)