Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

fanotify: report name info for FAN_DIR_MODIFY event

Report event FAN_DIR_MODIFY with name in a variable length record similar
to how fid's are reported. With name info reporting implemented, setting
FAN_DIR_MODIFY in mark mask is now allowed.

When events are reported with name, the reported fid identifies the
directory and the name follows the fid. The info record type for this
event info is FAN_EVENT_INFO_TYPE_DFID_NAME.

For now, all reported events have at most one info record which is
either FAN_EVENT_INFO_TYPE_FID or FAN_EVENT_INFO_TYPE_DFID_NAME (for
FAN_DIR_MODIFY). Later on, events "on child" will report both records.

There are several ways that an application can use this information:

1. When watching a single directory, the name is always relative to
the watched directory, so application need to fstatat(2) the name
relative to the watched directory.

2. When watching a set of directories, the application could keep a map
of dirfd for all watched directories and hash the map by fid obtained
with name_to_handle_at(2). When getting a name event, the fid in the
event info could be used to lookup the base dirfd in the map and then
call fstatat(2) with that dirfd.

3. When watching a filesystem (FAN_MARK_FILESYSTEM) or a large set of
directories, the application could use open_by_handle_at(2) with the fid
in event info to obtain dirfd for the directory where event happened and
call fstatat(2) with this dirfd.

The last option scales better for a large number of watched directories.
The first two options may be available in the future also for non
privileged fanotify watchers, because open_by_handle_at(2) requires
the CAP_DAC_READ_SEARCH capability.

Link: https://lore.kernel.org/r/20200319151022.31456-15-amir73il@gmail.com
Signed-off-by: Amir Goldstein <amir73il@gmail.com>
Signed-off-by: Jan Kara <jack@suse.cz>

authored by

Amir Goldstein and committed by
Jan Kara
44d705b0 cacfb956

+100 -30
+1 -1
fs/notify/fanotify/fanotify.c
··· 520 520 BUILD_BUG_ON(FAN_OPEN_EXEC != FS_OPEN_EXEC); 521 521 BUILD_BUG_ON(FAN_OPEN_EXEC_PERM != FS_OPEN_EXEC_PERM); 522 522 523 - BUILD_BUG_ON(HWEIGHT32(ALL_FANOTIFY_EVENT_BITS) != 19); 523 + BUILD_BUG_ON(HWEIGHT32(ALL_FANOTIFY_EVENT_BITS) != 20); 524 524 525 525 mask = fanotify_group_event_mask(group, iter_info, mask, data, 526 526 data_type);
+90 -27
fs/notify/fanotify/fanotify_user.c
··· 51 51 struct kmem_cache *fanotify_perm_event_cachep __read_mostly; 52 52 53 53 #define FANOTIFY_EVENT_ALIGN 4 54 + #define FANOTIFY_INFO_HDR_LEN \ 55 + (sizeof(struct fanotify_event_info_fid) + sizeof(struct file_handle)) 54 56 55 - static int fanotify_fid_info_len(int fh_len) 57 + static int fanotify_fid_info_len(int fh_len, int name_len) 56 58 { 57 - return roundup(sizeof(struct fanotify_event_info_fid) + 58 - sizeof(struct file_handle) + fh_len, 59 - FANOTIFY_EVENT_ALIGN); 59 + int info_len = fh_len; 60 + 61 + if (name_len) 62 + info_len += name_len + 1; 63 + 64 + return roundup(FANOTIFY_INFO_HDR_LEN + info_len, FANOTIFY_EVENT_ALIGN); 60 65 } 61 66 62 67 static int fanotify_event_info_len(struct fanotify_event *event) 63 68 { 69 + int info_len = 0; 64 70 int fh_len = fanotify_event_object_fh_len(event); 65 71 66 - if (!fh_len) 67 - return 0; 72 + if (fh_len) 73 + info_len += fanotify_fid_info_len(fh_len, 0); 68 74 69 - return fanotify_fid_info_len(fh_len); 75 + if (fanotify_event_name_len(event)) { 76 + struct fanotify_name_event *fne = FANOTIFY_NE(event); 77 + 78 + info_len += fanotify_fid_info_len(fne->dir_fh.len, 79 + fne->name_len); 80 + } 81 + 82 + return info_len; 70 83 } 71 84 72 85 /* ··· 217 204 return -ENOENT; 218 205 } 219 206 220 - static int copy_fid_to_user(__kernel_fsid_t *fsid, struct fanotify_fh *fh, 221 - char __user *buf) 207 + static int copy_info_to_user(__kernel_fsid_t *fsid, struct fanotify_fh *fh, 208 + const char *name, size_t name_len, 209 + char __user *buf, size_t count) 222 210 { 223 211 struct fanotify_event_info_fid info = { }; 224 212 struct file_handle handle = { }; 225 213 unsigned char bounce[FANOTIFY_INLINE_FH_LEN], *fh_buf; 226 214 size_t fh_len = fh ? fh->len : 0; 227 - size_t len = fanotify_fid_info_len(fh_len); 215 + size_t info_len = fanotify_fid_info_len(fh_len, name_len); 216 + size_t len = info_len; 228 217 229 - if (!len) 218 + pr_debug("%s: fh_len=%zu name_len=%zu, info_len=%zu, count=%zu\n", 219 + __func__, fh_len, name_len, info_len, count); 220 + 221 + if (!fh_len || (name && !name_len)) 230 222 return 0; 231 223 232 - if (WARN_ON_ONCE(len < sizeof(info) + sizeof(handle) + fh_len)) 224 + if (WARN_ON_ONCE(len < sizeof(info) || len > count)) 233 225 return -EFAULT; 234 226 235 - /* Copy event info fid header followed by vaiable sized file handle */ 236 - info.hdr.info_type = FAN_EVENT_INFO_TYPE_FID; 227 + /* 228 + * Copy event info fid header followed by variable sized file handle 229 + * and optionally followed by variable sized filename. 230 + */ 231 + info.hdr.info_type = name_len ? FAN_EVENT_INFO_TYPE_DFID_NAME : 232 + FAN_EVENT_INFO_TYPE_FID; 237 233 info.hdr.len = len; 238 234 info.fsid = *fsid; 239 235 if (copy_to_user(buf, &info, sizeof(info))) ··· 250 228 251 229 buf += sizeof(info); 252 230 len -= sizeof(info); 231 + if (WARN_ON_ONCE(len < sizeof(handle))) 232 + return -EFAULT; 233 + 253 234 handle.handle_type = fh->type; 254 235 handle.handle_bytes = fh_len; 255 236 if (copy_to_user(buf, &handle, sizeof(handle))) ··· 260 235 261 236 buf += sizeof(handle); 262 237 len -= sizeof(handle); 238 + if (WARN_ON_ONCE(len < fh_len)) 239 + return -EFAULT; 240 + 263 241 /* 264 - * For an inline fh, copy through stack to exclude the copy from 265 - * usercopy hardening protections. 242 + * For an inline fh and inline file name, copy through stack to exclude 243 + * the copy from usercopy hardening protections. 266 244 */ 267 245 fh_buf = fanotify_fh_buf(fh); 268 246 if (fh_len <= FANOTIFY_INLINE_FH_LEN) { ··· 275 247 if (copy_to_user(buf, fh_buf, fh_len)) 276 248 return -EFAULT; 277 249 278 - /* Pad with 0's */ 279 250 buf += fh_len; 280 251 len -= fh_len; 252 + 253 + if (name_len) { 254 + /* Copy the filename with terminating null */ 255 + name_len++; 256 + if (WARN_ON_ONCE(len < name_len)) 257 + return -EFAULT; 258 + 259 + if (copy_to_user(buf, name, name_len)) 260 + return -EFAULT; 261 + 262 + buf += name_len; 263 + len -= name_len; 264 + } 265 + 266 + /* Pad with 0's */ 281 267 WARN_ON_ONCE(len < 0 || len >= FANOTIFY_EVENT_ALIGN); 282 268 if (len > 0 && clear_user(buf, len)) 283 269 return -EFAULT; 284 270 285 - return 0; 271 + return info_len; 286 272 } 287 273 288 274 static ssize_t copy_event_to_user(struct fsnotify_group *group, ··· 310 268 311 269 pr_debug("%s: group=%p event=%p\n", __func__, group, event); 312 270 313 - metadata.event_len = FAN_EVENT_METADATA_LEN; 271 + metadata.event_len = FAN_EVENT_METADATA_LEN + 272 + fanotify_event_info_len(event); 314 273 metadata.metadata_len = FAN_EVENT_METADATA_LEN; 315 274 metadata.vers = FANOTIFY_METADATA_VERSION; 316 275 metadata.reserved = 0; 317 276 metadata.mask = event->mask & FANOTIFY_OUTGOING_EVENTS; 318 277 metadata.pid = pid_vnr(event->pid); 319 278 320 - if (fanotify_event_object_fh(event)) { 321 - metadata.event_len += fanotify_event_info_len(event); 322 - } else if (path && path->mnt && path->dentry) { 279 + if (path && path->mnt && path->dentry) { 323 280 fd = create_fd(group, path, &f); 324 281 if (fd < 0) 325 282 return fd; ··· 336 295 if (copy_to_user(buf, &metadata, FAN_EVENT_METADATA_LEN)) 337 296 goto out_close_fd; 338 297 298 + buf += FAN_EVENT_METADATA_LEN; 299 + count -= FAN_EVENT_METADATA_LEN; 300 + 339 301 if (fanotify_is_perm_event(event->mask)) 340 302 FANOTIFY_PERM(event)->fd = fd; 341 303 342 - if (f) { 304 + if (f) 343 305 fd_install(fd, f); 344 - } else if (fanotify_event_object_fh(event)) { 345 - ret = copy_fid_to_user(fanotify_event_fsid(event), 346 - fanotify_event_object_fh(event), 347 - buf + FAN_EVENT_METADATA_LEN); 306 + 307 + /* Event info records order is: dir fid + name, child fid */ 308 + if (fanotify_event_name_len(event)) { 309 + struct fanotify_name_event *fne = FANOTIFY_NE(event); 310 + 311 + ret = copy_info_to_user(fanotify_event_fsid(event), 312 + fanotify_event_dir_fh(event), 313 + fne->name, fne->name_len, 314 + buf, count); 348 315 if (ret < 0) 349 316 return ret; 317 + 318 + buf += ret; 319 + count -= ret; 320 + } 321 + 322 + if (fanotify_event_object_fh_len(event)) { 323 + ret = copy_info_to_user(fanotify_event_fsid(event), 324 + fanotify_event_object_fh(event), 325 + NULL, 0, buf, count); 326 + if (ret < 0) 327 + return ret; 328 + 329 + buf += ret; 330 + count -= ret; 350 331 } 351 332 352 333 return metadata.event_len;
+2 -1
include/linux/fanotify.h
··· 47 47 * Directory entry modification events - reported only to directory 48 48 * where entry is modified and not to a watching parent. 49 49 */ 50 - #define FANOTIFY_DIRENT_EVENTS (FAN_MOVE | FAN_CREATE | FAN_DELETE) 50 + #define FANOTIFY_DIRENT_EVENTS (FAN_MOVE | FAN_CREATE | FAN_DELETE | \ 51 + FAN_DIR_MODIFY) 51 52 52 53 /* Events that can only be reported with data type FSNOTIFY_EVENT_INODE */ 53 54 #define FANOTIFY_INODE_EVENTS (FANOTIFY_DIRENT_EVENTS | \
+7 -1
include/uapi/linux/fanotify.h
··· 117 117 }; 118 118 119 119 #define FAN_EVENT_INFO_TYPE_FID 1 120 + #define FAN_EVENT_INFO_TYPE_DFID_NAME 2 120 121 121 122 /* Variable length info record following event metadata */ 122 123 struct fanotify_event_info_header { ··· 126 125 __u16 len; 127 126 }; 128 127 129 - /* Unique file identifier info record */ 128 + /* 129 + * Unique file identifier info record. This is used both for 130 + * FAN_EVENT_INFO_TYPE_FID records and for FAN_EVENT_INFO_TYPE_DFID_NAME 131 + * records. For FAN_EVENT_INFO_TYPE_DFID_NAME there is additionally a null 132 + * terminated name immediately after the file handle. 133 + */ 130 134 struct fanotify_event_info_fid { 131 135 struct fanotify_event_info_header hdr; 132 136 __kernel_fsid_t fsid;