diff mbox series

fanotify: report FAN_PRE_MODIFY event before write to file range

Message ID 20250331165231.1466680-1-amir73il@gmail.com (mailing list archive)
State New
Headers show
Series fanotify: report FAN_PRE_MODIFY event before write to file range | expand

Commit Message

Amir Goldstein March 31, 2025, 4:52 p.m. UTC
In addition to FAN_PRE_ACCESS event before any access to a file range,
also report the FAN_PRE_MODIFY event in case of a write access.

This will allow userspace to subscribe only to pre-write access
notifications and to respond with error codes associated with write
operations using the FAN_DENY_ERRNO macro.

Signed-off-by: Amir Goldstein <amir73il@gmail.com>
---

Jan,

I was looking on th list for the reason we decided to drop FAN_PRE_MODIFY
from the pre-content patch set and I couldn't find it. It may have been
related to complications ot page fault hooks that are not not relevant?

I did find the decision to generate FAN_PRE_ACCESS on both read()/write(),
so maybe we thought there was no clear value for the FAN_PRE_MODIFY event?

In any case, I realized that we allowed returning custom errors with
FAN_DENY_ERRNO(ENOSPC), but that chosing the right error does require
knowing whether the call was read() or write().

Becaue mmap() cannot return write() errors like ENOSPC, I decided not
to generate FAN_PRE_MODIFY for writably-shared maps, but maybe we should
consider this.

This patch implement FAN_PRE_{ACCESS,MODIFY} as a combined event.
This is not like the way that we generate the permission events
FAN_OPEN_PERM and FAN_OPEN_EXEC_PERM as separate events.

I do not remember why we chose the seprate events in the case above.
I do remember why we chose separate events for FAN_ACCESS_PERM and
FAN_PRE_ACCESS - because they do not have the same info.

Anyway, this way makes more sense to me for FAN_PRE_{ACCESS,MODIFY}.

LTP test pushed to branch fan_hsm.

WDYT?

Amir.

P.S. #1: if you accept this, we may consider sending to stable 6.14 to
make FAN_PRE_MODIFY part of the initial version of the API.

P.S. #2: I am going to look now at FAN_PRE_ACCESS events on directories
that we left out, because we may want to include it in initial API if
it turns out to be simple.


 fs/notify/fanotify/fanotify.c    |  3 ++-
 fs/notify/fsnotify.c             | 12 ++++++++----
 include/linux/fanotify.h         |  2 +-
 include/linux/fsnotify.h         | 10 ++++++----
 include/linux/fsnotify_backend.h |  8 +++++---
 include/uapi/linux/fanotify.h    |  2 ++
 6 files changed, 24 insertions(+), 13 deletions(-)
diff mbox series

Patch

diff --git a/fs/notify/fanotify/fanotify.c b/fs/notify/fanotify/fanotify.c
index 6d386080faf2..6c877b3646ec 100644
--- a/fs/notify/fanotify/fanotify.c
+++ b/fs/notify/fanotify/fanotify.c
@@ -950,8 +950,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_PRE_MODIFY != FS_PRE_MODIFY);
 
-	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..7b364f965650 100644
--- a/fs/notify/fsnotify.c
+++ b/fs/notify/fsnotify.c
@@ -210,19 +210,23 @@  static inline bool fsnotify_object_watched(struct inode *inode, __u32 mnt_mask,
 
 /* Report pre-content event with optional range info */
 int fsnotify_pre_content(const struct path *path, const loff_t *ppos,
-			 size_t count)
+			 size_t count, bool write)
 {
+	__u32 mask = FS_PRE_ACCESS;
 	struct file_range range;
 
+	if (write)
+		mask |= FS_PRE_MODIFY;
+
 	/* Report page aligned range only when pos is known */
 	if (!ppos)
-		return fsnotify_path(path, FS_PRE_ACCESS);
+		return fsnotify_path(path, mask);
 
 	range.path = path;
 	range.pos = PAGE_ALIGN_DOWN(*ppos);
 	range.count = PAGE_ALIGN(*ppos + count) - range.pos;
 
-	return fsnotify_parent(path->dentry, FS_PRE_ACCESS, &range,
+	return fsnotify_parent(path->dentry, mask, &range,
 			       FSNOTIFY_EVENT_FILE_RANGE);
 }
 
@@ -745,7 +749,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 3c817dc6292e..85def9de2ef4 100644
--- a/include/linux/fanotify.h
+++ b/include/linux/fanotify.h
@@ -94,7 +94,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_PRE_MODIFY)
 
 /* 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 454d8e466958..9beea32c8980 100644
--- a/include/linux/fsnotify.h
+++ b/include/linux/fsnotify.h
@@ -144,7 +144,7 @@  static inline int fsnotify_file_area_perm(struct file *file, int perm_mask,
 	 */
 	lockdep_assert_once(file_write_not_started(file));
 
-	if (!(perm_mask & (MAY_READ | MAY_WRITE | MAY_ACCESS)))
+	if (!(perm_mask & (MAY_READ | MAY_WRITE)))
 		return 0;
 
 	if (likely(!FMODE_FSNOTIFY_PERM(file->f_mode)))
@@ -154,7 +154,8 @@  static inline int fsnotify_file_area_perm(struct file *file, int perm_mask,
 	 * read()/write() and other types of access generate pre-content events.
 	 */
 	if (unlikely(FMODE_FSNOTIFY_HSM(file->f_mode))) {
-		int ret = fsnotify_pre_content(&file->f_path, ppos, count);
+		int ret = fsnotify_pre_content(&file->f_path, ppos, count,
+					       perm_mask & MAY_WRITE);
 
 		if (ret)
 			return ret;
@@ -182,7 +183,8 @@  static inline int fsnotify_mmap_perm(struct file *file, int prot,
 	if (!file || likely(!FMODE_FSNOTIFY_HSM(file->f_mode)))
 		return 0;
 
-	return fsnotify_pre_content(&file->f_path, &off, len);
+	/* XXX: should we report pre-modify for writably shared maps */
+	return fsnotify_pre_content(&file->f_path, &off, len, false);
 }
 
 /*
@@ -197,7 +199,7 @@  static inline int fsnotify_truncate_perm(const struct path *path, loff_t length)
 					       FSNOTIFY_PRIO_PRE_CONTENT))
 		return 0;
 
-	return fsnotify_pre_content(path, &length, 0);
+	return fsnotify_pre_content(path, &length, 0, true);
 }
 
 /*
diff --git a/include/linux/fsnotify_backend.h b/include/linux/fsnotify_backend.h
index 6cd8d1d28b8b..396943093373 100644
--- a/include/linux/fsnotify_backend.h
+++ b/include/linux/fsnotify_backend.h
@@ -58,6 +58,7 @@ 
 /* #define FS_DIR_MODIFY	0x00080000 */	/* Deprecated (reserved) */
 
 #define FS_PRE_ACCESS		0x00100000	/* Pre-content access hook */
+#define FS_PRE_MODIFY		0x00200000	/* Pre-content modify hook */
 
 #define FS_MNT_ATTACH		0x01000000	/* Mount was attached */
 #define FS_MNT_DETACH		0x02000000	/* Mount was detached */
@@ -91,7 +92,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_PRE_MODIFY)
 
 #define ALL_FSNOTIFY_PERM_EVENTS (FSNOTIFY_CONTENT_PERM_EVENTS | \
 				  FSNOTIFY_PRE_CONTENT_EVENTS)
@@ -932,12 +933,13 @@  static inline void fsnotify_init_event(struct fsnotify_event *event)
 	INIT_LIST_HEAD(&event->list);
 }
 int fsnotify_pre_content(const struct path *path, const loff_t *ppos,
-			 size_t count);
+			 size_t count, bool write);
 
 #else
 
 static inline int fsnotify_pre_content(const struct path *path,
-				       const loff_t *ppos, size_t count)
+				       const loff_t *ppos, size_t count,
+				       bool write)
 {
 	return 0;
 }
diff --git a/include/uapi/linux/fanotify.h b/include/uapi/linux/fanotify.h
index e710967c7c26..a9728a550daa 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_PRE_MODIFY		0x00200000	/* Pre-content modify hook */
+
 #define FAN_MNT_ATTACH		0x01000000	/* Mount was attached */
 #define FAN_MNT_DETACH		0x02000000	/* Mount was detached */