[RFC PATCH v2 3/3] fanotify: introduce FAN_PATH_ACCESS event

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



The exiting FAN_PRE_ACCESS pre-content event on a directory can be used
to populate a directory before ian application is reading its content.

The new FAN_PATH_ACCESS pre-content event adds the ability to populate
a directory before lookup in the directory index.

The hook is called from lookup_slow_notify() when travesing a directory
during path lookup walk.  Because it is only relevant for directories,
the FAN_PATH_ACCESS event is generated regardless of FAN_ONDIR flag
in mark mask.

The permission hook cannot be sent to usersapce while lookup is in
RCU walk, so if dcache entries already exist, it is assumed that
FAN_PATH_ACCESS events are not needed, because an event was already
generated when dcache was populated.

To prevent a deadlock, when a directory access monitoring process uses
the event->fd obtained in a FAN_PATH_ACCESS event to lookup a path,
relative to event->fd, this lookup does not generate recursive
FAN_PATH_ACCESS events

Signed-off-by: Amir Goldstein <amir73il@xxxxxxxxx>
---
 fs/namei.c                       | 70 ++++++++++++++++++++++++++++----
 fs/notify/fanotify/fanotify.c    |  3 +-
 fs/notify/fsnotify.c             |  2 +-
 include/linux/fanotify.h         |  2 +-
 include/linux/fsnotify.h         | 26 ++++++++++++
 include/linux/fsnotify_backend.h |  4 +-
 include/linux/namei.h            |  3 ++
 include/uapi/linux/fanotify.h    |  2 +
 8 files changed, 101 insertions(+), 11 deletions(-)

diff --git a/fs/namei.c b/fs/namei.c
index 4bb889fc980b..b41fedc0f11f 100644
--- a/fs/namei.c
+++ b/fs/namei.c
@@ -657,6 +657,14 @@ struct nameidata {
 #define ND_ROOT_PRESET 1
 #define ND_ROOT_GRABBED 2
 #define ND_JUMPED 4
+#define ND_NONOTIFY 8
+
+static void nd_set_jumped(struct nameidata *nd)
+{
+	nd->state |= ND_JUMPED;
+	/* Maybe crossed sb/mount so need to re-test sb/mount watches */
+	nd->state &= ~ND_NONOTIFY;
+}
 
 static void __set_nameidata(struct nameidata *p, int dfd, struct filename *name)
 {
@@ -1051,7 +1059,7 @@ static int nd_jump_root(struct nameidata *nd)
 		path_get(&nd->path);
 		nd->inode = nd->path.dentry->d_inode;
 	}
-	nd->state |= ND_JUMPED;
+	nd_set_jumped(nd);
 	return 0;
 }
 
@@ -1079,7 +1087,7 @@ int nd_jump_link(const struct path *path)
 	path_put(&nd->path);
 	nd->path = *path;
 	nd->inode = nd->path.dentry->d_inode;
-	nd->state |= ND_JUMPED;
+	nd_set_jumped(nd);
 	return 0;
 
 err:
@@ -1594,7 +1602,7 @@ static bool __follow_mount_rcu(struct nameidata *nd, struct path *path)
 			if (mounted) {
 				path->mnt = &mounted->mnt;
 				dentry = path->dentry = mounted->mnt.mnt_root;
-				nd->state |= ND_JUMPED;
+				nd_set_jumped(nd);
 				nd->next_seq = read_seqcount_begin(&dentry->d_seq);
 				flags = dentry->d_flags;
 				// makes sure that non-RCU pathwalk could reach
@@ -1634,7 +1642,7 @@ static inline int handle_mounts(struct nameidata *nd, struct dentry *dentry,
 		if (unlikely(nd->flags & LOOKUP_NO_XDEV))
 			ret = -EXDEV;
 		else
-			nd->state |= ND_JUMPED;
+			nd_set_jumped(nd);
 	}
 	if (unlikely(ret)) {
 		dput(path->dentry);
@@ -1836,6 +1844,47 @@ static struct dentry *lookup_slow(const struct qstr *name,
 	return res;
 }
 
+static bool lookup_should_notify(struct nameidata *nd)
+{
+#ifdef CONFIG_FANOTIFY_ACCESS_PERMISSIONS
+	/* Was dirfd obtained from fanotify event->fd? */
+	if (unlikely(nd->flags & LOOKUP_NONOTIFY))
+		return false;
+
+	/*
+	 * An open coded version of the fsnotify mask checks in fsnotify()
+	 * optimized to check the sb/mount marks only once per jump.
+	 */
+	if (unlikely(nd->inode->i_fsnotify_mask & FS_PATH_ACCESS))
+		return true;
+
+	if (likely(nd->state & ND_NONOTIFY))
+		return false;
+
+	if (unlikely(nd->inode->i_sb->s_fsnotify_mask & FS_PATH_ACCESS) ||
+	    unlikely(real_mount(nd->path.mnt)->mnt_fsnotify_mask &
+		     FS_PATH_ACCESS))
+		return true;
+
+	/* Avoid testing sb/mount masks again until nd_set_jumped() */
+	nd->state |= ND_NONOTIFY;
+#endif
+	return false;
+}
+
+
+static struct dentry *lookup_slow_notify(struct nameidata *nd)
+{
+	if (lookup_should_notify(nd)) {
+		int err = fsnotify_lookup_perm(&nd->path, nd->inode, &nd->last);
+
+		if (unlikely(err))
+			return ERR_PTR(err);
+	}
+
+	return lookup_slow(&nd->last, nd->path.dentry, nd->flags);
+}
+
 static inline int may_lookup(struct mnt_idmap *idmap,
 			     struct nameidata *restrict nd)
 {
@@ -2135,7 +2184,7 @@ static const char *walk_component(struct nameidata *nd, int flags)
 	if (IS_ERR(dentry))
 		return ERR_CAST(dentry);
 	if (unlikely(!dentry)) {
-		dentry = lookup_slow(&nd->last, nd->path.dentry, nd->flags);
+		dentry = lookup_slow_notify(nd);
 		if (IS_ERR(dentry))
 			return ERR_CAST(dentry);
 	}
@@ -2461,7 +2510,7 @@ static int link_path_walk(const char *name, struct nameidata *nd)
 		switch(lastword) {
 		case LAST_WORD_IS_DOTDOT:
 			nd->last_type = LAST_DOTDOT;
-			nd->state |= ND_JUMPED;
+			nd_set_jumped(nd);
 			break;
 
 		case LAST_WORD_IS_DOT:
@@ -2541,7 +2590,7 @@ static const char *path_init(struct nameidata *nd, unsigned flags)
 		nd->seq = nd->next_seq = 0;
 
 	nd->flags = flags;
-	nd->state |= ND_JUMPED;
+	nd_set_jumped(nd);
 
 	nd->m_seq = __read_seqcount_begin(&mount_lock.seqcount);
 	nd->r_seq = __read_seqcount_begin(&rename_lock.seqcount);
@@ -2608,6 +2657,13 @@ static const char *path_init(struct nameidata *nd, unsigned flags)
 		if (*s && unlikely(!d_can_lookup(dentry)))
 			return ERR_PTR(-ENOTDIR);
 
+		/*
+		 * dfd was opened by fanotify and lookup_slow_notify()
+		 * shouldn't generate fanotify events.
+		 */
+		if (FMODE_FSNOTIFY_NONE(fd_file(f)->f_mode))
+			nd->flags |= LOOKUP_NONOTIFY;
+
 		nd->path = fd_file(f)->f_path;
 		if (flags & LOOKUP_RCU) {
 			nd->inode = nd->path.dentry->d_inode;
diff --git a/fs/notify/fanotify/fanotify.c b/fs/notify/fanotify/fanotify.c
index 7c9a2614e715..df9b10d717d2 100644
--- a/fs/notify/fanotify/fanotify.c
+++ b/fs/notify/fanotify/fanotify.c
@@ -952,8 +952,9 @@ static int fanotify_handle_event(struct fsnotify_group *group, u32 mask,
 	BUILD_BUG_ON(FAN_FS_ERROR != FS_ERROR);
 	BUILD_BUG_ON(FAN_RENAME != FS_RENAME);
 	BUILD_BUG_ON(FAN_PRE_ACCESS != FS_PRE_ACCESS);
+	BUILD_BUG_ON(FAN_PATH_ACCESS != FS_PATH_ACCESS);
 
-	BUILD_BUG_ON(HWEIGHT32(ALL_FANOTIFY_EVENT_BITS) != 24);
+	BUILD_BUG_ON(HWEIGHT32(ALL_FANOTIFY_EVENT_BITS) != 25);
 
 	mask = fanotify_group_event_mask(group, iter_info, &match_mask,
 					 mask, data, data_type, dir);
diff --git a/fs/notify/fsnotify.c b/fs/notify/fsnotify.c
index e2b4f17a48bb..004a24036a12 100644
--- a/fs/notify/fsnotify.c
+++ b/fs/notify/fsnotify.c
@@ -745,7 +745,7 @@ static __init int fsnotify_init(void)
 {
 	int ret;
 
-	BUILD_BUG_ON(HWEIGHT32(ALL_FSNOTIFY_BITS) != 26);
+	BUILD_BUG_ON(HWEIGHT32(ALL_FSNOTIFY_BITS) != 27);
 
 	ret = init_srcu_struct(&fsnotify_mark_srcu);
 	if (ret)
diff --git a/include/linux/fanotify.h b/include/linux/fanotify.h
index 879cff5eccd4..0eaae7a46a33 100644
--- a/include/linux/fanotify.h
+++ b/include/linux/fanotify.h
@@ -93,7 +93,7 @@
 #define FANOTIFY_CONTENT_PERM_EVENTS (FAN_OPEN_PERM | FAN_OPEN_EXEC_PERM | \
 				      FAN_ACCESS_PERM)
 /* Pre-content events can be used to fill file content */
-#define FANOTIFY_PRE_CONTENT_EVENTS  (FAN_PRE_ACCESS)
+#define FANOTIFY_PRE_CONTENT_EVENTS  (FAN_PRE_ACCESS | FAN_PATH_ACCESS)
 
 /* Events that require a permission response from user */
 #define FANOTIFY_PERM_EVENTS	(FANOTIFY_CONTENT_PERM_EVENTS | \
diff --git a/include/linux/fsnotify.h b/include/linux/fsnotify.h
index 175149167642..44b8496730a3 100644
--- a/include/linux/fsnotify.h
+++ b/include/linux/fsnotify.h
@@ -227,6 +227,25 @@ static inline int fsnotify_open_perm(struct file *file)
 	return fsnotify_path(&file->f_path, FS_OPEN_PERM);
 }
 
+/*
+ * fsnotify_lookup_perm - permission hook before path access
+ */
+static inline int fsnotify_lookup_perm(struct path *path,
+				       struct inode *dir,
+				       const struct qstr *name)
+{
+	if (!(dir->i_sb->s_iflags & SB_I_ALLOW_HSM) ||
+	    !fsnotify_sb_has_priority_watchers(dir->i_sb,
+					       FSNOTIFY_PRIO_PRE_CONTENT))
+		return 0;
+
+	if (WARN_ON_ONCE(!S_ISDIR(dir->i_mode)))
+		return 0;
+
+	return fsnotify(FS_PATH_ACCESS, path, FSNOTIFY_EVENT_PATH,
+			dir, name, NULL, 0);
+}
+
 #else
 static inline void file_set_fsnotify_mode_from_watchers(struct file *file)
 {
@@ -258,6 +277,13 @@ static inline int fsnotify_open_perm(struct file *file)
 {
 	return 0;
 }
+
+static inline int fsnotify_lookup_perm(struct path *path,
+				       struct inode *dir,
+				       const struct qstr *name)
+{
+	return 0;
+}
 #endif
 
 /*
diff --git a/include/linux/fsnotify_backend.h b/include/linux/fsnotify_backend.h
index d4034ddaf392..c9a71cb97f3b 100644
--- a/include/linux/fsnotify_backend.h
+++ b/include/linux/fsnotify_backend.h
@@ -43,6 +43,7 @@
 #define FS_OPEN_EXEC		0x00001000	/* File was opened for exec */
 
 #define FS_UNMOUNT		0x00002000	/* inode on umount fs */
+
 #define FS_Q_OVERFLOW		0x00004000	/* Event queued overflowed */
 #define FS_ERROR		0x00008000	/* Filesystem Error (fanotify) */
 
@@ -58,6 +59,7 @@
 /* #define FS_DIR_MODIFY	0x00080000 */	/* Deprecated (reserved) */
 
 #define FS_PRE_ACCESS		0x00100000	/* Pre-content access hook */
+#define FS_PATH_ACCESS		0x00200000	/* Pre-path access hook */
 
 #define FS_MNT_ATTACH		0x01000000	/* Mount was attached */
 #define FS_MNT_DETACH		0x02000000	/* Mount was detached */
@@ -91,7 +93,7 @@
 #define FSNOTIFY_CONTENT_PERM_EVENTS (FS_OPEN_PERM | FS_OPEN_EXEC_PERM | \
 				      FS_ACCESS_PERM)
 /* Pre-content events can be used to fill file content */
-#define FSNOTIFY_PRE_CONTENT_EVENTS  (FS_PRE_ACCESS)
+#define FSNOTIFY_PRE_CONTENT_EVENTS  (FS_PRE_ACCESS | FS_PATH_ACCESS)
 
 #define ALL_FSNOTIFY_PERM_EVENTS (FSNOTIFY_CONTENT_PERM_EVENTS | \
 				  FSNOTIFY_PRE_CONTENT_EVENTS)
diff --git a/include/linux/namei.h b/include/linux/namei.h
index 5d085428e471..f084b5493f02 100644
--- a/include/linux/namei.h
+++ b/include/linux/namei.h
@@ -49,6 +49,9 @@ enum {LAST_NORM, LAST_ROOT, LAST_DOT, LAST_DOTDOT};
 #define LOOKUP_IS_SCOPED (LOOKUP_BENEATH | LOOKUP_IN_ROOT)
 /* 3 spare bits for scoping */
 
+/* dirfd was opened by fanotify and lookup shouldn't generate fanotify events */
+#define LOOKUP_NONOTIFY		0x4000000
+
 extern int path_pts(struct path *path);
 
 extern int user_path_at(int, const char __user *, unsigned, struct path *);
diff --git a/include/uapi/linux/fanotify.h b/include/uapi/linux/fanotify.h
index e710967c7c26..190c2258f623 100644
--- a/include/uapi/linux/fanotify.h
+++ b/include/uapi/linux/fanotify.h
@@ -28,6 +28,8 @@
 /* #define FAN_DIR_MODIFY	0x00080000 */	/* Deprecated (reserved) */
 
 #define FAN_PRE_ACCESS		0x00100000	/* Pre-content access hook */
+#define FAN_PATH_ACCESS		0x00200000	/* Pre-path access hook */
+
 #define FAN_MNT_ATTACH		0x01000000	/* Mount was attached */
 #define FAN_MNT_DETACH		0x02000000	/* Mount was detached */
 
-- 
2.34.1





[Index of Archives]     [Linux Ext4 Filesystem]     [Union Filesystem]     [Filesystem Testing]     [Ceph Users]     [Ecryptfs]     [NTFS 3]     [AutoFS]     [Kernel Newbies]     [Share Photos]     [Security]     [Netfilter]     [Bugtraq]     [Yosemite News]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux Cachefs]     [Reiser Filesystem]     [Linux RAID]     [NTFS 3]     [Samba]     [Device Mapper]     [CEPH Development]

  Powered by Linux