summaryrefslogtreecommitdiff
path: root/fs/notify
diff options
context:
space:
mode:
authorAmir Goldstein <amir73il@gmail.com>2020-03-19 18:10:22 +0300
committerJan Kara <jack@suse.cz>2020-03-26 01:17:16 +0300
commit44d705b0370b1d581f46ff23e5d33e8b5ff8ec58 (patch)
tree8f80b2faa4e0d990ddcb397b01f3a8dcd00d1da7 /fs/notify
parentcacfb956d46edc5d7a1165161bfc6ca3de9519d9 (diff)
downloadlinux-44d705b0370b1d581f46ff23e5d33e8b5ff8ec58.tar.xz
fanotify: report name info for FAN_DIR_MODIFY event
Report event FAN_DIR_MODIFY with name in a variable length record similar to how fid's are reported. With name info reporting implemented, setting FAN_DIR_MODIFY in mark mask is now allowed. When events are reported with name, the reported fid identifies the directory and the name follows the fid. The info record type for this event info is FAN_EVENT_INFO_TYPE_DFID_NAME. For now, all reported events have at most one info record which is either FAN_EVENT_INFO_TYPE_FID or FAN_EVENT_INFO_TYPE_DFID_NAME (for FAN_DIR_MODIFY). Later on, events "on child" will report both records. There are several ways that an application can use this information: 1. When watching a single directory, the name is always relative to the watched directory, so application need to fstatat(2) the name relative to the watched directory. 2. When watching a set of directories, the application could keep a map of dirfd for all watched directories and hash the map by fid obtained with name_to_handle_at(2). When getting a name event, the fid in the event info could be used to lookup the base dirfd in the map and then call fstatat(2) with that dirfd. 3. When watching a filesystem (FAN_MARK_FILESYSTEM) or a large set of directories, the application could use open_by_handle_at(2) with the fid in event info to obtain dirfd for the directory where event happened and call fstatat(2) with this dirfd. The last option scales better for a large number of watched directories. The first two options may be available in the future also for non privileged fanotify watchers, because open_by_handle_at(2) requires the CAP_DAC_READ_SEARCH capability. Link: https://lore.kernel.org/r/20200319151022.31456-15-amir73il@gmail.com Signed-off-by: Amir Goldstein <amir73il@gmail.com> Signed-off-by: Jan Kara <jack@suse.cz>
Diffstat (limited to 'fs/notify')
-rw-r--r--fs/notify/fanotify/fanotify.c2
-rw-r--r--fs/notify/fanotify/fanotify_user.c117
2 files changed, 91 insertions, 28 deletions
diff --git a/fs/notify/fanotify/fanotify.c b/fs/notify/fanotify/fanotify.c
index 599654564b2a..4c1a4eb597d5 100644
--- a/fs/notify/fanotify/fanotify.c
+++ b/fs/notify/fanotify/fanotify.c
@@ -520,7 +520,7 @@ static int fanotify_handle_event(struct fsnotify_group *group,
BUILD_BUG_ON(FAN_OPEN_EXEC != FS_OPEN_EXEC);
BUILD_BUG_ON(FAN_OPEN_EXEC_PERM != FS_OPEN_EXEC_PERM);
- BUILD_BUG_ON(HWEIGHT32(ALL_FANOTIFY_EVENT_BITS) != 19);
+ BUILD_BUG_ON(HWEIGHT32(ALL_FANOTIFY_EVENT_BITS) != 20);
mask = fanotify_group_event_mask(group, iter_info, mask, data,
data_type);
diff --git a/fs/notify/fanotify/fanotify_user.c b/fs/notify/fanotify/fanotify_user.c
index a9d287a56098..42cb794c62ac 100644
--- a/fs/notify/fanotify/fanotify_user.c
+++ b/fs/notify/fanotify/fanotify_user.c
@@ -51,22 +51,35 @@ struct kmem_cache *fanotify_path_event_cachep __read_mostly;
struct kmem_cache *fanotify_perm_event_cachep __read_mostly;
#define FANOTIFY_EVENT_ALIGN 4
+#define FANOTIFY_INFO_HDR_LEN \
+ (sizeof(struct fanotify_event_info_fid) + sizeof(struct file_handle))
-static int fanotify_fid_info_len(int fh_len)
+static int fanotify_fid_info_len(int fh_len, int name_len)
{
- return roundup(sizeof(struct fanotify_event_info_fid) +
- sizeof(struct file_handle) + fh_len,
- FANOTIFY_EVENT_ALIGN);
+ int info_len = fh_len;
+
+ if (name_len)
+ info_len += name_len + 1;
+
+ return roundup(FANOTIFY_INFO_HDR_LEN + info_len, FANOTIFY_EVENT_ALIGN);
}
static int fanotify_event_info_len(struct fanotify_event *event)
{
+ int info_len = 0;
int fh_len = fanotify_event_object_fh_len(event);
- if (!fh_len)
- return 0;
+ if (fh_len)
+ info_len += fanotify_fid_info_len(fh_len, 0);
- return fanotify_fid_info_len(fh_len);
+ if (fanotify_event_name_len(event)) {
+ struct fanotify_name_event *fne = FANOTIFY_NE(event);
+
+ info_len += fanotify_fid_info_len(fne->dir_fh.len,
+ fne->name_len);
+ }
+
+ return info_len;
}
/*
@@ -204,23 +217,32 @@ static int process_access_response(struct fsnotify_group *group,
return -ENOENT;
}
-static int copy_fid_to_user(__kernel_fsid_t *fsid, struct fanotify_fh *fh,
- char __user *buf)
+static int copy_info_to_user(__kernel_fsid_t *fsid, struct fanotify_fh *fh,
+ const char *name, size_t name_len,
+ char __user *buf, size_t count)
{
struct fanotify_event_info_fid info = { };
struct file_handle handle = { };
unsigned char bounce[FANOTIFY_INLINE_FH_LEN], *fh_buf;
size_t fh_len = fh ? fh->len : 0;
- size_t len = fanotify_fid_info_len(fh_len);
+ size_t info_len = fanotify_fid_info_len(fh_len, name_len);
+ size_t len = info_len;
+
+ pr_debug("%s: fh_len=%zu name_len=%zu, info_len=%zu, count=%zu\n",
+ __func__, fh_len, name_len, info_len, count);
- if (!len)
+ if (!fh_len || (name && !name_len))
return 0;
- if (WARN_ON_ONCE(len < sizeof(info) + sizeof(handle) + fh_len))
+ if (WARN_ON_ONCE(len < sizeof(info) || len > count))
return -EFAULT;
- /* Copy event info fid header followed by vaiable sized file handle */
- info.hdr.info_type = FAN_EVENT_INFO_TYPE_FID;
+ /*
+ * Copy event info fid header followed by variable sized file handle
+ * and optionally followed by variable sized filename.
+ */
+ info.hdr.info_type = name_len ? FAN_EVENT_INFO_TYPE_DFID_NAME :
+ FAN_EVENT_INFO_TYPE_FID;
info.hdr.len = len;
info.fsid = *fsid;
if (copy_to_user(buf, &info, sizeof(info)))
@@ -228,6 +250,9 @@ static int copy_fid_to_user(__kernel_fsid_t *fsid, struct fanotify_fh *fh,
buf += sizeof(info);
len -= sizeof(info);
+ if (WARN_ON_ONCE(len < sizeof(handle)))
+ return -EFAULT;
+
handle.handle_type = fh->type;
handle.handle_bytes = fh_len;
if (copy_to_user(buf, &handle, sizeof(handle)))
@@ -235,9 +260,12 @@ static int copy_fid_to_user(__kernel_fsid_t *fsid, struct fanotify_fh *fh,
buf += sizeof(handle);
len -= sizeof(handle);
+ if (WARN_ON_ONCE(len < fh_len))
+ return -EFAULT;
+
/*
- * For an inline fh, copy through stack to exclude the copy from
- * usercopy hardening protections.
+ * For an inline fh and inline file name, copy through stack to exclude
+ * the copy from usercopy hardening protections.
*/
fh_buf = fanotify_fh_buf(fh);
if (fh_len <= FANOTIFY_INLINE_FH_LEN) {
@@ -247,14 +275,28 @@ static int copy_fid_to_user(__kernel_fsid_t *fsid, struct fanotify_fh *fh,
if (copy_to_user(buf, fh_buf, fh_len))
return -EFAULT;
- /* Pad with 0's */
buf += fh_len;
len -= fh_len;
+
+ if (name_len) {
+ /* Copy the filename with terminating null */
+ name_len++;
+ if (WARN_ON_ONCE(len < name_len))
+ return -EFAULT;
+
+ if (copy_to_user(buf, name, name_len))
+ return -EFAULT;
+
+ buf += name_len;
+ len -= name_len;
+ }
+
+ /* Pad with 0's */
WARN_ON_ONCE(len < 0 || len >= FANOTIFY_EVENT_ALIGN);
if (len > 0 && clear_user(buf, len))
return -EFAULT;
- return 0;
+ return info_len;
}
static ssize_t copy_event_to_user(struct fsnotify_group *group,
@@ -268,16 +310,15 @@ static ssize_t copy_event_to_user(struct fsnotify_group *group,
pr_debug("%s: group=%p event=%p\n", __func__, group, event);
- metadata.event_len = FAN_EVENT_METADATA_LEN;
+ metadata.event_len = FAN_EVENT_METADATA_LEN +
+ fanotify_event_info_len(event);
metadata.metadata_len = FAN_EVENT_METADATA_LEN;
metadata.vers = FANOTIFY_METADATA_VERSION;
metadata.reserved = 0;
metadata.mask = event->mask & FANOTIFY_OUTGOING_EVENTS;
metadata.pid = pid_vnr(event->pid);
- if (fanotify_event_object_fh(event)) {
- metadata.event_len += fanotify_event_info_len(event);
- } else if (path && path->mnt && path->dentry) {
+ if (path && path->mnt && path->dentry) {
fd = create_fd(group, path, &f);
if (fd < 0)
return fd;
@@ -295,17 +336,39 @@ static ssize_t copy_event_to_user(struct fsnotify_group *group,
if (copy_to_user(buf, &metadata, FAN_EVENT_METADATA_LEN))
goto out_close_fd;
+ buf += FAN_EVENT_METADATA_LEN;
+ count -= FAN_EVENT_METADATA_LEN;
+
if (fanotify_is_perm_event(event->mask))
FANOTIFY_PERM(event)->fd = fd;
- if (f) {
+ if (f)
fd_install(fd, f);
- } else if (fanotify_event_object_fh(event)) {
- ret = copy_fid_to_user(fanotify_event_fsid(event),
- fanotify_event_object_fh(event),
- buf + FAN_EVENT_METADATA_LEN);
+
+ /* Event info records order is: dir fid + name, child fid */
+ if (fanotify_event_name_len(event)) {
+ struct fanotify_name_event *fne = FANOTIFY_NE(event);
+
+ ret = copy_info_to_user(fanotify_event_fsid(event),
+ fanotify_event_dir_fh(event),
+ fne->name, fne->name_len,
+ buf, count);
if (ret < 0)
return ret;
+
+ buf += ret;
+ count -= ret;
+ }
+
+ if (fanotify_event_object_fh_len(event)) {
+ ret = copy_info_to_user(fanotify_event_fsid(event),
+ fanotify_event_object_fh(event),
+ NULL, 0, buf, count);
+ if (ret < 0)
+ return ret;
+
+ buf += ret;
+ count -= ret;
}
return metadata.event_len;