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

fanotify, inotify, dnotify, security: add security hook for fs notifications

As of now, setting watches on filesystem objects has, at most, applied a
check for read access to the inode, and in the case of fanotify, requires
CAP_SYS_ADMIN. No specific security hook or permission check has been
provided to control the setting of watches. Using any of inotify, dnotify,
or fanotify, it is possible to observe, not only write-like operations, but
even read access to a file. Modeling the watch as being merely a read from
the file is insufficient for the needs of SELinux. This is due to the fact
that read access should not necessarily imply access to information about
when another process reads from a file. Furthermore, fanotify watches grant
more power to an application in the form of permission events. While
notification events are solely, unidirectional (i.e. they only pass
information to the receiving application), permission events are blocking.
Permission events make a request to the receiving application which will
then reply with a decision as to whether or not that action may be
completed. This causes the issue of the watching application having the
ability to exercise control over the triggering process. Without drawing a
distinction within the permission check, the ability to read would imply
the greater ability to control an application. Additionally, mount and
superblock watches apply to all files within the same mount or superblock.
Read access to one file should not necessarily imply the ability to watch
all files accessed within a given mount or superblock.

In order to solve these issues, a new LSM hook is implemented and has been
placed within the system calls for marking filesystem objects with inotify,
fanotify, and dnotify watches. These calls to the hook are placed at the
point at which the target path has been resolved and are provided with the
path struct, the mask of requested notification events, and the type of
object on which the mark is being set (inode, superblock, or mount). The
mask and obj_type have already been translated into common FS_* values
shared by the entirety of the fs notification infrastructure. The path
struct is passed rather than just the inode so that the mount is available,
particularly for mount watches. This also allows for use of the hook by
pathname-based security modules. However, since the hook is intended for
use even by inode based security modules, it is not placed under the
CONFIG_SECURITY_PATH conditional. Otherwise, the inode-based security
modules would need to enable all of the path hooks, even though they do not
use any of them.

This only provides a hook at the point of setting a watch, and presumes
that permission to set a particular watch implies the ability to receive
all notification about that object which match the mask. This is all that
is required for SELinux. If other security modules require additional hooks
or infrastructure to control delivery of notification, these can be added
by them. It does not make sense for us to propose hooks for which we have
no implementation. The understanding that all notifications received by the
requesting application are all strictly of a type for which the application
has been granted permission shows that this implementation is sufficient in
its coverage.

Security modules wishing to provide complete control over fanotify must
also implement a security_file_open hook that validates that the access
requested by the watching application is authorized. Fanotify has the issue
that it returns a file descriptor with the file mode specified during
fanotify_init() to the watching process on event. This is already covered
by the LSM security_file_open hook if the security module implements
checking of the requested file mode there. Otherwise, a watching process
can obtain escalated access to a file for which it has not been authorized.

The selinux_path_notify hook implementation works by adding five new file
permissions: watch, watch_mount, watch_sb, watch_reads, and watch_with_perm
(descriptions about which will follow), and one new filesystem permission:
watch (which is applied to superblock checks). The hook then decides which
subset of these permissions must be held by the requesting application
based on the contents of the provided mask and the obj_type. The
selinux_file_open hook already checks the requested file mode and therefore
ensures that a watching process cannot escalate its access through
fanotify.

The watch, watch_mount, and watch_sb permissions are the baseline
permissions for setting a watch on an object and each are a requirement for
any watch to be set on a file, mount, or superblock respectively. It should
be noted that having either of the other two permissions (watch_reads and
watch_with_perm) does not imply the watch, watch_mount, or watch_sb
permission. Superblock watches further require the filesystem watch
permission to the superblock. As there is no labeled object in view for
mounts, there is no specific check for mount watches beyond watch_mount to
the inode. Such a check could be added in the future, if a suitable labeled
object existed representing the mount.

The watch_reads permission is required to receive notifications from
read-exclusive events on filesystem objects. These events include accessing
a file for the purpose of reading and closing a file which has been opened
read-only. This distinction has been drawn in order to provide a direct
indication in the policy for this otherwise not obvious capability. Read
access to a file should not necessarily imply the ability to observe read
events on a file.

Finally, watch_with_perm only applies to fanotify masks since it is the
only way to set a mask which allows for the blocking, permission event.
This permission is needed for any watch which is of this type. Though
fanotify requires CAP_SYS_ADMIN, this is insufficient as it gives implicit
trust to root, which we do not do, and does not support least privilege.

Signed-off-by: Aaron Goidel <acgoide@tycho.nsa.gov>
Acked-by: Casey Schaufler <casey@schaufler-ca.com>
Acked-by: Jan Kara <jack@suse.cz>
Signed-off-by: Paul Moore <paul@paul-moore.com>

authored by

Aaron Goidel and committed by
Paul Moore
ac5656d8 9b80c363

+113 -12
+12 -3
fs/notify/dnotify/dnotify.c
··· 22 22 #include <linux/sched/signal.h> 23 23 #include <linux/dnotify.h> 24 24 #include <linux/init.h> 25 + #include <linux/security.h> 25 26 #include <linux/spinlock.h> 26 27 #include <linux/slab.h> 27 28 #include <linux/fdtable.h> ··· 289 288 goto out_err; 290 289 } 291 290 291 + /* 292 + * convert the userspace DN_* "arg" to the internal FS_* 293 + * defined in fsnotify 294 + */ 295 + mask = convert_arg(arg); 296 + 297 + error = security_path_notify(&filp->f_path, mask, 298 + FSNOTIFY_OBJ_TYPE_INODE); 299 + if (error) 300 + goto out_err; 301 + 292 302 /* expect most fcntl to add new rather than augment old */ 293 303 dn = kmem_cache_alloc(dnotify_struct_cache, GFP_KERNEL); 294 304 if (!dn) { ··· 313 301 error = -ENOMEM; 314 302 goto out_err; 315 303 } 316 - 317 - /* convert the userspace DN_* "arg" to the internal FS_* defines in fsnotify */ 318 - mask = convert_arg(arg); 319 304 320 305 /* set up the new_fsn_mark and new_dn_mark */ 321 306 new_fsn_mark = &new_dn_mark->fsn_mark;
+17 -2
fs/notify/fanotify/fanotify_user.c
··· 528 528 }; 529 529 530 530 static int fanotify_find_path(int dfd, const char __user *filename, 531 - struct path *path, unsigned int flags) 531 + struct path *path, unsigned int flags, __u64 mask, 532 + unsigned int obj_type) 532 533 { 533 534 int ret; 534 535 ··· 568 567 569 568 /* you can only watch an inode if you have read permissions on it */ 570 569 ret = inode_permission(path->dentry->d_inode, MAY_READ); 570 + if (ret) { 571 + path_put(path); 572 + goto out; 573 + } 574 + 575 + ret = security_path_notify(path, mask, obj_type); 571 576 if (ret) 572 577 path_put(path); 578 + 573 579 out: 574 580 return ret; 575 581 } ··· 939 931 __kernel_fsid_t __fsid, *fsid = NULL; 940 932 u32 valid_mask = FANOTIFY_EVENTS | FANOTIFY_EVENT_FLAGS; 941 933 unsigned int mark_type = flags & FANOTIFY_MARK_TYPE_BITS; 934 + unsigned int obj_type; 942 935 int ret; 943 936 944 937 pr_debug("%s: fanotify_fd=%d flags=%x dfd=%d pathname=%p mask=%llx\n", ··· 954 945 955 946 switch (mark_type) { 956 947 case FAN_MARK_INODE: 948 + obj_type = FSNOTIFY_OBJ_TYPE_INODE; 949 + break; 957 950 case FAN_MARK_MOUNT: 951 + obj_type = FSNOTIFY_OBJ_TYPE_VFSMOUNT; 952 + break; 958 953 case FAN_MARK_FILESYSTEM: 954 + obj_type = FSNOTIFY_OBJ_TYPE_SB; 959 955 break; 960 956 default: 961 957 return -EINVAL; ··· 1028 1014 goto fput_and_out; 1029 1015 } 1030 1016 1031 - ret = fanotify_find_path(dfd, pathname, &path, flags); 1017 + ret = fanotify_find_path(dfd, pathname, &path, flags, 1018 + (mask & ALL_FSNOTIFY_EVENTS), obj_type); 1032 1019 if (ret) 1033 1020 goto fput_and_out; 1034 1021
+12 -2
fs/notify/inotify/inotify_user.c
··· 39 39 #include <linux/poll.h> 40 40 #include <linux/wait.h> 41 41 #include <linux/memcontrol.h> 42 + #include <linux/security.h> 42 43 43 44 #include "inotify.h" 44 45 #include "../fdinfo.h" ··· 343 342 /* 344 343 * find_inode - resolve a user-given path to a specific inode 345 344 */ 346 - static int inotify_find_inode(const char __user *dirname, struct path *path, unsigned flags) 345 + static int inotify_find_inode(const char __user *dirname, struct path *path, 346 + unsigned int flags, __u64 mask) 347 347 { 348 348 int error; 349 349 ··· 353 351 return error; 354 352 /* you can only watch an inode if you have read permissions on it */ 355 353 error = inode_permission(path->dentry->d_inode, MAY_READ); 354 + if (error) { 355 + path_put(path); 356 + return error; 357 + } 358 + error = security_path_notify(path, mask, 359 + FSNOTIFY_OBJ_TYPE_INODE); 356 360 if (error) 357 361 path_put(path); 362 + 358 363 return error; 359 364 } 360 365 ··· 753 744 if (mask & IN_ONLYDIR) 754 745 flags |= LOOKUP_DIRECTORY; 755 746 756 - ret = inotify_find_inode(pathname, &path, flags); 747 + ret = inotify_find_inode(pathname, &path, flags, 748 + (mask & IN_ALL_EVENTS)); 757 749 if (ret) 758 750 goto fput_and_out; 759 751
+8 -1
include/linux/lsm_hooks.h
··· 339 339 * Check for permission to change root directory. 340 340 * @path contains the path structure. 341 341 * Return 0 if permission is granted. 342 + * @path_notify: 343 + * Check permissions before setting a watch on events as defined by @mask, 344 + * on an object at @path, whose type is defined by @obj_type. 342 345 * @inode_readlink: 343 346 * Check the permission to read the symbolic link. 344 347 * @dentry contains the dentry structure for the file link. ··· 1538 1535 int (*path_chown)(const struct path *path, kuid_t uid, kgid_t gid); 1539 1536 int (*path_chroot)(const struct path *path); 1540 1537 #endif 1541 - 1538 + /* Needed for inode based security check */ 1539 + int (*path_notify)(const struct path *path, u64 mask, 1540 + unsigned int obj_type); 1542 1541 int (*inode_alloc_security)(struct inode *inode); 1543 1542 void (*inode_free_security)(struct inode *inode); 1544 1543 int (*inode_init_security)(struct inode *inode, struct inode *dir, ··· 1865 1860 struct hlist_head path_chown; 1866 1861 struct hlist_head path_chroot; 1867 1862 #endif 1863 + /* Needed for inode based modules as well */ 1864 + struct hlist_head path_notify; 1868 1865 struct hlist_head inode_alloc_security; 1869 1866 struct hlist_head inode_free_security; 1870 1867 struct hlist_head inode_init_security;
+8 -2
include/linux/security.h
··· 259 259 struct qstr *name, 260 260 const struct cred *old, 261 261 struct cred *new); 262 - 262 + int security_path_notify(const struct path *path, u64 mask, 263 + unsigned int obj_type); 263 264 int security_inode_alloc(struct inode *inode); 264 265 void security_inode_free(struct inode *inode); 265 266 int security_inode_init_security(struct inode *inode, struct inode *dir, ··· 388 387 int security_secid_to_secctx(u32 secid, char **secdata, u32 *seclen); 389 388 int security_secctx_to_secid(const char *secdata, u32 seclen, u32 *secid); 390 389 void security_release_secctx(char *secdata, u32 seclen); 391 - 392 390 void security_inode_invalidate_secctx(struct inode *inode); 393 391 int security_inode_notifysecctx(struct inode *inode, void *ctx, u32 ctxlen); 394 392 int security_inode_setsecctx(struct dentry *dentry, void *ctx, u32 ctxlen); ··· 617 617 618 618 static inline int security_move_mount(const struct path *from_path, 619 619 const struct path *to_path) 620 + { 621 + return 0; 622 + } 623 + 624 + static inline int security_path_notify(const struct path *path, u64 mask, 625 + unsigned int obj_type) 620 626 { 621 627 return 0; 622 628 }
+6
security/security.c
··· 871 871 return call_int_hook(move_mount, 0, from_path, to_path); 872 872 } 873 873 874 + int security_path_notify(const struct path *path, u64 mask, 875 + unsigned int obj_type) 876 + { 877 + return call_int_hook(path_notify, 0, path, mask, obj_type); 878 + } 879 + 874 880 int security_inode_alloc(struct inode *inode) 875 881 { 876 882 int rc = lsm_inode_alloc(inode);
+47
security/selinux/hooks.c
··· 92 92 #include <linux/kernfs.h> 93 93 #include <linux/stringhash.h> /* for hashlen_string() */ 94 94 #include <uapi/linux/mount.h> 95 + #include <linux/fsnotify.h> 96 + #include <linux/fanotify.h> 95 97 96 98 #include "avc.h" 97 99 #include "objsec.h" ··· 3261 3259 /* No one is allowed to remove a SELinux security label. 3262 3260 You can change the label, but all data must be labeled. */ 3263 3261 return -EACCES; 3262 + } 3263 + 3264 + static int selinux_path_notify(const struct path *path, u64 mask, 3265 + unsigned int obj_type) 3266 + { 3267 + int ret; 3268 + u32 perm; 3269 + 3270 + struct common_audit_data ad; 3271 + 3272 + ad.type = LSM_AUDIT_DATA_PATH; 3273 + ad.u.path = *path; 3274 + 3275 + /* 3276 + * Set permission needed based on the type of mark being set. 3277 + * Performs an additional check for sb watches. 3278 + */ 3279 + switch (obj_type) { 3280 + case FSNOTIFY_OBJ_TYPE_VFSMOUNT: 3281 + perm = FILE__WATCH_MOUNT; 3282 + break; 3283 + case FSNOTIFY_OBJ_TYPE_SB: 3284 + perm = FILE__WATCH_SB; 3285 + ret = superblock_has_perm(current_cred(), path->dentry->d_sb, 3286 + FILESYSTEM__WATCH, &ad); 3287 + if (ret) 3288 + return ret; 3289 + break; 3290 + case FSNOTIFY_OBJ_TYPE_INODE: 3291 + perm = FILE__WATCH; 3292 + break; 3293 + default: 3294 + return -EINVAL; 3295 + } 3296 + 3297 + /* blocking watches require the file:watch_with_perm permission */ 3298 + if (mask & (ALL_FSNOTIFY_PERM_EVENTS)) 3299 + perm |= FILE__WATCH_WITH_PERM; 3300 + 3301 + /* watches on read-like events need the file:watch_reads permission */ 3302 + if (mask & (FS_ACCESS | FS_ACCESS_PERM | FS_CLOSE_NOWRITE)) 3303 + perm |= FILE__WATCH_READS; 3304 + 3305 + return path_has_perm(current_cred(), path, perm); 3264 3306 } 3265 3307 3266 3308 /* ··· 6844 6798 LSM_HOOK_INIT(inode_getsecid, selinux_inode_getsecid), 6845 6799 LSM_HOOK_INIT(inode_copy_up, selinux_inode_copy_up), 6846 6800 LSM_HOOK_INIT(inode_copy_up_xattr, selinux_inode_copy_up_xattr), 6801 + LSM_HOOK_INIT(path_notify, selinux_path_notify), 6847 6802 6848 6803 LSM_HOOK_INIT(kernfs_init_security, selinux_kernfs_init_security), 6849 6804
+3 -2
security/selinux/include/classmap.h
··· 7 7 8 8 #define COMMON_FILE_PERMS COMMON_FILE_SOCK_PERMS, "unlink", "link", \ 9 9 "rename", "execute", "quotaon", "mounton", "audit_access", \ 10 - "open", "execmod" 10 + "open", "execmod", "watch", "watch_mount", "watch_sb", \ 11 + "watch_with_perm", "watch_reads" 11 12 12 13 #define COMMON_SOCK_PERMS COMMON_FILE_SOCK_PERMS, "bind", "connect", \ 13 14 "listen", "accept", "getopt", "setopt", "shutdown", "recvfrom", \ ··· 61 60 { "filesystem", 62 61 { "mount", "remount", "unmount", "getattr", 63 62 "relabelfrom", "relabelto", "associate", "quotamod", 64 - "quotaget", NULL } }, 63 + "quotaget", "watch", NULL } }, 65 64 { "file", 66 65 { COMMON_FILE_PERMS, 67 66 "execute_no_trans", "entrypoint", NULL } },