An fanotify event name is always recorded relative to a dir fh. Move the name_len members of fanotify_name_event into unused space in struct fanotify_fh. We add a name_offset member to allow packing a binary blob before the name string in the variable size buffer. We are going to use that space to store the child fid. It also fixes a bug in fanotify_alloc_name_event() which used an allocation size 7 bytes bigger than required size, because it used sizeof(struct fanotify_name_event) without deducting that 7 bytes alignment padding. Signed-off-by: Amir Goldstein <amir73il@xxxxxxxxx> --- fs/notify/fanotify/fanotify.c | 27 ++++++++----- fs/notify/fanotify/fanotify.h | 62 +++++++++++++++++++++++------- fs/notify/fanotify/fanotify_user.c | 23 +++++------ 3 files changed, 75 insertions(+), 37 deletions(-) diff --git a/fs/notify/fanotify/fanotify.c b/fs/notify/fanotify/fanotify.c index 3885bf63976b..3a2d48edaddd 100644 --- a/fs/notify/fanotify/fanotify.c +++ b/fs/notify/fanotify/fanotify.c @@ -52,15 +52,20 @@ static bool fanotify_fid_event_equal(struct fanotify_fid_event *ffe1, static bool fanotify_name_event_equal(struct fanotify_name_event *fne1, struct fanotify_name_event *fne2) { + struct fanotify_fh *dfh1 = &fne1->dir_fh; + struct fanotify_fh *dfh2 = &fne2->dir_fh; + /* Do not merge name events without dir fh */ - if (!fne1->dir_fh.len) + if (!dfh1->len) return false; - if (fne1->name_len != fne2->name_len || - !fanotify_fh_equal(&fne1->dir_fh, &fne2->dir_fh)) + if (dfh1->name_len != dfh2->name_len || + dfh1->name_offset != dfh2->name_offset || + !fanotify_fh_equal(dfh1, dfh2)) return false; - return !memcmp(fne1->name, fne2->name, fne1->name_len); + return !memcmp(fanotify_fh_name(dfh1), fanotify_fh_name(dfh2), + dfh1->name_len); } static bool fanotify_should_merge(struct fsnotify_event *old_fsn, @@ -284,8 +289,7 @@ static void fanotify_encode_fh(struct fanotify_fh *fh, struct inode *inode, void *buf = fh->buf; int err; - fh->type = FILEID_ROOT; - fh->len = 0; + fanotify_fh_init(fh); if (!inode) return; @@ -314,6 +318,10 @@ static void fanotify_encode_fh(struct fanotify_fh *fh, struct inode *inode, fh->type = type; fh->len = bytes; + if (fh->len > FANOTIFY_INLINE_FH_LEN) + fh->name_offset = FANOTIFY_INLINE_FH_LEN; + else + fh->name_offset = fh->len; return; @@ -401,6 +409,7 @@ struct fanotify_event *fanotify_alloc_name_event(struct inode *id, gfp_t gfp) { struct fanotify_name_event *fne; + struct fanotify_fh *dfh; fne = kmalloc(sizeof(*fne) + file_name->len + 1, gfp); if (!fne) @@ -408,9 +417,9 @@ struct fanotify_event *fanotify_alloc_name_event(struct inode *id, fne->fae.type = FANOTIFY_EVENT_TYPE_FID_NAME; fne->fsid = *fsid; - fanotify_encode_fh(&fne->dir_fh, id, gfp); - fne->name_len = file_name->len; - strcpy(fne->name, file_name->name); + dfh = &fne->dir_fh; + fanotify_encode_fh(dfh, id, gfp); + fanotify_fh_copy_name(dfh, file_name); return &fne->fae; } diff --git a/fs/notify/fanotify/fanotify.h b/fs/notify/fanotify/fanotify.h index 1b2a3bbe6008..8cb062eefd3e 100644 --- a/fs/notify/fanotify/fanotify.h +++ b/fs/notify/fanotify/fanotify.h @@ -23,13 +23,24 @@ enum { * stored in either the first or last 2 dwords. */ #define FANOTIFY_INLINE_FH_LEN (3 << 2) +#define FANOTIFY_FH_HDR_LEN offsetof(struct fanotify_fh, buf) struct fanotify_fh { - unsigned char buf[FANOTIFY_INLINE_FH_LEN]; u8 type; u8 len; + u8 name_offset; + u8 name_len; + unsigned char buf[FANOTIFY_INLINE_FH_LEN]; } __aligned(4); +static inline void fanotify_fh_init(struct fanotify_fh *fh) +{ + fh->type = FILEID_ROOT; + fh->len = 0; + fh->name_offset = 0; + fh->name_len = 0; +} + static inline bool fanotify_fh_has_ext_buf(struct fanotify_fh *fh) { return fh->len > FANOTIFY_INLINE_FH_LEN; @@ -37,6 +48,7 @@ static inline bool fanotify_fh_has_ext_buf(struct fanotify_fh *fh) static inline char **fanotify_fh_ext_buf_ptr(struct fanotify_fh *fh) { + BUILD_BUG_ON(FANOTIFY_FH_HDR_LEN % 4); BUILD_BUG_ON(__alignof__(char *) - 4 + sizeof(char *) > FANOTIFY_INLINE_FH_LEN); return (char **)ALIGN((unsigned long)(fh->buf), __alignof__(char *)); @@ -52,6 +64,35 @@ static inline void *fanotify_fh_buf(struct fanotify_fh *fh) return fanotify_fh_has_ext_buf(fh) ? fanotify_fh_ext_buf(fh) : fh->buf; } +static inline int fanotify_fh_blob_len(struct fanotify_fh *fh) +{ + if (fh->name_offset <= fh->len) + return 0; + + /* Is there a space between end of fh_buf and start of name? */ + return fh->name_offset - fh->len; +} + +static inline void *fanotify_fh_blob(struct fanotify_fh *fh) +{ + if (fh->name_offset <= fh->len) + return NULL; + + return fh->buf + fh->len; +} + +static inline const char *fanotify_fh_name(struct fanotify_fh *fh) +{ + return fh->name_len ? fh->buf + fh->name_offset : NULL; +} + +static inline void fanotify_fh_copy_name(struct fanotify_fh *fh, + const struct qstr *name) +{ + fh->name_len = name->len; + strcpy(fh->buf + fh->name_offset, name->name); +} + /* * Common structure for fanotify events. Concrete structs are allocated in * fanotify_handle_event() and freed when the information is retrieved by @@ -93,12 +134,16 @@ FANOTIFY_FE(struct fanotify_event *event) return container_of(event, struct fanotify_fid_event, fae); } +/* + * This is identical to struct fanotify_fid_event, but allocated with variable + * size kmalloc and should have positive value of dir_fh.name_len. + * Keeping the separate struct definition for semantics and type safety - + * an event should be cast to this type IFF it was allocated using kmalloc. + */ struct fanotify_name_event { struct fanotify_event fae; __kernel_fsid_t fsid; struct fanotify_fh dir_fh; - u8 name_len; - char name[]; }; static inline struct fanotify_name_event * @@ -142,17 +187,6 @@ 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; diff --git a/fs/notify/fanotify/fanotify_user.c b/fs/notify/fanotify/fanotify_user.c index 42b8cc51cb3f..af8268b44c68 100644 --- a/fs/notify/fanotify/fanotify_user.c +++ b/fs/notify/fanotify/fanotify_user.c @@ -68,17 +68,14 @@ static int fanotify_event_info_len(struct fanotify_event *event) { int info_len = 0; int fh_len = fanotify_event_object_fh_len(event); + struct fanotify_fh *dfh = fanotify_event_dir_fh(event); + + if (dfh) + info_len += fanotify_fid_info_len(dfh->len, dfh->name_len); if (fh_len) info_len += fanotify_fid_info_len(fh_len, 0); - 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; } @@ -305,6 +302,7 @@ static ssize_t copy_event_to_user(struct fsnotify_group *group, { struct fanotify_event_metadata metadata; struct path *path = fanotify_event_path(event); + struct fanotify_fh *dfh = fanotify_event_dir_fh(event); struct file *f = NULL; int ret, fd = FAN_NOFD; @@ -346,13 +344,10 @@ static ssize_t copy_event_to_user(struct fsnotify_group *group, fd_install(fd, f); /* 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 (dfh) { + ret = copy_info_to_user(fanotify_event_fsid(event), dfh, + fanotify_fh_name(dfh), + dfh->name_len, buf, count); if (ret < 0) return ret; -- 2.17.1