Skip to content

Commit

Permalink
fsnotify: invalidate dcache before IN_DELETE event
Browse files Browse the repository at this point in the history
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]>
  • Loading branch information
amir73il authored and jankara committed Jan 24, 2022
1 parent 217663f commit a37d9a1
Show file tree
Hide file tree
Showing 3 changed files with 50 additions and 15 deletions.
6 changes: 2 additions & 4 deletions fs/btrfs/ioctl.c
Original file line number Diff line number Diff line change
Expand Up @@ -3086,10 +3086,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);
Expand Down
10 changes: 5 additions & 5 deletions fs/namei.c
Original file line number Diff line number Diff line change
Expand Up @@ -3974,13 +3974,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);
Expand Down Expand Up @@ -4102,17 +4101,18 @@ int vfs_unlink(struct user_namespace *mnt_userns, struct inode *dir,
if (!error) {
dont_mount(dentry);
detach_mounts(dentry);
fsnotify_unlink(dir, dentry);
}
}
}
out:
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;
Expand Down
49 changes: 43 additions & 6 deletions include/linux/fsnotify.h
Original file line number Diff line number Diff line change
Expand Up @@ -224,17 +224,54 @@ 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
*
* Caller must make sure that dentry->d_name is stable.
*/
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);
}

/*
Expand All @@ -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);
}

/*
Expand Down

0 comments on commit a37d9a1

Please sign in to comment.