Commit 3d3d6733 authored by Linus Torvalds's avatar Linus Torvalds
Browse files

Merge tag 'fsnotify_for_v5.17-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/jack/linux-fs

Pull fanotify updates from Jan Kara:
 "Support for new FAN_RENAME fanotify event and support for reporting
  child info in directory fanotify events (FAN_REPORT_TARGET_FID)"

* tag 'fsnotify_for_v5.17-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/jack/linux-fs:
  fanotify: wire up FAN_RENAME event
  fanotify: report old and/or new parent+name in FAN_RENAME event
  fanotify: record either old name new name or both for FAN_RENAME
  fanotify: record old and new parent and name in FAN_RENAME event
  fanotify: support secondary dir fh and name in fanotify_info
  fanotify: use helpers to parcel fanotify_info buffer
  fanotify: use macros to get the offset to fanotify_info buffer
  fsnotify: generate FS_RENAME event with rich information
  fanotify: introduce group flag FAN_REPORT_TARGET_FID
  fsnotify: separate mark iterator type from object type enum
  fsnotify: clarify object type argument
parents f079ab01 8cc3b1cc
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -196,7 +196,7 @@ static __u32 convert_arg(unsigned long arg)
	if (arg & DN_ATTRIB)
		new_mask |= FS_ATTRIB;
	if (arg & DN_RENAME)
		new_mask |= FS_DN_RENAME;
		new_mask |= FS_RENAME;
	if (arg & DN_CREATE)
		new_mask |= (FS_CREATE | FS_MOVED_TO);

+161 −52
Original line number Diff line number Diff line
@@ -76,8 +76,10 @@ static bool fanotify_info_equal(struct fanotify_info *info1,
				struct fanotify_info *info2)
{
	if (info1->dir_fh_totlen != info2->dir_fh_totlen ||
	    info1->dir2_fh_totlen != info2->dir2_fh_totlen ||
	    info1->file_fh_totlen != info2->file_fh_totlen ||
	    info1->name_len != info2->name_len)
	    info1->name_len != info2->name_len ||
	    info1->name2_len != info2->name2_len)
		return false;

	if (info1->dir_fh_totlen &&
@@ -85,14 +87,24 @@ static bool fanotify_info_equal(struct fanotify_info *info1,
			       fanotify_info_dir_fh(info2)))
		return false;

	if (info1->dir2_fh_totlen &&
	    !fanotify_fh_equal(fanotify_info_dir2_fh(info1),
			       fanotify_info_dir2_fh(info2)))
		return false;

	if (info1->file_fh_totlen &&
	    !fanotify_fh_equal(fanotify_info_file_fh(info1),
			       fanotify_info_file_fh(info2)))
		return false;

	return !info1->name_len ||
		!memcmp(fanotify_info_name(info1), fanotify_info_name(info2),
			info1->name_len);
	if (info1->name_len &&
	    memcmp(fanotify_info_name(info1), fanotify_info_name(info2),
		   info1->name_len))
		return false;

	return !info1->name2_len ||
		!memcmp(fanotify_info_name2(info1), fanotify_info_name2(info2),
			info1->name2_len);
}

static bool fanotify_name_event_equal(struct fanotify_name_event *fne1,
@@ -141,6 +153,13 @@ static bool fanotify_should_merge(struct fanotify_event *old,
	if ((old->mask & FS_ISDIR) != (new->mask & FS_ISDIR))
		return false;

	/*
	 * FAN_RENAME event is reported with special info record types,
	 * so we cannot merge it with other events.
	 */
	if ((old->mask & FAN_RENAME) != (new->mask & FAN_RENAME))
		return false;

	switch (old->type) {
	case FANOTIFY_EVENT_TYPE_PATH:
		return fanotify_path_equal(fanotify_event_path(old),
@@ -272,8 +291,9 @@ static int fanotify_get_response(struct fsnotify_group *group,
 */
static u32 fanotify_group_event_mask(struct fsnotify_group *group,
				     struct fsnotify_iter_info *iter_info,
				     u32 event_mask, const void *data,
				     int data_type, struct inode *dir)
				     u32 *match_mask, u32 event_mask,
				     const void *data, int data_type,
				     struct inode *dir)
{
	__u32 marks_mask = 0, marks_ignored_mask = 0;
	__u32 test_mask, user_mask = FANOTIFY_OUTGOING_EVENTS |
@@ -299,7 +319,7 @@ static u32 fanotify_group_event_mask(struct fsnotify_group *group,
			return 0;
	}

	fsnotify_foreach_obj_type(type) {
	fsnotify_foreach_iter_type(type) {
		if (!fsnotify_iter_should_report_type(iter_info, type))
			continue;
		mark = iter_info->marks[type];
@@ -318,11 +338,14 @@ static u32 fanotify_group_event_mask(struct fsnotify_group *group,
		 * If the event is on a child and this mark is on a parent not
		 * watching children, don't send it!
		 */
		if (type == FSNOTIFY_OBJ_TYPE_PARENT &&
		if (type == FSNOTIFY_ITER_TYPE_PARENT &&
		    !(mark->mask & FS_EVENT_ON_CHILD))
			continue;

		marks_mask |= mark->mask;

		/* Record the mark types of this group that matched the event */
		*match_mask |= 1U << type;
	}

	test_mask = event_mask & marks_mask & ~marks_ignored_mask;
@@ -411,7 +434,7 @@ static int fanotify_encode_fh(struct fanotify_fh *fh, struct inode *inode,
	 * be zero in that case if encoding fh len failed.
	 */
	err = -ENOENT;
	if (fh_len < 4 || WARN_ON_ONCE(fh_len % 4))
	if (fh_len < 4 || WARN_ON_ONCE(fh_len % 4) || fh_len > MAX_HANDLE_SZ)
		goto out_err;

	/* No external buffer in a variable size allocated fh */
@@ -458,17 +481,41 @@ static int fanotify_encode_fh(struct fanotify_fh *fh, struct inode *inode,
}

/*
 * The inode to use as identifier when reporting fid depends on the event.
 * Report the modified directory inode on dirent modification events.
 * Report the "victim" inode otherwise.
 * FAN_REPORT_FID is ambiguous in that it reports the fid of the child for
 * some events and the fid of the parent for create/delete/move events.
 *
 * With the FAN_REPORT_TARGET_FID flag, the fid of the child is reported
 * also in create/delete/move events in addition to the fid of the parent
 * and the name of the child.
 */
static inline bool fanotify_report_child_fid(unsigned int fid_mode, u32 mask)
{
	if (mask & ALL_FSNOTIFY_DIRENT_EVENTS)
		return (fid_mode & FAN_REPORT_TARGET_FID);

	return (fid_mode & FAN_REPORT_FID) && !(mask & FAN_ONDIR);
}

/*
 * The inode to use as identifier when reporting fid depends on the event
 * and the group flags.
 *
 * With the group flag FAN_REPORT_TARGET_FID, always report the child fid.
 *
 * Without the group flag FAN_REPORT_TARGET_FID, report the modified directory
 * fid on dirent events and the child fid otherwise.
 *
 * For example:
 * FS_ATTRIB reports the child inode even if reported on a watched parent.
 * FS_CREATE reports the modified dir inode and not the created inode.
 * FS_ATTRIB reports the child fid even if reported on a watched parent.
 * FS_CREATE reports the modified dir fid without FAN_REPORT_TARGET_FID.
 *       and reports the created child fid with FAN_REPORT_TARGET_FID.
 */
static struct inode *fanotify_fid_inode(u32 event_mask, const void *data,
					int data_type, struct inode *dir)
					int data_type, struct inode *dir,
					unsigned int fid_mode)
{
	if (event_mask & ALL_FSNOTIFY_DIRENT_EVENTS)
	if ((event_mask & ALL_FSNOTIFY_DIRENT_EVENTS) &&
	    !(fid_mode & FAN_REPORT_TARGET_FID))
		return dir;

	return fsnotify_data_inode(data, data_type);
@@ -552,25 +599,34 @@ static struct fanotify_event *fanotify_alloc_fid_event(struct inode *id,
	return &ffe->fae;
}

static struct fanotify_event *fanotify_alloc_name_event(struct inode *id,
static struct fanotify_event *fanotify_alloc_name_event(struct inode *dir,
							__kernel_fsid_t *fsid,
							const struct qstr *name,
							struct inode *child,
							struct dentry *moved,
							unsigned int *hash,
							gfp_t gfp)
{
	struct fanotify_name_event *fne;
	struct fanotify_info *info;
	struct fanotify_fh *dfh, *ffh;
	unsigned int dir_fh_len = fanotify_encode_fh_len(id);
	struct inode *dir2 = moved ? d_inode(moved->d_parent) : NULL;
	const struct qstr *name2 = moved ? &moved->d_name : NULL;
	unsigned int dir_fh_len = fanotify_encode_fh_len(dir);
	unsigned int dir2_fh_len = fanotify_encode_fh_len(dir2);
	unsigned int child_fh_len = fanotify_encode_fh_len(child);
	unsigned int size;

	size = sizeof(*fne) + FANOTIFY_FH_HDR_LEN + dir_fh_len;
	unsigned long name_len = name ? name->len : 0;
	unsigned long name2_len = name2 ? name2->len : 0;
	unsigned int len, size;

	/* Reserve terminating null byte even for empty name */
	size = sizeof(*fne) + name_len + name2_len + 2;
	if (dir_fh_len)
		size += FANOTIFY_FH_HDR_LEN + dir_fh_len;
	if (dir2_fh_len)
		size += FANOTIFY_FH_HDR_LEN + dir2_fh_len;
	if (child_fh_len)
		size += FANOTIFY_FH_HDR_LEN + child_fh_len;
	if (name)
		size += name->len + 1;
	fne = kmalloc(size, gfp);
	if (!fne)
		return NULL;
@@ -580,24 +636,41 @@ static struct fanotify_event *fanotify_alloc_name_event(struct inode *id,
	*hash ^= fanotify_hash_fsid(fsid);
	info = &fne->info;
	fanotify_info_init(info);
	if (dir_fh_len) {
		dfh = fanotify_info_dir_fh(info);
	info->dir_fh_totlen = fanotify_encode_fh(dfh, id, dir_fh_len, hash, 0);
		len = fanotify_encode_fh(dfh, dir, dir_fh_len, hash, 0);
		fanotify_info_set_dir_fh(info, len);
	}
	if (dir2_fh_len) {
		dfh = fanotify_info_dir2_fh(info);
		len = fanotify_encode_fh(dfh, dir2, dir2_fh_len, hash, 0);
		fanotify_info_set_dir2_fh(info, len);
	}
	if (child_fh_len) {
		ffh = fanotify_info_file_fh(info);
		info->file_fh_totlen = fanotify_encode_fh(ffh, child,
							child_fh_len, hash, 0);
		len = fanotify_encode_fh(ffh, child, child_fh_len, hash, 0);
		fanotify_info_set_file_fh(info, len);
	}
	if (name) {
		long salt = name->len;

	if (name_len) {
		fanotify_info_copy_name(info, name);
		*hash ^= full_name_hash((void *)salt, name->name, name->len);
		*hash ^= full_name_hash((void *)name_len, name->name, name_len);
	}
	if (name2_len) {
		fanotify_info_copy_name2(info, name2);
		*hash ^= full_name_hash((void *)name2_len, name2->name,
					name2_len);
	}

	pr_debug("%s: ino=%lu size=%u dir_fh_len=%u child_fh_len=%u name_len=%u name='%.*s'\n",
		 __func__, id->i_ino, size, dir_fh_len, child_fh_len,
	pr_debug("%s: size=%u dir_fh_len=%u child_fh_len=%u name_len=%u name='%.*s'\n",
		 __func__, size, dir_fh_len, child_fh_len,
		 info->name_len, info->name_len, fanotify_info_name(info));

	if (dir2_fh_len) {
		pr_debug("%s: dir2_fh_len=%u name2_len=%u name2='%.*s'\n",
			 __func__, dir2_fh_len, info->name2_len,
			 info->name2_len, fanotify_info_name2(info));
	}

	return &fne->fae;
}

@@ -639,19 +712,21 @@ static struct fanotify_event *fanotify_alloc_error_event(
	return &fee->fae;
}

static struct fanotify_event *fanotify_alloc_event(struct fsnotify_group *group,
						   u32 mask, const void *data,
						   int data_type, struct inode *dir,
						   const struct qstr *file_name,
						   __kernel_fsid_t *fsid)
static struct fanotify_event *fanotify_alloc_event(
				struct fsnotify_group *group,
				u32 mask, const void *data, int data_type,
				struct inode *dir, const struct qstr *file_name,
				__kernel_fsid_t *fsid, u32 match_mask)
{
	struct fanotify_event *event = NULL;
	gfp_t gfp = GFP_KERNEL_ACCOUNT;
	struct inode *id = fanotify_fid_inode(mask, data, data_type, dir);
	unsigned int fid_mode = FAN_GROUP_FLAG(group, FANOTIFY_FID_BITS);
	struct inode *id = fanotify_fid_inode(mask, data, data_type, dir,
					      fid_mode);
	struct inode *dirid = fanotify_dfid_inode(mask, data, data_type, dir);
	const struct path *path = fsnotify_data_path(data, data_type);
	unsigned int fid_mode = FAN_GROUP_FLAG(group, FANOTIFY_FID_BITS);
	struct mem_cgroup *old_memcg;
	struct dentry *moved = NULL;
	struct inode *child = NULL;
	bool name_event = false;
	unsigned int hash = 0;
@@ -660,11 +735,10 @@ static struct fanotify_event *fanotify_alloc_event(struct fsnotify_group *group,

	if ((fid_mode & FAN_REPORT_DIR_FID) && dirid) {
		/*
		 * With both flags FAN_REPORT_DIR_FID and FAN_REPORT_FID, we
		 * report the child fid for events reported on a non-dir child
		 * For certain events and group flags, report the child fid
		 * in addition to reporting the parent fid and maybe child name.
		 */
		if ((fid_mode & FAN_REPORT_FID) && id != dirid && !ondir)
		if (fanotify_report_child_fid(fid_mode, mask) && id != dirid)
			child = id;

		id = dirid;
@@ -688,6 +762,38 @@ static struct fanotify_event *fanotify_alloc_event(struct fsnotify_group *group,
		} else if ((mask & ALL_FSNOTIFY_DIRENT_EVENTS) || !ondir) {
			name_event = true;
		}

		/*
		 * In the special case of FAN_RENAME event, use the match_mask
		 * to determine if we need to report only the old parent+name,
		 * only the new parent+name or both.
		 * 'dirid' and 'file_name' are the old parent+name and
		 * 'moved' has the new parent+name.
		 */
		if (mask & FAN_RENAME) {
			bool report_old, report_new;

			if (WARN_ON_ONCE(!match_mask))
				return NULL;

			/* Report both old and new parent+name if sb watching */
			report_old = report_new =
				match_mask & (1U << FSNOTIFY_ITER_TYPE_SB);
			report_old |=
				match_mask & (1U << FSNOTIFY_ITER_TYPE_INODE);
			report_new |=
				match_mask & (1U << FSNOTIFY_ITER_TYPE_INODE2);

			if (!report_old) {
				/* Do not report old parent+name */
				dirid = NULL;
				file_name = NULL;
			}
			if (report_new) {
				/* Report new parent+name */
				moved = fsnotify_data_dentry(data, data_type);
			}
		}
	}

	/*
@@ -709,9 +815,9 @@ static struct fanotify_event *fanotify_alloc_event(struct fsnotify_group *group,
	} else if (fanotify_is_error_event(mask)) {
		event = fanotify_alloc_error_event(group, fsid, data,
						   data_type, &hash);
	} else if (name_event && (file_name || child)) {
		event = fanotify_alloc_name_event(id, fsid, file_name, child,
						  &hash, gfp);
	} else if (name_event && (file_name || moved || child)) {
		event = fanotify_alloc_name_event(dirid, fsid, file_name, child,
						  moved, &hash, gfp);
	} else if (fid_mode) {
		event = fanotify_alloc_fid_event(id, fsid, &hash, gfp);
	} else {
@@ -746,7 +852,7 @@ static __kernel_fsid_t fanotify_get_fsid(struct fsnotify_iter_info *iter_info)
	int type;
	__kernel_fsid_t fsid = {};

	fsnotify_foreach_obj_type(type) {
	fsnotify_foreach_iter_type(type) {
		struct fsnotify_mark_connector *conn;

		if (!fsnotify_iter_should_report_type(iter_info, type))
@@ -800,6 +906,7 @@ static int fanotify_handle_event(struct fsnotify_group *group, u32 mask,
	struct fanotify_event *event;
	struct fsnotify_event *fsn_event;
	__kernel_fsid_t fsid = {};
	u32 match_mask = 0;

	BUILD_BUG_ON(FAN_ACCESS != FS_ACCESS);
	BUILD_BUG_ON(FAN_MODIFY != FS_MODIFY);
@@ -821,15 +928,17 @@ static int fanotify_handle_event(struct fsnotify_group *group, u32 mask,
	BUILD_BUG_ON(FAN_OPEN_EXEC != FS_OPEN_EXEC);
	BUILD_BUG_ON(FAN_OPEN_EXEC_PERM != FS_OPEN_EXEC_PERM);
	BUILD_BUG_ON(FAN_FS_ERROR != FS_ERROR);
	BUILD_BUG_ON(FAN_RENAME != FS_RENAME);

	BUILD_BUG_ON(HWEIGHT32(ALL_FANOTIFY_EVENT_BITS) != 20);
	BUILD_BUG_ON(HWEIGHT32(ALL_FANOTIFY_EVENT_BITS) != 21);

	mask = fanotify_group_event_mask(group, iter_info, mask, data,
					 data_type, dir);
	mask = fanotify_group_event_mask(group, iter_info, &match_mask,
					 mask, data, data_type, dir);
	if (!mask)
		return 0;

	pr_debug("%s: group=%p mask=%x\n", __func__, group, mask);
	pr_debug("%s: group=%p mask=%x report_mask=%x\n", __func__,
		 group, mask, match_mask);

	if (fanotify_is_perm_event(mask)) {
		/*
@@ -848,7 +957,7 @@ static int fanotify_handle_event(struct fsnotify_group *group, u32 mask,
	}

	event = fanotify_alloc_event(group, mask, data, data_type, dir,
				     file_name, &fsid);
				     file_name, &fsid, match_mask);
	ret = -ENOMEM;
	if (unlikely(!event)) {
		/*
+131 −11
Original line number Diff line number Diff line
@@ -40,15 +40,45 @@ struct fanotify_fh {
struct fanotify_info {
	/* size of dir_fh/file_fh including fanotify_fh hdr size */
	u8 dir_fh_totlen;
	u8 dir2_fh_totlen;
	u8 file_fh_totlen;
	u8 name_len;
	u8 pad;
	u8 name2_len;
	u8 pad[3];
	unsigned char buf[];
	/*
	 * (struct fanotify_fh) dir_fh starts at buf[0]
	 * (optional) file_fh starts at buf[dir_fh_totlen]
	 * name starts at buf[dir_fh_totlen + file_fh_totlen]
	 * (optional) dir2_fh starts at buf[dir_fh_totlen]
	 * (optional) file_fh starts at buf[dir_fh_totlen + dir2_fh_totlen]
	 * name starts at buf[dir_fh_totlen + dir2_fh_totlen + file_fh_totlen]
	 * ...
	 */
#define FANOTIFY_DIR_FH_SIZE(info)	((info)->dir_fh_totlen)
#define FANOTIFY_DIR2_FH_SIZE(info)	((info)->dir2_fh_totlen)
#define FANOTIFY_FILE_FH_SIZE(info)	((info)->file_fh_totlen)
#define FANOTIFY_NAME_SIZE(info)	((info)->name_len + 1)
#define FANOTIFY_NAME2_SIZE(info)	((info)->name2_len + 1)

#define FANOTIFY_DIR_FH_OFFSET(info)	0
#define FANOTIFY_DIR2_FH_OFFSET(info) \
	(FANOTIFY_DIR_FH_OFFSET(info) + FANOTIFY_DIR_FH_SIZE(info))
#define FANOTIFY_FILE_FH_OFFSET(info) \
	(FANOTIFY_DIR2_FH_OFFSET(info) + FANOTIFY_DIR2_FH_SIZE(info))
#define FANOTIFY_NAME_OFFSET(info) \
	(FANOTIFY_FILE_FH_OFFSET(info) + FANOTIFY_FILE_FH_SIZE(info))
#define FANOTIFY_NAME2_OFFSET(info) \
	(FANOTIFY_NAME_OFFSET(info) + FANOTIFY_NAME_SIZE(info))

#define FANOTIFY_DIR_FH_BUF(info) \
	((info)->buf + FANOTIFY_DIR_FH_OFFSET(info))
#define FANOTIFY_DIR2_FH_BUF(info) \
	((info)->buf + FANOTIFY_DIR2_FH_OFFSET(info))
#define FANOTIFY_FILE_FH_BUF(info) \
	((info)->buf + FANOTIFY_FILE_FH_OFFSET(info))
#define FANOTIFY_NAME_BUF(info) \
	((info)->buf + FANOTIFY_NAME_OFFSET(info))
#define FANOTIFY_NAME2_BUF(info) \
	((info)->buf + FANOTIFY_NAME2_OFFSET(info))
} __aligned(4);

static inline bool fanotify_fh_has_ext_buf(struct fanotify_fh *fh)
@@ -87,7 +117,21 @@ static inline struct fanotify_fh *fanotify_info_dir_fh(struct fanotify_info *inf
{
	BUILD_BUG_ON(offsetof(struct fanotify_info, buf) % 4);

	return (struct fanotify_fh *)info->buf;
	return (struct fanotify_fh *)FANOTIFY_DIR_FH_BUF(info);
}

static inline int fanotify_info_dir2_fh_len(struct fanotify_info *info)
{
	if (!info->dir2_fh_totlen ||
	    WARN_ON_ONCE(info->dir2_fh_totlen < FANOTIFY_FH_HDR_LEN))
		return 0;

	return info->dir2_fh_totlen - FANOTIFY_FH_HDR_LEN;
}

static inline struct fanotify_fh *fanotify_info_dir2_fh(struct fanotify_info *info)
{
	return (struct fanotify_fh *)FANOTIFY_DIR2_FH_BUF(info);
}

static inline int fanotify_info_file_fh_len(struct fanotify_info *info)
@@ -101,32 +145,90 @@ static inline int fanotify_info_file_fh_len(struct fanotify_info *info)

static inline struct fanotify_fh *fanotify_info_file_fh(struct fanotify_info *info)
{
	return (struct fanotify_fh *)(info->buf + info->dir_fh_totlen);
	return (struct fanotify_fh *)FANOTIFY_FILE_FH_BUF(info);
}

static inline char *fanotify_info_name(struct fanotify_info *info)
{
	if (!info->name_len)
		return NULL;

	return FANOTIFY_NAME_BUF(info);
}

static inline const char *fanotify_info_name(struct fanotify_info *info)
static inline char *fanotify_info_name2(struct fanotify_info *info)
{
	return info->buf + info->dir_fh_totlen + info->file_fh_totlen;
	if (!info->name2_len)
		return NULL;

	return FANOTIFY_NAME2_BUF(info);
}

static inline void fanotify_info_init(struct fanotify_info *info)
{
	BUILD_BUG_ON(FANOTIFY_FH_HDR_LEN + MAX_HANDLE_SZ > U8_MAX);
	BUILD_BUG_ON(NAME_MAX > U8_MAX);

	info->dir_fh_totlen = 0;
	info->dir2_fh_totlen = 0;
	info->file_fh_totlen = 0;
	info->name_len = 0;
	info->name2_len = 0;
}

static inline unsigned int fanotify_info_len(struct fanotify_info *info)
/* These set/copy helpers MUST be called by order */
static inline void fanotify_info_set_dir_fh(struct fanotify_info *info,
					    unsigned int totlen)
{
	return info->dir_fh_totlen + info->file_fh_totlen + info->name_len;
	if (WARN_ON_ONCE(info->dir2_fh_totlen > 0) ||
	    WARN_ON_ONCE(info->file_fh_totlen > 0) ||
	    WARN_ON_ONCE(info->name_len > 0) ||
	    WARN_ON_ONCE(info->name2_len > 0))
		return;

	info->dir_fh_totlen = totlen;
}

static inline void fanotify_info_set_dir2_fh(struct fanotify_info *info,
					     unsigned int totlen)
{
	if (WARN_ON_ONCE(info->file_fh_totlen > 0) ||
	    WARN_ON_ONCE(info->name_len > 0) ||
	    WARN_ON_ONCE(info->name2_len > 0))
		return;

	info->dir2_fh_totlen = totlen;
}

static inline void fanotify_info_set_file_fh(struct fanotify_info *info,
					     unsigned int totlen)
{
	if (WARN_ON_ONCE(info->name_len > 0) ||
	    WARN_ON_ONCE(info->name2_len > 0))
		return;

	info->file_fh_totlen = totlen;
}

static inline void fanotify_info_copy_name(struct fanotify_info *info,
					   const struct qstr *name)
{
	if (WARN_ON_ONCE(name->len > NAME_MAX) ||
	    WARN_ON_ONCE(info->name2_len > 0))
		return;

	info->name_len = name->len;
	strcpy(info->buf + info->dir_fh_totlen + info->file_fh_totlen,
	       name->name);
	strcpy(fanotify_info_name(info), name->name);
}

static inline void fanotify_info_copy_name2(struct fanotify_info *info,
					    const struct qstr *name)
{
	if (WARN_ON_ONCE(name->len > NAME_MAX))
		return;

	info->name2_len = name->len;
	strcpy(fanotify_info_name2(info), name->name);
}

/*
@@ -271,6 +373,13 @@ static inline int fanotify_event_dir_fh_len(struct fanotify_event *event)
	return info ? fanotify_info_dir_fh_len(info) : 0;
}

static inline int fanotify_event_dir2_fh_len(struct fanotify_event *event)
{
	struct fanotify_info *info = fanotify_event_info(event);

	return info ? fanotify_info_dir2_fh_len(info) : 0;
}

static inline bool fanotify_event_has_object_fh(struct fanotify_event *event)
{
	/* For error events, even zeroed fh are reported. */
@@ -284,6 +393,17 @@ static inline bool fanotify_event_has_dir_fh(struct fanotify_event *event)
	return fanotify_event_dir_fh_len(event) > 0;
}

static inline bool fanotify_event_has_dir2_fh(struct fanotify_event *event)
{
	return fanotify_event_dir2_fh_len(event) > 0;
}

static inline bool fanotify_event_has_any_dir_fh(struct fanotify_event *event)
{
	return fanotify_event_has_dir_fh(event) ||
		fanotify_event_has_dir2_fh(event);
}

struct fanotify_path_event {
	struct fanotify_event fae;
	struct path path;
+70 −12
Original line number Diff line number Diff line
@@ -129,12 +129,29 @@ static int fanotify_fid_info_len(int fh_len, int name_len)
		       FANOTIFY_EVENT_ALIGN);
}

/* FAN_RENAME may have one or two dir+name info records */
static int fanotify_dir_name_info_len(struct fanotify_event *event)
{
	struct fanotify_info *info = fanotify_event_info(event);
	int dir_fh_len = fanotify_event_dir_fh_len(event);
	int dir2_fh_len = fanotify_event_dir2_fh_len(event);
	int info_len = 0;

	if (dir_fh_len)
		info_len += fanotify_fid_info_len(dir_fh_len,
						  info->name_len);
	if (dir2_fh_len)
		info_len += fanotify_fid_info_len(dir2_fh_len,
						  info->name2_len);

	return info_len;
}

static size_t fanotify_event_len(unsigned int info_mode,
				 struct fanotify_event *event)
{
	size_t event_len = FAN_EVENT_METADATA_LEN;
	struct fanotify_info *info;
	int dir_fh_len;
	int fh_len;
	int dot_len = 0;

@@ -146,9 +163,8 @@ static size_t fanotify_event_len(unsigned int info_mode,

	info = fanotify_event_info(event);

	if (fanotify_event_has_dir_fh(event)) {
		dir_fh_len = fanotify_event_dir_fh_len(event);
		event_len += fanotify_fid_info_len(dir_fh_len, info->name_len);
	if (fanotify_event_has_any_dir_fh(event)) {
		event_len += fanotify_dir_name_info_len(event);
	} else if ((info_mode & FAN_REPORT_NAME) &&
		   (event->mask & FAN_ONDIR)) {
		/*
@@ -332,11 +348,10 @@ static int process_access_response(struct fsnotify_group *group,
static size_t copy_error_info_to_user(struct fanotify_event *event,
				      char __user *buf, int count)
{
	struct fanotify_event_info_error info;
	struct fanotify_event_info_error info = { };
	struct fanotify_error_event *fee = FANOTIFY_EE(event);

	info.hdr.info_type = FAN_EVENT_INFO_TYPE_ERROR;
	info.hdr.pad = 0;
	info.hdr.len = FANOTIFY_ERROR_INFO_LEN;

	if (WARN_ON(count < info.hdr.len))
@@ -380,6 +395,8 @@ static int copy_fid_info_to_user(__kernel_fsid_t *fsid, struct fanotify_fh *fh,
			return -EFAULT;
		break;
	case FAN_EVENT_INFO_TYPE_DFID_NAME:
	case FAN_EVENT_INFO_TYPE_OLD_DFID_NAME:
	case FAN_EVENT_INFO_TYPE_NEW_DFID_NAME:
		if (WARN_ON_ONCE(!name || !name_len))
			return -EFAULT;
		break;
@@ -479,11 +496,19 @@ static int copy_info_records_to_user(struct fanotify_event *event,
	unsigned int pidfd_mode = info_mode & FAN_REPORT_PIDFD;

	/*
	 * Event info records order is as follows: dir fid + name, child fid.
	 * Event info records order is as follows:
	 * 1. dir fid + name
	 * 2. (optional) new dir fid + new name
	 * 3. (optional) child fid
	 */
	if (fanotify_event_has_dir_fh(event)) {
		info_type = info->name_len ? FAN_EVENT_INFO_TYPE_DFID_NAME :
					     FAN_EVENT_INFO_TYPE_DFID;

		/* FAN_RENAME uses special info types */
		if (event->mask & FAN_RENAME)
			info_type = FAN_EVENT_INFO_TYPE_OLD_DFID_NAME;

		ret = copy_fid_info_to_user(fanotify_event_fsid(event),
					    fanotify_info_dir_fh(info),
					    info_type,
@@ -497,6 +522,22 @@ static int copy_info_records_to_user(struct fanotify_event *event,
		total_bytes += ret;
	}

	/* New dir fid+name may be reported in addition to old dir fid+name */
	if (fanotify_event_has_dir2_fh(event)) {
		info_type = FAN_EVENT_INFO_TYPE_NEW_DFID_NAME;
		ret = copy_fid_info_to_user(fanotify_event_fsid(event),
					    fanotify_info_dir2_fh(info),
					    info_type,
					    fanotify_info_name2(info),
					    info->name2_len, buf, count);
		if (ret < 0)
			return ret;

		buf += ret;
		count -= ret;
		total_bytes += ret;
	}

	if (fanotify_event_has_object_fh(event)) {
		const char *dot = NULL;
		int dot_len = 0;
@@ -1057,7 +1098,7 @@ static __u32 fanotify_mark_add_to_mask(struct fsnotify_mark *fsn_mark,

static struct fsnotify_mark *fanotify_add_new_mark(struct fsnotify_group *group,
						   fsnotify_connp_t *connp,
						   unsigned int type,
						   unsigned int obj_type,
						   __kernel_fsid_t *fsid)
{
	struct ucounts *ucounts = group->fanotify_data.ucounts;
@@ -1080,7 +1121,7 @@ static struct fsnotify_mark *fanotify_add_new_mark(struct fsnotify_group *group,
	}

	fsnotify_init_mark(mark, group);
	ret = fsnotify_add_mark_locked(mark, connp, type, 0, fsid);
	ret = fsnotify_add_mark_locked(mark, connp, obj_type, 0, fsid);
	if (ret) {
		fsnotify_put_mark(mark);
		goto out_dec_ucounts;
@@ -1105,7 +1146,7 @@ static int fanotify_group_init_error_pool(struct fsnotify_group *group)
}

static int fanotify_add_mark(struct fsnotify_group *group,
			     fsnotify_connp_t *connp, unsigned int type,
			     fsnotify_connp_t *connp, unsigned int obj_type,
			     __u32 mask, unsigned int flags,
			     __kernel_fsid_t *fsid)
{
@@ -1116,7 +1157,7 @@ static int fanotify_add_mark(struct fsnotify_group *group,
	mutex_lock(&group->mark_mutex);
	fsn_mark = fsnotify_find_mark(connp, group);
	if (!fsn_mark) {
		fsn_mark = fanotify_add_new_mark(group, connp, type, fsid);
		fsn_mark = fanotify_add_new_mark(group, connp, obj_type, fsid);
		if (IS_ERR(fsn_mark)) {
			mutex_unlock(&group->mark_mutex);
			return PTR_ERR(fsn_mark);
@@ -1275,6 +1316,15 @@ SYSCALL_DEFINE2(fanotify_init, unsigned int, flags, unsigned int, event_f_flags)
	if ((fid_mode & FAN_REPORT_NAME) && !(fid_mode & FAN_REPORT_DIR_FID))
		return -EINVAL;

	/*
	 * FAN_REPORT_TARGET_FID requires FAN_REPORT_NAME and FAN_REPORT_FID
	 * and is used as an indication to report both dir and child fid on all
	 * dirent events.
	 */
	if ((fid_mode & FAN_REPORT_TARGET_FID) &&
	    (!(fid_mode & FAN_REPORT_NAME) || !(fid_mode & FAN_REPORT_FID)))
		return -EINVAL;

	f_flags = O_RDWR | FMODE_NONOTIFY;
	if (flags & FAN_CLOEXEC)
		f_flags |= O_CLOEXEC;
@@ -1536,6 +1586,14 @@ static int do_fanotify_mark(int fanotify_fd, unsigned int flags, __u64 mask,
	    (!fid_mode || mark_type == FAN_MARK_MOUNT))
		goto fput_and_out;

	/*
	 * FAN_RENAME uses special info type records to report the old and
	 * new parent+name.  Reporting only old and new parent id is less
	 * useful and was not implemented.
	 */
	if (mask & FAN_RENAME && !(fid_mode & FAN_REPORT_NAME))
		goto fput_and_out;

	if (flags & FAN_MARK_FLUSH) {
		ret = 0;
		if (mark_type == FAN_MARK_MOUNT)
@@ -1667,7 +1725,7 @@ static int __init fanotify_user_setup(void)
				     FANOTIFY_DEFAULT_MAX_USER_MARKS);

	BUILD_BUG_ON(FANOTIFY_INIT_FLAGS & FANOTIFY_INTERNAL_GROUP_FLAGS);
	BUILD_BUG_ON(HWEIGHT32(FANOTIFY_INIT_FLAGS) != 11);
	BUILD_BUG_ON(HWEIGHT32(FANOTIFY_INIT_FLAGS) != 12);
	BUILD_BUG_ON(HWEIGHT32(FANOTIFY_MARK_FLAGS) != 9);

	fanotify_mark_cache = KMEM_CACHE(fsnotify_mark,
+37 −16

File changed.

Preview size limit exceeded, changes collapsed.

Loading