@@ -45,6 +45,12 @@ static int fanotify_merge(struct list_head *list, struct fsnotify_event *event)
return 0;
#endif
+ /*
+ * Don't merge a filename event with any other event
+ */
+ if (event->mask & FAN_FILENAME_EVENTS)
+ return 0;
+
list_for_each_entry_reverse(test_event, list, list) {
if (should_merge(test_event, event)) {
do_merge = true;
@@ -161,7 +167,8 @@ static bool fanotify_should_send_event(struct fsnotify_mark *inode_mark,
}
struct fanotify_event_info *fanotify_alloc_event(struct inode *inode, u32 mask,
- struct path *path)
+ struct path *path,
+ const char *file_name)
{
struct fanotify_event_info *event;
@@ -178,6 +185,31 @@ struct fanotify_event_info *fanotify_alloc_event(struct inode *inode, u32 mask,
goto init;
}
#endif
+
+ /*
+ * For filename events (create,delete,rename), path points to the
+ * directory and name holds the entry name, so allocate a variable
+ * length fanotify_file_event_info struct.
+ */
+ if (mask & FAN_FILENAME_EVENTS) {
+ struct fanotify_file_event_info *ffe;
+ int alloc_len = sizeof(*ffe);
+ int len = 0;
+
+ if ((mask & FAN_FILENAME_EVENTS) && file_name) {
+ len = strlen(file_name);
+ alloc_len += len + 1;
+ }
+ ffe = kmalloc(alloc_len, GFP_KERNEL);
+ if (!ffe)
+ return NULL;
+ event = &ffe->fae;
+ ffe->name_len = len;
+ if (len)
+ strcpy(ffe->name, file_name);
+ goto init;
+ }
+
event = kmem_cache_alloc(fanotify_event_cachep, GFP_KERNEL);
if (!event)
return NULL;
@@ -245,7 +277,7 @@ static int fanotify_handle_event(struct fsnotify_group *group,
pr_debug("%s: group=%p inode=%p mask=%x\n", __func__, group, inode,
mask);
- event = fanotify_alloc_event(inode, mask, &path);
+ event = fanotify_alloc_event(inode, mask, &path, file_name);
if (unlikely(!event))
return -ENOMEM;
@@ -292,6 +324,11 @@ static void fanotify_free_event(struct fsnotify_event *fsn_event)
return;
}
#endif
+ if (fsn_event->mask & FAN_FILENAME_EVENTS) {
+ kfree(FANOTIFY_FE(fsn_event));
+ return;
+ }
+
kmem_cache_free(fanotify_event_cachep, event);
}
@@ -20,6 +20,21 @@ struct fanotify_event_info {
struct pid *tgid;
};
+/*
+ * Structure for fanotify events with variable length data.
+ * It gets allocated in fanotify_handle_event() and freed
+ * when the information is retrieved by userspace
+ */
+struct fanotify_file_event_info {
+ struct fanotify_event_info fae;
+ /*
+ * For filename events (create,delete,rename), path points to the
+ * directory and name holds the entry name
+ */
+ int name_len;
+ char name[];
+};
+
#ifdef CONFIG_FANOTIFY_ACCESS_PERMISSIONS
/*
* Structure for permission fanotify events. It gets allocated and freed in
@@ -41,10 +56,17 @@ FANOTIFY_PE(struct fsnotify_event *fse)
}
#endif
+static inline struct fanotify_file_event_info *
+FANOTIFY_FE(struct fsnotify_event *fse)
+{
+ return container_of(fse, struct fanotify_file_event_info, fae.fse);
+}
+
static inline struct fanotify_event_info *FANOTIFY_E(struct fsnotify_event *fse)
{
return container_of(fse, struct fanotify_event_info, fse);
}
struct fanotify_event_info *fanotify_alloc_event(struct inode *inode, u32 mask,
- struct path *path);
+ struct path *path,
+ const char *file_name);
@@ -205,13 +205,22 @@ static int process_access_response(struct fsnotify_group *group,
}
#endif
+static int round_event_name_len(struct fanotify_file_event_info *event)
+{
+ if (!event->name_len)
+ return 0;
+ return roundup(event->name_len + 1, FAN_EVENT_METADATA_LEN);
+}
+
static ssize_t copy_event_to_user(struct fsnotify_group *group,
struct fsnotify_event *event,
char __user *buf)
{
struct fanotify_event_metadata fanotify_event_metadata;
+ struct fanotify_file_event_info *ffe = NULL;
struct file *f;
int fd, ret;
+ size_t pad_name_len = 0;
pr_debug("%s: group=%p event=%p\n", __func__, group, event);
@@ -219,12 +228,37 @@ static ssize_t copy_event_to_user(struct fsnotify_group *group,
if (ret < 0)
return ret;
+ if ((event->mask & FAN_FILENAME_EVENTS) &&
+ (group->fanotify_data.flags & FAN_EVENT_INFO_NAME)) {
+ ffe = FANOTIFY_FE(event);
+ pad_name_len = round_event_name_len(ffe);
+ fanotify_event_metadata.event_len += pad_name_len;
+ }
+
fd = fanotify_event_metadata.fd;
ret = -EFAULT;
if (copy_to_user(buf, &fanotify_event_metadata,
- fanotify_event_metadata.event_len))
+ FAN_EVENT_METADATA_LEN))
goto out_close_fd;
+ buf += FAN_EVENT_METADATA_LEN;
+
+ /*
+ * send the filename and pad to a multiple of FAN_EVENT_METADATA_LEN
+ * with zeros.
+ */
+ ret = -EFAULT;
+ if (ffe && pad_name_len) {
+ /* copy the filename */
+ if (copy_to_user(buf, ffe->name, ffe->name_len))
+ goto out_close_fd;
+ buf += ffe->name_len;
+
+ /* fill userspace with 0's */
+ if (clear_user(buf, pad_name_len - ffe->name_len))
+ goto out_close_fd;
+ }
+
#ifdef CONFIG_FANOTIFY_ACCESS_PERMISSIONS
if (event->mask & FAN_ALL_PERM_EVENTS)
FANOTIFY_PE(event)->fd = fd;
@@ -755,7 +789,7 @@ SYSCALL_DEFINE2(fanotify_init, unsigned int, flags, unsigned int, event_f_flags)
group->fanotify_data.user = user;
atomic_inc(&user->fanotify_listeners);
- oevent = fanotify_alloc_event(NULL, FS_Q_OVERFLOW, NULL);
+ oevent = fanotify_alloc_event(NULL, FS_Q_OVERFLOW, NULL, NULL);
if (unlikely(!oevent)) {
fd = -ENOMEM;
goto out_destroy_group;
@@ -919,6 +953,14 @@ SYSCALL_DEFINE5(fanotify_mark, int, fanotify_fd, unsigned int, flags,
!(group->fanotify_data.flags & FAN_EVENT_INFO_PARENT))
mask &= ~FAN_DENTRY_EVENTS;
+ /*
+ * Filename events are not interesting without the name inforamtion.
+ * Ignore filename events unless user explicitly set the new
+ * FAN_EVENT_INFO_NAME flag to fanotify_init().
+ */
+ if (!(group->fanotify_data.flags & FAN_EVENT_INFO_NAME))
+ mask &= ~FAN_FILENAME_EVENTS;
+
/* create/update an inode mark */
switch (flags & (FAN_MARK_ADD | FAN_MARK_REMOVE)) {
case FAN_MARK_ADD:
@@ -47,7 +47,9 @@
/* These bits determine the format of the reported events */
#define FAN_EVENT_INFO_PARENT 0x00000100 /* Event fd maybe of parent */
-#define FAN_ALL_EVENT_INFO_BITS (FAN_EVENT_INFO_PARENT)
+#define FAN_EVENT_INFO_NAME 0x00000200 /* Event data has filename */
+#define FAN_ALL_EVENT_INFO_BITS (FAN_EVENT_INFO_PARENT | \
+ FAN_EVENT_INFO_NAME)
#define FAN_ALL_INIT_FLAGS (FAN_CLOEXEC | FAN_NONBLOCK | \
FAN_ALL_CLASS_BITS | \
@@ -98,6 +100,10 @@
#define FAN_ALL_PERM_EVENTS (FAN_OPEN_PERM |\
FAN_ACCESS_PERM)
+/* Events on directory requiring to pass filename */
+#define FAN_FILENAME_EVENTS (FAN_MOVED_FROM | FAN_MOVED_TO |\
+ FAN_CREATE | FAN_DELETE)
+
#define FAN_ALL_OUTGOING_EVENTS (FAN_ALL_EVENTS |\
FAN_ALL_PERM_EVENTS |\
FAN_Q_OVERFLOW)
On filename events, allocate a variable length fanotify_file_event_info struct and store the filename, along with the parent directory path struct. When filename exists, The fanotify_event_metadata.event_len field includes the size of the metadata header and the size of the padded filename. User programs that use the fanotify macros FAN_EVENT_NEXT() and FAN_EVENT_OK() will not experience any breakage and new user programs can read the filename as a null terminated string from the event buffer at offset FAN_EVENT_METADATA_LEN. Nevertheless, programs that want to get the filename info are required to explicitly request it via the FAN_EVENT_INFO_NAME flag to fanotify_init(). This flag is only allowed to be set along with the default FAN_CLASS_NOTIF notification class. Signed-off-by: Amir Goldstein <amir73il@gmail.com> --- fs/notify/fanotify/fanotify.c | 41 +++++++++++++++++++++++++++++++-- fs/notify/fanotify/fanotify.h | 24 +++++++++++++++++++- fs/notify/fanotify/fanotify_user.c | 46 ++++++++++++++++++++++++++++++++++++-- include/uapi/linux/fanotify.h | 8 ++++++- 4 files changed, 113 insertions(+), 6 deletions(-)