Adds a flag IN_FULL_PATHS which causes inotify to return the full path instead of only a filename. Includes a permissions check on IN_MOVE_SELF to prevent exposing paths if the user does not have permission to view the new path. Signed-off-by: Oliver Ford <ojford@xxxxxxxxx> --- fs/notify/inotify/inotify_fsnotify.c | 55 ++++++++++++++++++++++------ fs/notify/inotify/inotify_user.c | 19 +++++++++- include/linux/inotify.h | 2 +- include/uapi/linux/inotify.h | 1 + 4 files changed, 63 insertions(+), 14 deletions(-) diff --git a/fs/notify/inotify/inotify_fsnotify.c b/fs/notify/inotify/inotify_fsnotify.c index 49cfe2ae6d23..6334d1d6d5f5 100644 --- a/fs/notify/inotify/inotify_fsnotify.c +++ b/fs/notify/inotify/inotify_fsnotify.c @@ -64,12 +64,39 @@ int inotify_handle_inode_event(struct fsnotify_mark *inode_mark, u32 mask, struct inotify_event_info *event; struct fsnotify_event *fsn_event; struct fsnotify_group *group = inode_mark->group; + struct dentry *en = NULL; int ret; int len = 0; int alloc_len = sizeof(struct inotify_event_info); struct mem_cgroup *old_memcg; - - if (name) { + char *path_buf, *path_bufp = NULL; + bool found_full_path = false; + + if (inode_mark->mask & IN_FULL_PATHS && inode) { + mask |= IN_FULL_PATHS; + en = d_find_any_alias(inode); + if (en) + found_full_path = true; + else if (dir) + en = d_find_any_alias(dir); + + if (en) { + path_buf = kmalloc(PATH_MAX, GFP_KERNEL); + if (unlikely(!path_buf)) + goto oom; + + path_bufp = dentry_path_raw(en, path_buf, PATH_MAX); + len = strlen(path_bufp); + alloc_len += len + 1; + + if (!found_full_path) { + *(path_bufp + len) = '/'; + strcat(path_bufp + len + 1, name->name); + len += name->len + 1; + alloc_len += name->len + 1; + } + } + } else if (name) { len = name->len; alloc_len += len + 1; } @@ -89,14 +116,8 @@ int inotify_handle_inode_event(struct fsnotify_mark *inode_mark, u32 mask, event = kmalloc(alloc_len, GFP_KERNEL_ACCOUNT | __GFP_RETRY_MAYFAIL); set_active_memcg(old_memcg); - if (unlikely(!event)) { - /* - * Treat lost event due to ENOMEM the same way as queue - * overflow to let userspace know event was lost. - */ - fsnotify_queue_overflow(group); - return -ENOMEM; - } + if (unlikely(!event)) + goto oom; /* * We now report FS_ISDIR flag with MOVE_SELF and DELETE_SELF events @@ -113,8 +134,13 @@ int inotify_handle_inode_event(struct fsnotify_mark *inode_mark, u32 mask, event->wd = i_mark->wd; event->sync_cookie = cookie; event->name_len = len; - if (len) + + if (path_bufp) { + strcpy(event->name, path_bufp); + kfree(path_buf); + } else if (len) { strcpy(event->name, name->name); + } ret = fsnotify_add_event(group, fsn_event, inotify_merge); if (ret) { @@ -126,6 +152,13 @@ int inotify_handle_inode_event(struct fsnotify_mark *inode_mark, u32 mask, fsnotify_destroy_mark(inode_mark, group); return 0; +oom: + /* + * Treat lost event due to ENOMEM the same way as queue + * overflow to let userspace know event was lost. + */ + fsnotify_queue_overflow(group); + return -ENOMEM; } static void inotify_freeing_mark(struct fsnotify_mark *fsn_mark, struct fsnotify_group *group) diff --git a/fs/notify/inotify/inotify_user.c b/fs/notify/inotify/inotify_user.c index ed42a189faa2..2a0ad59250ce 100644 --- a/fs/notify/inotify/inotify_user.c +++ b/fs/notify/inotify/inotify_user.c @@ -57,6 +57,7 @@ struct kmem_cache *inotify_inode_mark_cachep __read_mostly; static long it_zero = 0; static long it_int_max = INT_MAX; +static struct inotify_inode_mark *inotify_idr_find(struct fsnotify_group *, int); static struct ctl_table inotify_table[] = { { @@ -110,7 +111,7 @@ static inline __u32 inotify_arg_to_mask(struct inode *inode, u32 arg) mask |= FS_EVENT_ON_CHILD; /* mask off the flags used to open the fd */ - mask |= (arg & INOTIFY_USER_MASK); + mask |= (arg & (INOTIFY_USER_MASK | IN_FULL_PATHS)); return mask; } @@ -203,6 +204,8 @@ static ssize_t copy_event_to_user(struct fsnotify_group *group, { struct inotify_event inotify_event; struct inotify_event_info *event; + struct path event_path; + struct inotify_inode_mark *i_mark; size_t event_size = sizeof(struct inotify_event); size_t name_len; size_t pad_name_len; @@ -210,6 +213,18 @@ static ssize_t copy_event_to_user(struct fsnotify_group *group, pr_debug("%s: group=%p event=%p\n", __func__, group, fsn_event); event = INOTIFY_E(fsn_event); + /* ensure caller has access to view the full path */ + if (event->mask & IN_FULL_PATHS && event->mask & IN_MOVE_SELF && + kern_path(event->name, 0, &event_path)) { + i_mark = inotify_idr_find(group, event->wd); + if (likely(i_mark)) { + fsnotify_destroy_mark(&i_mark->fsn_mark, group); + /* match ref taken by inotify_idr_find */ + fsnotify_put_mark(&i_mark->fsn_mark); + } + return -EACCES; + } + name_len = event->name_len; /* * round up name length so it is a multiple of event_size @@ -860,7 +875,7 @@ static int __init inotify_user_setup(void) BUILD_BUG_ON(IN_IGNORED != FS_IN_IGNORED); BUILD_BUG_ON(IN_ISDIR != FS_ISDIR); - BUILD_BUG_ON(HWEIGHT32(ALL_INOTIFY_BITS) != 22); + BUILD_BUG_ON(HWEIGHT32(ALL_INOTIFY_BITS) != 23); inotify_inode_mark_cachep = KMEM_CACHE(inotify_inode_mark, SLAB_PANIC|SLAB_ACCOUNT); diff --git a/include/linux/inotify.h b/include/linux/inotify.h index 8d20caa1b268..11db0541cff5 100644 --- a/include/linux/inotify.h +++ b/include/linux/inotify.h @@ -15,6 +15,6 @@ IN_DELETE_SELF | IN_MOVE_SELF | IN_UNMOUNT | \ IN_Q_OVERFLOW | IN_IGNORED | IN_ONLYDIR | \ IN_DONT_FOLLOW | IN_EXCL_UNLINK | IN_MASK_ADD | \ - IN_MASK_CREATE | IN_ISDIR | IN_ONESHOT) + IN_MASK_CREATE | IN_ISDIR | IN_ONESHOT | IN_FULL_PATHS) #endif /* _LINUX_INOTIFY_H */ diff --git a/include/uapi/linux/inotify.h b/include/uapi/linux/inotify.h index 884b4846b630..d95e05aa9bd6 100644 --- a/include/uapi/linux/inotify.h +++ b/include/uapi/linux/inotify.h @@ -50,6 +50,7 @@ struct inotify_event { #define IN_MOVE (IN_MOVED_FROM | IN_MOVED_TO) /* moves */ /* special flags */ +#define IN_FULL_PATHS 0x00800000 /* return the absolute path in the name */ #define IN_ONLYDIR 0x01000000 /* only watch the path if it is a directory */ #define IN_DONT_FOLLOW 0x02000000 /* don't follow a sym link */ #define IN_EXCL_UNLINK 0x04000000 /* exclude events on unlinked objects */ -- 2.35.1