diff options
Diffstat (limited to 'security/selinux/hooks.c')
-rw-r--r-- | security/selinux/hooks.c | 434 |
1 files changed, 330 insertions, 104 deletions
diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index 1d0b37af2444d..116b4d644f689 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * NSA Security-Enhanced Linux (SELinux) security module * @@ -18,10 +19,6 @@ * Copyright (C) 2007 Hitachi Software Engineering Co., Ltd. * Yuichi Nakamura <ynakam@hitachisoft.jp> * Copyright (C) 2016 Mellanox Technologies - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2, - * as published by the Free Software Foundation. */ #include <linux/init.h> @@ -89,7 +86,11 @@ #include <linux/msg.h> #include <linux/shm.h> #include <linux/bpf.h> +#include <linux/kernfs.h> +#include <linux/stringhash.h> /* for hashlen_string() */ #include <uapi/linux/mount.h> +#include <linux/fsnotify.h> +#include <linux/fanotify.h> #include "avc.h" #include "objsec.h" @@ -195,7 +196,7 @@ static int selinux_lsm_notifier_avc_callback(u32 event) { if (event == AVC_CALLBACK_RESET) { sel_ib_pkey_flush(); - call_lsm_notifier(LSM_POLICY_CHANGE, NULL); + call_blocking_lsm_notifier(LSM_POLICY_CHANGE, NULL); } return 0; @@ -751,11 +752,13 @@ static int selinux_set_mnt_opts(struct super_block *sb, if (!strcmp(sb->s_type->name, "debugfs") || !strcmp(sb->s_type->name, "tracefs") || - !strcmp(sb->s_type->name, "sysfs") || - !strcmp(sb->s_type->name, "pstore") || + !strcmp(sb->s_type->name, "pstore")) + sbsec->flags |= SE_SBGENFS; + + if (!strcmp(sb->s_type->name, "sysfs") || !strcmp(sb->s_type->name, "cgroup") || !strcmp(sb->s_type->name, "cgroup2")) - sbsec->flags |= SE_SBGENFS; + sbsec->flags |= SE_SBGENFS | SE_SBGENFS_XATTR; if (!sbsec->behavior) { /* @@ -1048,15 +1051,24 @@ static int selinux_add_mnt_opt(const char *option, const char *val, int len, if (token == Opt_error) return -EINVAL; - if (token != Opt_seclabel) + if (token != Opt_seclabel) { val = kmemdup_nul(val, len, GFP_KERNEL); + if (!val) { + rc = -ENOMEM; + goto free_opt; + } + } rc = selinux_add_opt(token, val, mnt_opts); if (unlikely(rc)) { kfree(val); - if (*mnt_opts) { - selinux_free_mnt_opts(*mnt_opts); - *mnt_opts = NULL; - } + goto free_opt; + } + return rc; + +free_opt: + if (*mnt_opts) { + selinux_free_mnt_opts(*mnt_opts); + *mnt_opts = NULL; } return rc; } @@ -1354,6 +1366,67 @@ static int selinux_genfs_get_sid(struct dentry *dentry, return rc; } +static int inode_doinit_use_xattr(struct inode *inode, struct dentry *dentry, + u32 def_sid, u32 *sid) +{ +#define INITCONTEXTLEN 255 + char *context; + unsigned int len; + int rc; + + len = INITCONTEXTLEN; + context = kmalloc(len + 1, GFP_NOFS); + if (!context) + return -ENOMEM; + + context[len] = '\0'; + rc = __vfs_getxattr(dentry, inode, XATTR_NAME_SELINUX, context, len); + if (rc == -ERANGE) { + kfree(context); + + /* Need a larger buffer. Query for the right size. */ + rc = __vfs_getxattr(dentry, inode, XATTR_NAME_SELINUX, NULL, 0); + if (rc < 0) + return rc; + + len = rc; + context = kmalloc(len + 1, GFP_NOFS); + if (!context) + return -ENOMEM; + + context[len] = '\0'; + rc = __vfs_getxattr(dentry, inode, XATTR_NAME_SELINUX, + context, len); + } + if (rc < 0) { + kfree(context); + if (rc != -ENODATA) { + pr_warn("SELinux: %s: getxattr returned %d for dev=%s ino=%ld\n", + __func__, -rc, inode->i_sb->s_id, inode->i_ino); + return rc; + } + *sid = def_sid; + return 0; + } + + rc = security_context_to_sid_default(&selinux_state, context, rc, sid, + def_sid, GFP_NOFS); + if (rc) { + char *dev = inode->i_sb->s_id; + unsigned long ino = inode->i_ino; + + if (rc == -EINVAL) { + pr_notice_ratelimited("SELinux: inode=%lu on dev=%s was found to have an invalid context=%s. This indicates you may need to relabel the inode or the filesystem in question.\n", + ino, dev, context); + } else { + pr_warn("SELinux: %s: context_to_sid(%s) returned %d for dev=%s ino=%ld\n", + __func__, context, -rc, dev, ino); + } + } + kfree(context); + return 0; +} + /* The inode's security attributes must be initialized before first use. */ static int inode_doinit_with_dentry(struct inode *inode, struct dentry *opt_dentry) { @@ -1362,9 +1435,6 @@ static int inode_doinit_with_dentry(struct inode *inode, struct dentry *opt_dent u32 task_sid, sid = 0; u16 sclass; struct dentry *dentry; -#define INITCONTEXTLEN 255 - char *context = NULL; - unsigned len = 0; int rc = 0; if (isec->initialized == LABEL_INITIALIZED) @@ -1432,72 +1502,11 @@ static int inode_doinit_with_dentry(struct inode *inode, struct dentry *opt_dent goto out; } - len = INITCONTEXTLEN; - context = kmalloc(len+1, GFP_NOFS); - if (!context) { - rc = -ENOMEM; - dput(dentry); - goto out; - } - context[len] = '\0'; - rc = __vfs_getxattr(dentry, inode, XATTR_NAME_SELINUX, context, len); - if (rc == -ERANGE) { - kfree(context); - - /* Need a larger buffer. Query for the right size. */ - rc = __vfs_getxattr(dentry, inode, XATTR_NAME_SELINUX, NULL, 0); - if (rc < 0) { - dput(dentry); - goto out; - } - len = rc; - context = kmalloc(len+1, GFP_NOFS); - if (!context) { - rc = -ENOMEM; - dput(dentry); - goto out; - } - context[len] = '\0'; - rc = __vfs_getxattr(dentry, inode, XATTR_NAME_SELINUX, context, len); - } + rc = inode_doinit_use_xattr(inode, dentry, sbsec->def_sid, + &sid); dput(dentry); - if (rc < 0) { - if (rc != -ENODATA) { - pr_warn("SELinux: %s: getxattr returned " - "%d for dev=%s ino=%ld\n", __func__, - -rc, inode->i_sb->s_id, inode->i_ino); - kfree(context); - goto out; - } - /* Map ENODATA to the default file SID */ - sid = sbsec->def_sid; - rc = 0; - } else { - rc = security_context_to_sid_default(&selinux_state, - context, rc, &sid, - sbsec->def_sid, - GFP_NOFS); - if (rc) { - char *dev = inode->i_sb->s_id; - unsigned long ino = inode->i_ino; - - if (rc == -EINVAL) { - if (printk_ratelimit()) - pr_notice("SELinux: inode=%lu on dev=%s was found to have an invalid " - "context=%s. This indicates you may need to relabel the inode or the " - "filesystem in question.\n", ino, dev, context); - } else { - pr_warn("SELinux: %s: context_to_sid(%s) " - "returned %d for dev=%s ino=%ld\n", - __func__, context, -rc, dev, ino); - } - kfree(context); - /* Leave with the unlabeled SID */ - rc = 0; - break; - } - } - kfree(context); + if (rc) + goto out; break; case SECURITY_FS_USE_TASK: sid = task_sid; @@ -1548,9 +1557,21 @@ static int inode_doinit_with_dentry(struct inode *inode, struct dentry *opt_dent goto out; rc = selinux_genfs_get_sid(dentry, sclass, sbsec->flags, &sid); - dput(dentry); - if (rc) + if (rc) { + dput(dentry); goto out; + } + + if ((sbsec->flags & SE_SBGENFS_XATTR) && + (inode->i_opflags & IOP_XATTR)) { + rc = inode_doinit_use_xattr(inode, dentry, + sid, &sid); + if (rc) { + dput(dentry); + goto out; + } + } + dput(dentry); } break; } @@ -2528,9 +2549,8 @@ static void selinux_bprm_committing_creds(struct linux_binprm *bprm) static void selinux_bprm_committed_creds(struct linux_binprm *bprm) { const struct task_security_struct *tsec = selinux_cred(current_cred()); - struct itimerval itimer; u32 osid, sid; - int rc, i; + int rc; osid = tsec->osid; sid = tsec->sid; @@ -2548,11 +2568,8 @@ static void selinux_bprm_committed_creds(struct linux_binprm *bprm) rc = avc_has_perm(&selinux_state, osid, sid, SECCLASS_PROCESS, PROCESS__SIGINH, NULL); if (rc) { - if (IS_ENABLED(CONFIG_POSIX_TIMERS)) { - memset(&itimer, 0, sizeof itimer); - for (i = 0; i < 3; i++) - do_setitimer(i, &itimer, NULL); - } + clear_itimer(); + spin_lock_irq(¤t->sighand->siglock); if (!fatal_signal_pending(current)) { flush_sigqueue(¤t->pending); @@ -2603,10 +2620,11 @@ static int selinux_sb_eat_lsm_opts(char *options, void **mnt_opts) char *from = options; char *to = options; bool first = true; + int rc; while (1) { int len = opt_len(from); - int token, rc; + int token; char *arg = NULL; token = match_opt_prefix(from, len, &arg); @@ -2622,15 +2640,15 @@ static int selinux_sb_eat_lsm_opts(char *options, void **mnt_opts) *q++ = c; } arg = kmemdup_nul(arg, q - arg, GFP_KERNEL); + if (!arg) { + rc = -ENOMEM; + goto free_opt; + } } rc = selinux_add_opt(token, arg, mnt_opts); if (unlikely(rc)) { kfree(arg); - if (*mnt_opts) { - selinux_free_mnt_opts(*mnt_opts); - *mnt_opts = NULL; - } - return rc; + goto free_opt; } } else { if (!first) { // copy with preceding comma @@ -2648,6 +2666,13 @@ static int selinux_sb_eat_lsm_opts(char *options, void **mnt_opts) } *to = '\0'; return 0; + +free_opt: + if (*mnt_opts) { + selinux_free_mnt_opts(*mnt_opts); + *mnt_opts = NULL; + } + return rc; } static int selinux_sb_remount(struct super_block *sb, void *mnt_opts) @@ -3115,6 +3140,9 @@ static int selinux_inode_setxattr(struct dentry *dentry, const char *name, return dentry_has_perm(current_cred(), dentry, FILE__SETATTR); } + if (!selinux_state.initialized) + return (inode_owner_or_capable(inode) ? 0 : -EPERM); + sbsec = inode->i_sb->s_security; if (!(sbsec->flags & SBLABEL_MNT)) return -EOPNOTSUPP; @@ -3198,6 +3226,15 @@ static void selinux_inode_post_setxattr(struct dentry *dentry, const char *name, return; } + if (!selinux_state.initialized) { + /* If we haven't even been initialized, then we can't validate + * against a policy, so leave the label as invalid. It may + * resolve to a valid label on the next revalidation try if + * we've since initialized. + */ + return; + } + rc = security_context_to_sid_force(&selinux_state, value, size, &newsid); if (rc) { @@ -3248,6 +3285,50 @@ static int selinux_inode_removexattr(struct dentry *dentry, const char *name) return -EACCES; } +static int selinux_path_notify(const struct path *path, u64 mask, + unsigned int obj_type) +{ + int ret; + u32 perm; + + struct common_audit_data ad; + + ad.type = LSM_AUDIT_DATA_PATH; + ad.u.path = *path; + + /* + * Set permission needed based on the type of mark being set. + * Performs an additional check for sb watches. + */ + switch (obj_type) { + case FSNOTIFY_OBJ_TYPE_VFSMOUNT: + perm = FILE__WATCH_MOUNT; + break; + case FSNOTIFY_OBJ_TYPE_SB: + perm = FILE__WATCH_SB; + ret = superblock_has_perm(current_cred(), path->dentry->d_sb, + FILESYSTEM__WATCH, &ad); + if (ret) + return ret; + break; + case FSNOTIFY_OBJ_TYPE_INODE: + perm = FILE__WATCH; + break; + default: + return -EINVAL; + } + + /* blocking watches require the file:watch_with_perm permission */ + if (mask & (ALL_FSNOTIFY_PERM_EVENTS)) + perm |= FILE__WATCH_WITH_PERM; + + /* watches on read-like events need the file:watch_reads permission */ + if (mask & (FS_ACCESS | FS_ACCESS_PERM | FS_CLOSE_NOWRITE)) + perm |= FILE__WATCH_READS; + + return path_has_perm(current_cred(), path, perm); +} + /* * Copy the inode security context value to the user. * @@ -3371,6 +3452,67 @@ static int selinux_inode_copy_up_xattr(const char *name) return -EOPNOTSUPP; } +/* kernfs node operations */ + +static int selinux_kernfs_init_security(struct kernfs_node *kn_dir, + struct kernfs_node *kn) +{ + const struct task_security_struct *tsec = selinux_cred(current_cred()); + u32 parent_sid, newsid, clen; + int rc; + char *context; + + rc = kernfs_xattr_get(kn_dir, XATTR_NAME_SELINUX, NULL, 0); + if (rc == -ENODATA) + return 0; + else if (rc < 0) + return rc; + + clen = (u32)rc; + context = kmalloc(clen, GFP_KERNEL); + if (!context) + return -ENOMEM; + + rc = kernfs_xattr_get(kn_dir, XATTR_NAME_SELINUX, context, clen); + if (rc < 0) { + kfree(context); + return rc; + } + + rc = security_context_to_sid(&selinux_state, context, clen, &parent_sid, + GFP_KERNEL); + kfree(context); + if (rc) + return rc; + + if (tsec->create_sid) { + newsid = tsec->create_sid; + } else { + u16 secclass = inode_mode_to_security_class(kn->mode); + struct qstr q; + + q.name = kn->name; + q.hash_len = hashlen_string(kn_dir, kn->name); + + rc = security_transition_sid(&selinux_state, tsec->sid, + parent_sid, secclass, &q, + &newsid); + if (rc) + return rc; + } + + rc = security_sid_to_context_force(&selinux_state, newsid, + &context, &clen); + if (rc) + return rc; + + rc = kernfs_xattr_set(kn, XATTR_NAME_SELINUX, context, clen, + XATTR_CREATE); + kfree(context); + return rc; +} + + /* file security operations */ static int selinux_revalidate_file_permission(struct file *file, int mask) @@ -4438,7 +4580,7 @@ static int selinux_socket_bind(struct socket *sock, struct sockaddr *address, in struct lsm_network_audit net = {0,}; struct sockaddr_in *addr4 = NULL; struct sockaddr_in6 *addr6 = NULL; - u16 family_sa = address->sa_family; + u16 family_sa; unsigned short snum; u32 sid, node_perm; @@ -4448,6 +4590,9 @@ static int selinux_socket_bind(struct socket *sock, struct sockaddr *address, in * need to check address->sa_family as it is possible to have * sk->sk_family = PF_INET6 with addr->sa_family = AF_INET. */ + if (addrlen < offsetofend(struct sockaddr, sa_family)) + return -EINVAL; + family_sa = address->sa_family; switch (family_sa) { case AF_UNSPEC: case AF_INET: @@ -4486,8 +4631,8 @@ static int selinux_socket_bind(struct socket *sock, struct sockaddr *address, in inet_get_local_port_range(sock_net(sk), &low, &high); - if (snum < max(inet_prot_sock(sock_net(sk)), low) || - snum > high) { + if (inet_port_requires_bind_service(sock_net(sk), snum) || + snum < low || snum > high) { err = sel_netport_sid(sk->sk_protocol, snum, &sid); if (err) @@ -4560,6 +4705,14 @@ static int selinux_socket_connect_helper(struct socket *sock, err = sock_has_perm(sk, SOCKET__CONNECT); if (err) return err; + if (addrlen < offsetofend(struct sockaddr, sa_family)) + return -EINVAL; + + /* connect(AF_UNSPEC) has special handling, as it is a documented + * way to disconnect the socket + */ + if (address->sa_family == AF_UNSPEC) + return 0; /* * If a TCP, DCCP or SCTP socket, check name_connect permission @@ -6252,11 +6405,12 @@ static int selinux_setprocattr(const char *name, void *value, size_t size) } else if (!strcmp(name, "fscreate")) { tsec->create_sid = sid; } else if (!strcmp(name, "keycreate")) { - error = avc_has_perm(&selinux_state, - mysid, sid, SECCLASS_KEY, KEY__CREATE, - NULL); - if (error) - goto abort_change; + if (sid) { + error = avc_has_perm(&selinux_state, mysid, sid, + SECCLASS_KEY, KEY__CREATE, NULL); + if (error) + goto abort_change; + } tsec->keycreate_sid = sid; } else if (!strcmp(name, "sockcreate")) { tsec->sockcreate_sid = sid; @@ -6649,6 +6803,67 @@ struct lsm_blob_sizes selinux_blob_sizes __lsm_ro_after_init = { .lbs_msg_msg = sizeof(struct msg_security_struct), }; +#ifdef CONFIG_PERF_EVENTS +static int selinux_perf_event_open(struct perf_event_attr *attr, int type) +{ + u32 requested, sid = current_sid(); + + if (type == PERF_SECURITY_OPEN) + requested = PERF_EVENT__OPEN; + else if (type == PERF_SECURITY_CPU) + requested = PERF_EVENT__CPU; + else if (type == PERF_SECURITY_KERNEL) + requested = PERF_EVENT__KERNEL; + else if (type == PERF_SECURITY_TRACEPOINT) + requested = PERF_EVENT__TRACEPOINT; + else + return -EINVAL; + + return avc_has_perm(&selinux_state, sid, sid, SECCLASS_PERF_EVENT, + requested, NULL); +} + +static int selinux_perf_event_alloc(struct perf_event *event) +{ + struct perf_event_security_struct *perfsec; + + perfsec = kzalloc(sizeof(*perfsec), GFP_KERNEL); + if (!perfsec) + return -ENOMEM; + + perfsec->sid = current_sid(); + event->security = perfsec; + + return 0; +} + +static void selinux_perf_event_free(struct perf_event *event) +{ + struct perf_event_security_struct *perfsec = event->security; + + event->security = NULL; + kfree(perfsec); +} + +static int selinux_perf_event_read(struct perf_event *event) +{ + struct perf_event_security_struct *perfsec = event->security; + u32 sid = current_sid(); + + return avc_has_perm(&selinux_state, sid, perfsec->sid, + SECCLASS_PERF_EVENT, PERF_EVENT__READ, NULL); +} + +static int selinux_perf_event_write(struct perf_event *event) +{ + struct perf_event_security_struct *perfsec = event->security; + u32 sid = current_sid(); + + return avc_has_perm(&selinux_state, sid, perfsec->sid, + SECCLASS_PERF_EVENT, PERF_EVENT__WRITE, NULL); +} +#endif + static struct security_hook_list selinux_hooks[] __lsm_ro_after_init = { LSM_HOOK_INIT(binder_set_context_mgr, selinux_binder_set_context_mgr), LSM_HOOK_INIT(binder_transaction, selinux_binder_transaction), @@ -6718,6 +6933,9 @@ static struct security_hook_list selinux_hooks[] __lsm_ro_after_init = { LSM_HOOK_INIT(inode_getsecid, selinux_inode_getsecid), LSM_HOOK_INIT(inode_copy_up, selinux_inode_copy_up), LSM_HOOK_INIT(inode_copy_up_xattr, selinux_inode_copy_up_xattr), + LSM_HOOK_INIT(path_notify, selinux_path_notify), + + LSM_HOOK_INIT(kernfs_init_security, selinux_kernfs_init_security), LSM_HOOK_INIT(file_permission, selinux_file_permission), LSM_HOOK_INIT(file_alloc_security, selinux_file_alloc_security), @@ -6881,6 +7099,14 @@ static struct security_hook_list selinux_hooks[] __lsm_ro_after_init = { LSM_HOOK_INIT(bpf_map_free_security, selinux_bpf_map_free), LSM_HOOK_INIT(bpf_prog_free_security, selinux_bpf_prog_free), #endif + +#ifdef CONFIG_PERF_EVENTS + LSM_HOOK_INIT(perf_event_open, selinux_perf_event_open), + LSM_HOOK_INIT(perf_event_alloc, selinux_perf_event_alloc), + LSM_HOOK_INIT(perf_event_free, selinux_perf_event_free), + LSM_HOOK_INIT(perf_event_read, selinux_perf_event_read), + LSM_HOOK_INIT(perf_event_write, selinux_perf_event_write), +#endif }; static __init int selinux_init(void) |