diff --git a/fs/notify/fanotify/fanotify.c b/fs/notify/fanotify/fanotify.c index ef39664e389c..599654564b2a 100644 --- a/fs/notify/fanotify/fanotify.c +++ b/fs/notify/fanotify/fanotify.c @@ -53,6 +53,23 @@ static bool fanotify_fid_event_equal(struct fanotify_fid_event *ffe1, fanotify_fh_equal(&ffe1->object_fh, &ffe2->object_fh); } +static bool fanotify_name_event_equal(struct fanotify_name_event *fne1, + struct fanotify_name_event *fne2) +{ + /* + * Do not merge name events without dir fh. + * FAN_DIR_MODIFY does not encode object fh, so it may be empty. + */ + if (!fne1->dir_fh.len) + return false; + + if (fne1->name_len != fne2->name_len || + !fanotify_fh_equal(&fne1->dir_fh, &fne2->dir_fh)) + return false; + + return !memcmp(fne1->name, fne2->name, fne1->name_len); +} + static bool should_merge(struct fsnotify_event *old_fsn, struct fsnotify_event *new_fsn) { @@ -84,6 +101,9 @@ static bool should_merge(struct fsnotify_event *old_fsn, return fanotify_fid_event_equal(FANOTIFY_FE(old), FANOTIFY_FE(new)); + case FANOTIFY_EVENT_TYPE_FID_NAME: + return fanotify_name_event_equal(FANOTIFY_NE(old), + FANOTIFY_NE(new)); default: WARN_ON_ONCE(1); } @@ -262,6 +282,9 @@ static void fanotify_encode_fh(struct fanotify_fh *fh, struct inode *inode, void *buf = fh->buf; int err; + if (!inode) + goto out; + dwords = 0; err = -ENOENT; type = exportfs_encode_inode_fh(inode, NULL, &dwords, NULL); @@ -295,6 +318,7 @@ out_err: type, bytes, err); kfree(ext_buf); *fanotify_fh_ext_buf_ptr(fh) = NULL; +out: /* Report the event without a file identifier on encode error */ fh->type = FILEID_INVALID; fh->len = 0; @@ -320,10 +344,12 @@ static struct inode *fanotify_fid_inode(struct inode *to_tell, u32 event_mask, struct fanotify_event *fanotify_alloc_event(struct fsnotify_group *group, struct inode *inode, u32 mask, const void *data, int data_type, + const struct qstr *file_name, __kernel_fsid_t *fsid) { struct fanotify_event *event = NULL; struct fanotify_fid_event *ffe = NULL; + struct fanotify_name_event *fne = NULL; gfp_t gfp = GFP_KERNEL_ACCOUNT; struct inode *id = fanotify_fid_inode(inode, mask, data, data_type); const struct path *path = fsnotify_data_path(data, data_type); @@ -356,6 +382,23 @@ struct fanotify_event *fanotify_alloc_event(struct fsnotify_group *group, goto init; } + /* + * For FAN_DIR_MODIFY event, we report the fid of the directory and + * the name of the modified entry. + * Allocate an fanotify_name_event struct and copy the name. + */ + if (mask & FAN_DIR_MODIFY && !(WARN_ON_ONCE(!file_name))) { + fne = kmalloc(sizeof(*fne) + file_name->len + 1, gfp); + if (!fne) + goto out; + + event = &fne->fae; + event->type = FANOTIFY_EVENT_TYPE_FID_NAME; + fne->name_len = file_name->len; + strcpy(fne->name, file_name->name); + goto init; + } + if (FAN_GROUP_FLAG(group, FAN_REPORT_FID)) { ffe = kmem_cache_alloc(fanotify_fid_event_cachep, gfp); if (!ffe) @@ -374,7 +417,7 @@ struct fanotify_event *fanotify_alloc_event(struct fsnotify_group *group, event->type = FANOTIFY_EVENT_TYPE_PATH; } -init: __maybe_unused +init: /* * Use the victim inode instead of the watching inode as the id for * event queue, so event reported on parent is merged with event @@ -387,13 +430,16 @@ init: __maybe_unused else event->pid = get_pid(task_tgid(current)); - if (fanotify_event_object_fh(event)) { - ffe->object_fh.len = 0; - if (fsid) - ffe->fsid = *fsid; - if (id) - fanotify_encode_fh(&ffe->object_fh, id, gfp); - } else if (fanotify_event_has_path(event)) { + if (fsid && fanotify_event_fsid(event)) + *fanotify_event_fsid(event) = *fsid; + + if (fanotify_event_object_fh(event)) + fanotify_encode_fh(fanotify_event_object_fh(event), id, gfp); + + if (fanotify_event_dir_fh(event)) + fanotify_encode_fh(fanotify_event_dir_fh(event), id, gfp); + + if (fanotify_event_has_path(event)) { struct path *p = fanotify_event_path(event); if (path) { @@ -501,7 +547,7 @@ static int fanotify_handle_event(struct fsnotify_group *group, } event = fanotify_alloc_event(group, inode, mask, data, data_type, - &fsid); + file_name, &fsid); ret = -ENOMEM; if (unlikely(!event)) { /* @@ -563,6 +609,15 @@ static void fanotify_free_fid_event(struct fanotify_event *event) kmem_cache_free(fanotify_fid_event_cachep, ffe); } +static void fanotify_free_name_event(struct fanotify_event *event) +{ + struct fanotify_name_event *fne = FANOTIFY_NE(event); + + if (fanotify_fh_has_ext_buf(&fne->dir_fh)) + kfree(fanotify_fh_ext_buf(&fne->dir_fh)); + kfree(fne); +} + static void fanotify_free_event(struct fsnotify_event *fsn_event) { struct fanotify_event *event; @@ -579,6 +634,9 @@ static void fanotify_free_event(struct fsnotify_event *fsn_event) case FANOTIFY_EVENT_TYPE_FID: fanotify_free_fid_event(event); break; + case FANOTIFY_EVENT_TYPE_FID_NAME: + fanotify_free_name_event(event); + break; default: WARN_ON_ONCE(1); } diff --git a/fs/notify/fanotify/fanotify.h b/fs/notify/fanotify/fanotify.h index eecf4be3bfd1..35bfbf4a7aac 100644 --- a/fs/notify/fanotify/fanotify.h +++ b/fs/notify/fanotify/fanotify.h @@ -59,7 +59,8 @@ static inline void *fanotify_fh_buf(struct fanotify_fh *fh) * be freed and which concrete struct it may be cast to. */ enum fanotify_event_type { - FANOTIFY_EVENT_TYPE_FID, + FANOTIFY_EVENT_TYPE_FID, /* fixed length */ + FANOTIFY_EVENT_TYPE_FID_NAME, /* variable length */ FANOTIFY_EVENT_TYPE_PATH, FANOTIFY_EVENT_TYPE_PATH_PERM, }; @@ -83,10 +84,26 @@ FANOTIFY_FE(struct fanotify_event *event) return container_of(event, struct fanotify_fid_event, fae); } +struct fanotify_name_event { + struct fanotify_event fae; + __kernel_fsid_t fsid; + struct fanotify_fh dir_fh; + u8 name_len; + char name[0]; +}; + +static inline struct fanotify_name_event * +FANOTIFY_NE(struct fanotify_event *event) +{ + return container_of(event, struct fanotify_name_event, fae); +} + static inline __kernel_fsid_t *fanotify_event_fsid(struct fanotify_event *event) { if (event->type == FANOTIFY_EVENT_TYPE_FID) return &FANOTIFY_FE(event)->fsid; + else if (event->type == FANOTIFY_EVENT_TYPE_FID_NAME) + return &FANOTIFY_NE(event)->fsid; else return NULL; } @@ -100,6 +117,15 @@ static inline struct fanotify_fh *fanotify_event_object_fh( return NULL; } +static inline struct fanotify_fh *fanotify_event_dir_fh( + struct fanotify_event *event) +{ + if (event->type == FANOTIFY_EVENT_TYPE_FID_NAME) + return &FANOTIFY_NE(event)->dir_fh; + else + return NULL; +} + static inline int fanotify_event_object_fh_len(struct fanotify_event *event) { struct fanotify_fh *fh = fanotify_event_object_fh(event); @@ -107,6 +133,17 @@ static inline int fanotify_event_object_fh_len(struct fanotify_event *event) return fh ? fh->len : 0; } +static inline bool fanotify_event_has_name(struct fanotify_event *event) +{ + return event->type == FANOTIFY_EVENT_TYPE_FID_NAME; +} + +static inline int fanotify_event_name_len(struct fanotify_event *event) +{ + return fanotify_event_has_name(event) ? + FANOTIFY_NE(event)->name_len : 0; +} + struct fanotify_path_event { struct fanotify_event fae; struct path path; @@ -169,4 +206,5 @@ static inline struct path *fanotify_event_path(struct fanotify_event *event) struct fanotify_event *fanotify_alloc_event(struct fsnotify_group *group, struct inode *inode, u32 mask, const void *data, int data_type, + const struct qstr *file_name, __kernel_fsid_t *fsid); diff --git a/fs/notify/fanotify/fanotify_user.c b/fs/notify/fanotify/fanotify_user.c index b93585406aad..a9d287a56098 100644 --- a/fs/notify/fanotify/fanotify_user.c +++ b/fs/notify/fanotify/fanotify_user.c @@ -210,7 +210,7 @@ static int copy_fid_to_user(__kernel_fsid_t *fsid, struct fanotify_fh *fh, struct fanotify_event_info_fid info = { }; struct file_handle handle = { }; unsigned char bounce[FANOTIFY_INLINE_FH_LEN], *fh_buf; - size_t fh_len = fh->len; + size_t fh_len = fh ? fh->len : 0; size_t len = fanotify_fid_info_len(fh_len); if (!len) @@ -828,7 +828,7 @@ SYSCALL_DEFINE2(fanotify_init, unsigned int, flags, unsigned int, event_f_flags) group->memcg = get_mem_cgroup_from_mm(current->mm); oevent = fanotify_alloc_event(group, NULL, FS_Q_OVERFLOW, NULL, - FSNOTIFY_EVENT_NONE, NULL); + FSNOTIFY_EVENT_NONE, NULL, NULL); if (unlikely(!oevent)) { fd = -ENOMEM; goto out_destroy_group;