Commit 83b7a598 authored by Amir Goldstein's avatar Amir Goldstein Committed by Jan Kara
Browse files

fanotify: add basic support for FAN_REPORT_DIR_FID

For now, the flag is mutually exclusive with FAN_REPORT_FID.
Events include a single info record of type FAN_EVENT_INFO_TYPE_DFID
with a directory file handle.

For now, events are only reported for:
- Directory modification events
- Events on children of a watching directory
- Events on directory objects

Soon, we will add support for reporting the parent directory fid
for events on non-directories with filesystem/mount mark and
support for reporting both parent directory fid and child fid.

Link: https://lore.kernel.org/r/20200716084230.30611-19-amir73il@gmail.com


Signed-off-by: default avatarAmir Goldstein <amir73il@gmail.com>
Signed-off-by: default avatarJan Kara <jack@suse.cz>
parent 79cb299c
Loading
Loading
Loading
Loading
+32 −2
Original line number Diff line number Diff line
@@ -223,7 +223,7 @@ 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)
				     int data_type, struct inode *dir)
{
	__u32 marks_mask = 0, marks_ignored_mask = 0;
	__u32 test_mask, user_mask = FANOTIFY_OUTGOING_EVENTS |
@@ -243,6 +243,10 @@ static u32 fanotify_group_event_mask(struct fsnotify_group *group,
		/* Path type events are only relevant for files and dirs */
		if (!d_is_reg(path->dentry) && !d_can_lookup(path->dentry))
			return 0;
	} else if (!(fid_mode & FAN_REPORT_FID)) {
		/* Do we have a directory inode to report? */
		if (!dir && !(event_mask & FS_ISDIR))
			return 0;
	}

	fsnotify_foreach_obj_type(type) {
@@ -396,6 +400,28 @@ static struct inode *fanotify_fid_inode(u32 event_mask, const void *data,
	return fsnotify_data_inode(data, data_type);
}

/*
 * The inode to use as identifier when reporting dir fid depends on the event.
 * Report the modified directory inode on dirent modification events.
 * Report the "victim" inode if "victim" is a directory.
 * Report the parent inode if "victim" is not a directory and event is
 * reported to parent.
 * Otherwise, do not report dir fid.
 */
static struct inode *fanotify_dfid_inode(u32 event_mask, const void *data,
					 int data_type, struct inode *dir)
{
	struct inode *inode = fsnotify_data_inode(data, data_type);

	if (event_mask & ALL_FSNOTIFY_DIRENT_EVENTS)
		return dir;

	if (S_ISDIR(inode->i_mode))
		return inode;

	return dir;
}

static struct fanotify_event *fanotify_alloc_path_event(const struct path *path,
							gfp_t gfp)
{
@@ -491,10 +517,14 @@ static struct fanotify_event *fanotify_alloc_event(struct fsnotify_group *group,
	struct fanotify_event *event = NULL;
	gfp_t gfp = GFP_KERNEL_ACCOUNT;
	struct inode *id = fanotify_fid_inode(mask, data, data_type, dir);
	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);
	bool name_event = false;

	if ((fid_mode & FAN_REPORT_DIR_FID) && dirid)
		id = dirid;

	/*
	 * For queues with unlimited length lost events are not expected and
	 * can possibly have security implications. Avoid losing events when
@@ -605,7 +635,7 @@ static int fanotify_handle_event(struct fsnotify_group *group, u32 mask,
	BUILD_BUG_ON(HWEIGHT32(ALL_FANOTIFY_EVENT_BITS) != 19);

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

+61 −10
Original line number Diff line number Diff line
@@ -216,7 +216,7 @@ static int process_access_response(struct fsnotify_group *group,
}

static int copy_info_to_user(__kernel_fsid_t *fsid, struct fanotify_fh *fh,
			     const char *name, size_t name_len,
			     int info_type, const char *name, size_t name_len,
			     char __user *buf, size_t count)
{
	struct fanotify_event_info_fid info = { };
@@ -229,7 +229,7 @@ static int copy_info_to_user(__kernel_fsid_t *fsid, struct fanotify_fh *fh,
	pr_debug("%s: fh_len=%zu name_len=%zu, info_len=%zu, count=%zu\n",
		 __func__, fh_len, name_len, info_len, count);

	if (!fh_len || (name && !name_len))
	if (!fh_len)
		return 0;

	if (WARN_ON_ONCE(len < sizeof(info) || len > count))
@@ -239,8 +239,21 @@ static int copy_info_to_user(__kernel_fsid_t *fsid, struct fanotify_fh *fh,
	 * 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;
	switch (info_type) {
	case FAN_EVENT_INFO_TYPE_FID:
	case FAN_EVENT_INFO_TYPE_DFID:
		if (WARN_ON_ONCE(name_len))
			return -EFAULT;
		break;
	case FAN_EVENT_INFO_TYPE_DFID_NAME:
		if (WARN_ON_ONCE(!name || !name_len))
			return -EFAULT;
		break;
	default:
		return -EFAULT;
	}

	info.hdr.info_type = info_type;
	info.hdr.len = len;
	info.fsid = *fsid;
	if (copy_to_user(buf, &info, sizeof(info)))
@@ -304,8 +317,10 @@ 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_info *info = fanotify_event_info(event);
	unsigned int fid_mode = FAN_GROUP_FLAG(group, FANOTIFY_FID_BITS);
	struct file *f = NULL;
	int ret, fd = FAN_NOFD;
	int info_type = 0;

	pr_debug("%s: group=%p event=%p\n", __func__, group, event);

@@ -346,9 +361,10 @@ static ssize_t copy_event_to_user(struct fsnotify_group *group,

	/* Event info records order is: dir fid + name, child fid */
	if (fanotify_event_dir_fh_len(event)) {
		info_type = FAN_EVENT_INFO_TYPE_DFID_NAME;
		ret = copy_info_to_user(fanotify_event_fsid(event),
					fanotify_info_dir_fh(info),
					fanotify_info_name(info),
					info_type, fanotify_info_name(info),
					info->name_len, buf, count);
		if (ret < 0)
			return ret;
@@ -358,9 +374,33 @@ static ssize_t copy_event_to_user(struct fsnotify_group *group,
	}

	if (fanotify_event_object_fh_len(event)) {
		if (fid_mode == FAN_REPORT_FID || info_type) {
			/*
			 * With only group flag FAN_REPORT_FID only type FID is
			 * reported. Second info record type is always FID.
			 */
			info_type = FAN_EVENT_INFO_TYPE_FID;
		} else if ((event->mask & ALL_FSNOTIFY_DIRENT_EVENTS) ||
			   (event->mask & FAN_ONDIR)) {
			/*
			 * With group flag FAN_REPORT_DIR_FID, a single info
			 * record has type DFID for directory entry modification
			 * event and for event on a directory.
			 */
			info_type = FAN_EVENT_INFO_TYPE_DFID;
		} else {
			/*
			 * With group flags FAN_REPORT_DIR_FID|FAN_REPORT_FID,
			 * a single info record has type FID for event on a
			 * non-directory, when there is no directory to report.
			 * For example, on FAN_DELETE_SELF event.
			 */
			info_type = FAN_EVENT_INFO_TYPE_FID;
		}

		ret = copy_info_to_user(fanotify_event_fsid(event),
					fanotify_event_object_fh(event),
					NULL, 0, buf, count);
					info_type, NULL, 0, buf, count);
		if (ret < 0)
			return ret;

@@ -861,6 +901,8 @@ SYSCALL_DEFINE2(fanotify_init, unsigned int, flags, unsigned int, event_f_flags)
	struct fsnotify_group *group;
	int f_flags, fd;
	struct user_struct *user;
	unsigned int fid_mode = flags & FANOTIFY_FID_BITS;
	unsigned int class = flags & FANOTIFY_CLASS_BITS;

	pr_debug("%s: flags=%x event_f_flags=%x\n",
		 __func__, flags, event_f_flags);
@@ -887,10 +929,19 @@ SYSCALL_DEFINE2(fanotify_init, unsigned int, flags, unsigned int, event_f_flags)
		return -EINVAL;
	}

	if ((flags & FANOTIFY_FID_BITS) &&
	    (flags & FANOTIFY_CLASS_BITS) != FAN_CLASS_NOTIF)
	if (fid_mode && class != FAN_CLASS_NOTIF)
		return -EINVAL;

	/* Reporting either object fid or dir fid */
	switch (fid_mode) {
	case 0:
	case FAN_REPORT_FID:
	case FAN_REPORT_DIR_FID:
		break;
	default:
		return -EINVAL;
	}

	user = get_current_user();
	if (atomic_read(&user->fanotify_listeners) > FANOTIFY_DEFAULT_MAX_LISTENERS) {
		free_uid(user);
@@ -926,7 +977,7 @@ SYSCALL_DEFINE2(fanotify_init, unsigned int, flags, unsigned int, event_f_flags)
	group->fanotify_data.f_flags = event_f_flags;
	init_waitqueue_head(&group->fanotify_data.access_waitq);
	INIT_LIST_HEAD(&group->fanotify_data.access_list);
	switch (flags & FANOTIFY_CLASS_BITS) {
	switch (class) {
	case FAN_CLASS_NOTIF:
		group->priority = FS_PRIO_0;
		break;
@@ -1236,7 +1287,7 @@ COMPAT_SYSCALL_DEFINE6(fanotify_mark,
 */
static int __init fanotify_user_setup(void)
{
	BUILD_BUG_ON(HWEIGHT32(FANOTIFY_INIT_FLAGS) != 8);
	BUILD_BUG_ON(HWEIGHT32(FANOTIFY_INIT_FLAGS) != 9);
	BUILD_BUG_ON(HWEIGHT32(FANOTIFY_MARK_FLAGS) != 9);

	fanotify_mark_cache = KMEM_CACHE(fsnotify_mark,
+1 −1
Original line number Diff line number Diff line
@@ -18,7 +18,7 @@
#define FANOTIFY_CLASS_BITS	(FAN_CLASS_NOTIF | FAN_CLASS_CONTENT | \
				 FAN_CLASS_PRE_CONTENT)

#define FANOTIFY_FID_BITS	(FAN_REPORT_FID)
#define FANOTIFY_FID_BITS	(FAN_REPORT_FID | FAN_REPORT_DIR_FID)

#define FANOTIFY_INIT_FLAGS	(FANOTIFY_CLASS_BITS | FANOTIFY_FID_BITS | \
				 FAN_REPORT_TID | \
+7 −4
Original line number Diff line number Diff line
@@ -53,6 +53,7 @@
/* Flags to determine fanotify event format */
#define FAN_REPORT_TID		0x00000100	/* event->pid is thread id */
#define FAN_REPORT_FID		0x00000200	/* Report unique file id */
#define FAN_REPORT_DIR_FID	0x00000400	/* Report unique directory id */

/* Deprecated - do not use this in programs and do not add new flags here! */
#define FAN_ALL_INIT_FLAGS	(FAN_CLOEXEC | FAN_NONBLOCK | \
@@ -117,6 +118,7 @@ struct fanotify_event_metadata {

#define FAN_EVENT_INFO_TYPE_FID		1
#define FAN_EVENT_INFO_TYPE_DFID_NAME	2
#define FAN_EVENT_INFO_TYPE_DFID	3

/* Variable length info record following event metadata */
struct fanotify_event_info_header {
@@ -126,10 +128,11 @@ struct fanotify_event_info_header {
};

/*
 * Unique file identifier info record. This is used both for
 * FAN_EVENT_INFO_TYPE_FID records and for FAN_EVENT_INFO_TYPE_DFID_NAME
 * records. For FAN_EVENT_INFO_TYPE_DFID_NAME there is additionally a null
 * terminated name immediately after the file handle.
 * Unique file identifier info record.
 * This structure is used for records of types FAN_EVENT_INFO_TYPE_FID,
 * FAN_EVENT_INFO_TYPE_DFID and FAN_EVENT_INFO_TYPE_DFID_NAME.
 * For FAN_EVENT_INFO_TYPE_DFID_NAME there is additionally a null terminated
 * name immediately after the file handle.
 */
struct fanotify_event_info_fid {
	struct fanotify_event_info_header hdr;