diff options
Diffstat (limited to 'security/selinux')
53 files changed, 3570 insertions, 2268 deletions
diff --git a/security/selinux/.gitignore b/security/selinux/.gitignore index 2e5040a3d48b..168fae13ca5a 100644 --- a/security/selinux/.gitignore +++ b/security/selinux/.gitignore @@ -1,2 +1,3 @@ +# SPDX-License-Identifier: GPL-2.0-only av_permissions.h flask.h diff --git a/security/selinux/Kconfig b/security/selinux/Kconfig index 1014cb0ee956..9e921fc72538 100644 --- a/security/selinux/Kconfig +++ b/security/selinux/Kconfig @@ -88,6 +88,9 @@ config SECURITY_SELINUX_CHECKREQPROT_VALUE 'checkreqprot=' boot parameter. It may also be changed at runtime via /sys/fs/selinux/checkreqprot if authorized by policy. + WARNING: this option is deprecated and will be removed in a future + kernel release. + If you are unsure how to answer this question, answer 0. config SECURITY_SELINUX_SIDTAB_HASH_BITS diff --git a/security/selinux/Makefile b/security/selinux/Makefile index 2000f95fb197..776162444882 100644 --- a/security/selinux/Makefile +++ b/security/selinux/Makefile @@ -6,9 +6,9 @@ obj-$(CONFIG_SECURITY_SELINUX) := selinux.o selinux-y := avc.o hooks.o selinuxfs.o netlink.o nlmsgtab.o netif.o \ - netnode.o netport.o \ + netnode.o netport.o status.o \ ss/ebitmap.o ss/hashtab.o ss/symtab.o ss/sidtab.o ss/avtab.o \ - ss/policydb.o ss/services.o ss/conditional.o ss/mls.o ss/status.o + ss/policydb.o ss/services.o ss/conditional.o ss/mls.o ss/context.o selinux-$(CONFIG_SECURITY_NETWORK_XFRM) += xfrm.o @@ -16,6 +16,8 @@ selinux-$(CONFIG_NETLABEL) += netlabel.o selinux-$(CONFIG_SECURITY_INFINIBAND) += ibpkey.o +selinux-$(CONFIG_IMA) += ima.o + ccflags-y := -I$(srctree)/security/selinux -I$(srctree)/security/selinux/include $(addprefix $(obj)/,$(selinux-y)): $(obj)/flask.h diff --git a/security/selinux/avc.c b/security/selinux/avc.c index d18cb32a242a..9a43af0ebd7d 100644 --- a/security/selinux/avc.c +++ b/security/selinux/avc.c @@ -31,6 +31,9 @@ #include "avc_ss.h" #include "classmap.h" +#define CREATE_TRACE_POINTS +#include <trace/events/avc.h> + #define AVC_CACHE_SLOTS 512 #define AVC_DEF_CACHE_THRESHOLD 512 #define AVC_CACHE_RECLAIM 16 @@ -115,11 +118,11 @@ void avc_set_cache_threshold(struct selinux_avc *avc, avc->avc_cache_threshold = cache_threshold; } -static struct avc_callback_node *avc_callbacks; -static struct kmem_cache *avc_node_cachep; -static struct kmem_cache *avc_xperms_data_cachep; -static struct kmem_cache *avc_xperms_decision_cachep; -static struct kmem_cache *avc_xperms_cachep; +static struct avc_callback_node *avc_callbacks __ro_after_init; +static struct kmem_cache *avc_node_cachep __ro_after_init; +static struct kmem_cache *avc_xperms_data_cachep __ro_after_init; +static struct kmem_cache *avc_xperms_decision_cachep __ro_after_init; +static struct kmem_cache *avc_xperms_cachep __ro_after_init; static inline int avc_hash(u32 ssid, u32 tsid, u16 tclass) { @@ -294,26 +297,27 @@ static struct avc_xperms_decision_node struct avc_xperms_decision_node *xpd_node; struct extended_perms_decision *xpd; - xpd_node = kmem_cache_zalloc(avc_xperms_decision_cachep, GFP_NOWAIT); + xpd_node = kmem_cache_zalloc(avc_xperms_decision_cachep, + GFP_NOWAIT | __GFP_NOWARN); if (!xpd_node) return NULL; xpd = &xpd_node->xpd; if (which & XPERMS_ALLOWED) { xpd->allowed = kmem_cache_zalloc(avc_xperms_data_cachep, - GFP_NOWAIT); + GFP_NOWAIT | __GFP_NOWARN); if (!xpd->allowed) goto error; } if (which & XPERMS_AUDITALLOW) { xpd->auditallow = kmem_cache_zalloc(avc_xperms_data_cachep, - GFP_NOWAIT); + GFP_NOWAIT | __GFP_NOWARN); if (!xpd->auditallow) goto error; } if (which & XPERMS_DONTAUDIT) { xpd->dontaudit = kmem_cache_zalloc(avc_xperms_data_cachep, - GFP_NOWAIT); + GFP_NOWAIT | __GFP_NOWARN); if (!xpd->dontaudit) goto error; } @@ -341,7 +345,7 @@ static struct avc_xperms_node *avc_xperms_alloc(void) { struct avc_xperms_node *xp_node; - xp_node = kmem_cache_zalloc(avc_xperms_cachep, GFP_NOWAIT); + xp_node = kmem_cache_zalloc(avc_xperms_cachep, GFP_NOWAIT | __GFP_NOWARN); if (!xp_node) return xp_node; INIT_LIST_HEAD(&xp_node->xpd_head); @@ -497,7 +501,7 @@ static struct avc_node *avc_alloc_node(struct selinux_avc *avc) { struct avc_node *node; - node = kmem_cache_zalloc(avc_node_cachep, GFP_NOWAIT); + node = kmem_cache_zalloc(avc_node_cachep, GFP_NOWAIT | __GFP_NOWARN); if (!node) goto out; @@ -543,6 +547,7 @@ static inline struct avc_node *avc_search_node(struct selinux_avc *avc, /** * avc_lookup - Look up an AVC entry. + * @avc: the access vector cache * @ssid: source security identifier * @tsid: target security identifier * @tclass: target security class @@ -593,6 +598,7 @@ static int avc_latest_notif_update(struct selinux_avc *avc, /** * avc_insert - Insert an AVC entry. + * @avc: the access vector cache * @ssid: source security identifier * @tsid: target security identifier * @tclass: target security class @@ -662,7 +668,7 @@ static void avc_audit_pre_callback(struct audit_buffer *ab, void *a) struct common_audit_data *ad = a; struct selinux_audit_data *sad = ad->selinux_audit_data; u32 av = sad->audited; - const char **perms; + const char *const *perms; int i, perm; audit_log_format(ab, "avc: %s ", sad->denied ? "denied" : "granted"); @@ -702,33 +708,37 @@ static void avc_audit_post_callback(struct audit_buffer *ab, void *a) { struct common_audit_data *ad = a; struct selinux_audit_data *sad = ad->selinux_audit_data; - char *scontext; + char *scontext = NULL; + char *tcontext = NULL; + const char *tclass = NULL; u32 scontext_len; + u32 tcontext_len; int rc; rc = security_sid_to_context(sad->state, sad->ssid, &scontext, &scontext_len); if (rc) audit_log_format(ab, " ssid=%d", sad->ssid); - else { + else audit_log_format(ab, " scontext=%s", scontext); - kfree(scontext); - } - rc = security_sid_to_context(sad->state, sad->tsid, &scontext, - &scontext_len); + rc = security_sid_to_context(sad->state, sad->tsid, &tcontext, + &tcontext_len); if (rc) audit_log_format(ab, " tsid=%d", sad->tsid); - else { - audit_log_format(ab, " tcontext=%s", scontext); - kfree(scontext); - } + else + audit_log_format(ab, " tcontext=%s", tcontext); - audit_log_format(ab, " tclass=%s", secclass_map[sad->tclass-1].name); + tclass = secclass_map[sad->tclass-1].name; + audit_log_format(ab, " tclass=%s", tclass); if (sad->denied) audit_log_format(ab, " permissive=%u", sad->result ? 0 : 1); + trace_selinux_audited(sad, scontext, tcontext, tclass); + kfree(tcontext); + kfree(scontext); + /* in case of invalid context report also the actual context string */ rc = security_sid_to_context_inval(sad->state, sad->ssid, &scontext, &scontext_len); @@ -751,7 +761,11 @@ static void avc_audit_post_callback(struct audit_buffer *ab, void *a) } } -/* This is the slow part of avc audit with big stack footprint */ +/* + * This is the slow part of avc audit with big stack footprint. + * Note that it is non-blocking and can be called from under + * rcu_read_lock(). + */ noinline int slow_avc_audit(struct selinux_state *state, u32 ssid, u32 tsid, u16 tclass, u32 requested, u32 audited, u32 denied, int result, @@ -812,13 +826,18 @@ out: } /** - * avc_update_node Update an AVC entry + * avc_update_node - Update an AVC entry + * @avc: the access vector cache * @event : Updating event * @perms : Permission mask bits - * @ssid,@tsid,@tclass : identifier of an AVC entry + * @driver: xperm driver information + * @xperm: xperm permissions + * @ssid: AVC entry source sid + * @tsid: AVC entry target sid + * @tclass : AVC entry target object class * @seqno : sequence number when decision was made * @xpd: extended_perms_decision to be added to the node - * @flags: the AVC_* flags, e.g. AVC_NONBLOCKING, AVC_EXTENDED_PERMS, or 0. + * @flags: the AVC_* flags, e.g. AVC_EXTENDED_PERMS, or 0. * * if a valid AVC entry doesn't exist,this function returns -ENOENT. * if kmalloc() called internal returns NULL, this function returns -ENOMEM. @@ -837,21 +856,6 @@ static int avc_update_node(struct selinux_avc *avc, struct hlist_head *head; spinlock_t *lock; - /* - * If we are in a non-blocking code path, e.g. VFS RCU walk, - * then we must not add permissions to a cache entry - * because we will not audit the denial. Otherwise, - * during the subsequent blocking retry (e.g. VFS ref walk), we - * will find the permissions already granted in the cache entry - * and won't audit anything at all, leading to silent denials in - * permissive mode that only appear when in enforcing mode. - * - * See the corresponding handling of MAY_NOT_BLOCK in avc_audit() - * and selinux_inode_permission(). - */ - if (flags & AVC_NONBLOCKING) - return 0; - node = avc_alloc_node(avc); if (!node) { rc = -ENOMEM; @@ -931,6 +935,7 @@ out: /** * avc_flush - Flush the cache + * @avc: the access vector cache */ static void avc_flush(struct selinux_avc *avc) { @@ -959,6 +964,7 @@ static void avc_flush(struct selinux_avc *avc) /** * avc_ss_reset - Flush the cache and revalidate migrated permissions. + * @avc: the access vector cache * @seqno: policy sequence number */ int avc_ss_reset(struct selinux_avc *avc, u32 seqno) @@ -1053,7 +1059,7 @@ int avc_has_extended_perms(struct selinux_state *state, node = avc_lookup(state->avc, ssid, tsid, tclass); if (unlikely(!node)) { - node = avc_compute_av(state, ssid, tsid, tclass, &avd, xp_node); + avc_compute_av(state, ssid, tsid, tclass, &avd, xp_node); } else { memcpy(&avd, &node->ae.avd, sizeof(avd)); xp_node = node->ae.xp_node; @@ -1108,11 +1114,12 @@ decision: /** * avc_has_perm_noaudit - Check permissions but perform no auditing. + * @state: SELinux state * @ssid: source security identifier * @tsid: target security identifier * @tclass: target security class * @requested: requested permissions, interpreted based on @tclass - * @flags: AVC_STRICT, AVC_NONBLOCKING, or 0 + * @flags: AVC_STRICT or 0 * @avd: access vector decisions * * Check the AVC to determine whether the @requested permissions are granted @@ -1144,7 +1151,7 @@ inline int avc_has_perm_noaudit(struct selinux_state *state, node = avc_lookup(state->avc, ssid, tsid, tclass); if (unlikely(!node)) - node = avc_compute_av(state, ssid, tsid, tclass, avd, &xp_node); + avc_compute_av(state, ssid, tsid, tclass, avd, &xp_node); else memcpy(avd, &node->ae.avd, sizeof(*avd)); @@ -1159,6 +1166,7 @@ inline int avc_has_perm_noaudit(struct selinux_state *state, /** * avc_has_perm - Check permissions and perform any appropriate auditing. + * @state: SELinux state * @ssid: source security identifier * @tsid: target security identifier * @tclass: target security class @@ -1183,26 +1191,7 @@ int avc_has_perm(struct selinux_state *state, u32 ssid, u32 tsid, u16 tclass, &avd); rc2 = avc_audit(state, ssid, tsid, tclass, requested, &avd, rc, - auditdata, 0); - if (rc2) - return rc2; - return rc; -} - -int avc_has_perm_flags(struct selinux_state *state, - u32 ssid, u32 tsid, u16 tclass, u32 requested, - struct common_audit_data *auditdata, - int flags) -{ - struct av_decision avd; - int rc, rc2; - - rc = avc_has_perm_noaudit(state, ssid, tsid, tclass, requested, - (flags & MAY_NOT_BLOCK) ? AVC_NONBLOCKING : 0, - &avd); - - rc2 = avc_audit(state, ssid, tsid, tclass, requested, &avd, rc, - auditdata, flags); + auditdata); if (rc2) return rc2; return rc; diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index 1659b59fb5d7..f553c370397e 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -24,7 +24,7 @@ #include <linux/init.h> #include <linux/kd.h> #include <linux/kernel.h> -#include <linux/tracehook.h> +#include <linux/kernel_read_file.h> #include <linux/errno.h> #include <linux/sched/signal.h> #include <linux/sched/task.h> @@ -91,6 +91,7 @@ #include <uapi/linux/mount.h> #include <linux/fsnotify.h> #include <linux/fanotify.h> +#include <linux/io_uring.h> #include "avc.h" #include "objsec.h" @@ -142,8 +143,11 @@ static int __init checkreqprot_setup(char *str) { unsigned long checkreqprot; - if (!kstrtoul(str, 0, &checkreqprot)) + if (!kstrtoul(str, 0, &checkreqprot)) { selinux_checkreqprot_boot = checkreqprot ? 1 : 0; + if (checkreqprot) + pr_err("SELinux: checkreqprot set to 1 via kernel parameter. This is deprecated and will be rejected in a future kernel release.\n"); + } return 1; } __setup("checkreqprot=", checkreqprot_setup); @@ -207,10 +211,9 @@ static int selinux_lsm_notifier_avc_callback(u32 event) */ static void cred_init_security(void) { - struct cred *cred = (struct cred *) current->real_cred; struct task_security_struct *tsec; - tsec = selinux_cred(cred); + tsec = selinux_cred(unrcu_pointer(current->real_cred)); tsec->osid = tsec->sid = SECINITSID_KERNEL; } @@ -228,7 +231,7 @@ static inline u32 cred_sid(const struct cred *cred) /* * get the objective security ID of a task */ -static inline u32 task_sid(const struct task_struct *task) +static inline u32 task_sid_obj(const struct task_struct *task) { u32 sid; @@ -318,7 +321,7 @@ static void inode_free_security(struct inode *inode) if (!isec) return; - sbsec = inode->i_sb->s_security; + sbsec = selinux_superblock(inode->i_sb); /* * As not all inode security structures are in a list, we check for * empty list outside of the lock to make sure that we won't waste @@ -336,25 +339,16 @@ static void inode_free_security(struct inode *inode) } } -static void superblock_free_security(struct super_block *sb) -{ - struct superblock_security_struct *sbsec = sb->s_security; - sb->s_security = NULL; - kfree(sbsec); -} - struct selinux_mnt_opts { - const char *fscontext, *context, *rootcontext, *defcontext; + u32 fscontext_sid; + u32 context_sid; + u32 rootcontext_sid; + u32 defcontext_sid; }; static void selinux_free_mnt_opts(void *mnt_opts) { - struct selinux_mnt_opts *opts = mnt_opts; - kfree(opts->fscontext); - kfree(opts->context); - kfree(opts->rootcontext); - kfree(opts->defcontext); - kfree(opts); + kfree(mnt_opts); } enum { @@ -454,7 +448,7 @@ static int selinux_is_genfs_special_handling(struct super_block *sb) static int selinux_is_sblabel_mnt(struct super_block *sb) { - struct superblock_security_struct *sbsec = sb->s_security; + struct superblock_security_struct *sbsec = selinux_superblock(sb); /* * IMPORTANT: Double-check logic in this function when adding a new @@ -480,38 +474,66 @@ static int selinux_is_sblabel_mnt(struct super_block *sb) } } +static int sb_check_xattr_support(struct super_block *sb) +{ + struct superblock_security_struct *sbsec = selinux_superblock(sb); + struct dentry *root = sb->s_root; + struct inode *root_inode = d_backing_inode(root); + u32 sid; + int rc; + + /* + * Make sure that the xattr handler exists and that no + * error other than -ENODATA is returned by getxattr on + * the root directory. -ENODATA is ok, as this may be + * the first boot of the SELinux kernel before we have + * assigned xattr values to the filesystem. + */ + if (!(root_inode->i_opflags & IOP_XATTR)) { + pr_warn("SELinux: (dev %s, type %s) has no xattr support\n", + sb->s_id, sb->s_type->name); + goto fallback; + } + + rc = __vfs_getxattr(root, root_inode, XATTR_NAME_SELINUX, NULL, 0); + if (rc < 0 && rc != -ENODATA) { + if (rc == -EOPNOTSUPP) { + pr_warn("SELinux: (dev %s, type %s) has no security xattr handler\n", + sb->s_id, sb->s_type->name); + goto fallback; + } else { + pr_warn("SELinux: (dev %s, type %s) getxattr errno %d\n", + sb->s_id, sb->s_type->name, -rc); + return rc; + } + } + return 0; + +fallback: + /* No xattr support - try to fallback to genfs if possible. */ + rc = security_genfs_sid(&selinux_state, sb->s_type->name, "/", + SECCLASS_DIR, &sid); + if (rc) + return -EOPNOTSUPP; + + pr_warn("SELinux: (dev %s, type %s) falling back to genfs\n", + sb->s_id, sb->s_type->name); + sbsec->behavior = SECURITY_FS_USE_GENFS; + sbsec->sid = sid; + return 0; +} + static int sb_finish_set_opts(struct super_block *sb) { - struct superblock_security_struct *sbsec = sb->s_security; + struct superblock_security_struct *sbsec = selinux_superblock(sb); struct dentry *root = sb->s_root; struct inode *root_inode = d_backing_inode(root); int rc = 0; if (sbsec->behavior == SECURITY_FS_USE_XATTR) { - /* Make sure that the xattr handler exists and that no - error other than -ENODATA is returned by getxattr on - the root directory. -ENODATA is ok, as this may be - the first boot of the SELinux kernel before we have - assigned xattr values to the filesystem. */ - if (!(root_inode->i_opflags & IOP_XATTR)) { - pr_warn("SELinux: (dev %s, type %s) has no " - "xattr support\n", sb->s_id, sb->s_type->name); - rc = -EOPNOTSUPP; - goto out; - } - - rc = __vfs_getxattr(root, root_inode, XATTR_NAME_SELINUX, NULL, 0); - if (rc < 0 && rc != -ENODATA) { - if (rc == -EOPNOTSUPP) - pr_warn("SELinux: (dev %s, type " - "%s) has no security xattr handler\n", - sb->s_id, sb->s_type->name); - else - pr_warn("SELinux: (dev %s, type " - "%s) getxattr errno %d\n", sb->s_id, - sb->s_type->name, -rc); - goto out; - } + rc = sb_check_xattr_support(sb); + if (rc) + return rc; } sbsec->flags |= SE_SBINITIALIZED; @@ -550,7 +572,6 @@ static int sb_finish_set_opts(struct super_block *sb) spin_lock(&sbsec->isec_lock); } spin_unlock(&sbsec->isec_lock); -out: return rc; } @@ -574,17 +595,6 @@ static int bad_option(struct superblock_security_struct *sbsec, char flag, return 0; } -static int parse_sid(struct super_block *sb, const char *s, u32 *sid) -{ - int rc = security_context_str_to_sid(&selinux_state, s, - sid, GFP_KERNEL); - if (rc) - pr_warn("SELinux: security_context_str_to_sid" - "(%s) failed for (dev %s, type %s) errno=%d\n", - s, sb->s_id, sb->s_type->name, rc); - return rc; -} - /* * Allow filesystems with binary mount data to explicitly set mount point * labeling information. @@ -595,8 +605,8 @@ static int selinux_set_mnt_opts(struct super_block *sb, unsigned long *set_kern_flags) { const struct cred *cred = current_cred(); - struct superblock_security_struct *sbsec = sb->s_security; - struct dentry *root = sbsec->sb->s_root; + struct superblock_security_struct *sbsec = selinux_superblock(sb); + struct dentry *root = sb->s_root; struct selinux_mnt_opts *opts = mnt_opts; struct inode_security_struct *root_isec; u32 fscontext_sid = 0, context_sid = 0, rootcontext_sid = 0; @@ -631,7 +641,7 @@ static int selinux_set_mnt_opts(struct super_block *sb, * we need to skip the double mount verification. * * This does open a hole in which we will not notice if the first - * mount using this sb set explict options and a second mount using + * mount using this sb set explicit options and a second mount using * this sb does not set any security options. (The first options * will be used for both mounts) */ @@ -647,37 +657,29 @@ static int selinux_set_mnt_opts(struct super_block *sb, * than once with different security options. */ if (opts) { - if (opts->fscontext) { - rc = parse_sid(sb, opts->fscontext, &fscontext_sid); - if (rc) - goto out; + if (opts->fscontext_sid) { + fscontext_sid = opts->fscontext_sid; if (bad_option(sbsec, FSCONTEXT_MNT, sbsec->sid, fscontext_sid)) goto out_double_mount; sbsec->flags |= FSCONTEXT_MNT; } - if (opts->context) { - rc = parse_sid(sb, opts->context, &context_sid); - if (rc) - goto out; + if (opts->context_sid) { + context_sid = opts->context_sid; if (bad_option(sbsec, CONTEXT_MNT, sbsec->mntpoint_sid, context_sid)) goto out_double_mount; sbsec->flags |= CONTEXT_MNT; } - if (opts->rootcontext) { - rc = parse_sid(sb, opts->rootcontext, &rootcontext_sid); - if (rc) - goto out; + if (opts->rootcontext_sid) { + rootcontext_sid = opts->rootcontext_sid; if (bad_option(sbsec, ROOTCONTEXT_MNT, root_isec->sid, rootcontext_sid)) goto out_double_mount; sbsec->flags |= ROOTCONTEXT_MNT; } - if (opts->defcontext) { - rc = parse_sid(sb, opts->defcontext, &defcontext_sid); - if (rc) - goto out; + if (opts->defcontext_sid) { + defcontext_sid = opts->defcontext_sid; if (bad_option(sbsec, DEFCONTEXT_MNT, sbsec->def_sid, defcontext_sid)) goto out_double_mount; @@ -699,7 +701,9 @@ 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, "binder") || - !strcmp(sb->s_type->name, "pstore")) + !strcmp(sb->s_type->name, "bpf") || + !strcmp(sb->s_type->name, "pstore") || + !strcmp(sb->s_type->name, "securityfs")) sbsec->flags |= SE_SBGENFS; if (!strcmp(sb->s_type->name, "sysfs") || @@ -728,7 +732,8 @@ static int selinux_set_mnt_opts(struct super_block *sb, if (sb->s_user_ns != &init_user_ns && strcmp(sb->s_type->name, "tmpfs") && strcmp(sb->s_type->name, "ramfs") && - strcmp(sb->s_type->name, "devpts")) { + strcmp(sb->s_type->name, "devpts") && + strcmp(sb->s_type->name, "overlay")) { if (context_sid || fscontext_sid || rootcontext_sid || defcontext_sid) { rc = -EACCES; @@ -831,8 +836,8 @@ out_double_mount: static int selinux_cmp_sb_context(const struct super_block *oldsb, const struct super_block *newsb) { - struct superblock_security_struct *old = oldsb->s_security; - struct superblock_security_struct *new = newsb->s_security; + struct superblock_security_struct *old = selinux_superblock(oldsb); + struct superblock_security_struct *new = selinux_superblock(newsb); char oldflags = old->flags & SE_MNTMASK; char newflags = new->flags & SE_MNTMASK; @@ -864,8 +869,9 @@ static int selinux_sb_clone_mnt_opts(const struct super_block *oldsb, unsigned long *set_kern_flags) { int rc = 0; - const struct superblock_security_struct *oldsbsec = oldsb->s_security; - struct superblock_security_struct *newsbsec = newsb->s_security; + const struct superblock_security_struct *oldsbsec = + selinux_superblock(oldsb); + struct superblock_security_struct *newsbsec = selinux_superblock(newsb); int set_fscontext = (oldsbsec->flags & FSCONTEXT_MNT); int set_context = (oldsbsec->flags & CONTEXT_MNT); @@ -939,85 +945,67 @@ out: return rc; } +/* + * NOTE: the caller is resposible for freeing the memory even if on error. + */ static int selinux_add_opt(int token, const char *s, void **mnt_opts) { struct selinux_mnt_opts *opts = *mnt_opts; + u32 *dst_sid; + int rc; - if (token == Opt_seclabel) /* eaten and completely ignored */ + if (token == Opt_seclabel) + /* eaten and completely ignored */ return 0; + if (!s) + return -EINVAL; + + if (!selinux_initialized(&selinux_state)) { + pr_warn("SELinux: Unable to set superblock options before the security server is initialized\n"); + return -EINVAL; + } if (!opts) { - opts = kzalloc(sizeof(struct selinux_mnt_opts), GFP_KERNEL); + opts = kzalloc(sizeof(*opts), GFP_KERNEL); if (!opts) return -ENOMEM; *mnt_opts = opts; } - if (!s) - return -ENOMEM; + switch (token) { case Opt_context: - if (opts->context || opts->defcontext) - goto Einval; - opts->context = s; + if (opts->context_sid || opts->defcontext_sid) + goto err; + dst_sid = &opts->context_sid; break; case Opt_fscontext: - if (opts->fscontext) - goto Einval; - opts->fscontext = s; + if (opts->fscontext_sid) + goto err; + dst_sid = &opts->fscontext_sid; break; case Opt_rootcontext: - if (opts->rootcontext) - goto Einval; - opts->rootcontext = s; + if (opts->rootcontext_sid) + goto err; + dst_sid = &opts->rootcontext_sid; break; case Opt_defcontext: - if (opts->context || opts->defcontext) - goto Einval; - opts->defcontext = s; + if (opts->context_sid || opts->defcontext_sid) + goto err; + dst_sid = &opts->defcontext_sid; break; - } - return 0; -Einval: - pr_warn(SEL_MOUNT_FAIL_MSG); - return -EINVAL; -} - -static int selinux_add_mnt_opt(const char *option, const char *val, int len, - void **mnt_opts) -{ - int token = Opt_error; - int rc, i; - - for (i = 0; i < ARRAY_SIZE(tokens); i++) { - if (strcmp(option, tokens[i].name) == 0) { - token = tokens[i].opt; - break; - } - } - - if (token == Opt_error) + default: + WARN_ON(1); return -EINVAL; - - 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); - goto free_opt; } + rc = security_context_str_to_sid(&selinux_state, s, dst_sid, GFP_KERNEL); + if (rc) + pr_warn("SELinux: security_context_str_to_sid (%s) failed with errno=%d\n", + s, rc); return rc; -free_opt: - if (*mnt_opts) { - selinux_free_mnt_opts(*mnt_opts); - *mnt_opts = NULL; - } - return rc; +err: + pr_warn(SEL_MOUNT_FAIL_MSG); + return -EINVAL; } static int show_sid(struct seq_file *m, u32 sid) @@ -1029,7 +1017,7 @@ static int show_sid(struct seq_file *m, u32 sid) rc = security_sid_to_context(&selinux_state, sid, &context, &len); if (!rc) { - bool has_comma = context && strchr(context, ','); + bool has_comma = strchr(context, ','); seq_putc(m, '='); if (has_comma) @@ -1044,7 +1032,7 @@ static int show_sid(struct seq_file *m, u32 sid) static int selinux_sb_show_options(struct seq_file *m, struct super_block *sb) { - struct superblock_security_struct *sbsec = sb->s_security; + struct superblock_security_struct *sbsec = selinux_superblock(sb); int rc; if (!(sbsec->flags & SE_SBINITIALIZED)) @@ -1075,7 +1063,7 @@ static int selinux_sb_show_options(struct seq_file *m, struct super_block *sb) return rc; } if (sbsec->flags & ROOTCONTEXT_MNT) { - struct dentry *root = sbsec->sb->s_root; + struct dentry *root = sb->s_root; struct inode_security_struct *isec = backing_inode_security(root); seq_putc(m, ','); seq_puts(m, ROOTCONTEXT_STR); @@ -1115,7 +1103,8 @@ static inline u16 inode_mode_to_security_class(umode_t mode) static inline int default_protocol_stream(int protocol) { - return (protocol == IPPROTO_IP || protocol == IPPROTO_TCP); + return (protocol == IPPROTO_IP || protocol == IPPROTO_TCP || + protocol == IPPROTO_MPTCP); } static inline int default_protocol_dgram(int protocol) @@ -1266,7 +1255,9 @@ static inline u16 socket_type_to_security_class(int family, int type, int protoc return SECCLASS_SMC_SOCKET; case PF_XDP: return SECCLASS_XDP_SOCKET; -#if PF_MAX > 45 + case PF_MCTP: + return SECCLASS_MCTP_SOCKET; +#if PF_MAX > 46 #error New address family defined, please update this function. #endif } @@ -1394,7 +1385,7 @@ static int inode_doinit_with_dentry(struct inode *inode, struct dentry *opt_dent if (isec->sclass == SECCLASS_FILE) isec->sclass = inode_mode_to_security_class(inode->i_mode); - sbsec = inode->i_sb->s_security; + sbsec = selinux_superblock(inode->i_sb); if (!(sbsec->flags & SE_SBINITIALIZED)) { /* Defer initialization until selinux_complete_init, after the initial policy is loaded and the security @@ -1446,7 +1437,7 @@ static int inode_doinit_with_dentry(struct inode *inode, struct dentry *opt_dent * inode_doinit with a dentry, before these inodes could * be used again by userspace. */ - goto out; + goto out_invalid; } rc = inode_doinit_use_xattr(inode, dentry, sbsec->def_sid, @@ -1475,7 +1466,9 @@ static int inode_doinit_with_dentry(struct inode *inode, struct dentry *opt_dent /* Default to the fs superblock SID. */ sid = sbsec->sid; - if ((sbsec->flags & SE_SBGENFS) && !S_ISLNK(inode->i_mode)) { + if ((sbsec->flags & SE_SBGENFS) && + (!S_ISLNK(inode->i_mode) || + selinux_policycap_genfs_seclabel_symlinks())) { /* We must have a dentry to determine the label on * procfs inodes */ if (opt_dentry) { @@ -1501,7 +1494,7 @@ static int inode_doinit_with_dentry(struct inode *inode, struct dentry *opt_dent * could be used again by userspace. */ if (!dentry) - goto out; + goto out_invalid; rc = selinux_genfs_get_sid(dentry, sclass, sbsec->flags, &sid); if (rc) { @@ -1526,11 +1519,10 @@ static int inode_doinit_with_dentry(struct inode *inode, struct dentry *opt_dent out: spin_lock(&isec->lock); if (isec->initialized == LABEL_PENDING) { - if (!sid || rc) { + if (rc) { isec->initialized = LABEL_INVALID; goto out_unlock; } - isec->initialized = LABEL_INITIALIZED; isec->sid = sid; } @@ -1538,6 +1530,15 @@ out: out_unlock: spin_unlock(&isec->lock); return rc; + +out_invalid: + spin_lock(&isec->lock); + if (isec->initialized == LABEL_PENDING) { + isec->initialized = LABEL_INVALID; + isec->sid = sid; + } + spin_unlock(&isec->lock); + return 0; } /* Convert a Linux signal to an access vector. */ @@ -1602,7 +1603,7 @@ static int cred_has_capability(const struct cred *cred, sid, sid, sclass, av, 0, &avd); if (!(opts & CAP_OPT_NOAUDIT)) { int rc2 = avc_audit(&selinux_state, - sid, sid, sclass, av, &avd, rc, &ad, 0); + sid, sid, sclass, av, &avd, rc, &ad); if (rc2) return rc2; } @@ -1735,7 +1736,8 @@ selinux_determine_inode_label(const struct task_security_struct *tsec, const struct qstr *name, u16 tclass, u32 *_new_isid) { - const struct superblock_security_struct *sbsec = dir->i_sb->s_security; + const struct superblock_security_struct *sbsec = + selinux_superblock(dir->i_sb); if ((sbsec->flags & SE_SBINITIALIZED) && (sbsec->behavior == SECURITY_FS_USE_MNTPOINT)) { @@ -1766,7 +1768,7 @@ static int may_create(struct inode *dir, int rc; dsec = inode_security(dir); - sbsec = dir->i_sb->s_security; + sbsec = selinux_superblock(dir->i_sb); sid = tsec->sid; @@ -1915,7 +1917,7 @@ static int superblock_has_perm(const struct cred *cred, struct superblock_security_struct *sbsec; u32 sid = cred_sid(cred); - sbsec = sb->s_security; + sbsec = selinux_superblock(sb); return avc_has_perm(&selinux_state, sid, sbsec->sid, SECCLASS_FILESYSTEM, perms, ad); } @@ -1972,7 +1974,7 @@ static inline u32 file_to_av(struct file *file) } /* - * Convert a file to an access vector and include the correct open + * Convert a file to an access vector and include the correct * open permission. */ static inline u32 open_file_to_av(struct file *file) @@ -1989,22 +1991,19 @@ static inline u32 open_file_to_av(struct file *file) /* Hook functions begin here. */ -static int selinux_binder_set_context_mgr(struct task_struct *mgr) +static int selinux_binder_set_context_mgr(const struct cred *mgr) { - u32 mysid = current_sid(); - u32 mgrsid = task_sid(mgr); - return avc_has_perm(&selinux_state, - mysid, mgrsid, SECCLASS_BINDER, + current_sid(), cred_sid(mgr), SECCLASS_BINDER, BINDER__SET_CONTEXT_MGR, NULL); } -static int selinux_binder_transaction(struct task_struct *from, - struct task_struct *to) +static int selinux_binder_transaction(const struct cred *from, + const struct cred *to) { u32 mysid = current_sid(); - u32 fromsid = task_sid(from); - u32 tosid = task_sid(to); + u32 fromsid = cred_sid(from); + u32 tosid = cred_sid(to); int rc; if (mysid != fromsid) { @@ -2015,27 +2014,24 @@ static int selinux_binder_transaction(struct task_struct *from, return rc; } - return avc_has_perm(&selinux_state, - fromsid, tosid, SECCLASS_BINDER, BINDER__CALL, - NULL); + return avc_has_perm(&selinux_state, fromsid, tosid, + SECCLASS_BINDER, BINDER__CALL, NULL); } -static int selinux_binder_transfer_binder(struct task_struct *from, - struct task_struct *to) +static int selinux_binder_transfer_binder(const struct cred *from, + const struct cred *to) { - u32 fromsid = task_sid(from); - u32 tosid = task_sid(to); - return avc_has_perm(&selinux_state, - fromsid, tosid, SECCLASS_BINDER, BINDER__TRANSFER, + cred_sid(from), cred_sid(to), + SECCLASS_BINDER, BINDER__TRANSFER, NULL); } -static int selinux_binder_transfer_file(struct task_struct *from, - struct task_struct *to, +static int selinux_binder_transfer_file(const struct cred *from, + const struct cred *to, struct file *file) { - u32 sid = task_sid(to); + u32 sid = cred_sid(to); struct file_security_struct *fsec = selinux_file(file); struct dentry *dentry = file->f_path.dentry; struct inode_security_struct *isec; @@ -2071,10 +2067,10 @@ static int selinux_binder_transfer_file(struct task_struct *from, } static int selinux_ptrace_access_check(struct task_struct *child, - unsigned int mode) + unsigned int mode) { u32 sid = current_sid(); - u32 csid = task_sid(child); + u32 csid = task_sid_obj(child); if (mode & PTRACE_MODE_READ) return avc_has_perm(&selinux_state, @@ -2087,15 +2083,15 @@ static int selinux_ptrace_access_check(struct task_struct *child, static int selinux_ptrace_traceme(struct task_struct *parent) { return avc_has_perm(&selinux_state, - task_sid(parent), current_sid(), SECCLASS_PROCESS, - PROCESS__PTRACE, NULL); + task_sid_obj(parent), task_sid_obj(current), + SECCLASS_PROCESS, PROCESS__PTRACE, NULL); } static int selinux_capget(struct task_struct *target, kernel_cap_t *effective, kernel_cap_t *inheritable, kernel_cap_t *permitted) { return avc_has_perm(&selinux_state, - current_sid(), task_sid(target), SECCLASS_PROCESS, + current_sid(), task_sid_obj(target), SECCLASS_PROCESS, PROCESS__GETCAP, NULL); } @@ -2139,11 +2135,18 @@ static int selinux_quotactl(int cmds, int type, int id, struct super_block *sb) case Q_QUOTAOFF: case Q_SETINFO: case Q_SETQUOTA: + case Q_XQUOTAOFF: + case Q_XQUOTAON: + case Q_XSETQLIM: rc = superblock_has_perm(cred, sb, FILESYSTEM__QUOTAMOD, NULL); break; case Q_GETFMT: case Q_GETINFO: case Q_GETQUOTA: + case Q_XGETQUOTA: + case Q_XGETQSTAT: + case Q_XGETQSTATV: + case Q_XGETNEXTQUOTA: rc = superblock_has_perm(cred, sb, FILESYSTEM__QUOTAGET, NULL); break; default: @@ -2213,7 +2216,7 @@ static u32 ptrace_parent_sid(void) rcu_read_lock(); tracer = ptrace_parent(current); if (tracer) - sid = task_sid(tracer); + sid = task_sid_obj(tracer); rcu_read_unlock(); return sid; @@ -2273,7 +2276,7 @@ static int check_nnp_nosuid(const struct linux_binprm *bprm, return -EACCES; } -static int selinux_bprm_set_creds(struct linux_binprm *bprm) +static int selinux_bprm_creds_for_exec(struct linux_binprm *bprm) { const struct task_security_struct *old_tsec; struct task_security_struct *new_tsec; @@ -2284,8 +2287,6 @@ static int selinux_bprm_set_creds(struct linux_binprm *bprm) /* SELinux context only depends on initial program or script and not * the script interpreter */ - if (bprm->called_set_creds) - return 0; old_tsec = selinux_cred(current_cred()); new_tsec = selinux_cred(bprm->cred); @@ -2517,7 +2518,7 @@ static void selinux_bprm_committed_creds(struct linux_binprm *bprm) if (rc) { clear_itimer(); - spin_lock_irq(¤t->sighand->siglock); + spin_lock_irq(&unrcu_pointer(current->sighand)->siglock); if (!fatal_signal_pending(current)) { flush_sigqueue(¤t->pending); flush_sigqueue(¤t->signal->shared_pending); @@ -2525,13 +2526,13 @@ static void selinux_bprm_committed_creds(struct linux_binprm *bprm) sigemptyset(¤t->blocked); recalc_sigpending(); } - spin_unlock_irq(¤t->sighand->siglock); + spin_unlock_irq(&unrcu_pointer(current->sighand)->siglock); } /* Wake up the parent if it is waiting so that it can recheck * wait permission to the new task SID. */ read_lock(&tasklist_lock); - __wake_up_parent(current, current->real_parent); + __wake_up_parent(current, unrcu_pointer(current->real_parent)); read_unlock(&tasklist_lock); } @@ -2539,29 +2540,18 @@ static void selinux_bprm_committed_creds(struct linux_binprm *bprm) static int selinux_sb_alloc_security(struct super_block *sb) { - struct superblock_security_struct *sbsec; - - sbsec = kzalloc(sizeof(struct superblock_security_struct), GFP_KERNEL); - if (!sbsec) - return -ENOMEM; + struct superblock_security_struct *sbsec = selinux_superblock(sb); mutex_init(&sbsec->lock); INIT_LIST_HEAD(&sbsec->isec_head); spin_lock_init(&sbsec->isec_lock); - sbsec->sb = sb; sbsec->sid = SECINITSID_UNLABELED; sbsec->def_sid = SECINITSID_FILE; sbsec->mntpoint_sid = SECINITSID_UNLABELED; - sb->s_security = sbsec; return 0; } -static void selinux_sb_free_security(struct super_block *sb) -{ - superblock_free_security(sb); -} - static inline int opt_len(const char *s) { bool open_quote = false; @@ -2608,8 +2598,9 @@ static int selinux_sb_eat_lsm_opts(char *options, void **mnt_opts) } } rc = selinux_add_opt(token, arg, mnt_opts); + kfree(arg); + arg = NULL; if (unlikely(rc)) { - kfree(arg); goto free_opt; } } else { @@ -2637,12 +2628,55 @@ free_opt: return rc; } +static int selinux_sb_mnt_opts_compat(struct super_block *sb, void *mnt_opts) +{ + struct selinux_mnt_opts *opts = mnt_opts; + struct superblock_security_struct *sbsec = selinux_superblock(sb); + + /* + * Superblock not initialized (i.e. no options) - reject if any + * options specified, otherwise accept. + */ + if (!(sbsec->flags & SE_SBINITIALIZED)) + return opts ? 1 : 0; + + /* + * Superblock initialized and no options specified - reject if + * superblock has any options set, otherwise accept. + */ + if (!opts) + return (sbsec->flags & SE_MNTMASK) ? 1 : 0; + + if (opts->fscontext_sid) { + if (bad_option(sbsec, FSCONTEXT_MNT, sbsec->sid, + opts->fscontext_sid)) + return 1; + } + if (opts->context_sid) { + if (bad_option(sbsec, CONTEXT_MNT, sbsec->mntpoint_sid, + opts->context_sid)) + return 1; + } + if (opts->rootcontext_sid) { + struct inode_security_struct *root_isec; + + root_isec = backing_inode_security(sb->s_root); + if (bad_option(sbsec, ROOTCONTEXT_MNT, root_isec->sid, + opts->rootcontext_sid)) + return 1; + } + if (opts->defcontext_sid) { + if (bad_option(sbsec, DEFCONTEXT_MNT, sbsec->def_sid, + opts->defcontext_sid)) + return 1; + } + return 0; +} + static int selinux_sb_remount(struct super_block *sb, void *mnt_opts) { struct selinux_mnt_opts *opts = mnt_opts; - struct superblock_security_struct *sbsec = sb->s_security; - u32 sid; - int rc; + struct superblock_security_struct *sbsec = selinux_superblock(sb); if (!(sbsec->flags & SE_SBINITIALIZED)) return 0; @@ -2650,34 +2684,26 @@ static int selinux_sb_remount(struct super_block *sb, void *mnt_opts) if (!opts) return 0; - if (opts->fscontext) { - rc = parse_sid(sb, opts->fscontext, &sid); - if (rc) - return rc; - if (bad_option(sbsec, FSCONTEXT_MNT, sbsec->sid, sid)) + if (opts->fscontext_sid) { + if (bad_option(sbsec, FSCONTEXT_MNT, sbsec->sid, + opts->fscontext_sid)) goto out_bad_option; } - if (opts->context) { - rc = parse_sid(sb, opts->context, &sid); - if (rc) - return rc; - if (bad_option(sbsec, CONTEXT_MNT, sbsec->mntpoint_sid, sid)) + if (opts->context_sid) { + if (bad_option(sbsec, CONTEXT_MNT, sbsec->mntpoint_sid, + opts->context_sid)) goto out_bad_option; } - if (opts->rootcontext) { + if (opts->rootcontext_sid) { struct inode_security_struct *root_isec; root_isec = backing_inode_security(sb->s_root); - rc = parse_sid(sb, opts->rootcontext, &sid); - if (rc) - return rc; - if (bad_option(sbsec, ROOTCONTEXT_MNT, root_isec->sid, sid)) + if (bad_option(sbsec, ROOTCONTEXT_MNT, root_isec->sid, + opts->rootcontext_sid)) goto out_bad_option; } - if (opts->defcontext) { - rc = parse_sid(sb, opts->defcontext, &sid); - if (rc) - return rc; - if (bad_option(sbsec, DEFCONTEXT_MNT, sbsec->def_sid, sid)) + if (opts->defcontext_sid) { + if (bad_option(sbsec, DEFCONTEXT_MNT, sbsec->def_sid, + opts->defcontext_sid)) goto out_bad_option; } return 0; @@ -2744,38 +2770,12 @@ static int selinux_fs_context_dup(struct fs_context *fc, struct fs_context *src_fc) { const struct selinux_mnt_opts *src = src_fc->security; - struct selinux_mnt_opts *opts; if (!src) return 0; - fc->security = kzalloc(sizeof(struct selinux_mnt_opts), GFP_KERNEL); - if (!fc->security) - return -ENOMEM; - - opts = fc->security; - - if (src->fscontext) { - opts->fscontext = kstrdup(src->fscontext, GFP_KERNEL); - if (!opts->fscontext) - return -ENOMEM; - } - if (src->context) { - opts->context = kstrdup(src->context, GFP_KERNEL); - if (!opts->context) - return -ENOMEM; - } - if (src->rootcontext) { - opts->rootcontext = kstrdup(src->rootcontext, GFP_KERNEL); - if (!opts->rootcontext) - return -ENOMEM; - } - if (src->defcontext) { - opts->defcontext = kstrdup(src->defcontext, GFP_KERNEL); - if (!opts->defcontext) - return -ENOMEM; - } - return 0; + fc->security = kmemdup(src, sizeof(*src), GFP_KERNEL); + return fc->security ? 0 : -ENOMEM; } static const struct fs_parameter_spec selinux_fs_parameters[] = { @@ -2791,18 +2791,13 @@ static int selinux_fs_context_parse_param(struct fs_context *fc, struct fs_parameter *param) { struct fs_parse_result result; - int opt, rc; + int opt; opt = fs_parse(fc, selinux_fs_parameters, param, &result); if (opt < 0) return opt; - rc = selinux_add_opt(opt, param->string, &fc->security); - if (!rc) { - param->string = NULL; - rc = 1; - } - return rc; + return selinux_add_opt(opt, param->string, &fc->security); } /* inode security operations */ @@ -2829,7 +2824,8 @@ static void selinux_inode_free_security(struct inode *inode) } static int selinux_dentry_init_security(struct dentry *dentry, int mode, - const struct qstr *name, void **ctx, + const struct qstr *name, + const char **xattr_name, void **ctx, u32 *ctxlen) { u32 newsid; @@ -2842,6 +2838,9 @@ static int selinux_dentry_init_security(struct dentry *dentry, int mode, if (rc) return rc; + if (xattr_name) + *xattr_name = XATTR_NAME_SELINUX; + return security_sid_to_context(&selinux_state, newsid, (char **)ctx, ctxlen); } @@ -2878,7 +2877,7 @@ static int selinux_inode_init_security(struct inode *inode, struct inode *dir, int rc; char *context; - sbsec = dir->i_sb->s_security; + sbsec = selinux_superblock(dir->i_sb); newsid = tsec->create_sid; @@ -2915,6 +2914,62 @@ static int selinux_inode_init_security(struct inode *inode, struct inode *dir, return 0; } +static int selinux_inode_init_security_anon(struct inode *inode, + const struct qstr *name, + const struct inode *context_inode) +{ + const struct task_security_struct *tsec = selinux_cred(current_cred()); + struct common_audit_data ad; + struct inode_security_struct *isec; + int rc; + + if (unlikely(!selinux_initialized(&selinux_state))) + return 0; + + isec = selinux_inode(inode); + + /* + * We only get here once per ephemeral inode. The inode has + * been initialized via inode_alloc_security but is otherwise + * untouched. + */ + + if (context_inode) { + struct inode_security_struct *context_isec = + selinux_inode(context_inode); + if (context_isec->initialized != LABEL_INITIALIZED) { + pr_err("SELinux: context_inode is not initialized"); + return -EACCES; + } + + isec->sclass = context_isec->sclass; + isec->sid = context_isec->sid; + } else { + isec->sclass = SECCLASS_ANON_INODE; + rc = security_transition_sid( + &selinux_state, tsec->sid, tsec->sid, + isec->sclass, name, &isec->sid); + if (rc) + return rc; + } + + isec->initialized = LABEL_INITIALIZED; + /* + * Now that we've initialized security, check whether we're + * allowed to actually create this type of anonymous inode. + */ + + ad.type = LSM_AUDIT_DATA_ANONINODE; + ad.u.anonclass = name ? (const char *)name->name : "?"; + + return avc_has_perm(&selinux_state, + tsec->sid, + isec->sid, + isec->sclass, + FILE__CREATE, + &ad); +} + static int selinux_inode_create(struct inode *dir, struct dentry *dentry, umode_t mode) { return may_create(dir, dentry, SECCLASS_FILE); @@ -2980,9 +3035,8 @@ static int selinux_inode_follow_link(struct dentry *dentry, struct inode *inode, if (IS_ERR(isec)) return PTR_ERR(isec); - return avc_has_perm_flags(&selinux_state, - sid, isec->sid, isec->sclass, FILE__READ, &ad, - rcu ? MAY_NOT_BLOCK : 0); + return avc_has_perm(&selinux_state, + sid, isec->sid, isec->sclass, FILE__READ, &ad); } static noinline int audit_inode_permission(struct inode *inode, @@ -2991,17 +3045,13 @@ static noinline int audit_inode_permission(struct inode *inode, { struct common_audit_data ad; struct inode_security_struct *isec = selinux_inode(inode); - int rc; ad.type = LSM_AUDIT_DATA_INODE; ad.u.inode = inode; - rc = slow_avc_audit(&selinux_state, + return slow_avc_audit(&selinux_state, current_sid(), isec->sid, isec->sclass, perms, audited, denied, result, &ad); - if (rc) - return rc; - return 0; } static int selinux_inode_permission(struct inode *inode, int mask) @@ -3036,8 +3086,7 @@ static int selinux_inode_permission(struct inode *inode, int mask) return PTR_ERR(isec); rc = avc_has_perm_noaudit(&selinux_state, - sid, isec->sid, isec->sclass, perms, - no_block ? AVC_NONBLOCKING : 0, + sid, isec->sid, isec->sclass, perms, 0, &avd); audited = avc_audit_required(perms, &avd, rc, from_access ? FILE__AUDIT_ACCESS : 0, @@ -3045,10 +3094,6 @@ static int selinux_inode_permission(struct inode *inode, int mask) if (likely(!audited)) return rc; - /* fall back to ref-walk if we have to generate audit */ - if (no_block) - return -ECHILD; - rc2 = audit_inode_permission(inode, perms, audited, denied, rc); if (rc2) return rc2; @@ -3100,7 +3145,8 @@ static bool has_cap_mac_admin(bool audit) return true; } -static int selinux_inode_setxattr(struct dentry *dentry, const char *name, +static int selinux_inode_setxattr(struct user_namespace *mnt_userns, + struct dentry *dentry, const char *name, const void *value, size_t size, int flags) { struct inode *inode = d_backing_inode(dentry); @@ -3121,13 +3167,13 @@ static int selinux_inode_setxattr(struct dentry *dentry, const char *name, } if (!selinux_initialized(&selinux_state)) - return (inode_owner_or_capable(inode) ? 0 : -EPERM); + return (inode_owner_or_capable(mnt_userns, inode) ? 0 : -EPERM); - sbsec = inode->i_sb->s_security; + sbsec = selinux_superblock(inode->i_sb); if (!(sbsec->flags & SBLABEL_MNT)) return -EOPNOTSUPP; - if (!inode_owner_or_capable(inode)) + if (!inode_owner_or_capable(mnt_userns, inode)) return -EPERM; ad.type = LSM_AUDIT_DATA_DENTRY; @@ -3161,6 +3207,8 @@ static int selinux_inode_setxattr(struct dentry *dentry, const char *name, } ab = audit_log_start(audit_context(), GFP_ATOMIC, AUDIT_SELINUX_ERR); + if (!ab) + return rc; audit_log_format(ab, "op=setxattr invalid_context="); audit_log_n_untrustedstring(ab, value, audit_size); audit_log_end(ab); @@ -3230,8 +3278,6 @@ static void selinux_inode_post_setxattr(struct dentry *dentry, const char *name, isec->sid = newsid; isec->initialized = LABEL_INITIALIZED; spin_unlock(&isec->lock); - - return; } static int selinux_inode_getxattr(struct dentry *dentry, const char *name) @@ -3248,10 +3294,11 @@ static int selinux_inode_listxattr(struct dentry *dentry) return dentry_has_perm(cred, dentry, FILE__GETATTR); } -static int selinux_inode_removexattr(struct dentry *dentry, const char *name) +static int selinux_inode_removexattr(struct user_namespace *mnt_userns, + struct dentry *dentry, const char *name) { if (strcmp(name, XATTR_NAME_SELINUX)) { - int rc = cap_inode_removexattr(dentry, name); + int rc = cap_inode_removexattr(mnt_userns, dentry, name); if (rc) return rc; @@ -3260,6 +3307,9 @@ static int selinux_inode_removexattr(struct dentry *dentry, const char *name) return dentry_has_perm(current_cred(), dentry, FILE__SETATTR); } + if (!selinux_initialized(&selinux_state)) + return 0; + /* No one is allowed to remove a SELinux security label. You can change the label, but all data must be labeled. */ return -EACCES; @@ -3314,14 +3364,21 @@ static int selinux_path_notify(const struct path *path, u64 mask, * * Permission check is handled by selinux_inode_getxattr hook. */ -static int selinux_inode_getsecurity(struct inode *inode, const char *name, void **buffer, bool alloc) +static int selinux_inode_getsecurity(struct user_namespace *mnt_userns, + struct inode *inode, const char *name, + void **buffer, bool alloc) { u32 size; int error; char *context = NULL; struct inode_security_struct *isec; - if (strcmp(name, XATTR_SELINUX_SUFFIX)) + /* + * If we're not initialized yet, then we can't validate contexts, so + * just let vfs_getxattr fall back to using the on-disk xattr. + */ + if (!selinux_initialized(&selinux_state) || + strcmp(name, XATTR_SELINUX_SUFFIX)) return -EOPNOTSUPP; /* @@ -3357,13 +3414,14 @@ static int selinux_inode_setsecurity(struct inode *inode, const char *name, const void *value, size_t size, int flags) { struct inode_security_struct *isec = inode_security_novalidate(inode); - struct superblock_security_struct *sbsec = inode->i_sb->s_security; + struct superblock_security_struct *sbsec; u32 newsid; int rc; if (strcmp(name, XATTR_SELINUX_SUFFIX)) return -EOPNOTSUPP; + sbsec = selinux_superblock(inode->i_sb); if (!(sbsec->flags & SBLABEL_MNT)) return -EOPNOTSUPP; @@ -3386,6 +3444,10 @@ static int selinux_inode_setsecurity(struct inode *inode, const char *name, static int selinux_inode_listsecurity(struct inode *inode, char *buffer, size_t buffer_size) { const int len = sizeof(XATTR_NAME_SELINUX); + + if (!selinux_initialized(&selinux_state)) + return 0; + if (buffer && len <= buffer_size) memcpy(buffer, XATTR_NAME_SELINUX, len); return len; @@ -3590,26 +3652,20 @@ static int selinux_file_ioctl(struct file *file, unsigned int cmd, switch (cmd) { case FIONREAD: - /* fall through */ case FIBMAP: - /* fall through */ case FIGETBSZ: - /* fall through */ case FS_IOC_GETFLAGS: - /* fall through */ case FS_IOC_GETVERSION: error = file_has_perm(cred, file, FILE__GETATTR); break; case FS_IOC_SETFLAGS: - /* fall through */ case FS_IOC_SETVERSION: error = file_has_perm(cred, file, FILE__SETATTR); break; /* sys_ioctl() checks */ case FIONBIO: - /* fall through */ case FIOASYNC: error = file_has_perm(cred, file, 0); break; @@ -3620,6 +3676,12 @@ static int selinux_file_ioctl(struct file *file, unsigned int cmd, CAP_OPT_NONE, true); break; + case FIOCLEX: + case FIONCLEX: + if (!selinux_policycap_ioctl_skip_cloexec()) + error = ioctl_has_perm(cred, file, FILE__IOCTL, (u16) cmd); + break; + /* default case assumes that the command will go * to the file's ioctl() function. */ @@ -3699,7 +3761,7 @@ static int selinux_mmap_file(struct file *file, unsigned long reqprot, return rc; } - if (selinux_state.checkreqprot) + if (checkreqprot_get(&selinux_state)) prot = reqprot; return file_map_prot_check(file, prot, @@ -3713,7 +3775,7 @@ static int selinux_file_mprotect(struct vm_area_struct *vma, const struct cred *cred = current_cred(); u32 sid = cred_sid(cred); - if (selinux_state.checkreqprot) + if (checkreqprot_get(&selinux_state)) prot = reqprot; if (default_noexec && @@ -3767,7 +3829,7 @@ static int selinux_file_fcntl(struct file *file, unsigned int cmd, err = file_has_perm(cred, file, FILE__WRITE); break; } - /* fall through */ + fallthrough; case F_SETOWN: case F_SETSIG: case F_GETFL: @@ -3807,7 +3869,7 @@ static int selinux_file_send_sigiotask(struct task_struct *tsk, struct fown_struct *fown, int signum) { struct file *file; - u32 sid = task_sid(tsk); + u32 sid = task_sid_obj(tsk); u32 perm; struct file_security_struct *fsec; @@ -3992,13 +4054,14 @@ static int selinux_kernel_module_from_file(struct file *file) } static int selinux_kernel_read_file(struct file *file, - enum kernel_read_file_id id) + enum kernel_read_file_id id, + bool contents) { int rc = 0; switch (id) { case READING_MODULE: - rc = selinux_kernel_module_from_file(file); + rc = selinux_kernel_module_from_file(contents ? file : NULL); break; default: break; @@ -4007,13 +4070,14 @@ static int selinux_kernel_read_file(struct file *file, return rc; } -static int selinux_kernel_load_data(enum kernel_load_data_id id) +static int selinux_kernel_load_data(enum kernel_load_data_id id, bool contents) { int rc = 0; switch (id) { case LOADING_MODULE: rc = selinux_kernel_module_from_file(NULL); + break; default: break; } @@ -4024,47 +4088,52 @@ static int selinux_kernel_load_data(enum kernel_load_data_id id) static int selinux_task_setpgid(struct task_struct *p, pid_t pgid) { return avc_has_perm(&selinux_state, - current_sid(), task_sid(p), SECCLASS_PROCESS, + current_sid(), task_sid_obj(p), SECCLASS_PROCESS, PROCESS__SETPGID, NULL); } static int selinux_task_getpgid(struct task_struct *p) { return avc_has_perm(&selinux_state, - current_sid(), task_sid(p), SECCLASS_PROCESS, + current_sid(), task_sid_obj(p), SECCLASS_PROCESS, PROCESS__GETPGID, NULL); } static int selinux_task_getsid(struct task_struct *p) { return avc_has_perm(&selinux_state, - current_sid(), task_sid(p), SECCLASS_PROCESS, + current_sid(), task_sid_obj(p), SECCLASS_PROCESS, PROCESS__GETSESSION, NULL); } -static void selinux_task_getsecid(struct task_struct *p, u32 *secid) +static void selinux_current_getsecid_subj(u32 *secid) +{ + *secid = current_sid(); +} + +static void selinux_task_getsecid_obj(struct task_struct *p, u32 *secid) { - *secid = task_sid(p); + *secid = task_sid_obj(p); } static int selinux_task_setnice(struct task_struct *p, int nice) { return avc_has_perm(&selinux_state, - current_sid(), task_sid(p), SECCLASS_PROCESS, + current_sid(), task_sid_obj(p), SECCLASS_PROCESS, PROCESS__SETSCHED, NULL); } static int selinux_task_setioprio(struct task_struct *p, int ioprio) { return avc_has_perm(&selinux_state, - current_sid(), task_sid(p), SECCLASS_PROCESS, + current_sid(), task_sid_obj(p), SECCLASS_PROCESS, PROCESS__SETSCHED, NULL); } static int selinux_task_getioprio(struct task_struct *p) { return avc_has_perm(&selinux_state, - current_sid(), task_sid(p), SECCLASS_PROCESS, + current_sid(), task_sid_obj(p), SECCLASS_PROCESS, PROCESS__GETSCHED, NULL); } @@ -4095,7 +4164,7 @@ static int selinux_task_setrlimit(struct task_struct *p, unsigned int resource, upon context transitions. See selinux_bprm_committing_creds. */ if (old_rlim->rlim_max != new_rlim->rlim_max) return avc_has_perm(&selinux_state, - current_sid(), task_sid(p), + current_sid(), task_sid_obj(p), SECCLASS_PROCESS, PROCESS__SETRLIMIT, NULL); return 0; @@ -4104,21 +4173,21 @@ static int selinux_task_setrlimit(struct task_struct *p, unsigned int resource, static int selinux_task_setscheduler(struct task_struct *p) { return avc_has_perm(&selinux_state, - current_sid(), task_sid(p), SECCLASS_PROCESS, + current_sid(), task_sid_obj(p), SECCLASS_PROCESS, PROCESS__SETSCHED, NULL); } static int selinux_task_getscheduler(struct task_struct *p) { return avc_has_perm(&selinux_state, - current_sid(), task_sid(p), SECCLASS_PROCESS, + current_sid(), task_sid_obj(p), SECCLASS_PROCESS, PROCESS__GETSCHED, NULL); } static int selinux_task_movememory(struct task_struct *p) { return avc_has_perm(&selinux_state, - current_sid(), task_sid(p), SECCLASS_PROCESS, + current_sid(), task_sid_obj(p), SECCLASS_PROCESS, PROCESS__SETSCHED, NULL); } @@ -4137,14 +4206,14 @@ static int selinux_task_kill(struct task_struct *p, struct kernel_siginfo *info, else secid = cred_sid(cred); return avc_has_perm(&selinux_state, - secid, task_sid(p), SECCLASS_PROCESS, perm, NULL); + secid, task_sid_obj(p), SECCLASS_PROCESS, perm, NULL); } static void selinux_task_to_inode(struct task_struct *p, struct inode *inode) { struct inode_security_struct *isec = selinux_inode(inode); - u32 sid = task_sid(p); + u32 sid = task_sid_obj(p); spin_lock(&isec->lock); isec->sclass = inode_mode_to_security_class(inode->i_mode); @@ -4153,6 +4222,14 @@ static void selinux_task_to_inode(struct task_struct *p, spin_unlock(&isec->lock); } +static int selinux_userns_create(const struct cred *cred) +{ + u32 sid = current_sid(); + + return avc_has_perm(&selinux_state, sid, sid, SECCLASS_USER_NAMESPACE, + USER_NAMESPACE__CREATE, NULL); +} + /* Returns error only if unable to parse addresses */ static int selinux_parse_skb_ipv4(struct sk_buff *skb, struct common_audit_data *ad, u8 *proto) @@ -4428,7 +4505,7 @@ static int selinux_skb_peerlbl_sid(struct sk_buff *skb, u16 family, u32 *sid) * * If @skb_sid is valid then the user:role:type information from @sk_sid is * combined with the MLS information from @skb_sid in order to create - * @conn_sid. If @skb_sid is not valid then then @conn_sid is simply a copy + * @conn_sid. If @skb_sid is not valid then @conn_sid is simply a copy * of @sk_sid. Returns zero on success, negative values on failure. * */ @@ -5167,37 +5244,38 @@ static void selinux_sock_graft(struct sock *sk, struct socket *parent) sksec->sclass = isec->sclass; } -/* Called whenever SCTP receives an INIT chunk. This happens when an incoming - * connect(2), sctp_connectx(3) or sctp_sendmsg(3) (with no association - * already present). +/* + * Determines peer_secid for the asoc and updates socket's peer label + * if it's the first association on the socket. */ -static int selinux_sctp_assoc_request(struct sctp_endpoint *ep, - struct sk_buff *skb) +static int selinux_sctp_process_new_assoc(struct sctp_association *asoc, + struct sk_buff *skb) { - struct sk_security_struct *sksec = ep->base.sk->sk_security; + struct sock *sk = asoc->base.sk; + u16 family = sk->sk_family; + struct sk_security_struct *sksec = sk->sk_security; struct common_audit_data ad; struct lsm_network_audit net = {0,}; - u8 peerlbl_active; - u32 peer_sid = SECINITSID_UNLABELED; - u32 conn_sid; - int err = 0; + int err; - if (!selinux_policycap_extsockclass()) - return 0; + /* handle mapped IPv4 packets arriving via IPv6 sockets */ + if (family == PF_INET6 && skb->protocol == htons(ETH_P_IP)) + family = PF_INET; - peerlbl_active = selinux_peerlbl_enabled(); + if (selinux_peerlbl_enabled()) { + asoc->peer_secid = SECSID_NULL; - if (peerlbl_active) { /* This will return peer_sid = SECSID_NULL if there are * no peer labels, see security_net_peersid_resolve(). */ - err = selinux_skb_peerlbl_sid(skb, ep->base.sk->sk_family, - &peer_sid); + err = selinux_skb_peerlbl_sid(skb, family, &asoc->peer_secid); if (err) return err; - if (peer_sid == SECSID_NULL) - peer_sid = SECINITSID_UNLABELED; + if (asoc->peer_secid == SECSID_NULL) + asoc->peer_secid = SECINITSID_UNLABELED; + } else { + asoc->peer_secid = SECINITSID_UNLABELED; } if (sksec->sctp_assoc_state == SCTP_ASSOC_UNSET) { @@ -5208,36 +5286,76 @@ static int selinux_sctp_assoc_request(struct sctp_endpoint *ep, * then it is approved by policy and used as the primary * peer SID for getpeercon(3). */ - sksec->peer_sid = peer_sid; - } else if (sksec->peer_sid != peer_sid) { + sksec->peer_sid = asoc->peer_secid; + } else if (sksec->peer_sid != asoc->peer_secid) { /* Other association peer SIDs are checked to enforce * consistency among the peer SIDs. */ ad.type = LSM_AUDIT_DATA_NET; ad.u.net = &net; - ad.u.net->sk = ep->base.sk; + ad.u.net->sk = asoc->base.sk; err = avc_has_perm(&selinux_state, - sksec->peer_sid, peer_sid, sksec->sclass, - SCTP_SOCKET__ASSOCIATION, &ad); + sksec->peer_sid, asoc->peer_secid, + sksec->sclass, SCTP_SOCKET__ASSOCIATION, + &ad); if (err) return err; } + return 0; +} + +/* Called whenever SCTP receives an INIT or COOKIE ECHO chunk. This + * happens on an incoming connect(2), sctp_connectx(3) or + * sctp_sendmsg(3) (with no association already present). + */ +static int selinux_sctp_assoc_request(struct sctp_association *asoc, + struct sk_buff *skb) +{ + struct sk_security_struct *sksec = asoc->base.sk->sk_security; + u32 conn_sid; + int err; + + if (!selinux_policycap_extsockclass()) + return 0; + + err = selinux_sctp_process_new_assoc(asoc, skb); + if (err) + return err; /* Compute the MLS component for the connection and store - * the information in ep. This will be used by SCTP TCP type + * the information in asoc. This will be used by SCTP TCP type * sockets and peeled off connections as they cause a new * socket to be generated. selinux_sctp_sk_clone() will then * plug this into the new socket. */ - err = selinux_conn_sid(sksec->sid, peer_sid, &conn_sid); + err = selinux_conn_sid(sksec->sid, asoc->peer_secid, &conn_sid); if (err) return err; - ep->secid = conn_sid; - ep->peer_secid = peer_sid; + asoc->secid = conn_sid; /* Set any NetLabel labels including CIPSO/CALIPSO options. */ - return selinux_netlbl_sctp_assoc_request(ep, skb); + return selinux_netlbl_sctp_assoc_request(asoc, skb); +} + +/* Called when SCTP receives a COOKIE ACK chunk as the final + * response to an association request (initited by us). + */ +static int selinux_sctp_assoc_established(struct sctp_association *asoc, + struct sk_buff *skb) +{ + struct sk_security_struct *sksec = asoc->base.sk->sk_security; + + if (!selinux_policycap_extsockclass()) + return 0; + + /* Inherit secid from the parent socket - this will be picked up + * by selinux_sctp_sk_clone() if the association gets peeled off + * into a new socket. + */ + asoc->secid = sksec->sid; + + return selinux_sctp_process_new_assoc(asoc, skb); } /* Check if sctp IPv4/IPv6 addresses are valid for binding or connecting @@ -5298,7 +5416,7 @@ static int selinux_sctp_bind_connect(struct sock *sk, int optname, /* As selinux_sctp_bind_connect() is called by the * SCTP protocol layer, the socket is already locked, - * therefore selinux_netlbl_socket_connect_locked() is + * therefore selinux_netlbl_socket_connect_locked() * is called here. The situations handled are: * sctp_connectx(3), sctp_sendmsg(3), sendmsg(2), * whenever a new IP address is added or when a new @@ -5322,7 +5440,7 @@ static int selinux_sctp_bind_connect(struct sock *sk, int optname, } /* Called whenever a new socket is created by accept(2) or sctp_peeloff(3). */ -static void selinux_sctp_sk_clone(struct sctp_endpoint *ep, struct sock *sk, +static void selinux_sctp_sk_clone(struct sctp_association *asoc, struct sock *sk, struct sock *newsk) { struct sk_security_struct *sksec = sk->sk_security; @@ -5334,13 +5452,13 @@ static void selinux_sctp_sk_clone(struct sctp_endpoint *ep, struct sock *sk, if (!selinux_policycap_extsockclass()) return selinux_sk_clone_security(sk, newsk); - newsksec->sid = ep->secid; - newsksec->peer_sid = ep->peer_secid; + newsksec->sid = asoc->secid; + newsksec->peer_sid = asoc->peer_secid; newsksec->sclass = sksec->sclass; selinux_netlbl_sctp_sk_clone(sk, newsk); } -static int selinux_inet_conn_request(struct sock *sk, struct sk_buff *skb, +static int selinux_inet_conn_request(const struct sock *sk, struct sk_buff *skb, struct request_sock *req) { struct sk_security_struct *sksec = sk->sk_security; @@ -5414,9 +5532,9 @@ static void selinux_secmark_refcount_dec(void) } static void selinux_req_classify_flow(const struct request_sock *req, - struct flowi *fl) + struct flowi_common *flic) { - fl->flowi_secid = req->secid; + flic->flowic_secid = req->secid; } static int selinux_tun_dev_alloc_security(void **security) @@ -5503,40 +5621,41 @@ static int selinux_tun_dev_open(void *security) #ifdef CONFIG_NETFILTER -static unsigned int selinux_ip_forward(struct sk_buff *skb, - const struct net_device *indev, - u16 family) +static unsigned int selinux_ip_forward(void *priv, struct sk_buff *skb, + const struct nf_hook_state *state) { - int err; + int ifindex; + u16 family; char *addrp; u32 peer_sid; struct common_audit_data ad; struct lsm_network_audit net = {0,}; - u8 secmark_active; - u8 netlbl_active; - u8 peerlbl_active; + int secmark_active, peerlbl_active; if (!selinux_policycap_netpeer()) return NF_ACCEPT; secmark_active = selinux_secmark_enabled(); - netlbl_active = netlbl_enabled(); peerlbl_active = selinux_peerlbl_enabled(); if (!secmark_active && !peerlbl_active) return NF_ACCEPT; + family = state->pf; if (selinux_skb_peerlbl_sid(skb, family, &peer_sid) != 0) return NF_DROP; + ifindex = state->in->ifindex; ad.type = LSM_AUDIT_DATA_NET; ad.u.net = &net; - ad.u.net->netif = indev->ifindex; + ad.u.net->netif = ifindex; ad.u.net->family = family; if (selinux_parse_skb(skb, &ad, &addrp, 1, NULL) != 0) return NF_DROP; if (peerlbl_active) { - err = selinux_inet_sys_rcv_skb(dev_net(indev), indev->ifindex, + int err; + + err = selinux_inet_sys_rcv_skb(state->net, ifindex, addrp, family, peer_sid, &ad); if (err) { selinux_netlbl_err(skb, family, err, 1); @@ -5550,7 +5669,7 @@ static unsigned int selinux_ip_forward(struct sk_buff *skb, SECCLASS_PACKET, PACKET__FORWARD_IN, &ad)) return NF_DROP; - if (netlbl_active) + if (netlbl_enabled()) /* we do this in the FORWARD path and not the POST_ROUTING * path because we want to make sure we apply the necessary * labeling before IPsec is applied so we can leverage AH @@ -5561,24 +5680,8 @@ static unsigned int selinux_ip_forward(struct sk_buff *skb, return NF_ACCEPT; } -static unsigned int selinux_ipv4_forward(void *priv, - struct sk_buff *skb, - const struct nf_hook_state *state) -{ - return selinux_ip_forward(skb, state->in, PF_INET); -} - -#if IS_ENABLED(CONFIG_IPV6) -static unsigned int selinux_ipv6_forward(void *priv, - struct sk_buff *skb, - const struct nf_hook_state *state) -{ - return selinux_ip_forward(skb, state->in, PF_INET6); -} -#endif /* IPV6 */ - -static unsigned int selinux_ip_output(struct sk_buff *skb, - u16 family) +static unsigned int selinux_ip_output(void *priv, struct sk_buff *skb, + const struct nf_hook_state *state) { struct sock *sk; u32 sid; @@ -5613,48 +5716,32 @@ static unsigned int selinux_ip_output(struct sk_buff *skb, sid = sksec->sid; } else sid = SECINITSID_KERNEL; - if (selinux_netlbl_skbuff_setsid(skb, family, sid) != 0) + if (selinux_netlbl_skbuff_setsid(skb, state->pf, sid) != 0) return NF_DROP; return NF_ACCEPT; } -static unsigned int selinux_ipv4_output(void *priv, - struct sk_buff *skb, - const struct nf_hook_state *state) -{ - return selinux_ip_output(skb, PF_INET); -} - -#if IS_ENABLED(CONFIG_IPV6) -static unsigned int selinux_ipv6_output(void *priv, - struct sk_buff *skb, - const struct nf_hook_state *state) -{ - return selinux_ip_output(skb, PF_INET6); -} -#endif /* IPV6 */ static unsigned int selinux_ip_postroute_compat(struct sk_buff *skb, - int ifindex, - u16 family) + const struct nf_hook_state *state) { - struct sock *sk = skb_to_full_sk(skb); + struct sock *sk; struct sk_security_struct *sksec; struct common_audit_data ad; struct lsm_network_audit net = {0,}; - char *addrp; - u8 proto; + u8 proto = 0; + sk = skb_to_full_sk(skb); if (sk == NULL) return NF_ACCEPT; sksec = sk->sk_security; ad.type = LSM_AUDIT_DATA_NET; ad.u.net = &net; - ad.u.net->netif = ifindex; - ad.u.net->family = family; - if (selinux_parse_skb(skb, &ad, &addrp, 0, &proto)) + ad.u.net->netif = state->out->ifindex; + ad.u.net->family = state->pf; + if (selinux_parse_skb(skb, &ad, NULL, 0, &proto)) return NF_DROP; if (selinux_secmark_enabled()) @@ -5669,26 +5756,26 @@ static unsigned int selinux_ip_postroute_compat(struct sk_buff *skb, return NF_ACCEPT; } -static unsigned int selinux_ip_postroute(struct sk_buff *skb, - const struct net_device *outdev, - u16 family) +static unsigned int selinux_ip_postroute(void *priv, + struct sk_buff *skb, + const struct nf_hook_state *state) { + u16 family; u32 secmark_perm; u32 peer_sid; - int ifindex = outdev->ifindex; + int ifindex; struct sock *sk; struct common_audit_data ad; struct lsm_network_audit net = {0,}; char *addrp; - u8 secmark_active; - u8 peerlbl_active; + int secmark_active, peerlbl_active; /* If any sort of compatibility mode is enabled then handoff processing * to the selinux_ip_postroute_compat() function to deal with the * special handling. We do this in an attempt to keep this function * as fast and as clean as possible. */ if (!selinux_policycap_netpeer()) - return selinux_ip_postroute_compat(skb, ifindex, family); + return selinux_ip_postroute_compat(skb, state); secmark_active = selinux_secmark_enabled(); peerlbl_active = selinux_peerlbl_enabled(); @@ -5714,6 +5801,7 @@ static unsigned int selinux_ip_postroute(struct sk_buff *skb, return NF_ACCEPT; #endif + family = state->pf; if (sk == NULL) { /* Without an associated socket the packet is either coming * from the kernel or it is being forwarded; check the packet @@ -5774,6 +5862,7 @@ static unsigned int selinux_ip_postroute(struct sk_buff *skb, secmark_perm = PACKET__SEND; } + ifindex = state->out->ifindex; ad.type = LSM_AUDIT_DATA_NET; ad.u.net = &net; ad.u.net->netif = ifindex; @@ -5791,7 +5880,7 @@ static unsigned int selinux_ip_postroute(struct sk_buff *skb, u32 if_sid; u32 node_sid; - if (sel_netif_sid(dev_net(outdev), ifindex, &if_sid)) + if (sel_netif_sid(state->net, ifindex, &if_sid)) return NF_DROP; if (avc_has_perm(&selinux_state, peer_sid, if_sid, @@ -5808,61 +5897,64 @@ static unsigned int selinux_ip_postroute(struct sk_buff *skb, return NF_ACCEPT; } - -static unsigned int selinux_ipv4_postroute(void *priv, - struct sk_buff *skb, - const struct nf_hook_state *state) -{ - return selinux_ip_postroute(skb, state->out, PF_INET); -} - -#if IS_ENABLED(CONFIG_IPV6) -static unsigned int selinux_ipv6_postroute(void *priv, - struct sk_buff *skb, - const struct nf_hook_state *state) -{ - return selinux_ip_postroute(skb, state->out, PF_INET6); -} -#endif /* IPV6 */ - #endif /* CONFIG_NETFILTER */ static int selinux_netlink_send(struct sock *sk, struct sk_buff *skb) { - int err = 0; - u32 perm; + int rc = 0; + unsigned int msg_len; + unsigned int data_len = skb->len; + unsigned char *data = skb->data; struct nlmsghdr *nlh; struct sk_security_struct *sksec = sk->sk_security; + u16 sclass = sksec->sclass; + u32 perm; - if (skb->len < NLMSG_HDRLEN) { - err = -EINVAL; - goto out; - } - nlh = nlmsg_hdr(skb); + while (data_len >= nlmsg_total_size(0)) { + nlh = (struct nlmsghdr *)data; + + /* NOTE: the nlmsg_len field isn't reliably set by some netlink + * users which means we can't reject skb's with bogus + * length fields; our solution is to follow what + * netlink_rcv_skb() does and simply skip processing at + * messages with length fields that are clearly junk + */ + if (nlh->nlmsg_len < NLMSG_HDRLEN || nlh->nlmsg_len > data_len) + return 0; - err = selinux_nlmsg_lookup(sksec->sclass, nlh->nlmsg_type, &perm); - if (err) { - if (err == -EINVAL) { + rc = selinux_nlmsg_lookup(sclass, nlh->nlmsg_type, &perm); + if (rc == 0) { + rc = sock_has_perm(sk, perm); + if (rc) + return rc; + } else if (rc == -EINVAL) { + /* -EINVAL is a missing msg/perm mapping */ pr_warn_ratelimited("SELinux: unrecognized netlink" - " message: protocol=%hu nlmsg_type=%hu sclass=%s" - " pid=%d comm=%s\n", - sk->sk_protocol, nlh->nlmsg_type, - secclass_map[sksec->sclass - 1].name, - task_pid_nr(current), current->comm); - if (!enforcing_enabled(&selinux_state) || - security_get_allow_unknown(&selinux_state)) - err = 0; + " message: protocol=%hu nlmsg_type=%hu sclass=%s" + " pid=%d comm=%s\n", + sk->sk_protocol, nlh->nlmsg_type, + secclass_map[sclass - 1].name, + task_pid_nr(current), current->comm); + if (enforcing_enabled(&selinux_state) && + !security_get_allow_unknown(&selinux_state)) + return rc; + rc = 0; + } else if (rc == -ENOENT) { + /* -ENOENT is a missing socket/class mapping, ignore */ + rc = 0; + } else { + return rc; } - /* Ignore */ - if (err == -ENOENT) - err = 0; - goto out; + /* move to the next message after applying netlink padding */ + msg_len = NLMSG_ALIGN(nlh->nlmsg_len); + if (msg_len >= data_len) + return 0; + data_len -= msg_len; + data += msg_len; } - err = sock_has_perm(sk, perm); -out: - return err; + return rc; } static void ipc_init_security(struct ipc_security_struct *isec, u16 sclass) @@ -5903,7 +5995,6 @@ static int selinux_msg_queue_alloc_security(struct kern_ipc_perm *msq) struct ipc_security_struct *isec; struct common_audit_data ad; u32 sid = current_sid(); - int rc; isec = selinux_ipc(msq); ipc_init_security(isec, SECCLASS_MSGQ); @@ -5911,10 +6002,9 @@ static int selinux_msg_queue_alloc_security(struct kern_ipc_perm *msq) ad.type = LSM_AUDIT_DATA_IPC; ad.u.ipc_id = msq->key; - rc = avc_has_perm(&selinux_state, - sid, isec->sid, SECCLASS_MSGQ, - MSGQ__CREATE, &ad); - return rc; + return avc_has_perm(&selinux_state, + sid, isec->sid, SECCLASS_MSGQ, + MSGQ__CREATE, &ad); } static int selinux_msg_queue_associate(struct kern_ipc_perm *msq, int msqflg) @@ -6017,7 +6107,7 @@ static int selinux_msg_queue_msgrcv(struct kern_ipc_perm *msq, struct msg_msg *m struct ipc_security_struct *isec; struct msg_security_struct *msec; struct common_audit_data ad; - u32 sid = task_sid(target); + u32 sid = task_sid_obj(target); int rc; isec = selinux_ipc(msq); @@ -6042,7 +6132,6 @@ static int selinux_shm_alloc_security(struct kern_ipc_perm *shp) struct ipc_security_struct *isec; struct common_audit_data ad; u32 sid = current_sid(); - int rc; isec = selinux_ipc(shp); ipc_init_security(isec, SECCLASS_SHM); @@ -6050,10 +6139,9 @@ static int selinux_shm_alloc_security(struct kern_ipc_perm *shp) ad.type = LSM_AUDIT_DATA_IPC; ad.u.ipc_id = shp->key; - rc = avc_has_perm(&selinux_state, - sid, isec->sid, SECCLASS_SHM, - SHM__CREATE, &ad); - return rc; + return avc_has_perm(&selinux_state, + sid, isec->sid, SECCLASS_SHM, + SHM__CREATE, &ad); } static int selinux_shm_associate(struct kern_ipc_perm *shp, int shmflg) @@ -6127,7 +6215,6 @@ static int selinux_sem_alloc_security(struct kern_ipc_perm *sma) struct ipc_security_struct *isec; struct common_audit_data ad; u32 sid = current_sid(); - int rc; isec = selinux_ipc(sma); ipc_init_security(isec, SECCLASS_SEM); @@ -6135,10 +6222,9 @@ static int selinux_sem_alloc_security(struct kern_ipc_perm *sma) ad.type = LSM_AUDIT_DATA_IPC; ad.u.ipc_id = sma->key; - rc = avc_has_perm(&selinux_state, - sid, isec->sid, SECCLASS_SEM, - SEM__CREATE, &ad); - return rc; + return avc_has_perm(&selinux_state, + sid, isec->sid, SECCLASS_SEM, + SEM__CREATE, &ad); } static int selinux_sem_associate(struct kern_ipc_perm *sma, int semflg) @@ -6244,7 +6330,7 @@ static void selinux_d_instantiate(struct dentry *dentry, struct inode *inode) } static int selinux_getprocattr(struct task_struct *p, - char *name, char **value) + const char *name, char **value) { const struct task_security_struct *__tsec; u32 sid; @@ -6351,6 +6437,8 @@ static int selinux_setprocattr(const char *name, void *value, size_t size) ab = audit_log_start(audit_context(), GFP_ATOMIC, AUDIT_SELINUX_ERR); + if (!ab) + return error; audit_log_format(ab, "op=fscreate invalid_context="); audit_log_n_untrustedstring(ab, value, audit_size); audit_log_end(ab); @@ -6372,7 +6460,7 @@ static int selinux_setprocattr(const char *name, void *value, size_t size) /* Permission checking based on the specified context is performed during the actual operation (execve, open/mkdir/...), when we know the full context of the - operation. See selinux_bprm_set_creds for the execve + operation. See selinux_bprm_creds_for_exec for the execve checks and may_create for the file creation checks. The operation will then fail if the context is not permitted. */ tsec = selinux_cred(new); @@ -6396,7 +6484,6 @@ static int selinux_setprocattr(const char *name, void *value, size_t size) goto abort_change; /* Only allow single threaded processes to change context */ - error = -EPERM; if (!current_is_single_threaded()) { error = security_bounded_transition(&selinux_state, tsec->sid, sid); @@ -6483,14 +6570,15 @@ static int selinux_inode_notifysecctx(struct inode *inode, void *ctx, u32 ctxlen */ static int selinux_inode_setsecctx(struct dentry *dentry, void *ctx, u32 ctxlen) { - return __vfs_setxattr_noperm(dentry, XATTR_NAME_SELINUX, ctx, ctxlen, 0); + return __vfs_setxattr_noperm(&init_user_ns, dentry, XATTR_NAME_SELINUX, + ctx, ctxlen, 0); } static int selinux_inode_getsecctx(struct inode *inode, void **ctx, u32 *ctxlen) { int len = 0; - len = selinux_inode_getsecurity(inode, XATTR_SELINUX_SUFFIX, - ctx, true); + len = selinux_inode_getsecurity(&init_user_ns, inode, + XATTR_SELINUX_SUFFIX, ctx, true); if (len < 0) return len; *ctxlen = len; @@ -6528,20 +6616,43 @@ static void selinux_key_free(struct key *k) static int selinux_key_permission(key_ref_t key_ref, const struct cred *cred, - unsigned perm) + enum key_need_perm need_perm) { struct key *key; struct key_security_struct *ksec; - u32 sid; + u32 perm, sid; - /* if no specific permissions are requested, we skip the - permission check. No serious, additional covert channels - appear to be created. */ - if (perm == 0) + switch (need_perm) { + case KEY_NEED_VIEW: + perm = KEY__VIEW; + break; + case KEY_NEED_READ: + perm = KEY__READ; + break; + case KEY_NEED_WRITE: + perm = KEY__WRITE; + break; + case KEY_NEED_SEARCH: + perm = KEY__SEARCH; + break; + case KEY_NEED_LINK: + perm = KEY__LINK; + break; + case KEY_NEED_SETATTR: + perm = KEY__SETATTR; + break; + case KEY_NEED_UNLINK: + case KEY_SYSADMIN_OVERRIDE: + case KEY_AUTHTOKEN_OVERRIDE: + case KEY_DEFER_PERM_CHECK: return 0; + default: + WARN_ON(1); + return -EPERM; - sid = cred_sid(cred); + } + sid = cred_sid(cred); key = key_ref_to_ptr(key_ref); ksec = key->security; @@ -6563,6 +6674,17 @@ static int selinux_key_getsecurity(struct key *key, char **_buffer) *_buffer = context; return rc; } + +#ifdef CONFIG_KEY_NOTIFICATIONS +static int selinux_watch_key(struct key *key) +{ + struct key_security_struct *ksec = key->security; + u32 sid = current_sid(); + + return avc_has_perm(&selinux_state, + sid, ksec->sid, SECCLASS_KEY, KEY__VIEW, NULL); +} +#endif #endif #ifdef CONFIG_SECURITY_INFINIBAND @@ -6604,7 +6726,7 @@ static int selinux_ib_endport_manage_subnet(void *ib_sec, const char *dev_name, return err; ad.type = LSM_AUDIT_DATA_IBENDPORT; - strncpy(ibendport.dev_name, dev_name, sizeof(ibendport.dev_name)); + ibendport.dev_name = dev_name; ibendport.port = port_num; ad.u.ibendport = &ibendport; return avc_has_perm(&selinux_state, @@ -6670,7 +6792,7 @@ static u32 bpf_map_fmode_to_av(fmode_t fmode) } /* This function will check the file pass through unix socket or binder to see - * if it is a bpf related object. And apply correspinding checks on the bpf + * if it is a bpf related object. And apply corresponding checks on the bpf * object based on the type. The bpf maps and programs, not like other files and * socket, are using a shared anonymous inode inside the kernel as their inode. * So checking that inode cannot identify if the process have privilege to @@ -6771,40 +6893,13 @@ static void selinux_bpf_prog_free(struct bpf_prog_aux *aux) } #endif -static int selinux_lockdown(enum lockdown_reason what) -{ - struct common_audit_data ad; - u32 sid = current_sid(); - int invalid_reason = (what <= LOCKDOWN_NONE) || - (what == LOCKDOWN_INTEGRITY_MAX) || - (what >= LOCKDOWN_CONFIDENTIALITY_MAX); - - if (WARN(invalid_reason, "Invalid lockdown reason")) { - audit_log(audit_context(), - GFP_ATOMIC, AUDIT_SELINUX_ERR, - "lockdown_reason=invalid"); - return -EINVAL; - } - - ad.type = LSM_AUDIT_DATA_LOCKDOWN; - ad.u.reason = what; - - if (what <= LOCKDOWN_INTEGRITY_MAX) - return avc_has_perm(&selinux_state, - sid, sid, SECCLASS_LOCKDOWN, - LOCKDOWN__INTEGRITY, &ad); - else - return avc_has_perm(&selinux_state, - sid, sid, SECCLASS_LOCKDOWN, - LOCKDOWN__CONFIDENTIALITY, &ad); -} - struct lsm_blob_sizes selinux_blob_sizes __lsm_ro_after_init = { .lbs_cred = sizeof(struct task_security_struct), .lbs_file = sizeof(struct file_security_struct), .lbs_inode = sizeof(struct inode_security_struct), .lbs_ipc = sizeof(struct ipc_security_struct), .lbs_msg_msg = sizeof(struct msg_security_struct), + .lbs_superblock = sizeof(struct superblock_security_struct), }; #ifdef CONFIG_PERF_EVENTS @@ -6868,6 +6963,57 @@ static int selinux_perf_event_write(struct perf_event *event) } #endif +#ifdef CONFIG_IO_URING +/** + * selinux_uring_override_creds - check the requested cred override + * @new: the target creds + * + * Check to see if the current task is allowed to override it's credentials + * to service an io_uring operation. + */ +static int selinux_uring_override_creds(const struct cred *new) +{ + return avc_has_perm(&selinux_state, current_sid(), cred_sid(new), + SECCLASS_IO_URING, IO_URING__OVERRIDE_CREDS, NULL); +} + +/** + * selinux_uring_sqpoll - check if a io_uring polling thread can be created + * + * Check to see if the current task is allowed to create a new io_uring + * kernel polling thread. + */ +static int selinux_uring_sqpoll(void) +{ + int sid = current_sid(); + + return avc_has_perm(&selinux_state, sid, sid, + SECCLASS_IO_URING, IO_URING__SQPOLL, NULL); +} + +/** + * selinux_uring_cmd - check if IORING_OP_URING_CMD is allowed + * @ioucmd: the io_uring command structure + * + * Check to see if the current domain is allowed to execute an + * IORING_OP_URING_CMD against the device/file specified in @ioucmd. + * + */ +static int selinux_uring_cmd(struct io_uring_cmd *ioucmd) +{ + struct file *file = ioucmd->file; + struct inode *inode = file_inode(file); + struct inode_security_struct *isec = selinux_inode(inode); + struct common_audit_data ad; + + ad.type = LSM_AUDIT_DATA_FILE; + ad.u.file = file; + + return avc_has_perm(&selinux_state, current_sid(), isec->sid, + SECCLASS_IO_URING, IO_URING__CMD, &ad); +} +#endif /* CONFIG_IO_URING */ + /* * IMPORTANT NOTE: When adding new hooks, please be careful to keep this order: * 1. any hooks that don't belong to (2.) or (3.) below, @@ -6901,12 +7047,12 @@ static struct security_hook_list selinux_hooks[] __lsm_ro_after_init = { LSM_HOOK_INIT(netlink_send, selinux_netlink_send), - LSM_HOOK_INIT(bprm_set_creds, selinux_bprm_set_creds), + LSM_HOOK_INIT(bprm_creds_for_exec, selinux_bprm_creds_for_exec), LSM_HOOK_INIT(bprm_committing_creds, selinux_bprm_committing_creds), LSM_HOOK_INIT(bprm_committed_creds, selinux_bprm_committed_creds), - LSM_HOOK_INIT(sb_free_security, selinux_sb_free_security), LSM_HOOK_INIT(sb_free_mnt_opts, selinux_free_mnt_opts), + LSM_HOOK_INIT(sb_mnt_opts_compat, selinux_sb_mnt_opts_compat), LSM_HOOK_INIT(sb_remount, selinux_sb_remount), LSM_HOOK_INIT(sb_kern_mount, selinux_sb_kern_mount), LSM_HOOK_INIT(sb_show_options, selinux_sb_show_options), @@ -6923,6 +7069,7 @@ static struct security_hook_list selinux_hooks[] __lsm_ro_after_init = { LSM_HOOK_INIT(inode_free_security, selinux_inode_free_security), LSM_HOOK_INIT(inode_init_security, selinux_inode_init_security), + LSM_HOOK_INIT(inode_init_security_anon, selinux_inode_init_security_anon), LSM_HOOK_INIT(inode_create, selinux_inode_create), LSM_HOOK_INIT(inode_link, selinux_inode_link), LSM_HOOK_INIT(inode_unlink, selinux_inode_unlink), @@ -6977,7 +7124,8 @@ static struct security_hook_list selinux_hooks[] __lsm_ro_after_init = { LSM_HOOK_INIT(task_setpgid, selinux_task_setpgid), LSM_HOOK_INIT(task_getpgid, selinux_task_getpgid), LSM_HOOK_INIT(task_getsid, selinux_task_getsid), - LSM_HOOK_INIT(task_getsecid, selinux_task_getsecid), + LSM_HOOK_INIT(current_getsecid_subj, selinux_current_getsecid_subj), + LSM_HOOK_INIT(task_getsecid_obj, selinux_task_getsecid_obj), LSM_HOOK_INIT(task_setnice, selinux_task_setnice), LSM_HOOK_INIT(task_setioprio, selinux_task_setioprio), LSM_HOOK_INIT(task_getioprio, selinux_task_getioprio), @@ -6988,6 +7136,7 @@ static struct security_hook_list selinux_hooks[] __lsm_ro_after_init = { LSM_HOOK_INIT(task_movememory, selinux_task_movememory), LSM_HOOK_INIT(task_kill, selinux_task_kill), LSM_HOOK_INIT(task_to_inode, selinux_task_to_inode), + LSM_HOOK_INIT(userns_create, selinux_userns_create), LSM_HOOK_INIT(ipc_permission, selinux_ipc_permission), LSM_HOOK_INIT(ipc_getsecid, selinux_ipc_getsecid), @@ -7045,6 +7194,7 @@ static struct security_hook_list selinux_hooks[] __lsm_ro_after_init = { LSM_HOOK_INIT(sctp_assoc_request, selinux_sctp_assoc_request), LSM_HOOK_INIT(sctp_sk_clone, selinux_sctp_sk_clone), LSM_HOOK_INIT(sctp_bind_connect, selinux_sctp_bind_connect), + LSM_HOOK_INIT(sctp_assoc_established, selinux_sctp_assoc_established), LSM_HOOK_INIT(inet_conn_request, selinux_inet_conn_request), LSM_HOOK_INIT(inet_csk_clone, selinux_inet_csk_clone), LSM_HOOK_INIT(inet_conn_established, selinux_inet_conn_established), @@ -7078,6 +7228,9 @@ static struct security_hook_list selinux_hooks[] __lsm_ro_after_init = { LSM_HOOK_INIT(key_free, selinux_key_free), LSM_HOOK_INIT(key_permission, selinux_key_permission), LSM_HOOK_INIT(key_getsecurity, selinux_key_getsecurity), +#ifdef CONFIG_KEY_NOTIFICATIONS + LSM_HOOK_INIT(watch_key, selinux_watch_key), +#endif #endif #ifdef CONFIG_AUDIT @@ -7101,7 +7254,11 @@ static struct security_hook_list selinux_hooks[] __lsm_ro_after_init = { LSM_HOOK_INIT(perf_event_write, selinux_perf_event_write), #endif - LSM_HOOK_INIT(locked_down, selinux_lockdown), +#ifdef CONFIG_IO_URING + LSM_HOOK_INIT(uring_override_creds, selinux_uring_override_creds), + LSM_HOOK_INIT(uring_sqpoll, selinux_uring_sqpoll), + LSM_HOOK_INIT(uring_cmd, selinux_uring_cmd), +#endif /* * PUT "CLONING" (ACCESSING + ALLOCATING) HOOKS HERE @@ -7109,7 +7266,6 @@ static struct security_hook_list selinux_hooks[] __lsm_ro_after_init = { LSM_HOOK_INIT(fs_context_dup, selinux_fs_context_dup), LSM_HOOK_INIT(fs_context_parse_param, selinux_fs_context_parse_param), LSM_HOOK_INIT(sb_eat_lsm_opts, selinux_sb_eat_lsm_opts), - LSM_HOOK_INIT(sb_add_mnt_opt, selinux_add_mnt_opt), #ifdef CONFIG_SECURITY_NETWORK_XFRM LSM_HOOK_INIT(xfrm_policy_clone_security, selinux_xfrm_policy_clone), #endif @@ -7158,9 +7314,12 @@ static __init int selinux_init(void) memset(&selinux_state, 0, sizeof(selinux_state)); enforcing_set(&selinux_state, selinux_enforcing_boot); - selinux_state.checkreqprot = selinux_checkreqprot_boot; - selinux_ss_init(&selinux_state.ss); + if (CONFIG_SECURITY_SELINUX_CHECKREQPROT_VALUE) + pr_err("SELinux: CONFIG_SECURITY_SELINUX_CHECKREQPROT_VALUE is non-zero. This is deprecated and will be rejected in a future kernel release.\n"); + checkreqprot_set(&selinux_state, selinux_checkreqprot_boot); selinux_avc_init(&selinux_state.avc); + mutex_init(&selinux_state.status_lock); + mutex_init(&selinux_state.policy_mutex); /* Set the security state for the initial task. */ cred_init_security(); @@ -7221,38 +7380,38 @@ DEFINE_LSM(selinux) = { static const struct nf_hook_ops selinux_nf_ops[] = { { - .hook = selinux_ipv4_postroute, + .hook = selinux_ip_postroute, .pf = NFPROTO_IPV4, .hooknum = NF_INET_POST_ROUTING, .priority = NF_IP_PRI_SELINUX_LAST, }, { - .hook = selinux_ipv4_forward, + .hook = selinux_ip_forward, .pf = NFPROTO_IPV4, .hooknum = NF_INET_FORWARD, .priority = NF_IP_PRI_SELINUX_FIRST, }, { - .hook = selinux_ipv4_output, + .hook = selinux_ip_output, .pf = NFPROTO_IPV4, .hooknum = NF_INET_LOCAL_OUT, .priority = NF_IP_PRI_SELINUX_FIRST, }, #if IS_ENABLED(CONFIG_IPV6) { - .hook = selinux_ipv6_postroute, + .hook = selinux_ip_postroute, .pf = NFPROTO_IPV6, .hooknum = NF_INET_POST_ROUTING, .priority = NF_IP6_PRI_SELINUX_LAST, }, { - .hook = selinux_ipv6_forward, + .hook = selinux_ip_forward, .pf = NFPROTO_IPV6, .hooknum = NF_INET_FORWARD, .priority = NF_IP6_PRI_SELINUX_FIRST, }, { - .hook = selinux_ipv6_output, + .hook = selinux_ip_output, .pf = NFPROTO_IPV6, .hooknum = NF_INET_LOCAL_OUT, .priority = NF_IP6_PRI_SELINUX_FIRST, diff --git a/security/selinux/ibpkey.c b/security/selinux/ibpkey.c index f68a7617cfb9..5839ca7bb9c7 100644 --- a/security/selinux/ibpkey.c +++ b/security/selinux/ibpkey.c @@ -40,7 +40,6 @@ struct sel_ib_pkey { struct rcu_head rcu; }; -static LIST_HEAD(sel_ib_pkey_list); static DEFINE_SPINLOCK(sel_ib_pkey_lock); static struct sel_ib_pkey_bkt sel_ib_pkey_hash[SEL_PKEY_HASH_SIZE]; @@ -105,7 +104,7 @@ static void sel_ib_pkey_insert(struct sel_ib_pkey *pkey) tail = list_entry( rcu_dereference_protected( - sel_ib_pkey_hash[idx].list.prev, + list_tail_rcu(&sel_ib_pkey_hash[idx].list), lockdep_is_held(&sel_ib_pkey_lock)), struct sel_ib_pkey, list); list_del_rcu(&tail->list); @@ -151,8 +150,10 @@ static int sel_ib_pkey_sid_slow(u64 subnet_prefix, u16 pkey_num, u32 *sid) * is valid, it just won't be added to the cache. */ new = kzalloc(sizeof(*new), GFP_ATOMIC); - if (!new) + if (!new) { + ret = -ENOMEM; goto out; + } new->psec.subnet_prefix = subnet_prefix; new->psec.pkey = pkey_num; diff --git a/security/selinux/ima.c b/security/selinux/ima.c new file mode 100644 index 000000000000..a915b89d55b0 --- /dev/null +++ b/security/selinux/ima.c @@ -0,0 +1,125 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2021 Microsoft Corporation + * + * Author: Lakshmi Ramasubramanian (nramas@linux.microsoft.com) + * + * Measure critical data structures maintainted by SELinux + * using IMA subsystem. + */ +#include <linux/vmalloc.h> +#include <linux/ima.h> +#include "security.h" +#include "ima.h" + +/* + * selinux_ima_collect_state - Read selinux configuration settings + * + * @state: selinux_state + * + * On success returns the configuration settings string. + * On error, returns NULL. + */ +static char *selinux_ima_collect_state(struct selinux_state *state) +{ + const char *on = "=1;", *off = "=0;"; + char *buf; + int buf_len, len, i, rc; + + buf_len = strlen("initialized=0;enforcing=0;checkreqprot=0;") + 1; + + len = strlen(on); + for (i = 0; i < __POLICYDB_CAP_MAX; i++) + buf_len += strlen(selinux_policycap_names[i]) + len; + + buf = kzalloc(buf_len, GFP_KERNEL); + if (!buf) + return NULL; + + rc = strscpy(buf, "initialized", buf_len); + WARN_ON(rc < 0); + + rc = strlcat(buf, selinux_initialized(state) ? on : off, buf_len); + WARN_ON(rc >= buf_len); + + rc = strlcat(buf, "enforcing", buf_len); + WARN_ON(rc >= buf_len); + + rc = strlcat(buf, enforcing_enabled(state) ? on : off, buf_len); + WARN_ON(rc >= buf_len); + + rc = strlcat(buf, "checkreqprot", buf_len); + WARN_ON(rc >= buf_len); + + rc = strlcat(buf, checkreqprot_get(state) ? on : off, buf_len); + WARN_ON(rc >= buf_len); + + for (i = 0; i < __POLICYDB_CAP_MAX; i++) { + rc = strlcat(buf, selinux_policycap_names[i], buf_len); + WARN_ON(rc >= buf_len); + + rc = strlcat(buf, state->policycap[i] ? on : off, buf_len); + WARN_ON(rc >= buf_len); + } + + return buf; +} + +/* + * selinux_ima_measure_state_locked - Measure SELinux state and hash of policy + * + * @state: selinux state struct + */ +void selinux_ima_measure_state_locked(struct selinux_state *state) +{ + char *state_str = NULL; + void *policy = NULL; + size_t policy_len; + int rc = 0; + + lockdep_assert_held(&state->policy_mutex); + + state_str = selinux_ima_collect_state(state); + if (!state_str) { + pr_err("SELinux: %s: failed to read state.\n", __func__); + return; + } + + ima_measure_critical_data("selinux", "selinux-state", + state_str, strlen(state_str), false, + NULL, 0); + + kfree(state_str); + + /* + * Measure SELinux policy only after initialization is completed. + */ + if (!selinux_initialized(state)) + return; + + rc = security_read_state_kernel(state, &policy, &policy_len); + if (rc) { + pr_err("SELinux: %s: failed to read policy %d.\n", __func__, rc); + return; + } + + ima_measure_critical_data("selinux", "selinux-policy-hash", + policy, policy_len, true, + NULL, 0); + + vfree(policy); +} + +/* + * selinux_ima_measure_state - Measure SELinux state and hash of policy + * + * @state: selinux state struct + */ +void selinux_ima_measure_state(struct selinux_state *state) +{ + lockdep_assert_not_held(&state->policy_mutex); + + mutex_lock(&state->policy_mutex); + selinux_ima_measure_state_locked(state); + mutex_unlock(&state->policy_mutex); +} diff --git a/security/selinux/include/audit.h b/security/selinux/include/audit.h index 073a3d34a0d2..406bceb90c6c 100644 --- a/security/selinux/include/audit.h +++ b/security/selinux/include/audit.h @@ -12,10 +12,13 @@ #ifndef _SELINUX_AUDIT_H #define _SELINUX_AUDIT_H +#include <linux/audit.h> +#include <linux/types.h> + /** * selinux_audit_rule_init - alloc/init an selinux audit rule structure. * @field: the field this rule refers to - * @op: the operater the rule uses + * @op: the operator the rule uses * @rulestr: the text "target" of the rule * @rule: pointer to the new rule structure returned via this * @@ -51,7 +54,7 @@ int selinux_audit_rule_match(u32 sid, u32 field, u32 op, void *rule); * @rule: rule to be checked * Returns 1 if there are selinux fields specified in the rule, 0 otherwise. */ -int selinux_audit_rule_known(struct audit_krule *krule); +int selinux_audit_rule_known(struct audit_krule *rule); #endif /* _SELINUX_AUDIT_H */ diff --git a/security/selinux/include/avc.h b/security/selinux/include/avc.h index cf4cc3ef959b..5525b94fd266 100644 --- a/security/selinux/include/avc.h +++ b/security/selinux/include/avc.h @@ -53,7 +53,7 @@ struct selinux_audit_data { u32 denied; int result; struct selinux_state *state; -}; +} __randomize_layout; /* * AVC operations @@ -104,6 +104,7 @@ int slow_avc_audit(struct selinux_state *state, /** * avc_audit - Audit the granting or denial of permissions. + * @state: SELinux state * @ssid: source security identifier * @tsid: target security identifier * @tclass: target security class @@ -111,7 +112,6 @@ int slow_avc_audit(struct selinux_state *state, * @avd: access vector decisions * @result: result from avc_has_perm_noaudit * @a: auxiliary audit data - * @flags: VFS walk flags * * Audit the granting or denial of permissions in accordance * with the policy. This function is typically called by @@ -127,16 +127,12 @@ static inline int avc_audit(struct selinux_state *state, u16 tclass, u32 requested, struct av_decision *avd, int result, - struct common_audit_data *a, - int flags) + struct common_audit_data *a) { u32 audited, denied; audited = avc_audit_required(requested, avd, result, 0, &denied); if (likely(!audited)) return 0; - /* fall back to ref-walk if we have to generate audit */ - if (flags & MAY_NOT_BLOCK) - return -ECHILD; return slow_avc_audit(state, ssid, tsid, tclass, requested, audited, denied, result, a); @@ -144,7 +140,6 @@ static inline int avc_audit(struct selinux_state *state, #define AVC_STRICT 1 /* Ignore permissive mode. */ #define AVC_EXTENDED_PERMS 2 /* update extended permissions */ -#define AVC_NONBLOCKING 4 /* non blocking */ int avc_has_perm_noaudit(struct selinux_state *state, u32 ssid, u32 tsid, u16 tclass, u32 requested, @@ -155,11 +150,6 @@ int avc_has_perm(struct selinux_state *state, u32 ssid, u32 tsid, u16 tclass, u32 requested, struct common_audit_data *auditdata); -int avc_has_perm_flags(struct selinux_state *state, - u32 ssid, u32 tsid, - u16 tclass, u32 requested, - struct common_audit_data *auditdata, - int flags); int avc_has_extended_perms(struct selinux_state *state, u32 ssid, u32 tsid, u16 tclass, u32 requested, diff --git a/security/selinux/include/avc_ss.h b/security/selinux/include/avc_ss.h index 88c384c5c09e..42912c917fd4 100644 --- a/security/selinux/include/avc_ss.h +++ b/security/selinux/include/avc_ss.h @@ -7,7 +7,7 @@ #ifndef _SELINUX_AVC_SS_H_ #define _SELINUX_AVC_SS_H_ -#include "flask.h" +#include <linux/types.h> struct selinux_avc; int avc_ss_reset(struct selinux_avc *avc, u32 seqno); @@ -18,7 +18,7 @@ struct security_class_mapping { const char *perms[sizeof(u32) * 8 + 1]; }; -extern struct security_class_mapping secclass_map[]; +extern const struct security_class_mapping secclass_map[]; #endif /* _SELINUX_AVC_SS_H_ */ diff --git a/security/selinux/include/classmap.h b/security/selinux/include/classmap.h index 986f3ac14282..a3c380775d41 100644 --- a/security/selinux/include/classmap.h +++ b/security/selinux/include/classmap.h @@ -27,9 +27,10 @@ "audit_control", "setfcap" #define COMMON_CAP2_PERMS "mac_override", "mac_admin", "syslog", \ - "wake_alarm", "block_suspend", "audit_read" + "wake_alarm", "block_suspend", "audit_read", "perfmon", "bpf", \ + "checkpoint_restore" -#if CAP_LAST_CAP > CAP_AUDIT_READ +#if CAP_LAST_CAP > CAP_CHECKPOINT_RESTORE #error New capability defined, please update COMMON_CAP2_PERMS. #endif @@ -37,7 +38,7 @@ * Note: The name for any socket class should be suffixed by "socket", * and doesn't contain more than one substr of "socket". */ -struct security_class_mapping secclass_map[] = { +const struct security_class_mapping secclass_map[] = { { "security", { "compute_av", "compute_create", "compute_member", "check_context", "load_policy", "compute_relabel", @@ -241,16 +242,23 @@ struct security_class_mapping secclass_map[] = { { "infiniband_endport", { "manage_subnet", NULL } }, { "bpf", - {"map_create", "map_read", "map_write", "prog_load", "prog_run"} }, + { "map_create", "map_read", "map_write", "prog_load", "prog_run", + NULL } }, { "xdp_socket", { COMMON_SOCK_PERMS, NULL } }, + { "mctp_socket", + { COMMON_SOCK_PERMS, NULL } }, { "perf_event", - {"open", "cpu", "kernel", "tracepoint", "read", "write"} }, - { "lockdown", - { "integrity", "confidentiality", NULL } }, + { "open", "cpu", "kernel", "tracepoint", "read", "write", NULL } }, + { "anon_inode", + { COMMON_FILE_PERMS, NULL } }, + { "io_uring", + { "override_creds", "sqpoll", "cmd", NULL } }, + { "user_namespace", + { "create", NULL } }, { NULL } }; -#if PF_MAX > 45 +#if PF_MAX > 46 #error New address family defined, please update secclass_map. #endif diff --git a/security/selinux/include/conditional.h b/security/selinux/include/conditional.h index 0ab316f61da0..b09343346e3f 100644 --- a/security/selinux/include/conditional.h +++ b/security/selinux/include/conditional.h @@ -13,13 +13,11 @@ #include "security.h" -int security_get_bools(struct selinux_state *state, - int *len, char ***names, int **values); +int security_get_bools(struct selinux_policy *policy, + u32 *len, char ***names, int **values); -int security_set_bools(struct selinux_state *state, - int len, int *values); +int security_set_bools(struct selinux_state *state, u32 len, int *values); -int security_get_bool_value(struct selinux_state *state, - int index); +int security_get_bool_value(struct selinux_state *state, u32 index); #endif diff --git a/security/selinux/include/ibpkey.h b/security/selinux/include/ibpkey.h index e6ac1d23320b..c992f83b0aae 100644 --- a/security/selinux/include/ibpkey.h +++ b/security/selinux/include/ibpkey.h @@ -14,6 +14,8 @@ #ifndef _SELINUX_IB_PKEY_H #define _SELINUX_IB_PKEY_H +#include <linux/types.h> + #ifdef CONFIG_SECURITY_INFINIBAND void sel_ib_pkey_flush(void); int sel_ib_pkey_sid(u64 subnet_prefix, u16 pkey, u32 *sid); diff --git a/security/selinux/include/ima.h b/security/selinux/include/ima.h new file mode 100644 index 000000000000..75ca92b4a462 --- /dev/null +++ b/security/selinux/include/ima.h @@ -0,0 +1,30 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (C) 2021 Microsoft Corporation + * + * Author: Lakshmi Ramasubramanian (nramas@linux.microsoft.com) + * + * Measure critical data structures maintainted by SELinux + * using IMA subsystem. + */ + +#ifndef _SELINUX_IMA_H_ +#define _SELINUX_IMA_H_ + +#include "security.h" + +#ifdef CONFIG_IMA +extern void selinux_ima_measure_state(struct selinux_state *selinux_state); +extern void selinux_ima_measure_state_locked( + struct selinux_state *selinux_state); +#else +static inline void selinux_ima_measure_state(struct selinux_state *selinux_state) +{ +} +static inline void selinux_ima_measure_state_locked( + struct selinux_state *selinux_state) +{ +} +#endif + +#endif /* _SELINUX_IMA_H_ */ diff --git a/security/selinux/include/initial_sid_to_string.h b/security/selinux/include/initial_sid_to_string.h index 4f93f697f71c..60820517aa43 100644 --- a/security/selinux/include/initial_sid_to_string.h +++ b/security/selinux/include/initial_sid_to_string.h @@ -1,34 +1,32 @@ /* SPDX-License-Identifier: GPL-2.0 */ -/* This file is automatically generated. Do not edit. */ -static const char *initial_sid_to_string[] = -{ - "null", - "kernel", - "security", - "unlabeled", - "fs", - "file", - "file_labels", - "init", - "any_socket", - "port", - "netif", - "netmsg", - "node", - "igmp_packet", - "icmp_socket", - "tcp_socket", - "sysctl_modprobe", - "sysctl", - "sysctl_fs", - "sysctl_kernel", - "sysctl_net", - "sysctl_net_unix", - "sysctl_vm", - "sysctl_dev", - "kmod", - "policy", - "scmp_packet", - "devnull", +static const char *const initial_sid_to_string[] = { + NULL, + "kernel", + "security", + "unlabeled", + NULL, + "file", + NULL, + NULL, + "any_socket", + "port", + "netif", + "netmsg", + "node", + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + "devnull", }; diff --git a/security/selinux/include/netlabel.h b/security/selinux/include/netlabel.h index d30d8d7cdc9c..4d0456d3d459 100644 --- a/security/selinux/include/netlabel.h +++ b/security/selinux/include/netlabel.h @@ -39,7 +39,7 @@ int selinux_netlbl_skbuff_getsid(struct sk_buff *skb, int selinux_netlbl_skbuff_setsid(struct sk_buff *skb, u16 family, u32 sid); -int selinux_netlbl_sctp_assoc_request(struct sctp_endpoint *ep, +int selinux_netlbl_sctp_assoc_request(struct sctp_association *asoc, struct sk_buff *skb); int selinux_netlbl_inet_conn_request(struct request_sock *req, u16 family); void selinux_netlbl_inet_csk_clone(struct sock *sk, u16 family); @@ -98,13 +98,7 @@ static inline int selinux_netlbl_skbuff_setsid(struct sk_buff *skb, return 0; } -static inline int selinux_netlbl_conn_setsid(struct sock *sk, - struct sockaddr *addr) -{ - return 0; -} - -static inline int selinux_netlbl_sctp_assoc_request(struct sctp_endpoint *ep, +static inline int selinux_netlbl_sctp_assoc_request(struct sctp_association *asoc, struct sk_buff *skb) { return 0; diff --git a/security/selinux/include/netnode.h b/security/selinux/include/netnode.h index e3f784a85840..9b8b655a8cd3 100644 --- a/security/selinux/include/netnode.h +++ b/security/selinux/include/netnode.h @@ -17,6 +17,8 @@ #ifndef _SELINUX_NETNODE_H #define _SELINUX_NETNODE_H +#include <linux/types.h> + void sel_netnode_flush(void); int sel_netnode_sid(void *addr, u16 family, u32 *sid); diff --git a/security/selinux/include/netport.h b/security/selinux/include/netport.h index 31bc16e29cd1..9096a8289948 100644 --- a/security/selinux/include/netport.h +++ b/security/selinux/include/netport.h @@ -16,6 +16,8 @@ #ifndef _SELINUX_NETPORT_H #define _SELINUX_NETPORT_H +#include <linux/types.h> + void sel_netport_flush(void); int sel_netport_sid(u8 protocol, u16 pnum, u32 *sid); diff --git a/security/selinux/include/objsec.h b/security/selinux/include/objsec.h index 330b7b6d44e0..2953132408bf 100644 --- a/security/selinux/include/objsec.h +++ b/security/selinux/include/objsec.h @@ -61,7 +61,6 @@ struct file_security_struct { }; struct superblock_security_struct { - struct super_block *sb; /* back pointer to sb object */ u32 sid; /* SID of file system superblock */ u32 def_sid; /* default SID for labeling */ u32 mntpoint_sid; /* SECURITY_FS_USE_MNTPOINT context for files */ @@ -189,4 +188,10 @@ static inline u32 current_sid(void) return tsec->sid; } +static inline struct superblock_security_struct *selinux_superblock( + const struct super_block *superblock) +{ + return superblock->s_security + selinux_blob_sizes.lbs_superblock; +} + #endif /* _SELINUX_OBJSEC_H_ */ diff --git a/security/selinux/include/policycap.h b/security/selinux/include/policycap.h new file mode 100644 index 000000000000..f35d3458e71d --- /dev/null +++ b/security/selinux/include/policycap.h @@ -0,0 +1,21 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef _SELINUX_POLICYCAP_H_ +#define _SELINUX_POLICYCAP_H_ + +/* Policy capabilities */ +enum { + POLICYDB_CAP_NETPEER, + POLICYDB_CAP_OPENPERM, + POLICYDB_CAP_EXTSOCKCLASS, + POLICYDB_CAP_ALWAYSNETWORK, + POLICYDB_CAP_CGROUPSECLABEL, + POLICYDB_CAP_NNP_NOSUID_TRANSITION, + POLICYDB_CAP_GENFS_SECLABEL_SYMLINKS, + POLICYDB_CAP_IOCTL_SKIP_CLOEXEC, + __POLICYDB_CAP_MAX +}; +#define POLICYDB_CAP_MAX (__POLICYDB_CAP_MAX - 1) + +extern const char *const selinux_policycap_names[__POLICYDB_CAP_MAX]; + +#endif /* _SELINUX_POLICYCAP_H_ */ diff --git a/security/selinux/include/policycap_names.h b/security/selinux/include/policycap_names.h new file mode 100644 index 000000000000..2a87fc3702b8 --- /dev/null +++ b/security/selinux/include/policycap_names.h @@ -0,0 +1,19 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef _SELINUX_POLICYCAP_NAMES_H_ +#define _SELINUX_POLICYCAP_NAMES_H_ + +#include "policycap.h" + +/* Policy capability names */ +const char *const selinux_policycap_names[__POLICYDB_CAP_MAX] = { + "network_peer_controls", + "open_perms", + "extended_socket_class", + "always_check_network", + "cgroup_seclabel", + "nnp_nosuid_transition", + "genfs_seclabel_symlinks", + "ioctl_skip_cloexec" +}; + +#endif /* _SELINUX_POLICYCAP_NAMES_H_ */ diff --git a/security/selinux/include/security.h b/security/selinux/include/security.h index a39f9565d80b..393aff41d3ef 100644 --- a/security/selinux/include/security.h +++ b/security/selinux/include/security.h @@ -13,9 +13,13 @@ #include <linux/dcache.h> #include <linux/magic.h> #include <linux/types.h> +#include <linux/rcupdate.h> #include <linux/refcount.h> #include <linux/workqueue.h> +#include <linux/delay.h> +#include <linux/printk.h> #include "flask.h" +#include "policycap.h" #define SECSID_NULL 0x00000000 /* unspecified SID */ #define SECSID_WILD 0xffffffff /* wildcard SID */ @@ -41,10 +45,11 @@ #define POLICYDB_VERSION_XPERMS_IOCTL 30 #define POLICYDB_VERSION_INFINIBAND 31 #define POLICYDB_VERSION_GLBLUB 32 +#define POLICYDB_VERSION_COMP_FTRANS 33 /* compressed filename transitions */ /* Range of policy versions we understand*/ #define POLICYDB_VERSION_MIN POLICYDB_VERSION_BASE -#define POLICYDB_VERSION_MAX POLICYDB_VERSION_GLBLUB +#define POLICYDB_VERSION_MAX POLICYDB_VERSION_COMP_FTRANS /* Mask for just the mount related flags */ #define SE_MNTMASK 0x0f @@ -71,20 +76,6 @@ struct netlbl_lsm_secattr; extern int selinux_enabled_boot; -/* Policy capabilities */ -enum { - POLICYDB_CAPABILITY_NETPEER, - POLICYDB_CAPABILITY_OPENPERM, - POLICYDB_CAPABILITY_EXTSOCKCLASS, - POLICYDB_CAPABILITY_ALWAYSNETWORK, - POLICYDB_CAPABILITY_CGROUPSECLABEL, - POLICYDB_CAPABILITY_NNP_NOSUID_TRANSITION, - __POLICYDB_CAPABILITY_MAX -}; -#define POLICYDB_CAPABILITY_MAX (__POLICYDB_CAPABILITY_MAX - 1) - -extern const char *selinux_policycap_names[__POLICYDB_CAPABILITY_MAX]; - /* * type_datum properties * available at the kernel policy version >= POLICYDB_VERSION_BOUNDARY @@ -96,7 +87,7 @@ extern const char *selinux_policycap_names[__POLICYDB_CAPABILITY_MAX]; #define POLICYDB_BOUNDS_MAXDEPTH 4 struct selinux_avc; -struct selinux_ss; +struct selinux_policy; struct selinux_state { #ifdef CONFIG_SECURITY_SELINUX_DISABLE @@ -107,12 +98,16 @@ struct selinux_state { #endif bool checkreqprot; bool initialized; - bool policycap[__POLICYDB_CAPABILITY_MAX]; + bool policycap[__POLICYDB_CAP_MAX]; + + struct page *status_page; + struct mutex status_lock; + struct selinux_avc *avc; - struct selinux_ss *ss; + struct selinux_policy __rcu *policy; + struct mutex policy_mutex; } __randomize_layout; -void selinux_ss_init(struct selinux_ss **ss); void selinux_avc_init(struct selinux_avc **avc); extern struct selinux_state selinux_state; @@ -150,6 +145,18 @@ static inline void enforcing_set(struct selinux_state *state, bool value) } #endif +static inline bool checkreqprot_get(const struct selinux_state *state) +{ + return READ_ONCE(state->checkreqprot); +} + +static inline void checkreqprot_set(struct selinux_state *state, bool value) +{ + if (value) + pr_err("SELinux: https://github.com/SELinuxProject/selinux-kernel/wiki/DEPRECATE-checkreqprot\n"); + WRITE_ONCE(state->checkreqprot, value); +} + #ifdef CONFIG_SECURITY_SELINUX_DISABLE static inline bool selinux_disabled(struct selinux_state *state) { @@ -171,51 +178,77 @@ static inline bool selinux_policycap_netpeer(void) { struct selinux_state *state = &selinux_state; - return state->policycap[POLICYDB_CAPABILITY_NETPEER]; + return READ_ONCE(state->policycap[POLICYDB_CAP_NETPEER]); } static inline bool selinux_policycap_openperm(void) { struct selinux_state *state = &selinux_state; - return state->policycap[POLICYDB_CAPABILITY_OPENPERM]; + return READ_ONCE(state->policycap[POLICYDB_CAP_OPENPERM]); } static inline bool selinux_policycap_extsockclass(void) { struct selinux_state *state = &selinux_state; - return state->policycap[POLICYDB_CAPABILITY_EXTSOCKCLASS]; + return READ_ONCE(state->policycap[POLICYDB_CAP_EXTSOCKCLASS]); } static inline bool selinux_policycap_alwaysnetwork(void) { struct selinux_state *state = &selinux_state; - return state->policycap[POLICYDB_CAPABILITY_ALWAYSNETWORK]; + return READ_ONCE(state->policycap[POLICYDB_CAP_ALWAYSNETWORK]); } static inline bool selinux_policycap_cgroupseclabel(void) { struct selinux_state *state = &selinux_state; - return state->policycap[POLICYDB_CAPABILITY_CGROUPSECLABEL]; + return READ_ONCE(state->policycap[POLICYDB_CAP_CGROUPSECLABEL]); } static inline bool selinux_policycap_nnp_nosuid_transition(void) { struct selinux_state *state = &selinux_state; - return state->policycap[POLICYDB_CAPABILITY_NNP_NOSUID_TRANSITION]; + return READ_ONCE(state->policycap[POLICYDB_CAP_NNP_NOSUID_TRANSITION]); } +static inline bool selinux_policycap_genfs_seclabel_symlinks(void) +{ + struct selinux_state *state = &selinux_state; + + return READ_ONCE(state->policycap[POLICYDB_CAP_GENFS_SECLABEL_SYMLINKS]); +} + +static inline bool selinux_policycap_ioctl_skip_cloexec(void) +{ + struct selinux_state *state = &selinux_state; + + return READ_ONCE(state->policycap[POLICYDB_CAP_IOCTL_SKIP_CLOEXEC]); +} + +struct selinux_policy_convert_data; + +struct selinux_load_state { + struct selinux_policy *policy; + struct selinux_policy_convert_data *convert_data; +}; + int security_mls_enabled(struct selinux_state *state); int security_load_policy(struct selinux_state *state, - void *data, size_t len); + void *data, size_t len, + struct selinux_load_state *load_state); +void selinux_policy_commit(struct selinux_state *state, + struct selinux_load_state *load_state); +void selinux_policy_cancel(struct selinux_state *state, + struct selinux_load_state *load_state); int security_read_policy(struct selinux_state *state, void **data, size_t *len); -size_t security_policydb_len(struct selinux_state *state); - +int security_read_state_kernel(struct selinux_state *state, + void **data, size_t *len); int security_policycap_supported(struct selinux_state *state, unsigned int req_cap); @@ -232,8 +265,8 @@ struct av_decision { #define XPERMS_AUDITALLOW 2 #define XPERMS_DONTAUDIT 4 -#define security_xperm_set(perms, x) (perms[x >> 5] |= 1 << (x & 0x1f)) -#define security_xperm_test(perms, x) (1 & (perms[x >> 5] >> (x & 0x1f))) +#define security_xperm_set(perms, x) ((perms)[(x) >> 5] |= 1 << ((x) & 0x1f)) +#define security_xperm_test(perms, x) (1 & ((perms)[(x) >> 5] >> ((x) & 0x1f))) struct extended_perms_data { u32 p[8]; }; @@ -345,9 +378,9 @@ int security_net_peersid_resolve(struct selinux_state *state, u32 xfrm_sid, u32 *peer_sid); -int security_get_classes(struct selinux_state *state, +int security_get_classes(struct selinux_policy *policy, char ***classes, int *nclasses); -int security_get_permissions(struct selinux_state *state, +int security_get_permissions(struct selinux_policy *policy, char *class, char ***perms, int *nperms); int security_get_reject_unknown(struct selinux_state *state); int security_get_allow_unknown(struct selinux_state *state); @@ -364,7 +397,11 @@ int security_get_allow_unknown(struct selinux_state *state); int security_fs_use(struct selinux_state *state, struct super_block *sb); int security_genfs_sid(struct selinux_state *state, - const char *fstype, char *name, u16 sclass, + const char *fstype, const char *path, u16 sclass, + u32 *sid); + +int selinux_policy_genfs_sid(struct selinux_policy *policy, + const char *fstype, const char *path, u16 sclass, u32 *sid); #ifdef CONFIG_NETLABEL @@ -400,7 +437,7 @@ extern struct page *selinux_kernel_status_page(struct selinux_state *state); #define SELINUX_KERNEL_STATUS_VERSION 1 struct selinux_kernel_status { - u32 version; /* version number of thie structure */ + u32 version; /* version number of the structure */ u32 sequence; /* sequence number of seqlock logic */ u32 enforcing; /* current setting of enforcing mode */ u32 policyload; /* times of policy reloaded */ @@ -418,7 +455,6 @@ extern void selinux_complete_init(void); extern int selinux_disable(struct selinux_state *state); extern void exit_sel_fs(void); extern struct path selinux_null; -extern struct vfsmount *selinuxfs_mount; extern void selnl_notify_setenforce(int val); extern void selnl_notify_policyload(u32 seqno); extern int selinux_nlmsg_lookup(u16 sclass, u16 nlmsg_type, u32 *perm); diff --git a/security/selinux/include/xfrm.h b/security/selinux/include/xfrm.h index a0b465316292..c75839860200 100644 --- a/security/selinux/include/xfrm.h +++ b/security/selinux/include/xfrm.h @@ -8,7 +8,9 @@ #ifndef _SELINUX_XFRM_H_ #define _SELINUX_XFRM_H_ +#include <linux/lsm_audit.h> #include <net/flow.h> +#include <net/xfrm.h> int selinux_xfrm_policy_alloc(struct xfrm_sec_ctx **ctxp, struct xfrm_user_sec_ctx *uctx, @@ -23,10 +25,10 @@ int selinux_xfrm_state_alloc_acquire(struct xfrm_state *x, struct xfrm_sec_ctx *polsec, u32 secid); void selinux_xfrm_state_free(struct xfrm_state *x); int selinux_xfrm_state_delete(struct xfrm_state *x); -int selinux_xfrm_policy_lookup(struct xfrm_sec_ctx *ctx, u32 fl_secid, u8 dir); +int selinux_xfrm_policy_lookup(struct xfrm_sec_ctx *ctx, u32 fl_secid); int selinux_xfrm_state_pol_flow_match(struct xfrm_state *x, struct xfrm_policy *xp, - const struct flowi *fl); + const struct flowi_common *flic); #ifdef CONFIG_SECURITY_NETWORK_XFRM extern atomic_t selinux_xfrm_refcount; diff --git a/security/selinux/netif.c b/security/selinux/netif.c index 15b8c1bcd7d0..1ab03efe7494 100644 --- a/security/selinux/netif.c +++ b/security/selinux/netif.c @@ -36,7 +36,6 @@ struct sel_netif { }; static u32 sel_netif_total; -static LIST_HEAD(sel_netif_list); static DEFINE_SPINLOCK(sel_netif_lock); static struct list_head sel_netif_hash[SEL_NETIF_HASH_SIZE]; @@ -124,7 +123,7 @@ static void sel_netif_destroy(struct sel_netif *netif) * @sid: interface SID * * Description: - * This function determines the SID of a network interface by quering the + * This function determines the SID of a network interface by querying the * security policy. The result is added to the network interface table to * speedup future queries. Returns zero on success, negative values on * failure. diff --git a/security/selinux/netlabel.c b/security/selinux/netlabel.c index abaab7683840..1321f15799e2 100644 --- a/security/selinux/netlabel.c +++ b/security/selinux/netlabel.c @@ -29,6 +29,7 @@ /** * selinux_netlbl_sidlookup_cached - Cache a SID lookup * @skb: the packet + * @family: the packet's address family * @secattr: the NetLabel security attributes * @sid: the SID * @@ -128,6 +129,7 @@ void selinux_netlbl_cache_invalidate(void) /** * selinux_netlbl_err - Handle a NetLabel packet error * @skb: the packet + * @family: the packet's address family * @error: the error code * @gateway: true if host is acting as a gateway, false otherwise * @@ -160,7 +162,6 @@ void selinux_netlbl_sk_security_free(struct sk_security_struct *sksec) /** * selinux_netlbl_sk_security_reset - Reset the NetLabel fields * @sksec: the sk_security_struct - * @family: the socket family * * Description: * Called when the NetLabel state of a sk_security_struct needs to be reset. @@ -260,30 +261,30 @@ skbuff_setsid_return: /** * selinux_netlbl_sctp_assoc_request - Label an incoming sctp association. - * @ep: incoming association endpoint. + * @asoc: incoming association. * @skb: the packet. * * Description: - * A new incoming connection is represented by @ep, ...... + * A new incoming connection is represented by @asoc, ...... * Returns zero on success, negative values on failure. * */ -int selinux_netlbl_sctp_assoc_request(struct sctp_endpoint *ep, +int selinux_netlbl_sctp_assoc_request(struct sctp_association *asoc, struct sk_buff *skb) { int rc; struct netlbl_lsm_secattr secattr; - struct sk_security_struct *sksec = ep->base.sk->sk_security; + struct sk_security_struct *sksec = asoc->base.sk->sk_security; struct sockaddr_in addr4; struct sockaddr_in6 addr6; - if (ep->base.sk->sk_family != PF_INET && - ep->base.sk->sk_family != PF_INET6) + if (asoc->base.sk->sk_family != PF_INET && + asoc->base.sk->sk_family != PF_INET6) return 0; netlbl_secattr_init(&secattr); rc = security_netlbl_sid_to_secattr(&selinux_state, - ep->secid, &secattr); + asoc->secid, &secattr); if (rc != 0) goto assoc_request_return; @@ -293,11 +294,11 @@ int selinux_netlbl_sctp_assoc_request(struct sctp_endpoint *ep, if (ip_hdr(skb)->version == 4) { addr4.sin_family = AF_INET; addr4.sin_addr.s_addr = ip_hdr(skb)->saddr; - rc = netlbl_conn_setattr(ep->base.sk, (void *)&addr4, &secattr); + rc = netlbl_conn_setattr(asoc->base.sk, (void *)&addr4, &secattr); } else if (IS_ENABLED(CONFIG_IPV6) && ip_hdr(skb)->version == 6) { addr6.sin6_family = AF_INET6; addr6.sin6_addr = ipv6_hdr(skb)->saddr; - rc = netlbl_conn_setattr(ep->base.sk, (void *)&addr6, &secattr); + rc = netlbl_conn_setattr(asoc->base.sk, (void *)&addr6, &secattr); } else { rc = -EAFNOSUPPORT; } @@ -313,6 +314,7 @@ assoc_request_return: /** * selinux_netlbl_inet_conn_request - Label an incoming stream connection * @req: incoming connection request socket + * @family: the request socket's address family * * Description: * A new incoming connection request is represented by @req, we need to label @@ -343,6 +345,7 @@ inet_conn_request_return: /** * selinux_netlbl_inet_csk_clone - Initialize the newly created sock * @sk: the new sock + * @family: the sock's address family * * Description: * A new connection has been established using @sk, we've already labeled the @@ -378,7 +381,7 @@ void selinux_netlbl_sctp_sk_clone(struct sock *sk, struct sock *newsk) /** * selinux_netlbl_socket_post_create - Label a socket using NetLabel - * @sock: the socket to label + * @sk: the sock to label * @family: protocol family * * Description: diff --git a/security/selinux/netlink.c b/security/selinux/netlink.c index 621e2e9cd6a1..1760aee712fd 100644 --- a/security/selinux/netlink.c +++ b/security/selinux/netlink.c @@ -19,7 +19,7 @@ #include "security.h" -static struct sock *selnl; +static struct sock *selnl __ro_after_init; static int selnl_msglen(int msgtype) { diff --git a/security/selinux/netnode.c b/security/selinux/netnode.c index dff587d1e164..0ac7df9a9367 100644 --- a/security/selinux/netnode.c +++ b/security/selinux/netnode.c @@ -54,7 +54,6 @@ struct sel_netnode { * if this becomes a problem we can always add a hash table for each address * family later */ -static LIST_HEAD(sel_netnode_list); static DEFINE_SPINLOCK(sel_netnode_lock); static struct sel_netnode_bkt sel_netnode_hash[SEL_NETNODE_HASH_SIZE]; @@ -108,7 +107,7 @@ static struct sel_netnode *sel_netnode_find(const void *addr, u16 family) switch (family) { case PF_INET: - idx = sel_netnode_hashfn_ipv4(*(__be32 *)addr); + idx = sel_netnode_hashfn_ipv4(*(const __be32 *)addr); break; case PF_INET6: idx = sel_netnode_hashfn_ipv6(addr); @@ -122,7 +121,7 @@ static struct sel_netnode *sel_netnode_find(const void *addr, u16 family) if (node->nsec.family == family) switch (family) { case PF_INET: - if (node->nsec.addr.ipv4 == *(__be32 *)addr) + if (node->nsec.addr.ipv4 == *(const __be32 *)addr) return node; break; case PF_INET6: @@ -165,8 +164,9 @@ static void sel_netnode_insert(struct sel_netnode *node) if (sel_netnode_hash[idx].size == SEL_NETNODE_HASH_BKT_LIMIT) { struct sel_netnode *tail; tail = list_entry( - rcu_dereference_protected(sel_netnode_hash[idx].list.prev, - lockdep_is_held(&sel_netnode_lock)), + rcu_dereference_protected( + list_tail_rcu(&sel_netnode_hash[idx].list), + lockdep_is_held(&sel_netnode_lock)), struct sel_netnode, list); list_del_rcu(&tail->list); kfree_rcu(tail, rcu); @@ -181,7 +181,7 @@ static void sel_netnode_insert(struct sel_netnode *node) * @sid: node SID * * Description: - * This function determines the SID of a network address by quering the + * This function determines the SID of a network address by querying the * security policy. The result is added to the network address table to * speedup future queries. Returns zero on success, negative values on * failure. diff --git a/security/selinux/netport.c b/security/selinux/netport.c index de727f7489b7..8eec6347cf01 100644 --- a/security/selinux/netport.c +++ b/security/selinux/netport.c @@ -53,7 +53,6 @@ struct sel_netport { * if this becomes a problem we can always add a hash table for each address * family later */ -static LIST_HEAD(sel_netport_list); static DEFINE_SPINLOCK(sel_netport_lock); static struct sel_netport_bkt sel_netport_hash[SEL_NETPORT_HASH_SIZE]; @@ -74,7 +73,7 @@ static unsigned int sel_netport_hashfn(u16 pnum) /** * sel_netport_find - Search for a port record * @protocol: protocol - * @port: pnum + * @pnum: port * * Description: * Search the network port table and return the matching record. If an entry @@ -114,7 +113,7 @@ static void sel_netport_insert(struct sel_netport *port) struct sel_netport *tail; tail = list_entry( rcu_dereference_protected( - sel_netport_hash[idx].list.prev, + list_tail_rcu(&sel_netport_hash[idx].list), lockdep_is_held(&sel_netport_lock)), struct sel_netport, list); list_del_rcu(&tail->list); @@ -130,7 +129,7 @@ static void sel_netport_insert(struct sel_netport *port) * @sid: port SID * * Description: - * This function determines the SID of a network port by quering the security + * This function determines the SID of a network port by querying the security * policy. The result is added to the network port table to speedup future * queries. Returns zero on success, negative values on failure. * diff --git a/security/selinux/nlmsgtab.c b/security/selinux/nlmsgtab.c index b69231918686..2ee7b4ed43ef 100644 --- a/security/selinux/nlmsgtab.c +++ b/security/selinux/nlmsgtab.c @@ -25,8 +25,7 @@ struct nlmsg_perm { u32 perm; }; -static const struct nlmsg_perm nlmsg_route_perms[] = -{ +static const struct nlmsg_perm nlmsg_route_perms[] = { { RTM_NEWLINK, NETLINK_ROUTE_SOCKET__NLMSG_WRITE }, { RTM_DELLINK, NETLINK_ROUTE_SOCKET__NLMSG_WRITE }, { RTM_GETLINK, NETLINK_ROUTE_SOCKET__NLMSG_READ }, @@ -76,6 +75,7 @@ static const struct nlmsg_perm nlmsg_route_perms[] = { RTM_GETNSID, NETLINK_ROUTE_SOCKET__NLMSG_READ }, { RTM_NEWSTATS, NETLINK_ROUTE_SOCKET__NLMSG_READ }, { RTM_GETSTATS, NETLINK_ROUTE_SOCKET__NLMSG_READ }, + { RTM_SETSTATS, NETLINK_ROUTE_SOCKET__NLMSG_WRITE }, { RTM_NEWCACHEREPORT, NETLINK_ROUTE_SOCKET__NLMSG_READ }, { RTM_NEWCHAIN, NETLINK_ROUTE_SOCKET__NLMSG_WRITE }, { RTM_DELCHAIN, NETLINK_ROUTE_SOCKET__NLMSG_WRITE }, @@ -88,18 +88,22 @@ static const struct nlmsg_perm nlmsg_route_perms[] = { RTM_NEWVLAN, NETLINK_ROUTE_SOCKET__NLMSG_WRITE }, { RTM_DELVLAN, NETLINK_ROUTE_SOCKET__NLMSG_WRITE }, { RTM_GETVLAN, NETLINK_ROUTE_SOCKET__NLMSG_READ }, + { RTM_NEWNEXTHOPBUCKET, NETLINK_ROUTE_SOCKET__NLMSG_WRITE }, + { RTM_DELNEXTHOPBUCKET, NETLINK_ROUTE_SOCKET__NLMSG_WRITE }, + { RTM_GETNEXTHOPBUCKET, NETLINK_ROUTE_SOCKET__NLMSG_READ }, + { RTM_NEWTUNNEL, NETLINK_ROUTE_SOCKET__NLMSG_WRITE }, + { RTM_DELTUNNEL, NETLINK_ROUTE_SOCKET__NLMSG_WRITE }, + { RTM_GETTUNNEL, NETLINK_ROUTE_SOCKET__NLMSG_READ }, }; -static const struct nlmsg_perm nlmsg_tcpdiag_perms[] = -{ +static const struct nlmsg_perm nlmsg_tcpdiag_perms[] = { { TCPDIAG_GETSOCK, NETLINK_TCPDIAG_SOCKET__NLMSG_READ }, { DCCPDIAG_GETSOCK, NETLINK_TCPDIAG_SOCKET__NLMSG_READ }, { SOCK_DIAG_BY_FAMILY, NETLINK_TCPDIAG_SOCKET__NLMSG_READ }, { SOCK_DESTROY, NETLINK_TCPDIAG_SOCKET__NLMSG_WRITE }, }; -static const struct nlmsg_perm nlmsg_xfrm_perms[] = -{ +static const struct nlmsg_perm nlmsg_xfrm_perms[] = { { XFRM_MSG_NEWSA, NETLINK_XFRM_SOCKET__NLMSG_WRITE }, { XFRM_MSG_DELSA, NETLINK_XFRM_SOCKET__NLMSG_WRITE }, { XFRM_MSG_GETSA, NETLINK_XFRM_SOCKET__NLMSG_READ }, @@ -123,10 +127,11 @@ static const struct nlmsg_perm nlmsg_xfrm_perms[] = { XFRM_MSG_NEWSPDINFO, NETLINK_XFRM_SOCKET__NLMSG_WRITE }, { XFRM_MSG_GETSPDINFO, NETLINK_XFRM_SOCKET__NLMSG_READ }, { XFRM_MSG_MAPPING, NETLINK_XFRM_SOCKET__NLMSG_READ }, + { XFRM_MSG_SETDEFAULT, NETLINK_XFRM_SOCKET__NLMSG_WRITE }, + { XFRM_MSG_GETDEFAULT, NETLINK_XFRM_SOCKET__NLMSG_READ }, }; -static const struct nlmsg_perm nlmsg_audit_perms[] = -{ +static const struct nlmsg_perm nlmsg_audit_perms[] = { { AUDIT_GET, NETLINK_AUDIT_SOCKET__NLMSG_READ }, { AUDIT_SET, NETLINK_AUDIT_SOCKET__NLMSG_WRITE }, { AUDIT_LIST, NETLINK_AUDIT_SOCKET__NLMSG_READPRIV }, @@ -171,7 +176,7 @@ int selinux_nlmsg_lookup(u16 sclass, u16 nlmsg_type, u32 *perm) * structures at the top of this file with the new mappings * before updating the BUILD_BUG_ON() macro! */ - BUILD_BUG_ON(RTM_MAX != (RTM_NEWVLAN + 3)); + BUILD_BUG_ON(RTM_MAX != (RTM_NEWTUNNEL + 3)); err = nlmsg_perm(nlmsg_type, perm, nlmsg_route_perms, sizeof(nlmsg_route_perms)); break; @@ -186,7 +191,7 @@ int selinux_nlmsg_lookup(u16 sclass, u16 nlmsg_type, u32 *perm) * structures at the top of this file with the new mappings * before updating the BUILD_BUG_ON() macro! */ - BUILD_BUG_ON(XFRM_MSG_MAX != XFRM_MSG_MAPPING); + BUILD_BUG_ON(XFRM_MSG_MAX != XFRM_MSG_GETDEFAULT); err = nlmsg_perm(nlmsg_type, perm, nlmsg_xfrm_perms, sizeof(nlmsg_xfrm_perms)); break; diff --git a/security/selinux/selinuxfs.c b/security/selinux/selinuxfs.c index 79c710911a3c..a00d19139436 100644 --- a/security/selinux/selinuxfs.c +++ b/security/selinux/selinuxfs.c @@ -20,6 +20,7 @@ #include <linux/fs_context.h> #include <linux/mount.h> #include <linux/mutex.h> +#include <linux/namei.h> #include <linux/init.h> #include <linux/string.h> #include <linux/security.h> @@ -40,6 +41,7 @@ #include "security.h" #include "objsec.h" #include "conditional.h" +#include "ima.h" enum sel_inos { SEL_ROOT_INO = 2, @@ -69,12 +71,11 @@ struct selinux_fs_info { struct dentry *bool_dir; unsigned int bool_num; char **bool_pending_names; - unsigned int *bool_pending_values; + int *bool_pending_values; struct dentry *class_dir; unsigned long last_class_ino; bool policy_opened; struct dentry *policycap_dir; - struct mutex mutex; unsigned long last_ino; struct selinux_state *state; struct super_block *sb; @@ -88,7 +89,6 @@ static int selinux_fs_info_create(struct super_block *sb) if (!fsi) return -ENOMEM; - mutex_init(&fsi->mutex); fsi->last_ino = SEL_INO_NEXT - 1; fsi->state = &selinux_state; fsi->sb = sb; @@ -117,6 +117,10 @@ static void selinux_fs_info_free(struct super_block *sb) #define SEL_POLICYCAP_INO_OFFSET 0x08000000 #define SEL_INO_MASK 0x00ffffff +#define BOOL_DIR_NAME "booleans" +#define CLASS_DIR_NAME "class" +#define POLICYCAP_DIR_NAME "policy_capabilities" + #define TMPBUFLEN 12 static ssize_t sel_read_enforce(struct file *filp, char __user *buf, size_t count, loff_t *ppos) @@ -179,6 +183,8 @@ static ssize_t sel_write_enforce(struct file *file, const char __user *buf, selinux_status_update_setenforce(state, new_value); if (!new_value) call_blocking_lsm_notifier(LSM_POLICY_CHANGE, NULL); + + selinux_ima_measure_state(state); } length = count; out: @@ -287,6 +293,8 @@ static ssize_t sel_write_disable(struct file *file, const char __user *buf, * kernel releases until eventually it is removed */ pr_err("SELinux: Runtime disable is deprecated, use selinux=0 on the kernel cmdline.\n"); + pr_err("SELinux: https://github.com/SELinuxProject/selinux-kernel/wiki/DEPRECATE-runtime-disable\n"); + ssleep(5); if (count >= PAGE_SIZE) return -ENOMEM; @@ -346,14 +354,24 @@ static const struct file_operations sel_policyvers_ops = { }; /* declaration for sel_write_load */ -static int sel_make_bools(struct selinux_fs_info *fsi); -static int sel_make_classes(struct selinux_fs_info *fsi); -static int sel_make_policycap(struct selinux_fs_info *fsi); +static int sel_make_bools(struct selinux_policy *newpolicy, struct dentry *bool_dir, + unsigned int *bool_num, char ***bool_pending_names, + int **bool_pending_values); +static int sel_make_classes(struct selinux_policy *newpolicy, + struct dentry *class_dir, + unsigned long *last_class_ino); /* declaration for sel_make_class_dirs */ static struct dentry *sel_make_dir(struct dentry *dir, const char *name, unsigned long *ino); +/* declaration for sel_make_policy_nodes */ +static struct dentry *sel_make_disconnected_dir(struct super_block *sb, + unsigned long *ino); + +/* declaration for sel_make_policy_nodes */ +static void sel_remove_entries(struct dentry *de); + static ssize_t sel_read_mls(struct file *filp, char __user *buf, size_t count, loff_t *ppos) { @@ -385,7 +403,7 @@ static int sel_open_policy(struct inode *inode, struct file *filp) BUG_ON(filp->private_data); - mutex_lock(&fsi->mutex); + mutex_lock(&fsi->state->policy_mutex); rc = avc_has_perm(&selinux_state, current_sid(), SECINITSID_SECURITY, @@ -402,25 +420,25 @@ static int sel_open_policy(struct inode *inode, struct file *filp) if (!plm) goto err; - if (i_size_read(inode) != security_policydb_len(state)) { - inode_lock(inode); - i_size_write(inode, security_policydb_len(state)); - inode_unlock(inode); - } - rc = security_read_policy(state, &plm->data, &plm->len); if (rc) goto err; + if ((size_t)i_size_read(inode) != plm->len) { + inode_lock(inode); + i_size_write(inode, plm->len); + inode_unlock(inode); + } + fsi->policy_opened = 1; filp->private_data = plm; - mutex_unlock(&fsi->mutex); + mutex_unlock(&fsi->state->policy_mutex); return 0; err: - mutex_unlock(&fsi->mutex); + mutex_unlock(&fsi->state->policy_mutex); if (plm) vfree(plm->data); @@ -508,29 +526,90 @@ static const struct file_operations sel_policy_ops = { .llseek = generic_file_llseek, }; -static int sel_make_policy_nodes(struct selinux_fs_info *fsi) +static void sel_remove_old_bool_data(unsigned int bool_num, char **bool_names, + int *bool_values) { - int ret; + u32 i; - ret = sel_make_bools(fsi); - if (ret) { - pr_err("SELinux: failed to load policy booleans\n"); - return ret; - } + /* bool_dir cleanup */ + for (i = 0; i < bool_num; i++) + kfree(bool_names[i]); + kfree(bool_names); + kfree(bool_values); +} - ret = sel_make_classes(fsi); - if (ret) { - pr_err("SELinux: failed to load policy classes\n"); - return ret; +static int sel_make_policy_nodes(struct selinux_fs_info *fsi, + struct selinux_policy *newpolicy) +{ + int ret = 0; + struct dentry *tmp_parent, *tmp_bool_dir, *tmp_class_dir, *old_dentry; + unsigned int tmp_bool_num, old_bool_num; + char **tmp_bool_names, **old_bool_names; + int *tmp_bool_values, *old_bool_values; + unsigned long tmp_ino = fsi->last_ino; /* Don't increment last_ino in this function */ + + tmp_parent = sel_make_disconnected_dir(fsi->sb, &tmp_ino); + if (IS_ERR(tmp_parent)) + return PTR_ERR(tmp_parent); + + tmp_ino = fsi->bool_dir->d_inode->i_ino - 1; /* sel_make_dir will increment and set */ + tmp_bool_dir = sel_make_dir(tmp_parent, BOOL_DIR_NAME, &tmp_ino); + if (IS_ERR(tmp_bool_dir)) { + ret = PTR_ERR(tmp_bool_dir); + goto out; } - ret = sel_make_policycap(fsi); - if (ret) { - pr_err("SELinux: failed to load policy capabilities\n"); - return ret; + tmp_ino = fsi->class_dir->d_inode->i_ino - 1; /* sel_make_dir will increment and set */ + tmp_class_dir = sel_make_dir(tmp_parent, CLASS_DIR_NAME, &tmp_ino); + if (IS_ERR(tmp_class_dir)) { + ret = PTR_ERR(tmp_class_dir); + goto out; } - return 0; + ret = sel_make_bools(newpolicy, tmp_bool_dir, &tmp_bool_num, + &tmp_bool_names, &tmp_bool_values); + if (ret) + goto out; + + ret = sel_make_classes(newpolicy, tmp_class_dir, + &fsi->last_class_ino); + if (ret) + goto out; + + /* booleans */ + old_dentry = fsi->bool_dir; + lock_rename(tmp_bool_dir, old_dentry); + d_exchange(tmp_bool_dir, fsi->bool_dir); + + old_bool_num = fsi->bool_num; + old_bool_names = fsi->bool_pending_names; + old_bool_values = fsi->bool_pending_values; + + fsi->bool_num = tmp_bool_num; + fsi->bool_pending_names = tmp_bool_names; + fsi->bool_pending_values = tmp_bool_values; + + sel_remove_old_bool_data(old_bool_num, old_bool_names, old_bool_values); + + fsi->bool_dir = tmp_bool_dir; + unlock_rename(tmp_bool_dir, old_dentry); + + /* classes */ + old_dentry = fsi->class_dir; + lock_rename(tmp_class_dir, old_dentry); + d_exchange(tmp_class_dir, fsi->class_dir); + fsi->class_dir = tmp_class_dir; + unlock_rename(tmp_class_dir, old_dentry); + +out: + /* Since the other temporary dirs are children of tmp_parent + * this will handle all the cleanup in the case of a failure before + * the swapover + */ + sel_remove_entries(tmp_parent); + dput(tmp_parent); /* d_genocide() only handles the children */ + + return ret; } static ssize_t sel_write_load(struct file *file, const char __user *buf, @@ -538,10 +617,11 @@ static ssize_t sel_write_load(struct file *file, const char __user *buf, { struct selinux_fs_info *fsi = file_inode(file)->i_sb->s_fs_info; + struct selinux_load_state load_state; ssize_t length; void *data = NULL; - mutex_lock(&fsi->mutex); + mutex_lock(&fsi->state->policy_mutex); length = avc_has_perm(&selinux_state, current_sid(), SECINITSID_SECURITY, @@ -563,25 +643,29 @@ static ssize_t sel_write_load(struct file *file, const char __user *buf, if (copy_from_user(data, buf, count) != 0) goto out; - length = security_load_policy(fsi->state, data, count); + length = security_load_policy(fsi->state, data, count, &load_state); if (length) { pr_warn_ratelimited("SELinux: failed to load policy\n"); goto out; } - length = sel_make_policy_nodes(fsi); - if (length) - goto out1; + length = sel_make_policy_nodes(fsi, load_state.policy); + if (length) { + pr_warn_ratelimited("SELinux: failed to initialize selinuxfs\n"); + selinux_policy_cancel(fsi->state, &load_state); + goto out; + } + + selinux_policy_commit(fsi->state, &load_state); length = count; -out1: audit_log(audit_context(), GFP_KERNEL, AUDIT_MAC_POLICY_LOAD, "auid=%u ses=%u lsm=selinux res=1", from_kuid(&init_user_ns, audit_get_loginuid(current)), audit_get_sessionid(current)); out: - mutex_unlock(&fsi->mutex); + mutex_unlock(&fsi->state->policy_mutex); vfree(data); return length; } @@ -634,7 +718,8 @@ static ssize_t sel_read_checkreqprot(struct file *filp, char __user *buf, char tmpbuf[TMPBUFLEN]; ssize_t length; - length = scnprintf(tmpbuf, TMPBUFLEN, "%u", fsi->state->checkreqprot); + length = scnprintf(tmpbuf, TMPBUFLEN, "%u", + checkreqprot_get(fsi->state)); return simple_read_from_buffer(buf, count, ppos, tmpbuf, length); } @@ -668,8 +753,21 @@ static ssize_t sel_write_checkreqprot(struct file *file, const char __user *buf, if (sscanf(page, "%u", &new_value) != 1) goto out; - fsi->state->checkreqprot = new_value ? 1 : 0; + if (new_value) { + char comm[sizeof(current->comm)]; + + memcpy(comm, current->comm, sizeof(comm)); + pr_err("SELinux: %s (%d) set checkreqprot to 1. This is deprecated and will be rejected in a future kernel release.\n", + comm, current->pid); + } + + checkreqprot_set(fsi->state, (new_value ? 1 : 0)); + if (new_value) + ssleep(5); length = count; + + selinux_ima_measure_state(fsi->state); + out: kfree(page); return length; @@ -1178,7 +1276,7 @@ static ssize_t sel_read_bool(struct file *filep, char __user *buf, unsigned index = file_inode(filep)->i_ino & SEL_INO_MASK; const char *name = filep->f_path.dentry->d_name.name; - mutex_lock(&fsi->mutex); + mutex_lock(&fsi->state->policy_mutex); ret = -EINVAL; if (index >= fsi->bool_num || strcmp(name, @@ -1197,14 +1295,14 @@ static ssize_t sel_read_bool(struct file *filep, char __user *buf, } length = scnprintf(page, PAGE_SIZE, "%d %d", cur_enforcing, fsi->bool_pending_values[index]); - mutex_unlock(&fsi->mutex); + mutex_unlock(&fsi->state->policy_mutex); ret = simple_read_from_buffer(buf, count, ppos, page, length); out_free: free_page((unsigned long)page); return ret; out_unlock: - mutex_unlock(&fsi->mutex); + mutex_unlock(&fsi->state->policy_mutex); goto out_free; } @@ -1229,7 +1327,7 @@ static ssize_t sel_write_bool(struct file *filep, const char __user *buf, if (IS_ERR(page)) return PTR_ERR(page); - mutex_lock(&fsi->mutex); + mutex_lock(&fsi->state->policy_mutex); length = avc_has_perm(&selinux_state, current_sid(), SECINITSID_SECURITY, @@ -1254,7 +1352,7 @@ static ssize_t sel_write_bool(struct file *filep, const char __user *buf, length = count; out: - mutex_unlock(&fsi->mutex); + mutex_unlock(&fsi->state->policy_mutex); kfree(page); return length; } @@ -1285,7 +1383,7 @@ static ssize_t sel_commit_bools_write(struct file *filep, if (IS_ERR(page)) return PTR_ERR(page); - mutex_lock(&fsi->mutex); + mutex_lock(&fsi->state->policy_mutex); length = avc_has_perm(&selinux_state, current_sid(), SECINITSID_SECURITY, @@ -1307,7 +1405,7 @@ static ssize_t sel_commit_bools_write(struct file *filep, length = count; out: - mutex_unlock(&fsi->mutex); + mutex_unlock(&fsi->state->policy_mutex); kfree(page); return length; } @@ -1323,49 +1421,37 @@ static void sel_remove_entries(struct dentry *de) shrink_dcache_parent(de); } -#define BOOL_DIR_NAME "booleans" - -static int sel_make_bools(struct selinux_fs_info *fsi) +static int sel_make_bools(struct selinux_policy *newpolicy, struct dentry *bool_dir, + unsigned int *bool_num, char ***bool_pending_names, + int **bool_pending_values) { - int i, ret; + int ret; ssize_t len; struct dentry *dentry = NULL; - struct dentry *dir = fsi->bool_dir; struct inode *inode = NULL; struct inode_security_struct *isec; char **names = NULL, *page; - int num; + u32 i, num; int *values = NULL; u32 sid; - /* remove any existing files */ - for (i = 0; i < fsi->bool_num; i++) - kfree(fsi->bool_pending_names[i]); - kfree(fsi->bool_pending_names); - kfree(fsi->bool_pending_values); - fsi->bool_num = 0; - fsi->bool_pending_names = NULL; - fsi->bool_pending_values = NULL; - - sel_remove_entries(dir); - ret = -ENOMEM; page = (char *)get_zeroed_page(GFP_KERNEL); if (!page) goto out; - ret = security_get_bools(fsi->state, &num, &names, &values); + ret = security_get_bools(newpolicy, &num, &names, &values); if (ret) goto out; for (i = 0; i < num; i++) { ret = -ENOMEM; - dentry = d_alloc_name(dir, names[i]); + dentry = d_alloc_name(bool_dir, names[i]); if (!dentry) goto out; ret = -ENOMEM; - inode = sel_make_inode(dir->d_sb, S_IFREG | S_IRUGO | S_IWUSR); + inode = sel_make_inode(bool_dir->d_sb, S_IFREG | S_IRUGO | S_IWUSR); if (!inode) { dput(dentry); goto out; @@ -1380,7 +1466,7 @@ static int sel_make_bools(struct selinux_fs_info *fsi) } isec = selinux_inode(inode); - ret = security_genfs_sid(fsi->state, "selinuxfs", page, + ret = selinux_policy_genfs_sid(newpolicy, "selinuxfs", page, SECCLASS_FILE, &sid); if (ret) { pr_warn_ratelimited("SELinux: no sid found, defaulting to security isid for %s\n", @@ -1394,9 +1480,9 @@ static int sel_make_bools(struct selinux_fs_info *fsi) inode->i_ino = i|SEL_BOOL_INO_OFFSET; d_add(dentry, inode); } - fsi->bool_num = num; - fsi->bool_pending_names = names; - fsi->bool_pending_values = values; + *bool_num = num; + *bool_pending_names = names; + *bool_pending_values = values; free_page((unsigned long)page); return 0; @@ -1409,7 +1495,7 @@ out: kfree(names); } kfree(values); - sel_remove_entries(dir); + sel_remove_entries(bool_dir); return ret; } @@ -1536,6 +1622,7 @@ static struct avc_cache_stats *sel_avc_get_stat_idx(loff_t *idx) *idx = cpu + 1; return &per_cpu(avc_cache_stats, cpu); } + (*idx)++; return NULL; } @@ -1692,7 +1779,11 @@ static int sel_make_initcon_files(struct dentry *dir) for (i = 1; i <= SECINITSID_NUM; i++) { struct inode *inode; struct dentry *dentry; - dentry = d_alloc_name(dir, security_get_initial_sid_context(i)); + const char *s = security_get_initial_sid_context(i); + + if (!s) + continue; + dentry = d_alloc_name(dir, s); if (!dentry) return -ENOMEM; @@ -1778,14 +1869,14 @@ static const struct file_operations sel_policycap_ops = { .llseek = generic_file_llseek, }; -static int sel_make_perm_files(char *objclass, int classvalue, - struct dentry *dir) +static int sel_make_perm_files(struct selinux_policy *newpolicy, + char *objclass, int classvalue, + struct dentry *dir) { - struct selinux_fs_info *fsi = dir->d_sb->s_fs_info; int i, rc, nperms; char **perms; - rc = security_get_permissions(fsi->state, objclass, &perms, &nperms); + rc = security_get_permissions(newpolicy, objclass, &perms, &nperms); if (rc) return rc; @@ -1818,14 +1909,14 @@ out: return rc; } -static int sel_make_class_dir_entries(char *classname, int index, - struct dentry *dir) +static int sel_make_class_dir_entries(struct selinux_policy *newpolicy, + char *classname, int index, + struct dentry *dir) { struct super_block *sb = dir->d_sb; struct selinux_fs_info *fsi = sb->s_fs_info; struct dentry *dentry = NULL; struct inode *inode = NULL; - int rc; dentry = d_alloc_name(dir, "index"); if (!dentry) @@ -1845,39 +1936,36 @@ static int sel_make_class_dir_entries(char *classname, int index, if (IS_ERR(dentry)) return PTR_ERR(dentry); - rc = sel_make_perm_files(classname, index, dentry); - - return rc; + return sel_make_perm_files(newpolicy, classname, index, dentry); } -static int sel_make_classes(struct selinux_fs_info *fsi) +static int sel_make_classes(struct selinux_policy *newpolicy, + struct dentry *class_dir, + unsigned long *last_class_ino) { int rc, nclasses, i; char **classes; - /* delete any existing entries */ - sel_remove_entries(fsi->class_dir); - - rc = security_get_classes(fsi->state, &classes, &nclasses); + rc = security_get_classes(newpolicy, &classes, &nclasses); if (rc) return rc; /* +2 since classes are 1-indexed */ - fsi->last_class_ino = sel_class_to_ino(nclasses + 2); + *last_class_ino = sel_class_to_ino(nclasses + 2); for (i = 0; i < nclasses; i++) { struct dentry *class_name_dir; - class_name_dir = sel_make_dir(fsi->class_dir, classes[i], - &fsi->last_class_ino); + class_name_dir = sel_make_dir(class_dir, classes[i], + last_class_ino); if (IS_ERR(class_name_dir)) { rc = PTR_ERR(class_name_dir); goto out; } /* i+1 since class values are 1-indexed */ - rc = sel_make_class_dir_entries(classes[i], i + 1, + rc = sel_make_class_dir_entries(newpolicy, classes[i], i + 1, class_name_dir); if (rc) goto out; @@ -1896,9 +1984,7 @@ static int sel_make_policycap(struct selinux_fs_info *fsi) struct dentry *dentry = NULL; struct inode *inode = NULL; - sel_remove_entries(fsi->policycap_dir); - - for (iter = 0; iter <= POLICYDB_CAPABILITY_MAX; iter++) { + for (iter = 0; iter <= POLICYDB_CAP_MAX; iter++) { if (iter < ARRAY_SIZE(selinux_policycap_names)) dentry = d_alloc_name(fsi->policycap_dir, selinux_policycap_names[iter]); @@ -1949,6 +2035,22 @@ static struct dentry *sel_make_dir(struct dentry *dir, const char *name, return dentry; } +static struct dentry *sel_make_disconnected_dir(struct super_block *sb, + unsigned long *ino) +{ + struct inode *inode = sel_make_inode(sb, S_IFDIR | S_IRUGO | S_IXUGO); + + if (!inode) + return ERR_PTR(-ENOMEM); + + inode->i_op = &simple_dir_inode_operations; + inode->i_fop = &simple_dir_operations; + inode->i_ino = ++(*ino); + /* directory inodes start off with i_nlink == 2 (for "." entry) */ + inc_nlink(inode); + return d_obtain_alias(inode); +} + #define NULL_FILE_NAME "null" static int sel_fill_super(struct super_block *sb, struct fs_context *fc) @@ -2026,6 +2128,8 @@ static int sel_fill_super(struct super_block *sb, struct fs_context *fc) } ret = sel_make_avc_files(dentry); + if (ret) + goto err; dentry = sel_make_dir(sb->s_root, "ss", &fsi->last_ino); if (IS_ERR(dentry)) { @@ -2047,14 +2151,14 @@ static int sel_fill_super(struct super_block *sb, struct fs_context *fc) if (ret) goto err; - fsi->class_dir = sel_make_dir(sb->s_root, "class", &fsi->last_ino); + fsi->class_dir = sel_make_dir(sb->s_root, CLASS_DIR_NAME, &fsi->last_ino); if (IS_ERR(fsi->class_dir)) { ret = PTR_ERR(fsi->class_dir); fsi->class_dir = NULL; goto err; } - fsi->policycap_dir = sel_make_dir(sb->s_root, "policy_capabilities", + fsi->policycap_dir = sel_make_dir(sb->s_root, POLICYCAP_DIR_NAME, &fsi->last_ino); if (IS_ERR(fsi->policycap_dir)) { ret = PTR_ERR(fsi->policycap_dir); @@ -2062,9 +2166,12 @@ static int sel_fill_super(struct super_block *sb, struct fs_context *fc) goto err; } - ret = sel_make_policy_nodes(fsi); - if (ret) + ret = sel_make_policycap(fsi); + if (ret) { + pr_err("SELinux: failed to load policy capabilities\n"); goto err; + } + return 0; err: pr_err("SELinux: %s: failed while creating inodes\n", @@ -2102,8 +2209,8 @@ static struct file_system_type sel_fs_type = { .kill_sb = sel_kill_sb, }; -struct vfsmount *selinuxfs_mount; -struct path selinux_null; +static struct vfsmount *selinuxfs_mount __ro_after_init; +struct path selinux_null __ro_after_init; static int __init init_sel_fs(void) { diff --git a/security/selinux/ss/avtab.c b/security/selinux/ss/avtab.c index 8c5800750fa8..8480ec6c6e75 100644 --- a/security/selinux/ss/avtab.c +++ b/security/selinux/ss/avtab.c @@ -23,13 +23,13 @@ #include "avtab.h" #include "policydb.h" -static struct kmem_cache *avtab_node_cachep; -static struct kmem_cache *avtab_xperms_cachep; +static struct kmem_cache *avtab_node_cachep __ro_after_init; +static struct kmem_cache *avtab_xperms_cachep __ro_after_init; /* Based on MurmurHash3, written by Austin Appleby and placed in the * public domain. */ -static inline int avtab_hash(struct avtab_key *keyp, u32 mask) +static inline int avtab_hash(const struct avtab_key *keyp, u32 mask) { static const u32 c1 = 0xcc9e2d51; static const u32 c2 = 0x1b873593; @@ -40,15 +40,15 @@ static inline int avtab_hash(struct avtab_key *keyp, u32 mask) u32 hash = 0; -#define mix(input) { \ - u32 v = input; \ - v *= c1; \ - v = (v << r1) | (v >> (32 - r1)); \ - v *= c2; \ - hash ^= v; \ - hash = (hash << r2) | (hash >> (32 - r2)); \ - hash = hash * m + n; \ -} +#define mix(input) do { \ + u32 v = input; \ + v *= c1; \ + v = (v << r1) | (v >> (32 - r1)); \ + v *= c2; \ + hash ^= v; \ + hash = (hash << r2) | (hash >> (32 - r2)); \ + hash = hash * m + n; \ + } while (0) mix(keyp->target_class); mix(keyp->target_type); @@ -67,8 +67,8 @@ static inline int avtab_hash(struct avtab_key *keyp, u32 mask) static struct avtab_node* avtab_insert_node(struct avtab *h, int hvalue, - struct avtab_node *prev, struct avtab_node *cur, - struct avtab_key *key, struct avtab_datum *datum) + struct avtab_node *prev, + const struct avtab_key *key, const struct avtab_datum *datum) { struct avtab_node *newnode; struct avtab_extended_perms *xperms; @@ -103,13 +103,14 @@ avtab_insert_node(struct avtab *h, int hvalue, return newnode; } -static int avtab_insert(struct avtab *h, struct avtab_key *key, struct avtab_datum *datum) +static int avtab_insert(struct avtab *h, const struct avtab_key *key, + const struct avtab_datum *datum) { int hvalue; struct avtab_node *prev, *cur, *newnode; u16 specified = key->specified & ~(AVTAB_ENABLED|AVTAB_ENABLED_OLD); - if (!h) + if (!h || !h->nslot) return -EINVAL; hvalue = avtab_hash(key, h->mask); @@ -136,7 +137,7 @@ static int avtab_insert(struct avtab *h, struct avtab_key *key, struct avtab_dat break; } - newnode = avtab_insert_node(h, hvalue, prev, cur, key, datum); + newnode = avtab_insert_node(h, hvalue, prev, key, datum); if (!newnode) return -ENOMEM; @@ -147,14 +148,15 @@ static int avtab_insert(struct avtab *h, struct avtab_key *key, struct avtab_dat * key/specified mask into the table, as needed by the conditional avtab. * It also returns a pointer to the node inserted. */ -struct avtab_node * -avtab_insert_nonunique(struct avtab *h, struct avtab_key *key, struct avtab_datum *datum) +struct avtab_node *avtab_insert_nonunique(struct avtab *h, + const struct avtab_key *key, + const struct avtab_datum *datum) { int hvalue; struct avtab_node *prev, *cur; u16 specified = key->specified & ~(AVTAB_ENABLED|AVTAB_ENABLED_OLD); - if (!h) + if (!h || !h->nslot) return NULL; hvalue = avtab_hash(key, h->mask); for (prev = NULL, cur = h->htable[hvalue]; @@ -175,16 +177,16 @@ avtab_insert_nonunique(struct avtab *h, struct avtab_key *key, struct avtab_datu key->target_class < cur->key.target_class) break; } - return avtab_insert_node(h, hvalue, prev, cur, key, datum); + return avtab_insert_node(h, hvalue, prev, key, datum); } -struct avtab_datum *avtab_search(struct avtab *h, struct avtab_key *key) +struct avtab_datum *avtab_search(struct avtab *h, const struct avtab_key *key) { int hvalue; struct avtab_node *cur; u16 specified = key->specified & ~(AVTAB_ENABLED|AVTAB_ENABLED_OLD); - if (!h) + if (!h || !h->nslot) return NULL; hvalue = avtab_hash(key, h->mask); @@ -213,14 +215,14 @@ struct avtab_datum *avtab_search(struct avtab *h, struct avtab_key *key) /* This search function returns a node pointer, and can be used in * conjunction with avtab_search_next_node() */ -struct avtab_node* -avtab_search_node(struct avtab *h, struct avtab_key *key) +struct avtab_node *avtab_search_node(struct avtab *h, + const struct avtab_key *key) { int hvalue; struct avtab_node *cur; u16 specified = key->specified & ~(AVTAB_ENABLED|AVTAB_ENABLED_OLD); - if (!h) + if (!h || !h->nslot) return NULL; hvalue = avtab_hash(key, h->mask); @@ -295,52 +297,63 @@ void avtab_destroy(struct avtab *h) } kvfree(h->htable); h->htable = NULL; + h->nel = 0; h->nslot = 0; h->mask = 0; } -int avtab_init(struct avtab *h) +void avtab_init(struct avtab *h) { - kvfree(h->htable); h->htable = NULL; h->nel = 0; + h->nslot = 0; + h->mask = 0; +} + +static int avtab_alloc_common(struct avtab *h, u32 nslot) +{ + if (!nslot) + return 0; + + h->htable = kvcalloc(nslot, sizeof(void *), GFP_KERNEL); + if (!h->htable) + return -ENOMEM; + + h->nslot = nslot; + h->mask = nslot - 1; return 0; } int avtab_alloc(struct avtab *h, u32 nrules) { - u32 mask = 0; - u32 shift = 0; - u32 work = nrules; + int rc; u32 nslot = 0; - if (nrules == 0) - goto avtab_alloc_out; + if (nrules != 0) { + u32 shift = 1; + u32 work = nrules >> 3; + while (work) { + work >>= 1; + shift++; + } + nslot = 1 << shift; + if (nslot > MAX_AVTAB_HASH_BUCKETS) + nslot = MAX_AVTAB_HASH_BUCKETS; - while (work) { - work = work >> 1; - shift++; + rc = avtab_alloc_common(h, nslot); + if (rc) + return rc; } - if (shift > 2) - shift = shift - 2; - nslot = 1 << shift; - if (nslot > MAX_AVTAB_HASH_BUCKETS) - nslot = MAX_AVTAB_HASH_BUCKETS; - mask = nslot - 1; - - h->htable = kvcalloc(nslot, sizeof(void *), GFP_KERNEL); - if (!h->htable) - return -ENOMEM; - avtab_alloc_out: - h->nel = 0; - h->nslot = nslot; - h->mask = mask; - pr_debug("SELinux: %d avtab hash slots, %d rules.\n", - h->nslot, nrules); + pr_debug("SELinux: %d avtab hash slots, %d rules.\n", nslot, nrules); return 0; } +int avtab_alloc_dup(struct avtab *new, const struct avtab *orig) +{ + return avtab_alloc_common(new, orig->nslot); +} + void avtab_hash_eval(struct avtab *h, char *tag) { int i, chain_len, slots_used, max_chain_len; @@ -372,7 +385,7 @@ void avtab_hash_eval(struct avtab *h, char *tag) chain2_len_sum); } -static uint16_t spec_order[] = { +static const uint16_t spec_order[] = { AVTAB_ALLOWED, AVTAB_AUDITDENY, AVTAB_AUDITALLOW, @@ -385,8 +398,8 @@ static uint16_t spec_order[] = { }; int avtab_read_item(struct avtab *a, void *fp, struct policydb *pol, - int (*insertf)(struct avtab *a, struct avtab_key *k, - struct avtab_datum *d, void *p), + int (*insertf)(struct avtab *a, const struct avtab_key *k, + const struct avtab_datum *d, void *p), void *p) { __le16 buf16[4]; @@ -546,8 +559,8 @@ int avtab_read_item(struct avtab *a, void *fp, struct policydb *pol, return insertf(a, &key, &datum, p); } -static int avtab_insertf(struct avtab *a, struct avtab_key *k, - struct avtab_datum *d, void *p) +static int avtab_insertf(struct avtab *a, const struct avtab_key *k, + const struct avtab_datum *d, void *p) { return avtab_insert(a, k, d); } @@ -596,7 +609,7 @@ bad: goto out; } -int avtab_write_item(struct policydb *p, struct avtab_node *cur, void *fp) +int avtab_write_item(struct policydb *p, const struct avtab_node *cur, void *fp) { __le16 buf16[4]; __le32 buf32[ARRAY_SIZE(cur->datum.u.xperms->perms.p)]; diff --git a/security/selinux/ss/avtab.h b/security/selinux/ss/avtab.h index 837e938798ef..d3ebea8d146f 100644 --- a/security/selinux/ss/avtab.h +++ b/security/selinux/ss/avtab.h @@ -87,26 +87,29 @@ struct avtab { u32 mask; /* mask to compute hash func */ }; -int avtab_init(struct avtab *); +void avtab_init(struct avtab *h); int avtab_alloc(struct avtab *, u32); -struct avtab_datum *avtab_search(struct avtab *h, struct avtab_key *k); +int avtab_alloc_dup(struct avtab *new, const struct avtab *orig); +struct avtab_datum *avtab_search(struct avtab *h, const struct avtab_key *k); void avtab_destroy(struct avtab *h); void avtab_hash_eval(struct avtab *h, char *tag); struct policydb; int avtab_read_item(struct avtab *a, void *fp, struct policydb *pol, - int (*insert)(struct avtab *a, struct avtab_key *k, - struct avtab_datum *d, void *p), + int (*insert)(struct avtab *a, const struct avtab_key *k, + const struct avtab_datum *d, void *p), void *p); int avtab_read(struct avtab *a, void *fp, struct policydb *pol); -int avtab_write_item(struct policydb *p, struct avtab_node *cur, void *fp); +int avtab_write_item(struct policydb *p, const struct avtab_node *cur, void *fp); int avtab_write(struct policydb *p, struct avtab *a, void *fp); -struct avtab_node *avtab_insert_nonunique(struct avtab *h, struct avtab_key *key, - struct avtab_datum *datum); +struct avtab_node *avtab_insert_nonunique(struct avtab *h, + const struct avtab_key *key, + const struct avtab_datum *datum); -struct avtab_node *avtab_search_node(struct avtab *h, struct avtab_key *key); +struct avtab_node *avtab_search_node(struct avtab *h, + const struct avtab_key *key); struct avtab_node *avtab_search_node_next(struct avtab_node *node, int specified); diff --git a/security/selinux/ss/conditional.c b/security/selinux/ss/conditional.c index 70c378ee1a2f..e11219fdf9f7 100644 --- a/security/selinux/ss/conditional.c +++ b/security/selinux/ss/conditional.c @@ -23,18 +23,22 @@ */ static int cond_evaluate_expr(struct policydb *p, struct cond_expr *expr) { - - struct cond_expr *cur; + u32 i; int s[COND_EXPR_MAXDEPTH]; int sp = -1; - for (cur = expr; cur; cur = cur->next) { - switch (cur->expr_type) { + if (expr->len == 0) + return -1; + + for (i = 0; i < expr->len; i++) { + struct cond_expr_node *node = &expr->nodes[i]; + + switch (node->expr_type) { case COND_BOOL: if (sp == (COND_EXPR_MAXDEPTH - 1)) return -1; sp++; - s[sp] = p->bool_val_to_struct[cur->bool - 1]->state; + s[sp] = p->bool_val_to_struct[node->bool - 1]->state; break; case COND_NOT: if (sp < 0) @@ -85,90 +89,78 @@ static int cond_evaluate_expr(struct policydb *p, struct cond_expr *expr) * list appropriately. If the result of the expression is undefined * all of the rules are disabled for safety. */ -int evaluate_cond_node(struct policydb *p, struct cond_node *node) +static void evaluate_cond_node(struct policydb *p, struct cond_node *node) { + struct avtab_node *avnode; int new_state; - struct cond_av_list *cur; + u32 i; - new_state = cond_evaluate_expr(p, node->expr); + new_state = cond_evaluate_expr(p, &node->expr); if (new_state != node->cur_state) { node->cur_state = new_state; if (new_state == -1) pr_err("SELinux: expression result was undefined - disabling all rules.\n"); /* turn the rules on or off */ - for (cur = node->true_list; cur; cur = cur->next) { + for (i = 0; i < node->true_list.len; i++) { + avnode = node->true_list.nodes[i]; if (new_state <= 0) - cur->node->key.specified &= ~AVTAB_ENABLED; + avnode->key.specified &= ~AVTAB_ENABLED; else - cur->node->key.specified |= AVTAB_ENABLED; + avnode->key.specified |= AVTAB_ENABLED; } - for (cur = node->false_list; cur; cur = cur->next) { + for (i = 0; i < node->false_list.len; i++) { + avnode = node->false_list.nodes[i]; /* -1 or 1 */ if (new_state) - cur->node->key.specified &= ~AVTAB_ENABLED; + avnode->key.specified &= ~AVTAB_ENABLED; else - cur->node->key.specified |= AVTAB_ENABLED; + avnode->key.specified |= AVTAB_ENABLED; } } - return 0; } -int cond_policydb_init(struct policydb *p) +void evaluate_cond_nodes(struct policydb *p) { - int rc; + u32 i; - p->bool_val_to_struct = NULL; - p->cond_list = NULL; - - rc = avtab_init(&p->te_cond_avtab); - if (rc) - return rc; - - return 0; + for (i = 0; i < p->cond_list_len; i++) + evaluate_cond_node(p, &p->cond_list[i]); } -static void cond_av_list_destroy(struct cond_av_list *list) +void cond_policydb_init(struct policydb *p) { - struct cond_av_list *cur, *next; - for (cur = list; cur; cur = next) { - next = cur->next; - /* the avtab_ptr_t node is destroy by the avtab */ - kfree(cur); - } + p->bool_val_to_struct = NULL; + p->cond_list = NULL; + p->cond_list_len = 0; + + avtab_init(&p->te_cond_avtab); } static void cond_node_destroy(struct cond_node *node) { - struct cond_expr *cur_expr, *next_expr; - - for (cur_expr = node->expr; cur_expr; cur_expr = next_expr) { - next_expr = cur_expr->next; - kfree(cur_expr); - } - cond_av_list_destroy(node->true_list); - cond_av_list_destroy(node->false_list); - kfree(node); + kfree(node->expr.nodes); + /* the avtab_ptr_t nodes are destroyed by the avtab */ + kfree(node->true_list.nodes); + kfree(node->false_list.nodes); } -static void cond_list_destroy(struct cond_node *list) +static void cond_list_destroy(struct policydb *p) { - struct cond_node *next, *cur; - - if (list == NULL) - return; + u32 i; - for (cur = list; cur; cur = next) { - next = cur->next; - cond_node_destroy(cur); - } + for (i = 0; i < p->cond_list_len; i++) + cond_node_destroy(&p->cond_list[i]); + kfree(p->cond_list); + p->cond_list = NULL; + p->cond_list_len = 0; } void cond_policydb_destroy(struct policydb *p) { kfree(p->bool_val_to_struct); avtab_destroy(&p->te_cond_avtab); - cond_list_destroy(p->cond_list); + cond_list_destroy(p); } int cond_init_bool_indexes(struct policydb *p) @@ -213,7 +205,7 @@ static int bool_isvalid(struct cond_bool_datum *b) return 1; } -int cond_read_bool(struct policydb *p, struct hashtab *h, void *fp) +int cond_read_bool(struct policydb *p, struct symtab *s, void *fp) { char *key = NULL; struct cond_bool_datum *booldatum; @@ -225,7 +217,7 @@ int cond_read_bool(struct policydb *p, struct hashtab *h, void *fp) if (!booldatum) return -ENOMEM; - rc = next_entry(buf, fp, sizeof buf); + rc = next_entry(buf, fp, sizeof(buf)); if (rc) goto err; @@ -248,7 +240,7 @@ int cond_read_bool(struct policydb *p, struct hashtab *h, void *fp) if (rc) goto err; key[len] = '\0'; - rc = hashtab_insert(h, key, booldatum); + rc = symtab_insert(s, key, booldatum); if (rc) goto err; @@ -260,19 +252,19 @@ err: struct cond_insertf_data { struct policydb *p; + struct avtab_node **dst; struct cond_av_list *other; - struct cond_av_list *head; - struct cond_av_list *tail; }; -static int cond_insertf(struct avtab *a, struct avtab_key *k, struct avtab_datum *d, void *ptr) +static int cond_insertf(struct avtab *a, const struct avtab_key *k, + const struct avtab_datum *d, void *ptr) { struct cond_insertf_data *data = ptr; struct policydb *p = data->p; - struct cond_av_list *other = data->other, *list, *cur; + struct cond_av_list *other = data->other; struct avtab_node *node_ptr; - u8 found; - int rc = -EINVAL; + u32 i; + bool found; /* * For type rules we have to make certain there aren't any @@ -282,7 +274,7 @@ static int cond_insertf(struct avtab *a, struct avtab_key *k, struct avtab_datum if (k->specified & AVTAB_TYPE) { if (avtab_search(&p->te_avtab, k)) { pr_err("SELinux: type rule already exists outside of a conditional.\n"); - goto err; + return -EINVAL; } /* * If we are reading the false list other will be a pointer to @@ -297,24 +289,24 @@ static int cond_insertf(struct avtab *a, struct avtab_key *k, struct avtab_datum if (node_ptr) { if (avtab_search_node_next(node_ptr, k->specified)) { pr_err("SELinux: too many conflicting type rules.\n"); - goto err; + return -EINVAL; } - found = 0; - for (cur = other; cur; cur = cur->next) { - if (cur->node == node_ptr) { - found = 1; + found = false; + for (i = 0; i < other->len; i++) { + if (other->nodes[i] == node_ptr) { + found = true; break; } } if (!found) { pr_err("SELinux: conflicting type rules.\n"); - goto err; + return -EINVAL; } } } else { if (avtab_search(&p->te_cond_avtab, k)) { pr_err("SELinux: conflicting type rules when adding type rule for true.\n"); - goto err; + return -EINVAL; } } } @@ -322,39 +314,22 @@ static int cond_insertf(struct avtab *a, struct avtab_key *k, struct avtab_datum node_ptr = avtab_insert_nonunique(&p->te_cond_avtab, k, d); if (!node_ptr) { pr_err("SELinux: could not insert rule.\n"); - rc = -ENOMEM; - goto err; - } - - list = kzalloc(sizeof(*list), GFP_KERNEL); - if (!list) { - rc = -ENOMEM; - goto err; + return -ENOMEM; } - list->node = node_ptr; - if (!data->head) - data->head = list; - else - data->tail->next = list; - data->tail = list; + *data->dst = node_ptr; return 0; - -err: - cond_av_list_destroy(data->head); - data->head = NULL; - return rc; } -static int cond_read_av_list(struct policydb *p, void *fp, struct cond_av_list **ret_list, struct cond_av_list *other) +static int cond_read_av_list(struct policydb *p, void *fp, + struct cond_av_list *list, + struct cond_av_list *other) { - int i, rc; + int rc; __le32 buf[1]; - u32 len; + u32 i, len; struct cond_insertf_data data; - *ret_list = NULL; - rc = next_entry(buf, fp, sizeof(u32)); if (rc) return rc; @@ -363,22 +338,28 @@ static int cond_read_av_list(struct policydb *p, void *fp, struct cond_av_list * if (len == 0) return 0; + list->nodes = kcalloc(len, sizeof(*list->nodes), GFP_KERNEL); + if (!list->nodes) + return -ENOMEM; + data.p = p; data.other = other; - data.head = NULL; - data.tail = NULL; for (i = 0; i < len; i++) { + data.dst = &list->nodes[i]; rc = avtab_read_item(&p->te_cond_avtab, fp, p, cond_insertf, &data); - if (rc) + if (rc) { + kfree(list->nodes); + list->nodes = NULL; return rc; + } } - *ret_list = data.head; + list->len = len; return 0; } -static int expr_isvalid(struct policydb *p, struct cond_expr *expr) +static int expr_node_isvalid(struct policydb *p, struct cond_expr_node *expr) { if (expr->expr_type <= 0 || expr->expr_type > COND_LAST) { pr_err("SELinux: conditional expressions uses unknown operator.\n"); @@ -395,94 +376,73 @@ static int expr_isvalid(struct policydb *p, struct cond_expr *expr) static int cond_read_node(struct policydb *p, struct cond_node *node, void *fp) { __le32 buf[2]; - u32 len, i; + u32 i, len; int rc; - struct cond_expr *expr = NULL, *last = NULL; rc = next_entry(buf, fp, sizeof(u32) * 2); if (rc) - goto err; + return rc; node->cur_state = le32_to_cpu(buf[0]); /* expr */ len = le32_to_cpu(buf[1]); + node->expr.nodes = kcalloc(len, sizeof(*node->expr.nodes), GFP_KERNEL); + if (!node->expr.nodes) + return -ENOMEM; + + node->expr.len = len; for (i = 0; i < len; i++) { + struct cond_expr_node *expr = &node->expr.nodes[i]; + rc = next_entry(buf, fp, sizeof(u32) * 2); if (rc) - goto err; - - rc = -ENOMEM; - expr = kzalloc(sizeof(*expr), GFP_KERNEL); - if (!expr) - goto err; + return rc; expr->expr_type = le32_to_cpu(buf[0]); expr->bool = le32_to_cpu(buf[1]); - if (!expr_isvalid(p, expr)) { - rc = -EINVAL; - kfree(expr); - goto err; - } - - if (i == 0) - node->expr = expr; - else - last->next = expr; - last = expr; + if (!expr_node_isvalid(p, expr)) + return -EINVAL; } rc = cond_read_av_list(p, fp, &node->true_list, NULL); if (rc) - goto err; - rc = cond_read_av_list(p, fp, &node->false_list, node->true_list); - if (rc) - goto err; - return 0; -err: - cond_node_destroy(node); - return rc; + return rc; + return cond_read_av_list(p, fp, &node->false_list, &node->true_list); } int cond_read_list(struct policydb *p, void *fp) { - struct cond_node *node, *last = NULL; __le32 buf[1]; u32 i, len; int rc; - rc = next_entry(buf, fp, sizeof buf); + rc = next_entry(buf, fp, sizeof(buf)); if (rc) return rc; len = le32_to_cpu(buf[0]); + p->cond_list = kcalloc(len, sizeof(*p->cond_list), GFP_KERNEL); + if (!p->cond_list) + return -ENOMEM; + rc = avtab_alloc(&(p->te_cond_avtab), p->te_avtab.nel); if (rc) goto err; - for (i = 0; i < len; i++) { - rc = -ENOMEM; - node = kzalloc(sizeof(*node), GFP_KERNEL); - if (!node) - goto err; + p->cond_list_len = len; - rc = cond_read_node(p, node, fp); + for (i = 0; i < len; i++) { + rc = cond_read_node(p, &p->cond_list[i], fp); if (rc) goto err; - - if (i == 0) - p->cond_list = node; - else - last->next = node; - last = node; } return 0; err: - cond_list_destroy(p->cond_list); - p->cond_list = NULL; + cond_list_destroy(p); return rc; } @@ -522,24 +482,16 @@ static int cond_write_av_list(struct policydb *p, struct cond_av_list *list, struct policy_file *fp) { __le32 buf[1]; - struct cond_av_list *cur_list; - u32 len; + u32 i; int rc; - len = 0; - for (cur_list = list; cur_list != NULL; cur_list = cur_list->next) - len++; - - buf[0] = cpu_to_le32(len); + buf[0] = cpu_to_le32(list->len); rc = put_entry(buf, sizeof(u32), 1, fp); if (rc) return rc; - if (len == 0) - return 0; - - for (cur_list = list; cur_list != NULL; cur_list = cur_list->next) { - rc = avtab_write_item(p, cur_list->node, fp); + for (i = 0; i < list->len; i++) { + rc = avtab_write_item(p, list->nodes[i], fp); if (rc) return rc; } @@ -550,59 +502,51 @@ static int cond_write_av_list(struct policydb *p, static int cond_write_node(struct policydb *p, struct cond_node *node, struct policy_file *fp) { - struct cond_expr *cur_expr; __le32 buf[2]; int rc; - u32 len = 0; + u32 i; buf[0] = cpu_to_le32(node->cur_state); rc = put_entry(buf, sizeof(u32), 1, fp); if (rc) return rc; - for (cur_expr = node->expr; cur_expr != NULL; cur_expr = cur_expr->next) - len++; - - buf[0] = cpu_to_le32(len); + buf[0] = cpu_to_le32(node->expr.len); rc = put_entry(buf, sizeof(u32), 1, fp); if (rc) return rc; - for (cur_expr = node->expr; cur_expr != NULL; cur_expr = cur_expr->next) { - buf[0] = cpu_to_le32(cur_expr->expr_type); - buf[1] = cpu_to_le32(cur_expr->bool); + for (i = 0; i < node->expr.len; i++) { + buf[0] = cpu_to_le32(node->expr.nodes[i].expr_type); + buf[1] = cpu_to_le32(node->expr.nodes[i].bool); rc = put_entry(buf, sizeof(u32), 2, fp); if (rc) return rc; } - rc = cond_write_av_list(p, node->true_list, fp); + rc = cond_write_av_list(p, &node->true_list, fp); if (rc) return rc; - rc = cond_write_av_list(p, node->false_list, fp); + rc = cond_write_av_list(p, &node->false_list, fp); if (rc) return rc; return 0; } -int cond_write_list(struct policydb *p, struct cond_node *list, void *fp) +int cond_write_list(struct policydb *p, void *fp) { - struct cond_node *cur; - u32 len; + u32 i; __le32 buf[1]; int rc; - len = 0; - for (cur = list; cur != NULL; cur = cur->next) - len++; - buf[0] = cpu_to_le32(len); + buf[0] = cpu_to_le32(p->cond_list_len); rc = put_entry(buf, sizeof(u32), 1, fp); if (rc) return rc; - for (cur = list; cur != NULL; cur = cur->next) { - rc = cond_write_node(p, cur, fp); + for (i = 0; i < p->cond_list_len; i++) { + rc = cond_write_node(p, &p->cond_list[i], fp); if (rc) return rc; } @@ -623,8 +567,6 @@ void cond_compute_xperms(struct avtab *ctab, struct avtab_key *key, if (node->key.specified & AVTAB_ENABLED) services_compute_xperms_decision(xpermd, node); } - return; - } /* Determine whether additional permissions are granted by the conditional * av table, and if so, add them to the result @@ -658,3 +600,159 @@ void cond_compute_av(struct avtab *ctab, struct avtab_key *key, services_compute_xperms_drivers(xperms, node); } } + +static int cond_dup_av_list(struct cond_av_list *new, + struct cond_av_list *orig, + struct avtab *avtab) +{ + u32 i; + + memset(new, 0, sizeof(*new)); + + new->nodes = kcalloc(orig->len, sizeof(*new->nodes), GFP_KERNEL); + if (!new->nodes) + return -ENOMEM; + + for (i = 0; i < orig->len; i++) { + new->nodes[i] = avtab_insert_nonunique(avtab, + &orig->nodes[i]->key, + &orig->nodes[i]->datum); + if (!new->nodes[i]) + return -ENOMEM; + new->len++; + } + + return 0; +} + +static int duplicate_policydb_cond_list(struct policydb *newp, + struct policydb *origp) +{ + int rc; + u32 i; + + rc = avtab_alloc_dup(&newp->te_cond_avtab, &origp->te_cond_avtab); + if (rc) + return rc; + + newp->cond_list_len = 0; + newp->cond_list = kcalloc(origp->cond_list_len, + sizeof(*newp->cond_list), + GFP_KERNEL); + if (!newp->cond_list) + goto error; + + for (i = 0; i < origp->cond_list_len; i++) { + struct cond_node *newn = &newp->cond_list[i]; + struct cond_node *orign = &origp->cond_list[i]; + + newp->cond_list_len++; + + newn->cur_state = orign->cur_state; + newn->expr.nodes = kmemdup(orign->expr.nodes, + orign->expr.len * sizeof(*orign->expr.nodes), + GFP_KERNEL); + if (!newn->expr.nodes) + goto error; + + newn->expr.len = orign->expr.len; + + rc = cond_dup_av_list(&newn->true_list, &orign->true_list, + &newp->te_cond_avtab); + if (rc) + goto error; + + rc = cond_dup_av_list(&newn->false_list, &orign->false_list, + &newp->te_cond_avtab); + if (rc) + goto error; + } + + return 0; + +error: + avtab_destroy(&newp->te_cond_avtab); + cond_list_destroy(newp); + return -ENOMEM; +} + +static int cond_bools_destroy(void *key, void *datum, void *args) +{ + /* key was not copied so no need to free here */ + kfree(datum); + return 0; +} + +static int cond_bools_copy(struct hashtab_node *new, struct hashtab_node *orig, void *args) +{ + struct cond_bool_datum *datum; + + datum = kmemdup(orig->datum, sizeof(struct cond_bool_datum), + GFP_KERNEL); + if (!datum) + return -ENOMEM; + + new->key = orig->key; /* No need to copy, never modified */ + new->datum = datum; + return 0; +} + +static int cond_bools_index(void *key, void *datum, void *args) +{ + struct cond_bool_datum *booldatum, **cond_bool_array; + + booldatum = datum; + cond_bool_array = args; + cond_bool_array[booldatum->value - 1] = booldatum; + + return 0; +} + +static int duplicate_policydb_bools(struct policydb *newdb, + struct policydb *orig) +{ + struct cond_bool_datum **cond_bool_array; + int rc; + + cond_bool_array = kmalloc_array(orig->p_bools.nprim, + sizeof(*orig->bool_val_to_struct), + GFP_KERNEL); + if (!cond_bool_array) + return -ENOMEM; + + rc = hashtab_duplicate(&newdb->p_bools.table, &orig->p_bools.table, + cond_bools_copy, cond_bools_destroy, NULL); + if (rc) { + kfree(cond_bool_array); + return -ENOMEM; + } + + hashtab_map(&newdb->p_bools.table, cond_bools_index, cond_bool_array); + newdb->bool_val_to_struct = cond_bool_array; + + newdb->p_bools.nprim = orig->p_bools.nprim; + + return 0; +} + +void cond_policydb_destroy_dup(struct policydb *p) +{ + hashtab_map(&p->p_bools.table, cond_bools_destroy, NULL); + hashtab_destroy(&p->p_bools.table); + cond_policydb_destroy(p); +} + +int cond_policydb_dup(struct policydb *new, struct policydb *orig) +{ + cond_policydb_init(new); + + if (duplicate_policydb_bools(new, orig)) + return -ENOMEM; + + if (duplicate_policydb_cond_list(new, orig)) { + cond_policydb_destroy_dup(new); + return -ENOMEM; + } + + return 0; +} diff --git a/security/selinux/ss/conditional.h b/security/selinux/ss/conditional.h index ec846e45904c..e47ec6ddeaf6 100644 --- a/security/selinux/ss/conditional.h +++ b/security/selinux/ss/conditional.h @@ -19,7 +19,7 @@ * A conditional expression is a list of operators and operands * in reverse polish notation. */ -struct cond_expr { +struct cond_expr_node { #define COND_BOOL 1 /* plain bool */ #define COND_NOT 2 /* !bool */ #define COND_OR 3 /* bool || bool */ @@ -28,9 +28,13 @@ struct cond_expr { #define COND_EQ 6 /* bool == bool */ #define COND_NEQ 7 /* bool != bool */ #define COND_LAST COND_NEQ - __u32 expr_type; - __u32 bool; - struct cond_expr *next; + u32 expr_type; + u32 bool; +}; + +struct cond_expr { + struct cond_expr_node *nodes; + u32 len; }; /* @@ -39,8 +43,8 @@ struct cond_expr { * struct is for that list. */ struct cond_av_list { - struct avtab_node *node; - struct cond_av_list *next; + struct avtab_node **nodes; + u32 len; }; /* @@ -52,13 +56,12 @@ struct cond_av_list { */ struct cond_node { int cur_state; - struct cond_expr *expr; - struct cond_av_list *true_list; - struct cond_av_list *false_list; - struct cond_node *next; + struct cond_expr expr; + struct cond_av_list true_list; + struct cond_av_list false_list; }; -int cond_policydb_init(struct policydb *p); +void cond_policydb_init(struct policydb *p); void cond_policydb_destroy(struct policydb *p); int cond_init_bool_indexes(struct policydb *p); @@ -66,15 +69,17 @@ int cond_destroy_bool(void *key, void *datum, void *p); int cond_index_bool(void *key, void *datum, void *datap); -int cond_read_bool(struct policydb *p, struct hashtab *h, void *fp); +int cond_read_bool(struct policydb *p, struct symtab *s, void *fp); int cond_read_list(struct policydb *p, void *fp); int cond_write_bool(void *key, void *datum, void *ptr); -int cond_write_list(struct policydb *p, struct cond_node *list, void *fp); +int cond_write_list(struct policydb *p, void *fp); void cond_compute_av(struct avtab *ctab, struct avtab_key *key, struct av_decision *avd, struct extended_perms *xperms); void cond_compute_xperms(struct avtab *ctab, struct avtab_key *key, struct extended_perms_decision *xpermd); -int evaluate_cond_node(struct policydb *p, struct cond_node *node); +void evaluate_cond_nodes(struct policydb *p); +void cond_policydb_destroy_dup(struct policydb *p); +int cond_policydb_dup(struct policydb *new, struct policydb *orig); #endif /* _CONDITIONAL_H_ */ diff --git a/security/selinux/ss/context.c b/security/selinux/ss/context.c new file mode 100644 index 000000000000..38bc0aa524a6 --- /dev/null +++ b/security/selinux/ss/context.c @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Implementations of the security context functions. + * + * Author: Ondrej Mosnacek <omosnacek@gmail.com> + * Copyright (C) 2020 Red Hat, Inc. + */ + +#include <linux/jhash.h> + +#include "context.h" +#include "mls.h" + +u32 context_compute_hash(const struct context *c) +{ + u32 hash = 0; + + /* + * If a context is invalid, it will always be represented by a + * context struct with only the len & str set (and vice versa) + * under a given policy. Since context structs from different + * policies should never meet, it is safe to hash valid and + * invalid contexts differently. The context_cmp() function + * already operates under the same assumption. + */ + if (c->len) + return full_name_hash(NULL, c->str, c->len); + + hash = jhash_3words(c->user, c->role, c->type, hash); + hash = mls_range_hash(&c->range, hash); + return hash; +} diff --git a/security/selinux/ss/context.h b/security/selinux/ss/context.h index 3ba044fe02ed..eda32c3d4c0a 100644 --- a/security/selinux/ss/context.h +++ b/security/selinux/ss/context.h @@ -31,7 +31,6 @@ struct context { u32 len; /* length of string in bytes */ struct mls_range range; char *str; /* string representation if context cannot be mapped. */ - u32 hash; /* a hash of the string representation */ }; static inline void mls_context_init(struct context *c) @@ -39,7 +38,7 @@ static inline void mls_context_init(struct context *c) memset(&c->range, 0, sizeof(c->range)); } -static inline int mls_context_cpy(struct context *dst, struct context *src) +static inline int mls_context_cpy(struct context *dst, const struct context *src) { int rc; @@ -59,7 +58,7 @@ out: /* * Sets both levels in the MLS range of 'dst' to the low level of 'src'. */ -static inline int mls_context_cpy_low(struct context *dst, struct context *src) +static inline int mls_context_cpy_low(struct context *dst, const struct context *src) { int rc; @@ -79,7 +78,7 @@ out: /* * Sets both levels in the MLS range of 'dst' to the high level of 'src'. */ -static inline int mls_context_cpy_high(struct context *dst, struct context *src) +static inline int mls_context_cpy_high(struct context *dst, const struct context *src) { int rc; @@ -98,9 +97,10 @@ out: static inline int mls_context_glblub(struct context *dst, - struct context *c1, struct context *c2) + const struct context *c1, const struct context *c2) { - struct mls_range *dr = &dst->range, *r1 = &c1->range, *r2 = &c2->range; + struct mls_range *dr = &dst->range; + const struct mls_range *r1 = &c1->range, *r2 = &c2->range; int rc = 0; if (r1->level[1].sens < r2->level[0].sens || @@ -128,7 +128,7 @@ out: return rc; } -static inline int mls_context_cmp(struct context *c1, struct context *c2) +static inline int mls_context_cmp(const struct context *c1, const struct context *c2) { return ((c1->range.level[0].sens == c2->range.level[0].sens) && ebitmap_cmp(&c1->range.level[0].cat, &c2->range.level[0].cat) && @@ -148,7 +148,7 @@ static inline void context_init(struct context *c) memset(c, 0, sizeof(*c)); } -static inline int context_cpy(struct context *dst, struct context *src) +static inline int context_cpy(struct context *dst, const struct context *src) { int rc; @@ -169,23 +169,20 @@ static inline int context_cpy(struct context *dst, struct context *src) kfree(dst->str); return rc; } - dst->hash = src->hash; return 0; } static inline void context_destroy(struct context *c) { - c->user = c->role = c->type = c->hash = 0; + c->user = c->role = c->type = 0; kfree(c->str); c->str = NULL; c->len = 0; mls_context_destroy(c); } -static inline int context_cmp(struct context *c1, struct context *c2) +static inline int context_cmp(const struct context *c1, const struct context *c2) { - if (c1->hash && c2->hash && (c1->hash != c2->hash)) - return 0; if (c1->len && c2->len) return (c1->len == c2->len && !strcmp(c1->str, c2->str)); if (c1->len || c2->len) @@ -196,10 +193,7 @@ static inline int context_cmp(struct context *c1, struct context *c2) mls_context_cmp(c1, c2)); } -static inline unsigned int context_compute_hash(const char *s) -{ - return full_name_hash(NULL, s, strlen(s)); -} +u32 context_compute_hash(const struct context *c); #endif /* _SS_CONTEXT_H_ */ diff --git a/security/selinux/ss/ebitmap.c b/security/selinux/ss/ebitmap.c index c8c3663111e2..d31b87be9a1e 100644 --- a/security/selinux/ss/ebitmap.c +++ b/security/selinux/ss/ebitmap.c @@ -19,17 +19,18 @@ #include <linux/kernel.h> #include <linux/slab.h> #include <linux/errno.h> +#include <linux/jhash.h> #include <net/netlabel.h> #include "ebitmap.h" #include "policydb.h" #define BITS_PER_U64 (sizeof(u64) * 8) -static struct kmem_cache *ebitmap_node_cachep; +static struct kmem_cache *ebitmap_node_cachep __ro_after_init; -int ebitmap_cmp(struct ebitmap *e1, struct ebitmap *e2) +int ebitmap_cmp(const struct ebitmap *e1, const struct ebitmap *e2) { - struct ebitmap_node *n1, *n2; + const struct ebitmap_node *n1, *n2; if (e1->highbit != e2->highbit) return 0; @@ -49,9 +50,10 @@ int ebitmap_cmp(struct ebitmap *e1, struct ebitmap *e2) return 1; } -int ebitmap_cpy(struct ebitmap *dst, struct ebitmap *src) +int ebitmap_cpy(struct ebitmap *dst, const struct ebitmap *src) { - struct ebitmap_node *n, *new, *prev; + struct ebitmap_node *new, *prev; + const struct ebitmap_node *n; ebitmap_init(dst); n = src->node; @@ -77,7 +79,7 @@ int ebitmap_cpy(struct ebitmap *dst, struct ebitmap *src) return 0; } -int ebitmap_and(struct ebitmap *dst, struct ebitmap *e1, struct ebitmap *e2) +int ebitmap_and(struct ebitmap *dst, const struct ebitmap *e1, const struct ebitmap *e2) { struct ebitmap_node *n; int bit, rc; @@ -216,9 +218,9 @@ netlbl_import_failure: * if last_e2bit is non-zero, the highest set bit in e2 cannot exceed * last_e2bit. */ -int ebitmap_contains(struct ebitmap *e1, struct ebitmap *e2, u32 last_e2bit) +int ebitmap_contains(const struct ebitmap *e1, const struct ebitmap *e2, u32 last_e2bit) { - struct ebitmap_node *n1, *n2; + const struct ebitmap_node *n1, *n2; int i; if (e1->highbit < e2->highbit) @@ -257,9 +259,9 @@ int ebitmap_contains(struct ebitmap *e1, struct ebitmap *e2, u32 last_e2bit) return 1; } -int ebitmap_get_bit(struct ebitmap *e, unsigned long bit) +int ebitmap_get_bit(const struct ebitmap *e, unsigned long bit) { - struct ebitmap_node *n; + const struct ebitmap_node *n; if (e->highbit < bit) return 0; @@ -358,7 +360,6 @@ void ebitmap_destroy(struct ebitmap *e) e->highbit = 0; e->node = NULL; - return; } int ebitmap_read(struct ebitmap *e, void *fp) @@ -467,7 +468,7 @@ bad: goto out; } -int ebitmap_write(struct ebitmap *e, void *fp) +int ebitmap_write(const struct ebitmap *e, void *fp) { struct ebitmap_node *n; u32 count; @@ -542,6 +543,19 @@ int ebitmap_write(struct ebitmap *e, void *fp) return 0; } +u32 ebitmap_hash(const struct ebitmap *e, u32 hash) +{ + struct ebitmap_node *node; + + /* need to change hash even if ebitmap is empty */ + hash = jhash_1word(e->highbit, hash); + for (node = e->node; node; node = node->next) { + hash = jhash_1word(node->startbit, hash); + hash = jhash(node->maps, sizeof(node->maps), hash); + } + return hash; +} + void __init ebitmap_cache_init(void) { ebitmap_node_cachep = kmem_cache_create("ebitmap_node", diff --git a/security/selinux/ss/ebitmap.h b/security/selinux/ss/ebitmap.h index 9a23b81b8832..e5b57dc3fc53 100644 --- a/security/selinux/ss/ebitmap.h +++ b/security/selinux/ss/ebitmap.h @@ -44,7 +44,7 @@ struct ebitmap { #define ebitmap_length(e) ((e)->highbit) -static inline unsigned int ebitmap_start_positive(struct ebitmap *e, +static inline unsigned int ebitmap_start_positive(const struct ebitmap *e, struct ebitmap_node **n) { unsigned int ofs; @@ -62,7 +62,7 @@ static inline void ebitmap_init(struct ebitmap *e) memset(e, 0, sizeof(*e)); } -static inline unsigned int ebitmap_next_positive(struct ebitmap *e, +static inline unsigned int ebitmap_next_positive(const struct ebitmap *e, struct ebitmap_node **n, unsigned int bit) { @@ -85,7 +85,7 @@ static inline unsigned int ebitmap_next_positive(struct ebitmap *e, #define EBITMAP_NODE_OFFSET(node, bit) \ (((bit) - (node)->startbit) % EBITMAP_UNIT_SIZE) -static inline int ebitmap_node_get_bit(struct ebitmap_node *n, +static inline int ebitmap_node_get_bit(const struct ebitmap_node *n, unsigned int bit) { unsigned int index = EBITMAP_NODE_INDEX(n, bit); @@ -118,19 +118,20 @@ static inline void ebitmap_node_clr_bit(struct ebitmap_node *n, } #define ebitmap_for_each_positive_bit(e, n, bit) \ - for (bit = ebitmap_start_positive(e, &n); \ - bit < ebitmap_length(e); \ - bit = ebitmap_next_positive(e, &n, bit)) \ - -int ebitmap_cmp(struct ebitmap *e1, struct ebitmap *e2); -int ebitmap_cpy(struct ebitmap *dst, struct ebitmap *src); -int ebitmap_and(struct ebitmap *dst, struct ebitmap *e1, struct ebitmap *e2); -int ebitmap_contains(struct ebitmap *e1, struct ebitmap *e2, u32 last_e2bit); -int ebitmap_get_bit(struct ebitmap *e, unsigned long bit); + for ((bit) = ebitmap_start_positive(e, &(n)); \ + (bit) < ebitmap_length(e); \ + (bit) = ebitmap_next_positive(e, &(n), bit)) \ + +int ebitmap_cmp(const struct ebitmap *e1, const struct ebitmap *e2); +int ebitmap_cpy(struct ebitmap *dst, const struct ebitmap *src); +int ebitmap_and(struct ebitmap *dst, const struct ebitmap *e1, const struct ebitmap *e2); +int ebitmap_contains(const struct ebitmap *e1, const struct ebitmap *e2, u32 last_e2bit); +int ebitmap_get_bit(const struct ebitmap *e, unsigned long bit); int ebitmap_set_bit(struct ebitmap *e, unsigned long bit, int value); void ebitmap_destroy(struct ebitmap *e); int ebitmap_read(struct ebitmap *e, void *fp); -int ebitmap_write(struct ebitmap *e, void *fp); +int ebitmap_write(const struct ebitmap *e, void *fp); +u32 ebitmap_hash(const struct ebitmap *e, u32 hash); #ifdef CONFIG_NETLABEL int ebitmap_netlbl_export(struct ebitmap *ebmap, diff --git a/security/selinux/ss/hashtab.c b/security/selinux/ss/hashtab.c index ebfdaa31ee32..3fb8f9026e9b 100644 --- a/security/selinux/ss/hashtab.c +++ b/security/selinux/ss/hashtab.c @@ -7,103 +7,68 @@ #include <linux/kernel.h> #include <linux/slab.h> #include <linux/errno.h> -#include <linux/sched.h> #include "hashtab.h" +#include "security.h" -static struct kmem_cache *hashtab_node_cachep; +static struct kmem_cache *hashtab_node_cachep __ro_after_init; -struct hashtab *hashtab_create(u32 (*hash_value)(struct hashtab *h, const void *key), - int (*keycmp)(struct hashtab *h, const void *key1, const void *key2), - u32 size) +/* + * Here we simply round the number of elements up to the nearest power of two. + * I tried also other options like rounding down or rounding to the closest + * power of two (up or down based on which is closer), but I was unable to + * find any significant difference in lookup/insert performance that would + * justify switching to a different (less intuitive) formula. It could be that + * a different formula is actually more optimal, but any future changes here + * should be supported with performance/memory usage data. + * + * The total memory used by the htable arrays (only) with Fedora policy loaded + * is approximately 163 KB at the time of writing. + */ +static u32 hashtab_compute_size(u32 nel) { - struct hashtab *p; - u32 i; - - p = kzalloc(sizeof(*p), GFP_KERNEL); - if (!p) - return p; - - p->size = size; - p->nel = 0; - p->hash_value = hash_value; - p->keycmp = keycmp; - p->htable = kmalloc_array(size, sizeof(*p->htable), GFP_KERNEL); - if (!p->htable) { - kfree(p); - return NULL; - } - - for (i = 0; i < size; i++) - p->htable[i] = NULL; - - return p; + return nel == 0 ? 0 : roundup_pow_of_two(nel); } -int hashtab_insert(struct hashtab *h, void *key, void *datum) +int hashtab_init(struct hashtab *h, u32 nel_hint) { - u32 hvalue; - struct hashtab_node *prev, *cur, *newnode; - - cond_resched(); + u32 size = hashtab_compute_size(nel_hint); - if (!h || h->nel == HASHTAB_MAX_NODES) - return -EINVAL; + /* should already be zeroed, but better be safe */ + h->nel = 0; + h->size = 0; + h->htable = NULL; - hvalue = h->hash_value(h, key); - prev = NULL; - cur = h->htable[hvalue]; - while (cur && h->keycmp(h, key, cur->key) > 0) { - prev = cur; - cur = cur->next; + if (size) { + h->htable = kcalloc(size, sizeof(*h->htable), GFP_KERNEL); + if (!h->htable) + return -ENOMEM; + h->size = size; } + return 0; +} - if (cur && (h->keycmp(h, key, cur->key) == 0)) - return -EEXIST; +int __hashtab_insert(struct hashtab *h, struct hashtab_node **dst, + void *key, void *datum) +{ + struct hashtab_node *newnode; newnode = kmem_cache_zalloc(hashtab_node_cachep, GFP_KERNEL); if (!newnode) return -ENOMEM; newnode->key = key; newnode->datum = datum; - if (prev) { - newnode->next = prev->next; - prev->next = newnode; - } else { - newnode->next = h->htable[hvalue]; - h->htable[hvalue] = newnode; - } + newnode->next = *dst; + *dst = newnode; h->nel++; return 0; } -void *hashtab_search(struct hashtab *h, const void *key) -{ - u32 hvalue; - struct hashtab_node *cur; - - if (!h) - return NULL; - - hvalue = h->hash_value(h, key); - cur = h->htable[hvalue]; - while (cur && h->keycmp(h, key, cur->key) > 0) - cur = cur->next; - - if (!cur || (h->keycmp(h, key, cur->key) != 0)) - return NULL; - - return cur->datum; -} - void hashtab_destroy(struct hashtab *h) { u32 i; struct hashtab_node *cur, *temp; - if (!h) - return; - for (i = 0; i < h->size; i++) { cur = h->htable[i]; while (cur) { @@ -116,8 +81,6 @@ void hashtab_destroy(struct hashtab *h) kfree(h->htable); h->htable = NULL; - - kfree(h); } int hashtab_map(struct hashtab *h, @@ -128,9 +91,6 @@ int hashtab_map(struct hashtab *h, int ret; struct hashtab_node *cur; - if (!h) - return 0; - for (i = 0; i < h->size; i++) { cur = h->htable[i]; while (cur) { @@ -170,6 +130,60 @@ void hashtab_stat(struct hashtab *h, struct hashtab_info *info) info->max_chain_len = max_chain_len; } +int hashtab_duplicate(struct hashtab *new, struct hashtab *orig, + int (*copy)(struct hashtab_node *new, + struct hashtab_node *orig, void *args), + int (*destroy)(void *k, void *d, void *args), + void *args) +{ + struct hashtab_node *cur, *tmp, *tail; + int i, rc; + + memset(new, 0, sizeof(*new)); + + new->htable = kcalloc(orig->size, sizeof(*new->htable), GFP_KERNEL); + if (!new->htable) + return -ENOMEM; + + new->size = orig->size; + + for (i = 0; i < orig->size; i++) { + tail = NULL; + for (cur = orig->htable[i]; cur; cur = cur->next) { + tmp = kmem_cache_zalloc(hashtab_node_cachep, + GFP_KERNEL); + if (!tmp) + goto error; + rc = copy(tmp, cur, args); + if (rc) { + kmem_cache_free(hashtab_node_cachep, tmp); + goto error; + } + tmp->next = NULL; + if (!tail) + new->htable[i] = tmp; + else + tail->next = tmp; + tail = tmp; + new->nel++; + } + } + + return 0; + + error: + for (i = 0; i < new->size; i++) { + for (cur = new->htable[i]; cur; cur = tmp) { + tmp = cur->next; + destroy(cur->key, cur->datum, args); + kmem_cache_free(hashtab_node_cachep, cur); + } + } + kfree(new->htable); + memset(new, 0, sizeof(*new)); + return -ENOMEM; +} + void __init hashtab_cache_init(void) { hashtab_node_cachep = kmem_cache_create("hashtab_node", diff --git a/security/selinux/ss/hashtab.h b/security/selinux/ss/hashtab.h index 3e3e42bfd150..043a773bf0b7 100644 --- a/security/selinux/ss/hashtab.h +++ b/security/selinux/ss/hashtab.h @@ -11,7 +11,17 @@ #ifndef _SS_HASHTAB_H_ #define _SS_HASHTAB_H_ -#define HASHTAB_MAX_NODES 0xffffffff +#include <linux/types.h> +#include <linux/errno.h> +#include <linux/sched.h> + +#define HASHTAB_MAX_NODES U32_MAX + +struct hashtab_key_params { + u32 (*hash)(const void *key); /* hash function */ + int (*cmp)(const void *key1, const void *key2); + /* key comparison function */ +}; struct hashtab_node { void *key; @@ -23,10 +33,6 @@ struct hashtab { struct hashtab_node **htable; /* hash table */ u32 size; /* number of slots in hash table */ u32 nel; /* number of elements in hash table */ - u32 (*hash_value)(struct hashtab *h, const void *key); - /* hash function */ - int (*keycmp)(struct hashtab *h, const void *key1, const void *key2); - /* key comparison function */ }; struct hashtab_info { @@ -35,14 +41,14 @@ struct hashtab_info { }; /* - * Creates a new hash table with the specified characteristics. + * Initializes a new hash table with the specified characteristics. * - * Returns NULL if insufficent space is available or - * the new hash table otherwise. + * Returns -ENOMEM if insufficient space is available or 0 otherwise. */ -struct hashtab *hashtab_create(u32 (*hash_value)(struct hashtab *h, const void *key), - int (*keycmp)(struct hashtab *h, const void *key1, const void *key2), - u32 size); +int hashtab_init(struct hashtab *h, u32 nel_hint); + +int __hashtab_insert(struct hashtab *h, struct hashtab_node **dst, + void *key, void *datum); /* * Inserts the specified (key, datum) pair into the specified hash table. @@ -52,7 +58,34 @@ struct hashtab *hashtab_create(u32 (*hash_value)(struct hashtab *h, const void * * -EINVAL for general errors or 0 otherwise. */ -int hashtab_insert(struct hashtab *h, void *k, void *d); +static inline int hashtab_insert(struct hashtab *h, void *key, void *datum, + struct hashtab_key_params key_params) +{ + u32 hvalue; + struct hashtab_node *prev, *cur; + + cond_resched(); + + if (!h->size || h->nel == HASHTAB_MAX_NODES) + return -EINVAL; + + hvalue = key_params.hash(key) & (h->size - 1); + prev = NULL; + cur = h->htable[hvalue]; + while (cur) { + int cmp = key_params.cmp(key, cur->key); + + if (cmp == 0) + return -EEXIST; + if (cmp < 0) + break; + prev = cur; + cur = cur->next; + } + + return __hashtab_insert(h, prev ? &prev->next : &h->htable[hvalue], + key, datum); +} /* * Searches for the entry with the specified key in the hash table. @@ -60,7 +93,28 @@ int hashtab_insert(struct hashtab *h, void *k, void *d); * Returns NULL if no entry has the specified key or * the datum of the entry otherwise. */ -void *hashtab_search(struct hashtab *h, const void *k); +static inline void *hashtab_search(struct hashtab *h, const void *key, + struct hashtab_key_params key_params) +{ + u32 hvalue; + struct hashtab_node *cur; + + if (!h->size) + return NULL; + + hvalue = key_params.hash(key) & (h->size - 1); + cur = h->htable[hvalue]; + while (cur) { + int cmp = key_params.cmp(key, cur->key); + + if (cmp == 0) + return cur->datum; + if (cmp < 0) + break; + cur = cur->next; + } + return NULL; +} /* * Destroys the specified hash table. @@ -82,6 +136,12 @@ int hashtab_map(struct hashtab *h, int (*apply)(void *k, void *d, void *args), void *args); +int hashtab_duplicate(struct hashtab *new, struct hashtab *orig, + int (*copy)(struct hashtab_node *new, + struct hashtab_node *orig, void *args), + int (*destroy)(void *k, void *d, void *args), + void *args); + /* Fill info with some hash table statistics */ void hashtab_stat(struct hashtab *h, struct hashtab_info *info); diff --git a/security/selinux/ss/mls.c b/security/selinux/ss/mls.c index ec5e3d1da9ac..99571b19d4a9 100644 --- a/security/selinux/ss/mls.c +++ b/security/selinux/ss/mls.c @@ -156,7 +156,6 @@ void mls_sid_to_context(struct policydb *p, } *scontext = scontextp; - return; } int mls_level_isvalid(struct policydb *p, struct mls_level *l) @@ -165,8 +164,8 @@ int mls_level_isvalid(struct policydb *p, struct mls_level *l) if (!l->sens || l->sens > p->p_levels.nprim) return 0; - levdatum = hashtab_search(p->p_levels.table, - sym_name(p, SYM_LEVELS, l->sens - 1)); + levdatum = symtab_search(&p->p_levels, + sym_name(p, SYM_LEVELS, l->sens - 1)); if (!levdatum) return 0; @@ -293,7 +292,7 @@ int mls_context_to_sid(struct policydb *pol, *(next_cat++) = '\0'; /* Parse sensitivity. */ - levdatum = hashtab_search(pol->p_levels.table, sensitivity); + levdatum = symtab_search(&pol->p_levels, sensitivity); if (!levdatum) return -EINVAL; context->range.level[l].sens = levdatum->level->sens; @@ -312,7 +311,7 @@ int mls_context_to_sid(struct policydb *pol, *rngptr++ = '\0'; } - catdatum = hashtab_search(pol->p_cats.table, cur_cat); + catdatum = symtab_search(&pol->p_cats, cur_cat); if (!catdatum) return -EINVAL; @@ -325,7 +324,7 @@ int mls_context_to_sid(struct policydb *pol, if (rngptr == NULL) continue; - rngdatum = hashtab_search(pol->p_cats.table, rngptr); + rngdatum = symtab_search(&pol->p_cats, rngptr); if (!rngdatum) return -EINVAL; @@ -458,9 +457,10 @@ int mls_convert_context(struct policydb *oldp, return 0; for (l = 0; l < 2; l++) { - levdatum = hashtab_search(newp->p_levels.table, - sym_name(oldp, SYM_LEVELS, - oldc->range.level[l].sens - 1)); + char *name = sym_name(oldp, SYM_LEVELS, + oldc->range.level[l].sens - 1); + + levdatum = symtab_search(&newp->p_levels, name); if (!levdatum) return -EINVAL; @@ -470,8 +470,8 @@ int mls_convert_context(struct policydb *oldp, node, i) { int rc; - catdatum = hashtab_search(newp->p_cats.table, - sym_name(oldp, SYM_CATS, i)); + catdatum = symtab_search(&newp->p_cats, + sym_name(oldp, SYM_CATS, i)); if (!catdatum) return -EINVAL; rc = ebitmap_set_bit(&newc->range.level[l].cat, @@ -506,7 +506,7 @@ int mls_compute_sid(struct policydb *p, rtr.source_type = scontext->type; rtr.target_type = tcontext->type; rtr.target_class = tclass; - r = hashtab_search(p->range_tr, &rtr); + r = policydb_rangetr_search(p, &rtr); if (r) return mls_range_set(newcontext, r); @@ -534,9 +534,9 @@ int mls_compute_sid(struct policydb *p, scontext, tcontext); } - /* Fallthrough */ + fallthrough; case AVTAB_CHANGE: - if ((tclass == p->process_class) || (sock == true)) + if ((tclass == p->process_class) || sock) /* Use the process MLS attributes. */ return mls_context_cpy(newcontext, scontext); else @@ -545,8 +545,6 @@ int mls_compute_sid(struct policydb *p, case AVTAB_MEMBER: /* Use the process effective MLS attributes. */ return mls_context_cpy_low(newcontext, scontext); - - /* fall through */ } return -EINVAL; } @@ -554,6 +552,7 @@ int mls_compute_sid(struct policydb *p, #ifdef CONFIG_NETLABEL /** * mls_export_netlbl_lvl - Export the MLS sensitivity levels to NetLabel + * @p: the policy * @context: the security context * @secattr: the NetLabel security attributes * @@ -575,6 +574,7 @@ void mls_export_netlbl_lvl(struct policydb *p, /** * mls_import_netlbl_lvl - Import the NetLabel MLS sensitivity levels + * @p: the policy * @context: the security context * @secattr: the NetLabel security attributes * @@ -596,6 +596,7 @@ void mls_import_netlbl_lvl(struct policydb *p, /** * mls_export_netlbl_cat - Export the MLS categories to NetLabel + * @p: the policy * @context: the security context * @secattr: the NetLabel security attributes * @@ -623,6 +624,7 @@ int mls_export_netlbl_cat(struct policydb *p, /** * mls_import_netlbl_cat - Import the MLS categories from NetLabel + * @p: the policy * @context: the security context * @secattr: the NetLabel security attributes * diff --git a/security/selinux/ss/mls.h b/security/selinux/ss/mls.h index 7954b1e60b64..15cacde0ff61 100644 --- a/security/selinux/ss/mls.h +++ b/security/selinux/ss/mls.h @@ -22,7 +22,10 @@ #ifndef _SS_MLS_H_ #define _SS_MLS_H_ +#include <linux/jhash.h> + #include "context.h" +#include "ebitmap.h" #include "policydb.h" int mls_compute_context_len(struct policydb *p, struct context *context); @@ -101,5 +104,13 @@ static inline int mls_import_netlbl_cat(struct policydb *p, } #endif +static inline u32 mls_range_hash(const struct mls_range *r, u32 hash) +{ + hash = jhash_2words(r->level[0].sens, r->level[1].sens, hash); + hash = ebitmap_hash(&r->level[0].cat, hash); + hash = ebitmap_hash(&r->level[1].cat, hash); + return hash; +} + #endif /* _SS_MLS_H */ diff --git a/security/selinux/ss/mls_types.h b/security/selinux/ss/mls_types.h index 068e0d7809db..7d48d5e52233 100644 --- a/security/selinux/ss/mls_types.h +++ b/security/selinux/ss/mls_types.h @@ -27,13 +27,13 @@ struct mls_range { struct mls_level level[2]; /* low == level[0], high == level[1] */ }; -static inline int mls_level_eq(struct mls_level *l1, struct mls_level *l2) +static inline int mls_level_eq(const struct mls_level *l1, const struct mls_level *l2) { return ((l1->sens == l2->sens) && ebitmap_cmp(&l1->cat, &l2->cat)); } -static inline int mls_level_dom(struct mls_level *l1, struct mls_level *l2) +static inline int mls_level_dom(const struct mls_level *l1, const struct mls_level *l2) { return ((l1->sens >= l2->sens) && ebitmap_contains(&l1->cat, &l2->cat, 0)); diff --git a/security/selinux/ss/policydb.c b/security/selinux/ss/policydb.c index 2aa7f2e1a8e7..adcfb63b3550 100644 --- a/security/selinux/ss/policydb.c +++ b/security/selinux/ss/policydb.c @@ -41,8 +41,6 @@ #include "mls.h" #include "services.h" -#define _DEBUG_HASHES - #ifdef DEBUG_HASHES static const char *symtab_name[SYM_NUM] = { "common prefixes", @@ -56,17 +54,6 @@ static const char *symtab_name[SYM_NUM] = { }; #endif -static unsigned int symtab_sizes[SYM_NUM] = { - 2, - 32, - 16, - 512, - 128, - 16, - 16, - 16, -}; - struct policydb_compat_info { int version; int sym_num; @@ -74,7 +61,7 @@ struct policydb_compat_info { }; /* These need to be updated if SYM_NUM or OCON_NUM changes */ -static struct policydb_compat_info policydb_compat[] = { +static const struct policydb_compat_info policydb_compat[] = { { .version = POLICYDB_VERSION_BASE, .sym_num = SYM_NUM - 3, @@ -165,20 +152,23 @@ static struct policydb_compat_info policydb_compat[] = { .sym_num = SYM_NUM, .ocon_num = OCON_NUM, }, + { + .version = POLICYDB_VERSION_COMP_FTRANS, + .sym_num = SYM_NUM, + .ocon_num = OCON_NUM, + }, }; -static struct policydb_compat_info *policydb_lookup_compat(int version) +static const struct policydb_compat_info *policydb_lookup_compat(int version) { int i; - struct policydb_compat_info *info = NULL; for (i = 0; i < ARRAY_SIZE(policydb_compat); i++) { - if (policydb_compat[i].version == version) { - info = &policydb_compat[i]; - break; - } + if (policydb_compat[i].version == version) + return &policydb_compat[i]; } - return info; + + return NULL; } /* @@ -201,8 +191,8 @@ static int common_destroy(void *key, void *datum, void *p) kfree(key); if (datum) { comdatum = datum; - hashtab_map(comdatum->permissions.table, perm_destroy, NULL); - hashtab_destroy(comdatum->permissions.table); + hashtab_map(&comdatum->permissions.table, perm_destroy, NULL); + hashtab_destroy(&comdatum->permissions.table); } kfree(datum); return 0; @@ -230,8 +220,8 @@ static int cls_destroy(void *key, void *datum, void *p) kfree(key); if (datum) { cladatum = datum; - hashtab_map(cladatum->permissions.table, perm_destroy, NULL); - hashtab_destroy(cladatum->permissions.table); + hashtab_map(&cladatum->permissions.table, perm_destroy, NULL); + hashtab_destroy(&cladatum->permissions.table); constraint = cladatum->constraints; while (constraint) { e = constraint->expr; @@ -322,8 +312,7 @@ static int cat_destroy(void *key, void *datum, void *p) return 0; } -static int (*destroy_f[SYM_NUM]) (void *key, void *datum, void *datap) = -{ +static int (*const destroy_f[SYM_NUM]) (void *key, void *datum, void *datap) = { common_destroy, cls_destroy, role_destroy, @@ -336,11 +325,17 @@ static int (*destroy_f[SYM_NUM]) (void *key, void *datum, void *datap) = static int filenametr_destroy(void *key, void *datum, void *p) { - struct filename_trans *ft = key; + struct filename_trans_key *ft = key; + struct filename_trans_datum *next, *d = datum; kfree(ft->name); kfree(key); - kfree(datum); + do { + ebitmap_destroy(&d->stypes); + next = d->next; + kfree(d); + d = next; + } while (unlikely(d)); cond_resched(); return 0; } @@ -357,6 +352,13 @@ static int range_tr_destroy(void *key, void *datum, void *p) return 0; } +static int role_tr_destroy(void *key, void *datum, void *p) +{ + kfree(key); + kfree(datum); + return 0; +} + static void ocontext_destroy(struct ocontext *c, int i) { if (!c) @@ -393,7 +395,7 @@ static int roles_init(struct policydb *p) if (!key) goto out; - rc = hashtab_insert(p->p_roles.table, key, role); + rc = symtab_insert(&p->p_roles, key, role); if (rc) goto out; @@ -404,31 +406,27 @@ out: return rc; } -static u32 filenametr_hash(struct hashtab *h, const void *k) +static u32 filenametr_hash(const void *k) { - const struct filename_trans *ft = k; + const struct filename_trans_key *ft = k; unsigned long hash; unsigned int byte_num; unsigned char focus; - hash = ft->stype ^ ft->ttype ^ ft->tclass; + hash = ft->ttype ^ ft->tclass; byte_num = 0; while ((focus = ft->name[byte_num++])) hash = partial_name_hash(focus, hash); - return hash & (h->size - 1); + return hash; } -static int filenametr_cmp(struct hashtab *h, const void *k1, const void *k2) +static int filenametr_cmp(const void *k1, const void *k2) { - const struct filename_trans *ft1 = k1; - const struct filename_trans *ft2 = k2; + const struct filename_trans_key *ft1 = k1; + const struct filename_trans_key *ft2 = k2; int v; - v = ft1->stype - ft2->stype; - if (v) - return v; - v = ft1->ttype - ft2->ttype; if (v) return v; @@ -441,15 +439,26 @@ static int filenametr_cmp(struct hashtab *h, const void *k1, const void *k2) } -static u32 rangetr_hash(struct hashtab *h, const void *k) +static const struct hashtab_key_params filenametr_key_params = { + .hash = filenametr_hash, + .cmp = filenametr_cmp, +}; + +struct filename_trans_datum *policydb_filenametr_search( + struct policydb *p, struct filename_trans_key *key) +{ + return hashtab_search(&p->filename_trans, key, filenametr_key_params); +} + +static u32 rangetr_hash(const void *k) { const struct range_trans *key = k; - return (key->source_type + (key->target_type << 3) + - (key->target_class << 5)) & (h->size - 1); + return key->source_type + (key->target_type << 3) + + (key->target_class << 5); } -static int rangetr_cmp(struct hashtab *h, const void *k1, const void *k2) +static int rangetr_cmp(const void *k1, const void *k2) { const struct range_trans *key1 = k1, *key2 = k2; int v; @@ -467,59 +476,64 @@ static int rangetr_cmp(struct hashtab *h, const void *k1, const void *k2) return v; } -/* - * Initialize a policy database structure. - */ -static int policydb_init(struct policydb *p) +static const struct hashtab_key_params rangetr_key_params = { + .hash = rangetr_hash, + .cmp = rangetr_cmp, +}; + +struct mls_range *policydb_rangetr_search(struct policydb *p, + struct range_trans *key) { - int i, rc; + return hashtab_search(&p->range_tr, key, rangetr_key_params); +} - memset(p, 0, sizeof(*p)); +static u32 role_trans_hash(const void *k) +{ + const struct role_trans_key *key = k; - for (i = 0; i < SYM_NUM; i++) { - rc = symtab_init(&p->symtab[i], symtab_sizes[i]); - if (rc) - goto out; - } + return key->role + (key->type << 3) + (key->tclass << 5); +} - rc = avtab_init(&p->te_avtab); - if (rc) - goto out; +static int role_trans_cmp(const void *k1, const void *k2) +{ + const struct role_trans_key *key1 = k1, *key2 = k2; + int v; - rc = roles_init(p); - if (rc) - goto out; + v = key1->role - key2->role; + if (v) + return v; - rc = cond_policydb_init(p); - if (rc) - goto out; + v = key1->type - key2->type; + if (v) + return v; - p->filename_trans = hashtab_create(filenametr_hash, filenametr_cmp, - (1 << 10)); - if (!p->filename_trans) { - rc = -ENOMEM; - goto out; - } + return key1->tclass - key2->tclass; +} - p->range_tr = hashtab_create(rangetr_hash, rangetr_cmp, 256); - if (!p->range_tr) { - rc = -ENOMEM; - goto out; - } +static const struct hashtab_key_params roletr_key_params = { + .hash = role_trans_hash, + .cmp = role_trans_cmp, +}; + +struct role_trans_datum *policydb_roletr_search(struct policydb *p, + struct role_trans_key *key) +{ + return hashtab_search(&p->role_tr, key, roletr_key_params); +} + +/* + * Initialize a policy database structure. + */ +static void policydb_init(struct policydb *p) +{ + memset(p, 0, sizeof(*p)); + + avtab_init(&p->te_avtab); + cond_policydb_init(p); ebitmap_init(&p->filename_trans_ttypes); ebitmap_init(&p->policycaps); ebitmap_init(&p->permissive_map); - - return 0; -out: - hashtab_destroy(p->filename_trans); - hashtab_destroy(p->range_tr); - for (i = 0; i < SYM_NUM; i++) { - hashtab_map(p->symtab[i].table, destroy_f[i], NULL); - hashtab_destroy(p->symtab[i].table); - } - return rc; } /* @@ -653,8 +667,7 @@ static int cat_index(void *key, void *datum, void *datap) return 0; } -static int (*index_f[SYM_NUM]) (void *key, void *datum, void *datap) = -{ +static int (*const index_f[SYM_NUM]) (void *key, void *datum, void *datap) = { common_index, class_index, role_index, @@ -681,11 +694,11 @@ static void symtab_hash_eval(struct symtab *s) int i; for (i = 0; i < SYM_NUM; i++) - hash_eval(s[i].table, symtab_name[i]); + hash_eval(&s[i].table, symtab_name[i]); } #else -static inline void hash_eval(struct hashtab *h, char *hash_name) +static inline void hash_eval(struct hashtab *h, const char *hash_name) { } #endif @@ -752,7 +765,7 @@ static int policydb_index(struct policydb *p) if (!p->sym_val_to_name[i]) return -ENOMEM; - rc = hashtab_map(p->symtab[i].table, index_f[i], p); + rc = hashtab_map(&p->symtab[i].table, index_f[i], p); if (rc) goto out; } @@ -770,12 +783,11 @@ void policydb_destroy(struct policydb *p) struct genfs *g, *gtmp; int i; struct role_allow *ra, *lra = NULL; - struct role_trans *tr, *ltr = NULL; for (i = 0; i < SYM_NUM; i++) { cond_resched(); - hashtab_map(p->symtab[i].table, destroy_f[i], NULL); - hashtab_destroy(p->symtab[i].table); + hashtab_map(&p->symtab[i].table, destroy_f[i], NULL); + hashtab_destroy(&p->symtab[i].table); } for (i = 0; i < SYM_NUM; i++) @@ -817,12 +829,8 @@ void policydb_destroy(struct policydb *p) cond_policydb_destroy(p); - for (tr = p->role_tr; tr; tr = tr->next) { - cond_resched(); - kfree(ltr); - ltr = tr; - } - kfree(ltr); + hashtab_map(&p->role_tr, role_tr_destroy, NULL); + hashtab_destroy(&p->role_tr); for (ra = p->role_allow; ra; ra = ra->next) { cond_resched(); @@ -831,11 +839,11 @@ void policydb_destroy(struct policydb *p) } kfree(lra); - hashtab_map(p->filename_trans, filenametr_destroy, NULL); - hashtab_destroy(p->filename_trans); + hashtab_map(&p->filename_trans, filenametr_destroy, NULL); + hashtab_destroy(&p->filename_trans); - hashtab_map(p->range_tr, range_tr_destroy, NULL); - hashtab_destroy(p->range_tr); + hashtab_map(&p->range_tr, range_tr_destroy, NULL); + hashtab_destroy(&p->range_tr); if (p->type_attr_map_array) { for (i = 0; i < p->p_types.nprim; i++) @@ -860,41 +868,33 @@ int policydb_load_isids(struct policydb *p, struct sidtab *s) rc = sidtab_init(s); if (rc) { pr_err("SELinux: out of memory on SID table init\n"); - goto out; + return rc; } head = p->ocontexts[OCON_ISID]; for (c = head; c; c = c->next) { - rc = -EINVAL; - if (!c->context[0].user) { - pr_err("SELinux: SID %s was never defined.\n", - c->u.name); - sidtab_destroy(s); - goto out; - } - if (c->sid[0] == SECSID_NULL || c->sid[0] > SECINITSID_NUM) { - pr_err("SELinux: Initial SID %s out of range.\n", - c->u.name); - sidtab_destroy(s); - goto out; - } - rc = context_add_hash(p, &c->context[0]); - if (rc) { + u32 sid = c->sid[0]; + const char *name = security_get_initial_sid_context(sid); + + if (sid == SECSID_NULL) { + pr_err("SELinux: SID 0 was assigned a context.\n"); sidtab_destroy(s); - goto out; + return -EINVAL; } - rc = sidtab_set_initial(s, c->sid[0], &c->context[0]); + /* Ignore initial SIDs unused by this kernel. */ + if (!name) + continue; + + rc = sidtab_set_initial(s, sid, &c->context[0]); if (rc) { pr_err("SELinux: unable to load initial SID %s.\n", - c->u.name); + name); sidtab_destroy(s); - goto out; + return rc; } } - rc = 0; -out: - return rc; + return 0; } int policydb_class_isvalid(struct policydb *p, unsigned int class) @@ -1078,18 +1078,18 @@ static int str_read(char **strp, gfp_t flags, void *fp, u32 len) if (!str) return -ENOMEM; - /* it's expected the caller should free the str */ - *strp = str; - rc = next_entry(str, fp, len); - if (rc) + if (rc) { + kfree(str); return rc; + } str[len] = '\0'; + *strp = str; return 0; } -static int perm_read(struct policydb *p, struct hashtab *h, void *fp) +static int perm_read(struct policydb *p, struct symtab *s, void *fp) { char *key = NULL; struct perm_datum *perdatum; @@ -1112,7 +1112,7 @@ static int perm_read(struct policydb *p, struct hashtab *h, void *fp) if (rc) goto bad; - rc = hashtab_insert(h, key, perdatum); + rc = symtab_insert(s, key, perdatum); if (rc) goto bad; @@ -1122,7 +1122,7 @@ bad: return rc; } -static int common_read(struct policydb *p, struct hashtab *h, void *fp) +static int common_read(struct policydb *p, struct symtab *s, void *fp) { char *key = NULL; struct common_datum *comdatum; @@ -1140,24 +1140,24 @@ static int common_read(struct policydb *p, struct hashtab *h, void *fp) len = le32_to_cpu(buf[0]); comdatum->value = le32_to_cpu(buf[1]); + nel = le32_to_cpu(buf[3]); - rc = symtab_init(&comdatum->permissions, PERM_SYMTAB_SIZE); + rc = symtab_init(&comdatum->permissions, nel); if (rc) goto bad; comdatum->permissions.nprim = le32_to_cpu(buf[2]); - nel = le32_to_cpu(buf[3]); rc = str_read(&key, GFP_KERNEL, fp, len); if (rc) goto bad; for (i = 0; i < nel; i++) { - rc = perm_read(p, comdatum->permissions.table, fp); + rc = perm_read(p, &comdatum->permissions, fp); if (rc) goto bad; } - rc = hashtab_insert(h, key, comdatum); + rc = symtab_insert(s, key, comdatum); if (rc) goto bad; return 0; @@ -1262,10 +1262,9 @@ static int read_cons_helper(struct policydb *p, if (rc) return rc; if (p->policyvers >= - POLICYDB_VERSION_CONSTRAINT_NAMES) { - e->type_names = kzalloc(sizeof - (*e->type_names), - GFP_KERNEL); + POLICYDB_VERSION_CONSTRAINT_NAMES) { + e->type_names = kzalloc(sizeof + (*e->type_names), GFP_KERNEL); if (!e->type_names) return -ENOMEM; type_set_init(e->type_names); @@ -1287,7 +1286,7 @@ static int read_cons_helper(struct policydb *p, return 0; } -static int class_read(struct policydb *p, struct hashtab *h, void *fp) +static int class_read(struct policydb *p, struct symtab *s, void *fp) { char *key = NULL; struct class_datum *cladatum; @@ -1306,12 +1305,12 @@ static int class_read(struct policydb *p, struct hashtab *h, void *fp) len = le32_to_cpu(buf[0]); len2 = le32_to_cpu(buf[1]); cladatum->value = le32_to_cpu(buf[2]); + nel = le32_to_cpu(buf[4]); - rc = symtab_init(&cladatum->permissions, PERM_SYMTAB_SIZE); + rc = symtab_init(&cladatum->permissions, nel); if (rc) goto bad; cladatum->permissions.nprim = le32_to_cpu(buf[3]); - nel = le32_to_cpu(buf[4]); ncons = le32_to_cpu(buf[5]); @@ -1325,7 +1324,8 @@ static int class_read(struct policydb *p, struct hashtab *h, void *fp) goto bad; rc = -EINVAL; - cladatum->comdatum = hashtab_search(p->p_commons.table, cladatum->comkey); + cladatum->comdatum = symtab_search(&p->p_commons, + cladatum->comkey); if (!cladatum->comdatum) { pr_err("SELinux: unknown common %s\n", cladatum->comkey); @@ -1333,7 +1333,7 @@ static int class_read(struct policydb *p, struct hashtab *h, void *fp) } } for (i = 0; i < nel; i++) { - rc = perm_read(p, cladatum->permissions.table, fp); + rc = perm_read(p, &cladatum->permissions, fp); if (rc) goto bad; } @@ -1371,7 +1371,7 @@ static int class_read(struct policydb *p, struct hashtab *h, void *fp) cladatum->default_type = le32_to_cpu(buf[0]); } - rc = hashtab_insert(h, key, cladatum); + rc = symtab_insert(s, key, cladatum); if (rc) goto bad; @@ -1381,7 +1381,7 @@ bad: return rc; } -static int role_read(struct policydb *p, struct hashtab *h, void *fp) +static int role_read(struct policydb *p, struct symtab *s, void *fp) { char *key = NULL; struct role_datum *role; @@ -1428,7 +1428,7 @@ static int role_read(struct policydb *p, struct hashtab *h, void *fp) goto bad; } - rc = hashtab_insert(h, key, role); + rc = symtab_insert(s, key, role); if (rc) goto bad; return 0; @@ -1437,7 +1437,7 @@ bad: return rc; } -static int type_read(struct policydb *p, struct hashtab *h, void *fp) +static int type_read(struct policydb *p, struct symtab *s, void *fp) { char *key = NULL; struct type_datum *typdatum; @@ -1475,7 +1475,7 @@ static int type_read(struct policydb *p, struct hashtab *h, void *fp) if (rc) goto bad; - rc = hashtab_insert(h, key, typdatum); + rc = symtab_insert(s, key, typdatum); if (rc) goto bad; return 0; @@ -1511,7 +1511,7 @@ static int mls_read_level(struct mls_level *lp, void *fp) return 0; } -static int user_read(struct policydb *p, struct hashtab *h, void *fp) +static int user_read(struct policydb *p, struct symtab *s, void *fp) { char *key = NULL; struct user_datum *usrdatum; @@ -1552,7 +1552,7 @@ static int user_read(struct policydb *p, struct hashtab *h, void *fp) goto bad; } - rc = hashtab_insert(h, key, usrdatum); + rc = symtab_insert(s, key, usrdatum); if (rc) goto bad; return 0; @@ -1561,7 +1561,7 @@ bad: return rc; } -static int sens_read(struct policydb *p, struct hashtab *h, void *fp) +static int sens_read(struct policydb *p, struct symtab *s, void *fp) { char *key = NULL; struct level_datum *levdatum; @@ -1593,7 +1593,7 @@ static int sens_read(struct policydb *p, struct hashtab *h, void *fp) if (rc) goto bad; - rc = hashtab_insert(h, key, levdatum); + rc = symtab_insert(s, key, levdatum); if (rc) goto bad; return 0; @@ -1602,7 +1602,7 @@ bad: return rc; } -static int cat_read(struct policydb *p, struct hashtab *h, void *fp) +static int cat_read(struct policydb *p, struct symtab *s, void *fp) { char *key = NULL; struct cat_datum *catdatum; @@ -1626,7 +1626,7 @@ static int cat_read(struct policydb *p, struct hashtab *h, void *fp) if (rc) goto bad; - rc = hashtab_insert(h, key, catdatum); + rc = symtab_insert(s, key, catdatum); if (rc) goto bad; return 0; @@ -1635,8 +1635,8 @@ bad: return rc; } -static int (*read_f[SYM_NUM]) (struct policydb *p, struct hashtab *h, void *fp) = -{ +static int (*const read_f[SYM_NUM]) (struct policydb *p, + struct symtab *s, void *fp) = { common_read, class_read, role_read, @@ -1756,18 +1756,15 @@ static int policydb_bounds_sanity_check(struct policydb *p) if (p->policyvers < POLICYDB_VERSION_BOUNDARY) return 0; - rc = hashtab_map(p->p_users.table, - user_bounds_sanity_check, p); + rc = hashtab_map(&p->p_users.table, user_bounds_sanity_check, p); if (rc) return rc; - rc = hashtab_map(p->p_roles.table, - role_bounds_sanity_check, p); + rc = hashtab_map(&p->p_roles.table, role_bounds_sanity_check, p); if (rc) return rc; - rc = hashtab_map(p->p_types.table, - type_bounds_sanity_check, p); + rc = hashtab_map(&p->p_types.table, type_bounds_sanity_check, p); if (rc) return rc; @@ -1778,7 +1775,7 @@ u16 string_to_security_class(struct policydb *p, const char *name) { struct class_datum *cladatum; - cladatum = hashtab_search(p->p_classes.table, name); + cladatum = symtab_search(&p->p_classes, name); if (!cladatum) return 0; @@ -1797,11 +1794,9 @@ u32 string_to_av_perm(struct policydb *p, u16 tclass, const char *name) cladatum = p->class_val_to_struct[tclass-1]; comdatum = cladatum->comdatum; if (comdatum) - perdatum = hashtab_search(comdatum->permissions.table, - name); + perdatum = symtab_search(&comdatum->permissions, name); if (!perdatum) - perdatum = hashtab_search(cladatum->permissions.table, - name); + perdatum = symtab_search(&cladatum->permissions, name); if (!perdatum) return 0; @@ -1824,6 +1819,11 @@ static int range_read(struct policydb *p, void *fp) return rc; nel = le32_to_cpu(buf[0]); + + rc = hashtab_init(&p->range_tr, nel); + if (rc) + return rc; + for (i = 0; i < nel; i++) { rc = -ENOMEM; rt = kzalloc(sizeof(*rt), GFP_KERNEL); @@ -1865,14 +1865,14 @@ static int range_read(struct policydb *p, void *fp) goto out; } - rc = hashtab_insert(p->range_tr, rt, r); + rc = hashtab_insert(&p->range_tr, rt, r, rangetr_key_params); if (rc) goto out; rt = NULL; r = NULL; } - hash_eval(p->range_tr, "rangetr"); + hash_eval(&p->range_tr, "rangetr"); rc = 0; out: kfree(rt); @@ -1880,88 +1880,220 @@ out: return rc; } -static int filename_trans_read(struct policydb *p, void *fp) +static int filename_trans_read_helper_compat(struct policydb *p, void *fp) { - struct filename_trans *ft; - struct filename_trans_datum *otype; - char *name; - u32 nel, len; + struct filename_trans_key key, *ft = NULL; + struct filename_trans_datum *last, *datum = NULL; + char *name = NULL; + u32 len, stype, otype; __le32 buf[4]; - int rc, i; - - if (p->policyvers < POLICYDB_VERSION_FILENAME_TRANS) - return 0; + int rc; + /* length of the path component string */ rc = next_entry(buf, fp, sizeof(u32)); if (rc) return rc; - nel = le32_to_cpu(buf[0]); + len = le32_to_cpu(buf[0]); - for (i = 0; i < nel; i++) { - otype = NULL; - name = NULL; + /* path component string */ + rc = str_read(&name, GFP_KERNEL, fp, len); + if (rc) + return rc; + + rc = next_entry(buf, fp, sizeof(u32) * 4); + if (rc) + goto out; + + stype = le32_to_cpu(buf[0]); + key.ttype = le32_to_cpu(buf[1]); + key.tclass = le32_to_cpu(buf[2]); + key.name = name; + + otype = le32_to_cpu(buf[3]); + last = NULL; + datum = policydb_filenametr_search(p, &key); + while (datum) { + if (unlikely(ebitmap_get_bit(&datum->stypes, stype - 1))) { + /* conflicting/duplicate rules are ignored */ + datum = NULL; + goto out; + } + if (likely(datum->otype == otype)) + break; + last = datum; + datum = datum->next; + } + if (!datum) { rc = -ENOMEM; - ft = kzalloc(sizeof(*ft), GFP_KERNEL); - if (!ft) + datum = kmalloc(sizeof(*datum), GFP_KERNEL); + if (!datum) goto out; + ebitmap_init(&datum->stypes); + datum->otype = otype; + datum->next = NULL; + + if (unlikely(last)) { + last->next = datum; + } else { + rc = -ENOMEM; + ft = kmemdup(&key, sizeof(key), GFP_KERNEL); + if (!ft) + goto out; + + rc = hashtab_insert(&p->filename_trans, ft, datum, + filenametr_key_params); + if (rc) + goto out; + name = NULL; + + rc = ebitmap_set_bit(&p->filename_trans_ttypes, + key.ttype, 1); + if (rc) + return rc; + } + } + kfree(name); + return ebitmap_set_bit(&datum->stypes, stype - 1, 1); + +out: + kfree(ft); + kfree(name); + kfree(datum); + return rc; +} + +static int filename_trans_read_helper(struct policydb *p, void *fp) +{ + struct filename_trans_key *ft = NULL; + struct filename_trans_datum **dst, *datum, *first = NULL; + char *name = NULL; + u32 len, ttype, tclass, ndatum, i; + __le32 buf[3]; + int rc; + + /* length of the path component string */ + rc = next_entry(buf, fp, sizeof(u32)); + if (rc) + return rc; + len = le32_to_cpu(buf[0]); + + /* path component string */ + rc = str_read(&name, GFP_KERNEL, fp, len); + if (rc) + return rc; + + rc = next_entry(buf, fp, sizeof(u32) * 3); + if (rc) + goto out; + + ttype = le32_to_cpu(buf[0]); + tclass = le32_to_cpu(buf[1]); + + ndatum = le32_to_cpu(buf[2]); + if (ndatum == 0) { + pr_err("SELinux: Filename transition key with no datum\n"); + rc = -ENOENT; + goto out; + } + + dst = &first; + for (i = 0; i < ndatum; i++) { rc = -ENOMEM; - otype = kmalloc(sizeof(*otype), GFP_KERNEL); - if (!otype) + datum = kmalloc(sizeof(*datum), GFP_KERNEL); + if (!datum) goto out; - /* length of the path component string */ - rc = next_entry(buf, fp, sizeof(u32)); + *dst = datum; + + /* ebitmap_read() will at least init the bitmap */ + rc = ebitmap_read(&datum->stypes, fp); if (rc) goto out; - len = le32_to_cpu(buf[0]); - /* path component string */ - rc = str_read(&name, GFP_KERNEL, fp, len); + rc = next_entry(buf, fp, sizeof(u32)); if (rc) goto out; - ft->name = name; + datum->otype = le32_to_cpu(buf[0]); + datum->next = NULL; - rc = next_entry(buf, fp, sizeof(u32) * 4); - if (rc) - goto out; + dst = &datum->next; + } - ft->stype = le32_to_cpu(buf[0]); - ft->ttype = le32_to_cpu(buf[1]); - ft->tclass = le32_to_cpu(buf[2]); + rc = -ENOMEM; + ft = kmalloc(sizeof(*ft), GFP_KERNEL); + if (!ft) + goto out; - otype->otype = le32_to_cpu(buf[3]); + ft->ttype = ttype; + ft->tclass = tclass; + ft->name = name; - rc = ebitmap_set_bit(&p->filename_trans_ttypes, ft->ttype, 1); - if (rc) - goto out; + rc = hashtab_insert(&p->filename_trans, ft, first, + filenametr_key_params); + if (rc == -EEXIST) + pr_err("SELinux: Duplicate filename transition key\n"); + if (rc) + goto out; + + return ebitmap_set_bit(&p->filename_trans_ttypes, ttype, 1); - rc = hashtab_insert(p->filename_trans, ft, otype); - if (rc) { - /* - * Do not return -EEXIST to the caller, or the system - * will not boot. - */ - if (rc != -EEXIST) - goto out; - /* But free memory to avoid memory leak. */ - kfree(ft); - kfree(name); - kfree(otype); - } - } - hash_eval(p->filename_trans, "filenametr"); - return 0; out: kfree(ft); kfree(name); - kfree(otype); + while (first) { + datum = first; + first = first->next; + ebitmap_destroy(&datum->stypes); + kfree(datum); + } return rc; } +static int filename_trans_read(struct policydb *p, void *fp) +{ + u32 nel; + __le32 buf[1]; + int rc, i; + + if (p->policyvers < POLICYDB_VERSION_FILENAME_TRANS) + return 0; + + rc = next_entry(buf, fp, sizeof(u32)); + if (rc) + return rc; + nel = le32_to_cpu(buf[0]); + + if (p->policyvers < POLICYDB_VERSION_COMP_FTRANS) { + p->compat_filename_trans_count = nel; + + rc = hashtab_init(&p->filename_trans, (1 << 11)); + if (rc) + return rc; + + for (i = 0; i < nel; i++) { + rc = filename_trans_read_helper_compat(p, fp); + if (rc) + return rc; + } + } else { + rc = hashtab_init(&p->filename_trans, nel); + if (rc) + return rc; + + for (i = 0; i < nel; i++) { + rc = filename_trans_read_helper(p, fp); + if (rc) + return rc; + } + } + hash_eval(&p->filename_trans, "filenametr"); + return 0; +} + static int genfs_read(struct policydb *p, void *fp) { int i, j, rc; @@ -2075,7 +2207,7 @@ out: return rc; } -static int ocontext_read(struct policydb *p, struct policydb_compat_info *info, +static int ocontext_read(struct policydb *p, const struct policydb_compat_info *info, void *fp) { int i, j, rc; @@ -2264,17 +2396,16 @@ out: int policydb_read(struct policydb *p, void *fp) { struct role_allow *ra, *lra; - struct role_trans *tr, *ltr; + struct role_trans_key *rtk = NULL; + struct role_trans_datum *rtd = NULL; int i, j, rc; __le32 buf[4]; - u32 len, nprim, nel; + u32 len, nprim, nel, perm; char *policydb_str; - struct policydb_compat_info *info; + const struct policydb_compat_info *info; - rc = policydb_init(p); - if (rc) - return rc; + policydb_init(p); /* Read the magic number and string length. */ rc = next_entry(buf, fp, sizeof(u32) * 2); @@ -2390,8 +2521,19 @@ int policydb_read(struct policydb *p, void *fp) goto bad; nprim = le32_to_cpu(buf[0]); nel = le32_to_cpu(buf[1]); + + rc = symtab_init(&p->symtab[i], nel); + if (rc) + goto out; + + if (i == SYM_ROLES) { + rc = roles_init(p); + if (rc) + goto out; + } + for (j = 0; j < nel; j++) { - rc = read_f[i](p, p->symtab[i].table, fp); + rc = read_f[i](p, &p->symtab[i], fp); if (rc) goto bad; } @@ -2401,8 +2543,10 @@ int policydb_read(struct policydb *p, void *fp) rc = -EINVAL; p->process_class = string_to_security_class(p, "process"); - if (!p->process_class) + if (!p->process_class) { + pr_err("SELinux: process class is required, not defined in policy\n"); goto bad; + } rc = avtab_read(&p->te_avtab, fp, p); if (rc) @@ -2418,39 +2562,49 @@ int policydb_read(struct policydb *p, void *fp) if (rc) goto bad; nel = le32_to_cpu(buf[0]); - ltr = NULL; + + rc = hashtab_init(&p->role_tr, nel); + if (rc) + goto bad; for (i = 0; i < nel; i++) { rc = -ENOMEM; - tr = kzalloc(sizeof(*tr), GFP_KERNEL); - if (!tr) + rtk = kmalloc(sizeof(*rtk), GFP_KERNEL); + if (!rtk) goto bad; - if (ltr) - ltr->next = tr; - else - p->role_tr = tr; + + rc = -ENOMEM; + rtd = kmalloc(sizeof(*rtd), GFP_KERNEL); + if (!rtd) + goto bad; + rc = next_entry(buf, fp, sizeof(u32)*3); if (rc) goto bad; - rc = -EINVAL; - tr->role = le32_to_cpu(buf[0]); - tr->type = le32_to_cpu(buf[1]); - tr->new_role = le32_to_cpu(buf[2]); + rtk->role = le32_to_cpu(buf[0]); + rtk->type = le32_to_cpu(buf[1]); + rtd->new_role = le32_to_cpu(buf[2]); if (p->policyvers >= POLICYDB_VERSION_ROLETRANS) { rc = next_entry(buf, fp, sizeof(u32)); if (rc) goto bad; - tr->tclass = le32_to_cpu(buf[0]); + rtk->tclass = le32_to_cpu(buf[0]); } else - tr->tclass = p->process_class; + rtk->tclass = p->process_class; rc = -EINVAL; - if (!policydb_role_isvalid(p, tr->role) || - !policydb_type_isvalid(p, tr->type) || - !policydb_class_isvalid(p, tr->tclass) || - !policydb_role_isvalid(p, tr->new_role)) + if (!policydb_role_isvalid(p, rtk->role) || + !policydb_type_isvalid(p, rtk->type) || + !policydb_class_isvalid(p, rtk->tclass) || + !policydb_role_isvalid(p, rtd->new_role)) goto bad; - ltr = tr; + + rc = hashtab_insert(&p->role_tr, rtk, rtd, roletr_key_params); + if (rc) + goto bad; + + rtk = NULL; + rtd = NULL; } rc = next_entry(buf, fp, sizeof(u32)); @@ -2489,10 +2643,18 @@ int policydb_read(struct policydb *p, void *fp) goto bad; rc = -EINVAL; - p->process_trans_perms = string_to_av_perm(p, p->process_class, "transition"); - p->process_trans_perms |= string_to_av_perm(p, p->process_class, "dyntransition"); - if (!p->process_trans_perms) + perm = string_to_av_perm(p, p->process_class, "transition"); + if (!perm) { + pr_err("SELinux: process transition permission is required, not defined in policy\n"); + goto bad; + } + p->process_trans_perms = perm; + perm = string_to_av_perm(p, p->process_class, "dyntransition"); + if (!perm) { + pr_err("SELinux: process dyntransition permission is required, not defined in policy\n"); goto bad; + } + p->process_trans_perms |= perm; rc = ocontext_read(p, info, fp); if (rc) @@ -2506,6 +2668,7 @@ int policydb_read(struct policydb *p, void *fp) if (rc) goto bad; + rc = -ENOMEM; p->type_attr_map_array = kvcalloc(p->p_types.nprim, sizeof(*p->type_attr_map_array), GFP_KERNEL); @@ -2538,6 +2701,8 @@ int policydb_read(struct policydb *p, void *fp) out: return rc; bad: + kfree(rtk); + kfree(rtd); policydb_destroy(p); goto out; } @@ -2655,39 +2820,45 @@ static int cat_write(void *vkey, void *datum, void *ptr) return 0; } -static int role_trans_write(struct policydb *p, void *fp) +static int role_trans_write_one(void *key, void *datum, void *ptr) { - struct role_trans *r = p->role_tr; - struct role_trans *tr; + struct role_trans_key *rtk = key; + struct role_trans_datum *rtd = datum; + struct policy_data *pd = ptr; + void *fp = pd->fp; + struct policydb *p = pd->p; __le32 buf[3]; - size_t nel; int rc; - nel = 0; - for (tr = r; tr; tr = tr->next) - nel++; - buf[0] = cpu_to_le32(nel); - rc = put_entry(buf, sizeof(u32), 1, fp); + buf[0] = cpu_to_le32(rtk->role); + buf[1] = cpu_to_le32(rtk->type); + buf[2] = cpu_to_le32(rtd->new_role); + rc = put_entry(buf, sizeof(u32), 3, fp); if (rc) return rc; - for (tr = r; tr; tr = tr->next) { - buf[0] = cpu_to_le32(tr->role); - buf[1] = cpu_to_le32(tr->type); - buf[2] = cpu_to_le32(tr->new_role); - rc = put_entry(buf, sizeof(u32), 3, fp); + if (p->policyvers >= POLICYDB_VERSION_ROLETRANS) { + buf[0] = cpu_to_le32(rtk->tclass); + rc = put_entry(buf, sizeof(u32), 1, fp); if (rc) return rc; - if (p->policyvers >= POLICYDB_VERSION_ROLETRANS) { - buf[0] = cpu_to_le32(tr->tclass); - rc = put_entry(buf, sizeof(u32), 1, fp); - if (rc) - return rc; - } } - return 0; } +static int role_trans_write(struct policydb *p, void *fp) +{ + struct policy_data pd = { .p = p, .fp = fp }; + __le32 buf[1]; + int rc; + + buf[0] = cpu_to_le32(p->role_tr.nel); + rc = put_entry(buf, sizeof(u32), 1, fp); + if (rc) + return rc; + + return hashtab_map(&p->role_tr, role_trans_write_one, &pd); +} + static int role_allow_write(struct role_allow *r, void *fp) { struct role_allow *ra; @@ -2779,7 +2950,7 @@ static int common_write(void *vkey, void *datum, void *ptr) buf[0] = cpu_to_le32(len); buf[1] = cpu_to_le32(comdatum->value); buf[2] = cpu_to_le32(comdatum->permissions.nprim); - buf[3] = cpu_to_le32(comdatum->permissions.table->nel); + buf[3] = cpu_to_le32(comdatum->permissions.table.nel); rc = put_entry(buf, sizeof(u32), 4, fp); if (rc) return rc; @@ -2788,7 +2959,7 @@ static int common_write(void *vkey, void *datum, void *ptr) if (rc) return rc; - rc = hashtab_map(comdatum->permissions.table, perm_write, fp); + rc = hashtab_map(&comdatum->permissions.table, perm_write, fp); if (rc) return rc; @@ -2887,10 +3058,7 @@ static int class_write(void *vkey, void *datum, void *ptr) buf[1] = cpu_to_le32(len2); buf[2] = cpu_to_le32(cladatum->value); buf[3] = cpu_to_le32(cladatum->permissions.nprim); - if (cladatum->permissions.table) - buf[4] = cpu_to_le32(cladatum->permissions.table->nel); - else - buf[4] = 0; + buf[4] = cpu_to_le32(cladatum->permissions.table.nel); buf[5] = cpu_to_le32(ncons); rc = put_entry(buf, sizeof(u32), 6, fp); if (rc) @@ -2906,7 +3074,7 @@ static int class_write(void *vkey, void *datum, void *ptr) return rc; } - rc = hashtab_map(cladatum->permissions.table, perm_write, fp); + rc = hashtab_map(&cladatum->permissions.table, perm_write, fp); if (rc) return rc; @@ -3069,9 +3237,7 @@ static int user_write(void *vkey, void *datum, void *ptr) return 0; } -static int (*write_f[SYM_NUM]) (void *key, void *datum, - void *datap) = -{ +static int (*const write_f[SYM_NUM]) (void *key, void *datum, void *datap) = { common_write, class_write, role_write, @@ -3082,7 +3248,7 @@ static int (*write_f[SYM_NUM]) (void *key, void *datum, cat_write, }; -static int ocontext_write(struct policydb *p, struct policydb_compat_info *info, +static int ocontext_write(struct policydb *p, const struct policydb_compat_info *info, void *fp) { unsigned int i, j, rc; @@ -3264,14 +3430,6 @@ static int genfs_write(struct policydb *p, void *fp) return 0; } -static int hashtab_cnt(void *key, void *data, void *ptr) -{ - int *cnt = ptr; - *cnt = *cnt + 1; - - return 0; -} - static int range_write_helper(void *key, void *data, void *ptr) { __le32 buf[2]; @@ -3303,41 +3461,71 @@ static int range_write_helper(void *key, void *data, void *ptr) static int range_write(struct policydb *p, void *fp) { __le32 buf[1]; - int rc, nel; + int rc; struct policy_data pd; pd.p = p; pd.fp = fp; - /* count the number of entries in the hashtab */ - nel = 0; - rc = hashtab_map(p->range_tr, hashtab_cnt, &nel); - if (rc) - return rc; - - buf[0] = cpu_to_le32(nel); + buf[0] = cpu_to_le32(p->range_tr.nel); rc = put_entry(buf, sizeof(u32), 1, fp); if (rc) return rc; /* actually write all of the entries */ - rc = hashtab_map(p->range_tr, range_write_helper, &pd); + rc = hashtab_map(&p->range_tr, range_write_helper, &pd); if (rc) return rc; return 0; } -static int filename_write_helper(void *key, void *data, void *ptr) +static int filename_write_helper_compat(void *key, void *data, void *ptr) { + struct filename_trans_key *ft = key; + struct filename_trans_datum *datum = data; + struct ebitmap_node *node; + void *fp = ptr; __le32 buf[4]; - struct filename_trans *ft = key; - struct filename_trans_datum *otype = data; + int rc; + u32 bit, len = strlen(ft->name); + + do { + ebitmap_for_each_positive_bit(&datum->stypes, node, bit) { + buf[0] = cpu_to_le32(len); + rc = put_entry(buf, sizeof(u32), 1, fp); + if (rc) + return rc; + + rc = put_entry(ft->name, sizeof(char), len, fp); + if (rc) + return rc; + + buf[0] = cpu_to_le32(bit + 1); + buf[1] = cpu_to_le32(ft->ttype); + buf[2] = cpu_to_le32(ft->tclass); + buf[3] = cpu_to_le32(datum->otype); + + rc = put_entry(buf, sizeof(u32), 4, fp); + if (rc) + return rc; + } + + datum = datum->next; + } while (unlikely(datum)); + + return 0; +} + +static int filename_write_helper(void *key, void *data, void *ptr) +{ + struct filename_trans_key *ft = key; + struct filename_trans_datum *datum; void *fp = ptr; + __le32 buf[3]; int rc; - u32 len; + u32 ndatum, len = strlen(ft->name); - len = strlen(ft->name); buf[0] = cpu_to_le32(len); rc = put_entry(buf, sizeof(u32), 1, fp); if (rc) @@ -3347,42 +3535,62 @@ static int filename_write_helper(void *key, void *data, void *ptr) if (rc) return rc; - buf[0] = cpu_to_le32(ft->stype); - buf[1] = cpu_to_le32(ft->ttype); - buf[2] = cpu_to_le32(ft->tclass); - buf[3] = cpu_to_le32(otype->otype); + ndatum = 0; + datum = data; + do { + ndatum++; + datum = datum->next; + } while (unlikely(datum)); - rc = put_entry(buf, sizeof(u32), 4, fp); + buf[0] = cpu_to_le32(ft->ttype); + buf[1] = cpu_to_le32(ft->tclass); + buf[2] = cpu_to_le32(ndatum); + rc = put_entry(buf, sizeof(u32), 3, fp); if (rc) return rc; + datum = data; + do { + rc = ebitmap_write(&datum->stypes, fp); + if (rc) + return rc; + + buf[0] = cpu_to_le32(datum->otype); + rc = put_entry(buf, sizeof(u32), 1, fp); + if (rc) + return rc; + + datum = datum->next; + } while (unlikely(datum)); + return 0; } static int filename_trans_write(struct policydb *p, void *fp) { - u32 nel; __le32 buf[1]; int rc; if (p->policyvers < POLICYDB_VERSION_FILENAME_TRANS) return 0; - nel = 0; - rc = hashtab_map(p->filename_trans, hashtab_cnt, &nel); - if (rc) - return rc; - - buf[0] = cpu_to_le32(nel); - rc = put_entry(buf, sizeof(u32), 1, fp); - if (rc) - return rc; + if (p->policyvers < POLICYDB_VERSION_COMP_FTRANS) { + buf[0] = cpu_to_le32(p->compat_filename_trans_count); + rc = put_entry(buf, sizeof(u32), 1, fp); + if (rc) + return rc; - rc = hashtab_map(p->filename_trans, filename_write_helper, fp); - if (rc) - return rc; + rc = hashtab_map(&p->filename_trans, + filename_write_helper_compat, fp); + } else { + buf[0] = cpu_to_le32(p->filename_trans.nel); + rc = put_entry(buf, sizeof(u32), 1, fp); + if (rc) + return rc; - return 0; + rc = hashtab_map(&p->filename_trans, filename_write_helper, fp); + } + return rc; } /* @@ -3397,7 +3605,7 @@ int policydb_write(struct policydb *p, void *fp) __le32 buf[4]; u32 config; size_t len; - struct policydb_compat_info *info; + const struct policydb_compat_info *info; /* * refuse to write policy older than compressed avtab @@ -3469,12 +3677,12 @@ int policydb_write(struct policydb *p, void *fp) pd.p = p; buf[0] = cpu_to_le32(p->symtab[i].nprim); - buf[1] = cpu_to_le32(p->symtab[i].table->nel); + buf[1] = cpu_to_le32(p->symtab[i].table.nel); rc = put_entry(buf, sizeof(u32), 2, fp); if (rc) return rc; - rc = hashtab_map(p->symtab[i].table, write_f[i], &pd); + rc = hashtab_map(&p->symtab[i].table, write_f[i], &pd); if (rc) return rc; } @@ -3483,7 +3691,7 @@ int policydb_write(struct policydb *p, void *fp) if (rc) return rc; - rc = cond_write_list(p, p->cond_list, fp); + rc = cond_write_list(p, fp); if (rc) return rc; diff --git a/security/selinux/ss/policydb.h b/security/selinux/ss/policydb.h index 69b24191fa38..ffc4e7bad205 100644 --- a/security/selinux/ss/policydb.h +++ b/security/selinux/ss/policydb.h @@ -81,23 +81,26 @@ struct role_datum { struct ebitmap types; /* set of authorized types for role */ }; -struct role_trans { +struct role_trans_key { u32 role; /* current role */ u32 type; /* program executable type, or new object type */ u32 tclass; /* process class, or new object class */ +}; + +struct role_trans_datum { u32 new_role; /* new role */ - struct role_trans *next; }; -struct filename_trans { - u32 stype; /* current process */ +struct filename_trans_key { u32 ttype; /* parent dir context */ u16 tclass; /* class of new object */ const char *name; /* last path component */ }; struct filename_trans_datum { - u32 otype; /* expected of new object */ + struct ebitmap stypes; /* bitmap of source types for this otype */ + u32 otype; /* resulting type of new object */ + struct filename_trans_datum *next; /* record for next otype*/ }; struct role_allow { @@ -260,20 +263,23 @@ struct policydb { struct avtab te_avtab; /* role transitions */ - struct role_trans *role_tr; + struct hashtab role_tr; /* file transitions with the last path component */ /* quickly exclude lookups when parent ttype has no rules */ struct ebitmap filename_trans_ttypes; /* actual set of filename_trans rules */ - struct hashtab *filename_trans; + struct hashtab filename_trans; + /* only used if policyvers < POLICYDB_VERSION_COMP_FTRANS */ + u32 compat_filename_trans_count; /* bools indexed by (value - 1) */ struct cond_bool_datum **bool_val_to_struct; /* type enforcement conditional access vectors and transitions */ struct avtab te_cond_avtab; - /* linked list indexing te_cond_avtab by conditional */ + /* array indexing te_cond_avtab by conditional */ struct cond_node *cond_list; + u32 cond_list_len; /* role allows */ struct role_allow *role_allow; @@ -288,7 +294,7 @@ struct policydb { struct genfs *genfs; /* range transitions table (range_trans_key -> mls_range) */ - struct hashtab *range_tr; + struct hashtab range_tr; /* type -> attribute reverse mapping */ struct ebitmap *type_attr_map_array; @@ -318,7 +324,14 @@ extern int policydb_role_isvalid(struct policydb *p, unsigned int role); extern int policydb_read(struct policydb *p, void *fp); extern int policydb_write(struct policydb *p, void *fp); -#define PERM_SYMTAB_SIZE 32 +extern struct filename_trans_datum *policydb_filenametr_search( + struct policydb *p, struct filename_trans_key *key); + +extern struct mls_range *policydb_rangetr_search( + struct policydb *p, struct range_trans *key); + +extern struct role_trans_datum *policydb_roletr_search( + struct policydb *p, struct role_trans_key *key); #define POLICYDB_CONFIG_MLS 1 @@ -357,6 +370,8 @@ static inline int put_entry(const void *buf, size_t bytes, int num, struct polic { size_t len = bytes * num; + if (len > fp->len) + return -EINVAL; memcpy(fp->data, buf, len); fp->data += len; fp->len -= len; diff --git a/security/selinux/ss/services.c b/security/selinux/ss/services.c index 216ce602a2b5..64a6a37dc36d 100644 --- a/security/selinux/ss/services.c +++ b/security/selinux/ss/services.c @@ -46,8 +46,8 @@ #include <linux/in.h> #include <linux/sched.h> #include <linux/audit.h> -#include <linux/mutex.h> #include <linux/vmalloc.h> +#include <linux/lsm_hooks.h> #include <net/netlabel.h> #include "flask.h" @@ -65,25 +65,19 @@ #include "xfrm.h" #include "ebitmap.h" #include "audit.h" +#include "policycap_names.h" +#include "ima.h" -/* Policy capability names */ -const char *selinux_policycap_names[__POLICYDB_CAPABILITY_MAX] = { - "network_peer_controls", - "open_perms", - "extended_socket_class", - "always_check_network", - "cgroup_seclabel", - "nnp_nosuid_transition" +struct convert_context_args { + struct selinux_state *state; + struct policydb *oldp; + struct policydb *newp; }; -static struct selinux_ss selinux_ss; - -void selinux_ss_init(struct selinux_ss **ss) -{ - rwlock_init(&selinux_ss.policy_rwlock); - mutex_init(&selinux_ss.status_lock); - *ss = &selinux_ss; -} +struct selinux_policy_convert_data { + struct convert_context_args args; + struct sidtab_convert_params sidtab_params; +}; /* Forward declaration. */ static int context_struct_to_string(struct policydb *policydb, @@ -105,7 +99,7 @@ static void context_struct_compute_av(struct policydb *policydb, struct extended_perms *xperms); static int selinux_set_mapping(struct policydb *pol, - struct security_class_mapping *map, + const struct security_class_mapping *map, struct selinux_map *out_map) { u16 i, j; @@ -127,7 +121,7 @@ static int selinux_set_mapping(struct policydb *pol, /* Store the raw class and permission values */ j = 0; while (map[j].name) { - struct security_class_mapping *p_in = map + (j++); + const struct security_class_mapping *p_in = map + (j++); struct selinux_mapping *p_out = out_map->mapping + j; /* An empty class string skips ahead */ @@ -249,9 +243,17 @@ static void map_decision(struct selinux_map *map, int security_mls_enabled(struct selinux_state *state) { - struct policydb *p = &state->ss->policydb; + int mls_enabled; + struct selinux_policy *policy; + + if (!selinux_initialized(state)) + return 0; - return p->mls_enabled; + rcu_read_lock(); + policy = rcu_dereference(state->policy); + mls_enabled = policy->policydb.mls_enabled; + rcu_read_unlock(); + return mls_enabled; } /* @@ -356,27 +358,27 @@ static int constraint_expr_eval(struct policydb *policydb, l2 = &(tcontext->range.level[1]); goto mls_ops; mls_ops: - switch (e->op) { - case CEXPR_EQ: - s[++sp] = mls_level_eq(l1, l2); - continue; - case CEXPR_NEQ: - s[++sp] = !mls_level_eq(l1, l2); - continue; - case CEXPR_DOM: - s[++sp] = mls_level_dom(l1, l2); - continue; - case CEXPR_DOMBY: - s[++sp] = mls_level_dom(l2, l1); - continue; - case CEXPR_INCOMP: - s[++sp] = mls_level_incomp(l2, l1); - continue; - default: - BUG(); - return 0; - } - break; + switch (e->op) { + case CEXPR_EQ: + s[++sp] = mls_level_eq(l1, l2); + continue; + case CEXPR_NEQ: + s[++sp] = !mls_level_eq(l1, l2); + continue; + case CEXPR_DOM: + s[++sp] = mls_level_dom(l1, l2); + continue; + case CEXPR_DOMBY: + s[++sp] = mls_level_dom(l2, l1); + continue; + case CEXPR_INCOMP: + s[++sp] = mls_level_incomp(l2, l1); + continue; + default: + BUG(); + return 0; + } + break; default: BUG(); return 0; @@ -483,11 +485,11 @@ static void security_dump_masked_av(struct policydb *policydb, /* init permission_names */ if (common_dat && - hashtab_map(common_dat->permissions.table, + hashtab_map(&common_dat->permissions.table, dump_masked_av_helper, permission_names) < 0) goto out; - if (hashtab_map(tclass_dat->permissions.table, + if (hashtab_map(&tclass_dat->permissions.table, dump_masked_av_helper, permission_names) < 0) goto out; @@ -527,8 +529,6 @@ out: /* release scontext/tcontext */ kfree(tcontext_name); kfree(scontext_name); - - return; } /* @@ -607,9 +607,7 @@ void services_compute_xperms_drivers( node->datum.u.xperms->driver); } - /* If no ioctl commands are allowed, ignore auditallow and auditdeny */ - if (node->key.specified & AVTAB_XPERMS_ALLOWED) - xperms->len = 1; + xperms->len = 1; } /* @@ -722,13 +720,14 @@ static void context_struct_compute_av(struct policydb *policydb, } static int security_validtrans_handle_fail(struct selinux_state *state, - struct sidtab_entry *oentry, - struct sidtab_entry *nentry, - struct sidtab_entry *tentry, - u16 tclass) -{ - struct policydb *p = &state->ss->policydb; - struct sidtab *sidtab = state->ss->sidtab; + struct selinux_policy *policy, + struct sidtab_entry *oentry, + struct sidtab_entry *nentry, + struct sidtab_entry *tentry, + u16 tclass) +{ + struct policydb *p = &policy->policydb; + struct sidtab *sidtab = policy->sidtab; char *o = NULL, *n = NULL, *t = NULL; u32 olen, nlen, tlen; @@ -756,6 +755,7 @@ static int security_compute_validatetrans(struct selinux_state *state, u32 oldsid, u32 newsid, u32 tasksid, u16 orig_tclass, bool user) { + struct selinux_policy *policy; struct policydb *policydb; struct sidtab *sidtab; struct sidtab_entry *oentry; @@ -770,13 +770,14 @@ static int security_compute_validatetrans(struct selinux_state *state, if (!selinux_initialized(state)) return 0; - read_lock(&state->ss->policy_rwlock); + rcu_read_lock(); - policydb = &state->ss->policydb; - sidtab = state->ss->sidtab; + policy = rcu_dereference(state->policy); + policydb = &policy->policydb; + sidtab = policy->sidtab; if (!user) - tclass = unmap_class(&state->ss->map, orig_tclass); + tclass = unmap_class(&policy->map, orig_tclass); else tclass = orig_tclass; @@ -819,17 +820,18 @@ static int security_compute_validatetrans(struct selinux_state *state, rc = -EPERM; else rc = security_validtrans_handle_fail(state, - oentry, - nentry, - tentry, - tclass); + policy, + oentry, + nentry, + tentry, + tclass); goto out; } constraint = constraint->next; } out: - read_unlock(&state->ss->policy_rwlock); + rcu_read_unlock(); return rc; } @@ -855,12 +857,14 @@ int security_validate_transition(struct selinux_state *state, * It returns 0, if @newsid is bounded by @oldsid. * Otherwise, it returns error code. * + * @state: SELinux state * @oldsid : current security identifier * @newsid : destinated security identifier */ int security_bounded_transition(struct selinux_state *state, u32 old_sid, u32 new_sid) { + struct selinux_policy *policy; struct policydb *policydb; struct sidtab *sidtab; struct sidtab_entry *old_entry, *new_entry; @@ -871,10 +875,10 @@ int security_bounded_transition(struct selinux_state *state, if (!selinux_initialized(state)) return 0; - read_lock(&state->ss->policy_rwlock); - - policydb = &state->ss->policydb; - sidtab = state->ss->sidtab; + rcu_read_lock(); + policy = rcu_dereference(state->policy); + policydb = &policy->policydb; + sidtab = policy->sidtab; rc = -EINVAL; old_entry = sidtab_search_entry(sidtab, old_sid); @@ -935,17 +939,20 @@ int security_bounded_transition(struct selinux_state *state, kfree(old_name); } out: - read_unlock(&state->ss->policy_rwlock); + rcu_read_unlock(); return rc; } -static void avd_init(struct selinux_state *state, struct av_decision *avd) +static void avd_init(struct selinux_policy *policy, struct av_decision *avd) { avd->allowed = 0; avd->auditallow = 0; avd->auditdeny = 0xffffffff; - avd->seqno = state->ss->latest_granting; + if (policy) + avd->seqno = policy->latest_granting; + else + avd->seqno = 0; avd->flags = 0; } @@ -1010,6 +1017,7 @@ void security_compute_xperms_decision(struct selinux_state *state, u8 driver, struct extended_perms_decision *xpermd) { + struct selinux_policy *policy; struct policydb *policydb; struct sidtab *sidtab; u16 tclass; @@ -1026,12 +1034,13 @@ void security_compute_xperms_decision(struct selinux_state *state, memset(xpermd->auditallow->p, 0, sizeof(xpermd->auditallow->p)); memset(xpermd->dontaudit->p, 0, sizeof(xpermd->dontaudit->p)); - read_lock(&state->ss->policy_rwlock); + rcu_read_lock(); if (!selinux_initialized(state)) goto allow; - policydb = &state->ss->policydb; - sidtab = state->ss->sidtab; + policy = rcu_dereference(state->policy); + policydb = &policy->policydb; + sidtab = policy->sidtab; scontext = sidtab_search(sidtab, ssid); if (!scontext) { @@ -1047,7 +1056,7 @@ void security_compute_xperms_decision(struct selinux_state *state, goto out; } - tclass = unmap_class(&state->ss->map, orig_tclass); + tclass = unmap_class(&policy->map, orig_tclass); if (unlikely(orig_tclass && !tclass)) { if (policydb->allow_unknown) goto allow; @@ -1079,7 +1088,7 @@ void security_compute_xperms_decision(struct selinux_state *state, } } out: - read_unlock(&state->ss->policy_rwlock); + rcu_read_unlock(); return; allow: memset(xpermd->allowed->p, 0xff, sizeof(xpermd->allowed->p)); @@ -1088,9 +1097,10 @@ allow: /** * security_compute_av - Compute access vector decisions. + * @state: SELinux state * @ssid: source security identifier * @tsid: target security identifier - * @tclass: target security class + * @orig_tclass: target security class * @avd: access vector decisions * @xperms: extended permissions * @@ -1104,19 +1114,21 @@ void security_compute_av(struct selinux_state *state, struct av_decision *avd, struct extended_perms *xperms) { + struct selinux_policy *policy; struct policydb *policydb; struct sidtab *sidtab; u16 tclass; struct context *scontext = NULL, *tcontext = NULL; - read_lock(&state->ss->policy_rwlock); - avd_init(state, avd); + rcu_read_lock(); + policy = rcu_dereference(state->policy); + avd_init(policy, avd); xperms->len = 0; if (!selinux_initialized(state)) goto allow; - policydb = &state->ss->policydb; - sidtab = state->ss->sidtab; + policydb = &policy->policydb; + sidtab = policy->sidtab; scontext = sidtab_search(sidtab, ssid); if (!scontext) { @@ -1136,7 +1148,7 @@ void security_compute_av(struct selinux_state *state, goto out; } - tclass = unmap_class(&state->ss->map, orig_tclass); + tclass = unmap_class(&policy->map, orig_tclass); if (unlikely(orig_tclass && !tclass)) { if (policydb->allow_unknown) goto allow; @@ -1144,10 +1156,10 @@ void security_compute_av(struct selinux_state *state, } context_struct_compute_av(policydb, scontext, tcontext, tclass, avd, xperms); - map_decision(&state->ss->map, orig_tclass, avd, + map_decision(&policy->map, orig_tclass, avd, policydb->allow_unknown); out: - read_unlock(&state->ss->policy_rwlock); + rcu_read_unlock(); return; allow: avd->allowed = 0xffffffff; @@ -1160,17 +1172,19 @@ void security_compute_av_user(struct selinux_state *state, u16 tclass, struct av_decision *avd) { + struct selinux_policy *policy; struct policydb *policydb; struct sidtab *sidtab; struct context *scontext = NULL, *tcontext = NULL; - read_lock(&state->ss->policy_rwlock); - avd_init(state, avd); + rcu_read_lock(); + policy = rcu_dereference(state->policy); + avd_init(policy, avd); if (!selinux_initialized(state)) goto allow; - policydb = &state->ss->policydb; - sidtab = state->ss->sidtab; + policydb = &policy->policydb; + sidtab = policy->sidtab; scontext = sidtab_search(sidtab, ssid); if (!scontext) { @@ -1199,7 +1213,7 @@ void security_compute_av_user(struct selinux_state *state, context_struct_compute_av(policydb, scontext, tcontext, tclass, avd, NULL); out: - read_unlock(&state->ss->policy_rwlock); + rcu_read_unlock(); return; allow: avd->allowed = 0xffffffff; @@ -1284,6 +1298,7 @@ static int sidtab_entry_to_string(struct policydb *p, int security_sidtab_hash_stats(struct selinux_state *state, char *page) { + struct selinux_policy *policy; int rc; if (!selinux_initialized(state)) { @@ -1292,9 +1307,10 @@ int security_sidtab_hash_stats(struct selinux_state *state, char *page) return -EINVAL; } - read_lock(&state->ss->policy_rwlock); - rc = sidtab_hash_stats(state->ss->sidtab, page); - read_unlock(&state->ss->policy_rwlock); + rcu_read_lock(); + policy = rcu_dereference(state->policy); + rc = sidtab_hash_stats(policy->sidtab, page); + rcu_read_unlock(); return rc; } @@ -1311,6 +1327,7 @@ static int security_sid_to_context_core(struct selinux_state *state, u32 *scontext_len, int force, int only_invalid) { + struct selinux_policy *policy; struct policydb *policydb; struct sidtab *sidtab; struct sidtab_entry *entry; @@ -1323,27 +1340,27 @@ static int security_sid_to_context_core(struct selinux_state *state, if (!selinux_initialized(state)) { if (sid <= SECINITSID_NUM) { char *scontextp; + const char *s = initial_sid_to_string[sid]; - *scontext_len = strlen(initial_sid_to_string[sid]) + 1; + if (!s) + return -EINVAL; + *scontext_len = strlen(s) + 1; if (!scontext) - goto out; - scontextp = kmemdup(initial_sid_to_string[sid], - *scontext_len, GFP_ATOMIC); - if (!scontextp) { - rc = -ENOMEM; - goto out; - } + return 0; + scontextp = kmemdup(s, *scontext_len, GFP_ATOMIC); + if (!scontextp) + return -ENOMEM; *scontext = scontextp; - goto out; + return 0; } pr_err("SELinux: %s: called before initial " "load_policy on unknown SID %d\n", __func__, sid); - rc = -EINVAL; - goto out; + return -EINVAL; } - read_lock(&state->ss->policy_rwlock); - policydb = &state->ss->policydb; - sidtab = state->ss->sidtab; + rcu_read_lock(); + policy = rcu_dereference(state->policy); + policydb = &policy->policydb; + sidtab = policy->sidtab; if (force) entry = sidtab_search_entry_force(sidtab, sid); @@ -1362,14 +1379,14 @@ static int security_sid_to_context_core(struct selinux_state *state, scontext_len); out_unlock: - read_unlock(&state->ss->policy_rwlock); -out: + rcu_read_unlock(); return rc; } /** * security_sid_to_context - Obtain a context for a given SID. + * @state: SELinux state * @sid: security identifier, SID * @scontext: security context * @scontext_len: length in bytes @@ -1395,6 +1412,7 @@ int security_sid_to_context_force(struct selinux_state *state, u32 sid, /** * security_sid_to_context_inval - Obtain a context for a given SID if it * is invalid. + * @state: SELinux state * @sid: security identifier, SID * @scontext: security context * @scontext_len: length in bytes @@ -1432,7 +1450,7 @@ static int string_to_context_struct(struct policydb *pol, /* Parse the security context. */ rc = -EINVAL; - scontextp = (char *) scontext; + scontextp = scontext; /* Extract the user. */ p = scontextp; @@ -1444,7 +1462,7 @@ static int string_to_context_struct(struct policydb *pol, *p++ = 0; - usrdatum = hashtab_search(pol->p_users.table, scontextp); + usrdatum = symtab_search(&pol->p_users, scontextp); if (!usrdatum) goto out; @@ -1460,7 +1478,7 @@ static int string_to_context_struct(struct policydb *pol, *p++ = 0; - role = hashtab_search(pol->p_roles.table, scontextp); + role = symtab_search(&pol->p_roles, scontextp); if (!role) goto out; ctx->role = role->value; @@ -1472,7 +1490,7 @@ static int string_to_context_struct(struct policydb *pol, oldc = *p; *p++ = 0; - typdatum = hashtab_search(pol->p_types.table, scontextp); + typdatum = symtab_search(&pol->p_types, scontextp); if (!typdatum || typdatum->attribute) goto out; @@ -1493,47 +1511,12 @@ out: return rc; } -int context_add_hash(struct policydb *policydb, - struct context *context) -{ - int rc; - char *str; - int len; - - if (context->str) { - context->hash = context_compute_hash(context->str); - } else { - rc = context_struct_to_string(policydb, context, - &str, &len); - if (rc) - return rc; - context->hash = context_compute_hash(str); - kfree(str); - } - return 0; -} - -static int context_struct_to_sid(struct selinux_state *state, - struct context *context, u32 *sid) -{ - int rc; - struct sidtab *sidtab = state->ss->sidtab; - struct policydb *policydb = &state->ss->policydb; - - if (!context->hash) { - rc = context_add_hash(policydb, context); - if (rc) - return rc; - } - - return sidtab_context_to_sid(sidtab, context, sid); -} - static int security_context_to_sid_core(struct selinux_state *state, const char *scontext, u32 scontext_len, u32 *sid, u32 def_sid, gfp_t gfp_flags, int force) { + struct selinux_policy *policy; struct policydb *policydb; struct sidtab *sidtab; char *scontext2, *str = NULL; @@ -1553,7 +1536,9 @@ static int security_context_to_sid_core(struct selinux_state *state, int i; for (i = 1; i < SECINITSID_NUM; i++) { - if (!strcmp(initial_sid_to_string[i], scontext2)) { + const char *s = initial_sid_to_string[i]; + + if (s && !strcmp(s, scontext2)) { *sid = i; goto out; } @@ -1570,9 +1555,11 @@ static int security_context_to_sid_core(struct selinux_state *state, if (!str) goto out; } - read_lock(&state->ss->policy_rwlock); - policydb = &state->ss->policydb; - sidtab = state->ss->sidtab; +retry: + rcu_read_lock(); + policy = rcu_dereference(state->policy); + policydb = &policy->policydb; + sidtab = policy->sidtab; rc = string_to_context_struct(policydb, sidtab, scontext2, &context, def_sid); if (rc == -EINVAL && force) { @@ -1581,10 +1568,19 @@ static int security_context_to_sid_core(struct selinux_state *state, str = NULL; } else if (rc) goto out_unlock; - rc = context_struct_to_sid(state, &context, sid); + rc = sidtab_context_to_sid(sidtab, &context, sid); + if (rc == -ESTALE) { + rcu_read_unlock(); + if (context.str) { + str = context.str; + context.str = NULL; + } + context_destroy(&context); + goto retry; + } context_destroy(&context); out_unlock: - read_unlock(&state->ss->policy_rwlock); + rcu_read_unlock(); out: kfree(scontext2); kfree(str); @@ -1593,6 +1589,7 @@ out: /** * security_context_to_sid - Obtain a SID for a given security context. + * @state: SELinux state * @scontext: security context * @scontext_len: length in bytes * @sid: security identifier, SID @@ -1622,10 +1619,12 @@ int security_context_str_to_sid(struct selinux_state *state, * security_context_to_sid_default - Obtain a SID for a given security context, * falling back to specified default if needed. * + * @state: SELinux state * @scontext: security context * @scontext_len: length in bytes * @sid: security identifier, SID * @def_sid: default SID to assign on error + * @gfp_flags: the allocator get-free-page (GFP) flags * * Obtains a SID associated with the security context that * has the string representation specified by @scontext. @@ -1654,13 +1653,14 @@ int security_context_to_sid_force(struct selinux_state *state, static int compute_sid_handle_invalid_context( struct selinux_state *state, + struct selinux_policy *policy, struct sidtab_entry *sentry, struct sidtab_entry *tentry, u16 tclass, struct context *newcontext) { - struct policydb *policydb = &state->ss->policydb; - struct sidtab *sidtab = state->ss->sidtab; + struct policydb *policydb = &policy->policydb; + struct sidtab *sidtab = policy->sidtab; char *s = NULL, *t = NULL, *n = NULL; u32 slen, tlen, nlen; struct audit_buffer *ab; @@ -1672,6 +1672,8 @@ static int compute_sid_handle_invalid_context( if (context_struct_to_string(policydb, newcontext, &n, &nlen)) goto out; ab = audit_log_start(audit_context(), GFP_ATOMIC, AUDIT_SELINUX_ERR); + if (!ab) + goto out; audit_log_format(ab, "op=security_compute_sid invalid_context="); /* no need to record the NUL with untrusted strings */ @@ -1693,8 +1695,8 @@ static void filename_compute_type(struct policydb *policydb, u32 stype, u32 ttype, u16 tclass, const char *objname) { - struct filename_trans ft; - struct filename_trans_datum *otype; + struct filename_trans_key ft; + struct filename_trans_datum *datum; /* * Most filename trans rules are going to live in specific directories @@ -1704,14 +1706,18 @@ static void filename_compute_type(struct policydb *policydb, if (!ebitmap_get_bit(&policydb->filename_trans_ttypes, ttype)) return; - ft.stype = stype; ft.ttype = ttype; ft.tclass = tclass; ft.name = objname; - otype = hashtab_search(policydb->filename_trans, &ft); - if (otype) - newcontext->type = otype->otype; + datum = policydb_filenametr_search(policydb, &ft); + while (datum) { + if (ebitmap_get_bit(&datum->stypes, stype - 1)) { + newcontext->type = datum->otype; + return; + } + datum = datum->next; + } } static int security_compute_sid(struct selinux_state *state, @@ -1723,12 +1729,12 @@ static int security_compute_sid(struct selinux_state *state, u32 *out_sid, bool kern) { + struct selinux_policy *policy; struct policydb *policydb; struct sidtab *sidtab; - struct class_datum *cladatum = NULL; + struct class_datum *cladatum; struct context *scontext, *tcontext, newcontext; struct sidtab_entry *sentry, *tentry; - struct role_trans *roletr = NULL; struct avtab_key avkey; struct avtab_datum *avdatum; struct avtab_node *node; @@ -1748,21 +1754,25 @@ static int security_compute_sid(struct selinux_state *state, goto out; } +retry: + cladatum = NULL; context_init(&newcontext); - read_lock(&state->ss->policy_rwlock); + rcu_read_lock(); + + policy = rcu_dereference(state->policy); if (kern) { - tclass = unmap_class(&state->ss->map, orig_tclass); + tclass = unmap_class(&policy->map, orig_tclass); sock = security_is_socket_class(orig_tclass); } else { tclass = orig_tclass; - sock = security_is_socket_class(map_class(&state->ss->map, + sock = security_is_socket_class(map_class(&policy->map, tclass)); } - policydb = &state->ss->policydb; - sidtab = state->ss->sidtab; + policydb = &policy->policydb; + sidtab = policy->sidtab; sentry = sidtab_search_entry(sidtab, ssid); if (!sentry) { @@ -1809,7 +1819,7 @@ static int security_compute_sid(struct selinux_state *state, } else if (cladatum && cladatum->default_role == DEFAULT_TARGET) { newcontext.role = tcontext->role; } else { - if ((tclass == policydb->process_class) || (sock == true)) + if ((tclass == policydb->process_class) || sock) newcontext.role = scontext->role; else newcontext.role = OBJECT_R_VAL; @@ -1821,7 +1831,7 @@ static int security_compute_sid(struct selinux_state *state, } else if (cladatum && cladatum->default_type == DEFAULT_TARGET) { newcontext.type = tcontext->type; } else { - if ((tclass == policydb->process_class) || (sock == true)) { + if ((tclass == policydb->process_class) || sock) { /* Use the type of process. */ newcontext.type = scontext->type; } else { @@ -1861,16 +1871,16 @@ static int security_compute_sid(struct selinux_state *state, /* Check for class-specific changes. */ if (specified & AVTAB_TRANSITION) { /* Look for a role transition rule. */ - for (roletr = policydb->role_tr; roletr; - roletr = roletr->next) { - if ((roletr->role == scontext->role) && - (roletr->type == tcontext->type) && - (roletr->tclass == tclass)) { - /* Use the role transition rule. */ - newcontext.role = roletr->new_role; - break; - } - } + struct role_trans_datum *rtd; + struct role_trans_key rtk = { + .role = scontext->role, + .type = tcontext->type, + .tclass = tclass, + }; + + rtd = policydb_roletr_search(policydb, &rtk); + if (rtd) + newcontext.role = rtd->new_role; } /* Set the MLS attributes. @@ -1882,15 +1892,21 @@ static int security_compute_sid(struct selinux_state *state, /* Check the validity of the context. */ if (!policydb_context_isvalid(policydb, &newcontext)) { - rc = compute_sid_handle_invalid_context(state, sentry, tentry, - tclass, &newcontext); + rc = compute_sid_handle_invalid_context(state, policy, sentry, + tentry, tclass, + &newcontext); if (rc) goto out_unlock; } /* Obtain the sid for the context. */ - rc = context_struct_to_sid(state, &newcontext, out_sid); + rc = sidtab_context_to_sid(sidtab, &newcontext, out_sid); + if (rc == -ESTALE) { + rcu_read_unlock(); + context_destroy(&newcontext); + goto retry; + } out_unlock: - read_unlock(&state->ss->policy_rwlock); + rcu_read_unlock(); context_destroy(&newcontext); out: return rc; @@ -1898,9 +1914,11 @@ out: /** * security_transition_sid - Compute the SID for a new subject/object. + * @state: SELinux state * @ssid: source security identifier * @tsid: target security identifier * @tclass: target security class + * @qstr: object name * @out_sid: security identifier for new subject/object * * Compute a SID to use for labeling a new subject or object in the @@ -1929,6 +1947,7 @@ int security_transition_sid_user(struct selinux_state *state, /** * security_member_sid - Compute the SID for member selection. + * @state: SELinux state * @ssid: source security identifier * @tsid: target security identifier * @tclass: target security class @@ -1953,6 +1972,7 @@ int security_member_sid(struct selinux_state *state, /** * security_change_sid - Compute the SID for object relabeling. + * @state: SELinux state * @ssid: source security identifier * @tsid: target security identifier * @tclass: target security class @@ -1977,9 +1997,9 @@ int security_change_sid(struct selinux_state *state, static inline int convert_context_handle_invalid_context( struct selinux_state *state, + struct policydb *policydb, struct context *context) { - struct policydb *policydb = &state->ss->policydb; char *s; u32 len; @@ -1994,12 +2014,6 @@ static inline int convert_context_handle_invalid_context( return 0; } -struct convert_context_args { - struct selinux_state *state; - struct policydb *oldp; - struct policydb *newp; -}; - /* * Convert the values in the security context * structure `oldc' from the values specified @@ -2008,7 +2022,8 @@ struct convert_context_args { * in `newc'. Verify that the context is valid * under the new policy. */ -static int convert_context(struct context *oldc, struct context *newc, void *p) +static int convert_context(struct context *oldc, struct context *newc, void *p, + gfp_t gfp_flags) { struct convert_context_args *args; struct ocontext *oc; @@ -2022,7 +2037,7 @@ static int convert_context(struct context *oldc, struct context *newc, void *p) args = p; if (oldc->str) { - s = kstrdup(oldc->str, GFP_KERNEL); + s = kstrdup(oldc->str, gfp_flags); if (!s) return -ENOMEM; @@ -2040,7 +2055,6 @@ static int convert_context(struct context *oldc, struct context *newc, void *p) context_init(newc); newc->str = s; newc->len = oldc->len; - newc->hash = oldc->hash; return 0; } kfree(s); @@ -2058,27 +2072,24 @@ static int convert_context(struct context *oldc, struct context *newc, void *p) context_init(newc); /* Convert the user. */ - rc = -EINVAL; - usrdatum = hashtab_search(args->newp->p_users.table, - sym_name(args->oldp, - SYM_USERS, oldc->user - 1)); + usrdatum = symtab_search(&args->newp->p_users, + sym_name(args->oldp, + SYM_USERS, oldc->user - 1)); if (!usrdatum) goto bad; newc->user = usrdatum->value; /* Convert the role. */ - rc = -EINVAL; - role = hashtab_search(args->newp->p_roles.table, - sym_name(args->oldp, SYM_ROLES, oldc->role - 1)); + role = symtab_search(&args->newp->p_roles, + sym_name(args->oldp, SYM_ROLES, oldc->role - 1)); if (!role) goto bad; newc->role = role->value; /* Convert the type. */ - rc = -EINVAL; - typdatum = hashtab_search(args->newp->p_types.table, - sym_name(args->oldp, - SYM_TYPES, oldc->type - 1)); + typdatum = symtab_search(&args->newp->p_types, + sym_name(args->oldp, + SYM_TYPES, oldc->type - 1)); if (!typdatum) goto bad; newc->type = typdatum->value; @@ -2099,7 +2110,6 @@ static int convert_context(struct context *oldc, struct context *newc, void *p) oc = args->newp->ocontexts[OCON_ISID]; while (oc && oc->sid[0] != SECINITSID_UNLABELED) oc = oc->next; - rc = -EINVAL; if (!oc) { pr_err("SELinux: unable to look up" " the initial SIDs list\n"); @@ -2112,15 +2122,13 @@ static int convert_context(struct context *oldc, struct context *newc, void *p) /* Check the validity of the new context. */ if (!policydb_context_isvalid(args->newp, newc)) { - rc = convert_context_handle_invalid_context(args->state, oldc); + rc = convert_context_handle_invalid_context(args->state, + args->oldp, + oldc); if (rc) goto bad; } - rc = context_add_hash(args->newp, newc); - if (rc) - goto bad; - return 0; bad: /* Map old representation to string and save it. */ @@ -2130,20 +2138,23 @@ bad: context_destroy(newc); newc->str = s; newc->len = len; - newc->hash = context_compute_hash(s); pr_info("SELinux: Context %s became invalid (unmapped).\n", newc->str); return 0; } -static void security_load_policycaps(struct selinux_state *state) +static void security_load_policycaps(struct selinux_state *state, + struct selinux_policy *policy) { - struct policydb *p = &state->ss->policydb; + struct policydb *p; unsigned int i; struct ebitmap_node *node; + p = &policy->policydb; + for (i = 0; i < ARRAY_SIZE(state->policycap); i++) - state->policycap[i] = ebitmap_get_bit(&p->policycaps, i); + WRITE_ONCE(state->policycap[i], + ebitmap_get_bit(&p->policycaps, i)); for (i = 0; i < ARRAY_SIZE(selinux_policycap_names); i++) pr_info("SELinux: policy capability %s=%d\n", @@ -2157,188 +2168,257 @@ static void security_load_policycaps(struct selinux_state *state) } } -static int security_preserve_bools(struct selinux_state *state, - struct policydb *newpolicydb); +static int security_preserve_bools(struct selinux_policy *oldpolicy, + struct selinux_policy *newpolicy); + +static void selinux_policy_free(struct selinux_policy *policy) +{ + if (!policy) + return; + + sidtab_destroy(policy->sidtab); + kfree(policy->map.mapping); + policydb_destroy(&policy->policydb); + kfree(policy->sidtab); + kfree(policy); +} + +static void selinux_policy_cond_free(struct selinux_policy *policy) +{ + cond_policydb_destroy_dup(&policy->policydb); + kfree(policy); +} + +void selinux_policy_cancel(struct selinux_state *state, + struct selinux_load_state *load_state) +{ + struct selinux_policy *oldpolicy; + + oldpolicy = rcu_dereference_protected(state->policy, + lockdep_is_held(&state->policy_mutex)); + + sidtab_cancel_convert(oldpolicy->sidtab); + selinux_policy_free(load_state->policy); + kfree(load_state->convert_data); +} + +static void selinux_notify_policy_change(struct selinux_state *state, + u32 seqno) +{ + /* Flush external caches and notify userspace of policy load */ + avc_ss_reset(state->avc, seqno); + selnl_notify_policyload(seqno); + selinux_status_update_policyload(state, seqno); + selinux_netlbl_cache_invalidate(); + selinux_xfrm_notify_policyload(); + selinux_ima_measure_state_locked(state); +} + +void selinux_policy_commit(struct selinux_state *state, + struct selinux_load_state *load_state) +{ + struct selinux_policy *oldpolicy, *newpolicy = load_state->policy; + unsigned long flags; + u32 seqno; + + oldpolicy = rcu_dereference_protected(state->policy, + lockdep_is_held(&state->policy_mutex)); + + /* If switching between different policy types, log MLS status */ + if (oldpolicy) { + if (oldpolicy->policydb.mls_enabled && !newpolicy->policydb.mls_enabled) + pr_info("SELinux: Disabling MLS support...\n"); + else if (!oldpolicy->policydb.mls_enabled && newpolicy->policydb.mls_enabled) + pr_info("SELinux: Enabling MLS support...\n"); + } + + /* Set latest granting seqno for new policy. */ + if (oldpolicy) + newpolicy->latest_granting = oldpolicy->latest_granting + 1; + else + newpolicy->latest_granting = 1; + seqno = newpolicy->latest_granting; + + /* Install the new policy. */ + if (oldpolicy) { + sidtab_freeze_begin(oldpolicy->sidtab, &flags); + rcu_assign_pointer(state->policy, newpolicy); + sidtab_freeze_end(oldpolicy->sidtab, &flags); + } else { + rcu_assign_pointer(state->policy, newpolicy); + } + + /* Load the policycaps from the new policy */ + security_load_policycaps(state, newpolicy); + + if (!selinux_initialized(state)) { + /* + * After first policy load, the security server is + * marked as initialized and ready to handle requests and + * any objects created prior to policy load are then labeled. + */ + selinux_mark_initialized(state); + selinux_complete_init(); + } + + /* Free the old policy */ + synchronize_rcu(); + selinux_policy_free(oldpolicy); + kfree(load_state->convert_data); + + /* Notify others of the policy change */ + selinux_notify_policy_change(state, seqno); +} /** * security_load_policy - Load a security policy configuration. + * @state: SELinux state * @data: binary policy data * @len: length of data in bytes + * @load_state: policy load state * * Load a new set of security policy configuration data, * validate it and convert the SID table as necessary. * This function will flush the access vector cache after * loading the new policy. */ -int security_load_policy(struct selinux_state *state, void *data, size_t len) +int security_load_policy(struct selinux_state *state, void *data, size_t len, + struct selinux_load_state *load_state) { - struct policydb *policydb; - struct sidtab *oldsidtab, *newsidtab; - struct policydb *oldpolicydb, *newpolicydb; - struct selinux_mapping *oldmapping; - struct selinux_map newmap; - struct sidtab_convert_params convert_params; - struct convert_context_args args; - u32 seqno; + struct selinux_policy *newpolicy, *oldpolicy; + struct selinux_policy_convert_data *convert_data; int rc = 0; struct policy_file file = { data, len }, *fp = &file; - policydb = &state->ss->policydb; - - newsidtab = kmalloc(sizeof(*newsidtab), GFP_KERNEL); - if (!newsidtab) + newpolicy = kzalloc(sizeof(*newpolicy), GFP_KERNEL); + if (!newpolicy) return -ENOMEM; - if (!selinux_initialized(state)) { - rc = policydb_read(policydb, fp); - if (rc) { - kfree(newsidtab); - return rc; - } - - policydb->len = len; - rc = selinux_set_mapping(policydb, secclass_map, - &state->ss->map); - if (rc) { - kfree(newsidtab); - policydb_destroy(policydb); - return rc; - } - - rc = policydb_load_isids(policydb, newsidtab); - if (rc) { - kfree(newsidtab); - policydb_destroy(policydb); - return rc; - } - - state->ss->sidtab = newsidtab; - security_load_policycaps(state); - selinux_mark_initialized(state); - seqno = ++state->ss->latest_granting; - selinux_complete_init(); - avc_ss_reset(state->avc, seqno); - selnl_notify_policyload(seqno); - selinux_status_update_policyload(state, seqno); - selinux_netlbl_cache_invalidate(); - selinux_xfrm_notify_policyload(); - return 0; - } - - oldpolicydb = kcalloc(2, sizeof(*oldpolicydb), GFP_KERNEL); - if (!oldpolicydb) { - kfree(newsidtab); - return -ENOMEM; + newpolicy->sidtab = kzalloc(sizeof(*newpolicy->sidtab), GFP_KERNEL); + if (!newpolicy->sidtab) { + rc = -ENOMEM; + goto err_policy; } - newpolicydb = oldpolicydb + 1; - rc = policydb_read(newpolicydb, fp); - if (rc) { - kfree(newsidtab); - goto out; - } + rc = policydb_read(&newpolicy->policydb, fp); + if (rc) + goto err_sidtab; - newpolicydb->len = len; - /* If switching between different policy types, log MLS status */ - if (policydb->mls_enabled && !newpolicydb->mls_enabled) - pr_info("SELinux: Disabling MLS support...\n"); - else if (!policydb->mls_enabled && newpolicydb->mls_enabled) - pr_info("SELinux: Enabling MLS support...\n"); + newpolicy->policydb.len = len; + rc = selinux_set_mapping(&newpolicy->policydb, secclass_map, + &newpolicy->map); + if (rc) + goto err_policydb; - rc = policydb_load_isids(newpolicydb, newsidtab); + rc = policydb_load_isids(&newpolicy->policydb, newpolicy->sidtab); if (rc) { pr_err("SELinux: unable to load the initial SIDs\n"); - policydb_destroy(newpolicydb); - kfree(newsidtab); - goto out; + goto err_mapping; } - rc = selinux_set_mapping(newpolicydb, secclass_map, &newmap); - if (rc) - goto err; + if (!selinux_initialized(state)) { + /* First policy load, so no need to preserve state from old policy */ + load_state->policy = newpolicy; + load_state->convert_data = NULL; + return 0; + } + + oldpolicy = rcu_dereference_protected(state->policy, + lockdep_is_held(&state->policy_mutex)); - rc = security_preserve_bools(state, newpolicydb); + /* Preserve active boolean values from the old policy */ + rc = security_preserve_bools(oldpolicy, newpolicy); if (rc) { pr_err("SELinux: unable to preserve booleans\n"); - goto err; + goto err_free_isids; } - oldsidtab = state->ss->sidtab; + convert_data = kmalloc(sizeof(*convert_data), GFP_KERNEL); + if (!convert_data) { + rc = -ENOMEM; + goto err_free_isids; + } /* * Convert the internal representations of contexts * in the new SID table. */ - args.state = state; - args.oldp = policydb; - args.newp = newpolicydb; + convert_data->args.state = state; + convert_data->args.oldp = &oldpolicy->policydb; + convert_data->args.newp = &newpolicy->policydb; - convert_params.func = convert_context; - convert_params.args = &args; - convert_params.target = newsidtab; + convert_data->sidtab_params.func = convert_context; + convert_data->sidtab_params.args = &convert_data->args; + convert_data->sidtab_params.target = newpolicy->sidtab; - rc = sidtab_convert(oldsidtab, &convert_params); + rc = sidtab_convert(oldpolicy->sidtab, &convert_data->sidtab_params); if (rc) { pr_err("SELinux: unable to convert the internal" " representation of contexts in the new SID" " table\n"); - goto err; + goto err_free_convert_data; } - /* Save the old policydb and SID table to free later. */ - memcpy(oldpolicydb, policydb, sizeof(*policydb)); - - /* Install the new policydb and SID table. */ - write_lock_irq(&state->ss->policy_rwlock); - memcpy(policydb, newpolicydb, sizeof(*policydb)); - state->ss->sidtab = newsidtab; - security_load_policycaps(state); - oldmapping = state->ss->map.mapping; - state->ss->map.mapping = newmap.mapping; - state->ss->map.size = newmap.size; - seqno = ++state->ss->latest_granting; - write_unlock_irq(&state->ss->policy_rwlock); - - /* Free the old policydb and SID table. */ - policydb_destroy(oldpolicydb); - sidtab_destroy(oldsidtab); - kfree(oldsidtab); - kfree(oldmapping); - - avc_ss_reset(state->avc, seqno); - selnl_notify_policyload(seqno); - selinux_status_update_policyload(state, seqno); - selinux_netlbl_cache_invalidate(); - selinux_xfrm_notify_policyload(); - - rc = 0; - goto out; + load_state->policy = newpolicy; + load_state->convert_data = convert_data; + return 0; -err: - kfree(newmap.mapping); - sidtab_destroy(newsidtab); - kfree(newsidtab); - policydb_destroy(newpolicydb); +err_free_convert_data: + kfree(convert_data); +err_free_isids: + sidtab_destroy(newpolicy->sidtab); +err_mapping: + kfree(newpolicy->map.mapping); +err_policydb: + policydb_destroy(&newpolicy->policydb); +err_sidtab: + kfree(newpolicy->sidtab); +err_policy: + kfree(newpolicy); -out: - kfree(oldpolicydb); return rc; } -size_t security_policydb_len(struct selinux_state *state) +/** + * ocontext_to_sid - Helper to safely get sid for an ocontext + * @sidtab: SID table + * @c: ocontext structure + * @index: index of the context entry (0 or 1) + * @out_sid: pointer to the resulting SID value + * + * For all ocontexts except OCON_ISID the SID fields are populated + * on-demand when needed. Since updating the SID value is an SMP-sensitive + * operation, this helper must be used to do that safely. + * + * WARNING: This function may return -ESTALE, indicating that the caller + * must retry the operation after re-acquiring the policy pointer! + */ +static int ocontext_to_sid(struct sidtab *sidtab, struct ocontext *c, + size_t index, u32 *out_sid) { - struct policydb *p = &state->ss->policydb; - size_t len; + int rc; + u32 sid; - read_lock(&state->ss->policy_rwlock); - len = p->len; - read_unlock(&state->ss->policy_rwlock); + /* Ensure the associated sidtab entry is visible to this thread. */ + sid = smp_load_acquire(&c->sid[index]); + if (!sid) { + rc = sidtab_context_to_sid(sidtab, &c->context[index], &sid); + if (rc) + return rc; - return len; + /* + * Ensure the new sidtab entry is visible to other threads + * when they see the SID. + */ + smp_store_release(&c->sid[index], sid); + } + *out_sid = sid; + return 0; } /** * security_port_sid - Obtain the SID for a port. + * @state: SELinux state * @protocol: protocol number * @port: port number * @out_sid: security identifier @@ -2346,13 +2426,23 @@ size_t security_policydb_len(struct selinux_state *state) int security_port_sid(struct selinux_state *state, u8 protocol, u16 port, u32 *out_sid) { + struct selinux_policy *policy; struct policydb *policydb; + struct sidtab *sidtab; struct ocontext *c; - int rc = 0; + int rc; - read_lock(&state->ss->policy_rwlock); + if (!selinux_initialized(state)) { + *out_sid = SECINITSID_PORT; + return 0; + } - policydb = &state->ss->policydb; +retry: + rc = 0; + rcu_read_lock(); + policy = rcu_dereference(state->policy); + policydb = &policy->policydb; + sidtab = policy->sidtab; c = policydb->ocontexts[OCON_PORT]; while (c) { @@ -2364,24 +2454,25 @@ int security_port_sid(struct selinux_state *state, } if (c) { - if (!c->sid[0]) { - rc = context_struct_to_sid(state, &c->context[0], - &c->sid[0]); - if (rc) - goto out; + rc = ocontext_to_sid(sidtab, c, 0, out_sid); + if (rc == -ESTALE) { + rcu_read_unlock(); + goto retry; } - *out_sid = c->sid[0]; + if (rc) + goto out; } else { *out_sid = SECINITSID_PORT; } out: - read_unlock(&state->ss->policy_rwlock); + rcu_read_unlock(); return rc; } /** - * security_pkey_sid - Obtain the SID for a pkey. + * security_ib_pkey_sid - Obtain the SID for a pkey. + * @state: SELinux state * @subnet_prefix: Subnet Prefix * @pkey_num: pkey number * @out_sid: security identifier @@ -2389,13 +2480,23 @@ out: int security_ib_pkey_sid(struct selinux_state *state, u64 subnet_prefix, u16 pkey_num, u32 *out_sid) { + struct selinux_policy *policy; struct policydb *policydb; + struct sidtab *sidtab; struct ocontext *c; - int rc = 0; + int rc; - read_lock(&state->ss->policy_rwlock); + if (!selinux_initialized(state)) { + *out_sid = SECINITSID_UNLABELED; + return 0; + } - policydb = &state->ss->policydb; +retry: + rc = 0; + rcu_read_lock(); + policy = rcu_dereference(state->policy); + policydb = &policy->policydb; + sidtab = policy->sidtab; c = policydb->ocontexts[OCON_IBPKEY]; while (c) { @@ -2408,38 +2509,48 @@ int security_ib_pkey_sid(struct selinux_state *state, } if (c) { - if (!c->sid[0]) { - rc = context_struct_to_sid(state, - &c->context[0], - &c->sid[0]); - if (rc) - goto out; + rc = ocontext_to_sid(sidtab, c, 0, out_sid); + if (rc == -ESTALE) { + rcu_read_unlock(); + goto retry; } - *out_sid = c->sid[0]; + if (rc) + goto out; } else *out_sid = SECINITSID_UNLABELED; out: - read_unlock(&state->ss->policy_rwlock); + rcu_read_unlock(); return rc; } /** * security_ib_endport_sid - Obtain the SID for a subnet management interface. + * @state: SELinux state * @dev_name: device name - * @port: port number + * @port_num: port number * @out_sid: security identifier */ int security_ib_endport_sid(struct selinux_state *state, const char *dev_name, u8 port_num, u32 *out_sid) { + struct selinux_policy *policy; struct policydb *policydb; + struct sidtab *sidtab; struct ocontext *c; - int rc = 0; + int rc; - read_lock(&state->ss->policy_rwlock); + if (!selinux_initialized(state)) { + *out_sid = SECINITSID_UNLABELED; + return 0; + } - policydb = &state->ss->policydb; +retry: + rc = 0; + rcu_read_lock(); + policy = rcu_dereference(state->policy); + policydb = &policy->policydb; + sidtab = policy->sidtab; c = policydb->ocontexts[OCON_IBENDPORT]; while (c) { @@ -2453,36 +2564,47 @@ int security_ib_endport_sid(struct selinux_state *state, } if (c) { - if (!c->sid[0]) { - rc = context_struct_to_sid(state, &c->context[0], - &c->sid[0]); - if (rc) - goto out; + rc = ocontext_to_sid(sidtab, c, 0, out_sid); + if (rc == -ESTALE) { + rcu_read_unlock(); + goto retry; } - *out_sid = c->sid[0]; + if (rc) + goto out; } else *out_sid = SECINITSID_UNLABELED; out: - read_unlock(&state->ss->policy_rwlock); + rcu_read_unlock(); return rc; } /** * security_netif_sid - Obtain the SID for a network interface. + * @state: SELinux state * @name: interface name * @if_sid: interface SID */ int security_netif_sid(struct selinux_state *state, char *name, u32 *if_sid) { + struct selinux_policy *policy; struct policydb *policydb; - int rc = 0; + struct sidtab *sidtab; + int rc; struct ocontext *c; - read_lock(&state->ss->policy_rwlock); + if (!selinux_initialized(state)) { + *if_sid = SECINITSID_NETIF; + return 0; + } - policydb = &state->ss->policydb; +retry: + rc = 0; + rcu_read_lock(); + policy = rcu_dereference(state->policy); + policydb = &policy->policydb; + sidtab = policy->sidtab; c = policydb->ocontexts[OCON_NETIF]; while (c) { @@ -2492,22 +2614,18 @@ int security_netif_sid(struct selinux_state *state, } if (c) { - if (!c->sid[0] || !c->sid[1]) { - rc = context_struct_to_sid(state, &c->context[0], - &c->sid[0]); - if (rc) - goto out; - rc = context_struct_to_sid(state, &c->context[1], - &c->sid[1]); - if (rc) - goto out; + rc = ocontext_to_sid(sidtab, c, 0, if_sid); + if (rc == -ESTALE) { + rcu_read_unlock(); + goto retry; } - *if_sid = c->sid[0]; + if (rc) + goto out; } else *if_sid = SECINITSID_NETIF; out: - read_unlock(&state->ss->policy_rwlock); + rcu_read_unlock(); return rc; } @@ -2526,6 +2644,7 @@ static int match_ipv6_addrmask(u32 *input, u32 *addr, u32 *mask) /** * security_node_sid - Obtain the SID for a node (host). + * @state: SELinux state * @domain: communication domain aka address family * @addrp: address * @addrlen: address length in bytes @@ -2537,13 +2656,22 @@ int security_node_sid(struct selinux_state *state, u32 addrlen, u32 *out_sid) { + struct selinux_policy *policy; struct policydb *policydb; + struct sidtab *sidtab; int rc; struct ocontext *c; - read_lock(&state->ss->policy_rwlock); + if (!selinux_initialized(state)) { + *out_sid = SECINITSID_NODE; + return 0; + } - policydb = &state->ss->policydb; +retry: + rcu_read_lock(); + policy = rcu_dereference(state->policy); + policydb = &policy->policydb; + sidtab = policy->sidtab; switch (domain) { case AF_INET: { @@ -2584,21 +2712,20 @@ int security_node_sid(struct selinux_state *state, } if (c) { - if (!c->sid[0]) { - rc = context_struct_to_sid(state, - &c->context[0], - &c->sid[0]); - if (rc) - goto out; + rc = ocontext_to_sid(sidtab, c, 0, out_sid); + if (rc == -ESTALE) { + rcu_read_unlock(); + goto retry; } - *out_sid = c->sid[0]; + if (rc) + goto out; } else { *out_sid = SECINITSID_NODE; } rc = 0; out: - read_unlock(&state->ss->policy_rwlock); + rcu_read_unlock(); return rc; } @@ -2606,6 +2733,7 @@ out: /** * security_get_user_sids - Obtain reachable SIDs for a user. + * @state: SELinux state * @fromsid: starting SID * @username: username * @sids: array of reachable SIDs for user @@ -2624,26 +2752,33 @@ int security_get_user_sids(struct selinux_state *state, u32 **sids, u32 *nel) { + struct selinux_policy *policy; struct policydb *policydb; struct sidtab *sidtab; struct context *fromcon, usercon; u32 *mysids = NULL, *mysids2, sid; - u32 mynel = 0, maxnel = SIDS_NEL; + u32 i, j, mynel, maxnel = SIDS_NEL; struct user_datum *user; struct role_datum *role; struct ebitmap_node *rnode, *tnode; - int rc = 0, i, j; + int rc; *sids = NULL; *nel = 0; if (!selinux_initialized(state)) - goto out; + return 0; - read_lock(&state->ss->policy_rwlock); + mysids = kcalloc(maxnel, sizeof(*mysids), GFP_KERNEL); + if (!mysids) + return -ENOMEM; - policydb = &state->ss->policydb; - sidtab = state->ss->sidtab; +retry: + mynel = 0; + rcu_read_lock(); + policy = rcu_dereference(state->policy); + policydb = &policy->policydb; + sidtab = policy->sidtab; context_init(&usercon); @@ -2653,33 +2788,27 @@ int security_get_user_sids(struct selinux_state *state, goto out_unlock; rc = -EINVAL; - user = hashtab_search(policydb->p_users.table, username); + user = symtab_search(&policydb->p_users, username); if (!user) goto out_unlock; usercon.user = user->value; - rc = -ENOMEM; - mysids = kcalloc(maxnel, sizeof(*mysids), GFP_ATOMIC); - if (!mysids) - goto out_unlock; - ebitmap_for_each_positive_bit(&user->roles, rnode, i) { role = policydb->role_val_to_struct[i]; usercon.role = i + 1; ebitmap_for_each_positive_bit(&role->types, tnode, j) { usercon.type = j + 1; - /* - * The same context struct is reused here so the hash - * must be reset. - */ - usercon.hash = 0; if (mls_setup_user_range(policydb, fromcon, user, &usercon)) continue; - rc = context_struct_to_sid(state, &usercon, &sid); + rc = sidtab_context_to_sid(sidtab, &usercon, &sid); + if (rc == -ESTALE) { + rcu_read_unlock(); + goto retry; + } if (rc) goto out_unlock; if (mynel < maxnel) { @@ -2699,17 +2828,17 @@ int security_get_user_sids(struct selinux_state *state, } rc = 0; out_unlock: - read_unlock(&state->ss->policy_rwlock); + rcu_read_unlock(); if (rc || !mynel) { kfree(mysids); - goto out; + return rc; } rc = -ENOMEM; mysids2 = kcalloc(mynel, sizeof(*mysids2), GFP_KERNEL); if (!mysids2) { kfree(mysids); - goto out; + return rc; } for (i = 0, j = 0; i < mynel; i++) { struct av_decision dummy_avd; @@ -2722,44 +2851,45 @@ out_unlock: mysids2[j++] = mysids[i]; cond_resched(); } - rc = 0; kfree(mysids); *sids = mysids2; *nel = j; -out: - return rc; + return 0; } /** * __security_genfs_sid - Helper to obtain a SID for a file in a filesystem + * @policy: policy * @fstype: filesystem type * @path: path from root of mount - * @sclass: file security class + * @orig_sclass: file security class * @sid: SID for path * * Obtain a SID to use for a file in a filesystem that * cannot support xattr or use a fixed labeling behavior like * transition SIDs or task SIDs. * - * The caller must acquire the policy_rwlock before calling this function. + * WARNING: This function may return -ESTALE, indicating that the caller + * must retry the operation after re-acquiring the policy pointer! */ -static inline int __security_genfs_sid(struct selinux_state *state, +static inline int __security_genfs_sid(struct selinux_policy *policy, const char *fstype, - char *path, + const char *path, u16 orig_sclass, u32 *sid) { - struct policydb *policydb = &state->ss->policydb; + struct policydb *policydb = &policy->policydb; + struct sidtab *sidtab = policy->sidtab; int len; u16 sclass; struct genfs *genfs; struct ocontext *c; - int rc, cmp = 0; + int cmp = 0; while (path[0] == '/' && path[1] == '/') path++; - sclass = unmap_class(&state->ss->map, orig_sclass); + sclass = unmap_class(&policy->map, orig_sclass); *sid = SECINITSID_UNLABELED; for (genfs = policydb->genfs; genfs; genfs = genfs->next) { @@ -2768,9 +2898,8 @@ static inline int __security_genfs_sid(struct selinux_state *state, break; } - rc = -ENOENT; if (!genfs || cmp) - goto out; + return -ENOENT; for (c = genfs->head; c; c = c->next) { len = strlen(c->u.name); @@ -2779,27 +2908,18 @@ static inline int __security_genfs_sid(struct selinux_state *state, break; } - rc = -ENOENT; if (!c) - goto out; - - if (!c->sid[0]) { - rc = context_struct_to_sid(state, &c->context[0], &c->sid[0]); - if (rc) - goto out; - } + return -ENOENT; - *sid = c->sid[0]; - rc = 0; -out: - return rc; + return ocontext_to_sid(sidtab, c, 0, sid); } /** * security_genfs_sid - Obtain a SID for a file in a filesystem + * @state: SELinux state * @fstype: filesystem type * @path: path from root of mount - * @sclass: file security class + * @orig_sclass: file security class * @sid: SID for path * * Acquire policy_rwlock before calling __security_genfs_sid() and release @@ -2807,33 +2927,64 @@ out: */ int security_genfs_sid(struct selinux_state *state, const char *fstype, - char *path, + const char *path, u16 orig_sclass, u32 *sid) { + struct selinux_policy *policy; int retval; - read_lock(&state->ss->policy_rwlock); - retval = __security_genfs_sid(state, fstype, path, orig_sclass, sid); - read_unlock(&state->ss->policy_rwlock); + if (!selinux_initialized(state)) { + *sid = SECINITSID_UNLABELED; + return 0; + } + + do { + rcu_read_lock(); + policy = rcu_dereference(state->policy); + retval = __security_genfs_sid(policy, fstype, path, + orig_sclass, sid); + rcu_read_unlock(); + } while (retval == -ESTALE); return retval; } +int selinux_policy_genfs_sid(struct selinux_policy *policy, + const char *fstype, + const char *path, + u16 orig_sclass, + u32 *sid) +{ + /* no lock required, policy is not yet accessible by other threads */ + return __security_genfs_sid(policy, fstype, path, orig_sclass, sid); +} + /** * security_fs_use - Determine how to handle labeling for a filesystem. + * @state: SELinux state * @sb: superblock in question */ int security_fs_use(struct selinux_state *state, struct super_block *sb) { + struct selinux_policy *policy; struct policydb *policydb; - int rc = 0; + struct sidtab *sidtab; + int rc; struct ocontext *c; - struct superblock_security_struct *sbsec = sb->s_security; + struct superblock_security_struct *sbsec = selinux_superblock(sb); const char *fstype = sb->s_type->name; - read_lock(&state->ss->policy_rwlock); + if (!selinux_initialized(state)) { + sbsec->behavior = SECURITY_FS_USE_NONE; + sbsec->sid = SECINITSID_UNLABELED; + return 0; + } - policydb = &state->ss->policydb; +retry: + rcu_read_lock(); + policy = rcu_dereference(state->policy); + policydb = &policy->policydb; + sidtab = policy->sidtab; c = policydb->ocontexts[OCON_FSUSE]; while (c) { @@ -2844,16 +2995,20 @@ int security_fs_use(struct selinux_state *state, struct super_block *sb) if (c) { sbsec->behavior = c->v.behavior; - if (!c->sid[0]) { - rc = context_struct_to_sid(state, &c->context[0], - &c->sid[0]); - if (rc) - goto out; + rc = ocontext_to_sid(sidtab, c, 0, &sbsec->sid); + if (rc == -ESTALE) { + rcu_read_unlock(); + goto retry; } - sbsec->sid = c->sid[0]; + if (rc) + goto out; } else { - rc = __security_genfs_sid(state, fstype, "/", SECCLASS_DIR, - &sbsec->sid); + rc = __security_genfs_sid(policy, fstype, "/", + SECCLASS_DIR, &sbsec->sid); + if (rc == -ESTALE) { + rcu_read_unlock(); + goto retry; + } if (rc) { sbsec->behavior = SECURITY_FS_USE_NONE; rc = 0; @@ -2863,26 +3018,18 @@ int security_fs_use(struct selinux_state *state, struct super_block *sb) } out: - read_unlock(&state->ss->policy_rwlock); + rcu_read_unlock(); return rc; } -int security_get_bools(struct selinux_state *state, - int *len, char ***names, int **values) +int security_get_bools(struct selinux_policy *policy, + u32 *len, char ***names, int **values) { struct policydb *policydb; - int i, rc; - - if (!selinux_initialized(state)) { - *len = 0; - *names = NULL; - *values = NULL; - return 0; - } - - read_lock(&state->ss->policy_rwlock); + u32 i; + int rc; - policydb = &state->ss->policydb; + policydb = &policy->policydb; *names = NULL; *values = NULL; @@ -2913,80 +3060,106 @@ int security_get_bools(struct selinux_state *state, } rc = 0; out: - read_unlock(&state->ss->policy_rwlock); return rc; err: if (*names) { for (i = 0; i < *len; i++) kfree((*names)[i]); + kfree(*names); } kfree(*values); + *len = 0; + *names = NULL; + *values = NULL; goto out; } -int security_set_bools(struct selinux_state *state, int len, int *values) +int security_set_bools(struct selinux_state *state, u32 len, int *values) { - struct policydb *policydb; - int i, rc; - int lenp, seqno = 0; - struct cond_node *cur; + struct selinux_policy *newpolicy, *oldpolicy; + int rc; + u32 i, seqno = 0; - write_lock_irq(&state->ss->policy_rwlock); + if (!selinux_initialized(state)) + return -EINVAL; - policydb = &state->ss->policydb; + oldpolicy = rcu_dereference_protected(state->policy, + lockdep_is_held(&state->policy_mutex)); - rc = -EFAULT; - lenp = policydb->p_bools.nprim; - if (len != lenp) - goto out; + /* Consistency check on number of booleans, should never fail */ + if (WARN_ON(len != oldpolicy->policydb.p_bools.nprim)) + return -EINVAL; + newpolicy = kmemdup(oldpolicy, sizeof(*newpolicy), GFP_KERNEL); + if (!newpolicy) + return -ENOMEM; + + /* + * Deep copy only the parts of the policydb that might be + * modified as a result of changing booleans. + */ + rc = cond_policydb_dup(&newpolicy->policydb, &oldpolicy->policydb); + if (rc) { + kfree(newpolicy); + return -ENOMEM; + } + + /* Update the boolean states in the copy */ for (i = 0; i < len; i++) { - if (!!values[i] != policydb->bool_val_to_struct[i]->state) { + int new_state = !!values[i]; + int old_state = newpolicy->policydb.bool_val_to_struct[i]->state; + + if (new_state != old_state) { audit_log(audit_context(), GFP_ATOMIC, AUDIT_MAC_CONFIG_CHANGE, "bool=%s val=%d old_val=%d auid=%u ses=%u", - sym_name(policydb, SYM_BOOLS, i), - !!values[i], - policydb->bool_val_to_struct[i]->state, + sym_name(&newpolicy->policydb, SYM_BOOLS, i), + new_state, + old_state, from_kuid(&init_user_ns, audit_get_loginuid(current)), audit_get_sessionid(current)); + newpolicy->policydb.bool_val_to_struct[i]->state = new_state; } - if (values[i]) - policydb->bool_val_to_struct[i]->state = 1; - else - policydb->bool_val_to_struct[i]->state = 0; } - for (cur = policydb->cond_list; cur; cur = cur->next) { - rc = evaluate_cond_node(policydb, cur); - if (rc) - goto out; - } + /* Re-evaluate the conditional rules in the copy */ + evaluate_cond_nodes(&newpolicy->policydb); - seqno = ++state->ss->latest_granting; - rc = 0; -out: - write_unlock_irq(&state->ss->policy_rwlock); - if (!rc) { - avc_ss_reset(state->avc, seqno); - selnl_notify_policyload(seqno); - selinux_status_update_policyload(state, seqno); - selinux_xfrm_notify_policyload(); - } - return rc; + /* Set latest granting seqno for new policy */ + newpolicy->latest_granting = oldpolicy->latest_granting + 1; + seqno = newpolicy->latest_granting; + + /* Install the new policy */ + rcu_assign_pointer(state->policy, newpolicy); + + /* + * Free the conditional portions of the old policydb + * that were copied for the new policy, and the oldpolicy + * structure itself but not what it references. + */ + synchronize_rcu(); + selinux_policy_cond_free(oldpolicy); + + /* Notify others of the policy change */ + selinux_notify_policy_change(state, seqno); + return 0; } int security_get_bool_value(struct selinux_state *state, - int index) + u32 index) { + struct selinux_policy *policy; struct policydb *policydb; int rc; - int len; + u32 len; - read_lock(&state->ss->policy_rwlock); + if (!selinux_initialized(state)) + return 0; - policydb = &state->ss->policydb; + rcu_read_lock(); + policy = rcu_dereference(state->policy); + policydb = &policy->policydb; rc = -EFAULT; len = policydb->p_bools.nprim; @@ -2995,31 +3168,28 @@ int security_get_bool_value(struct selinux_state *state, rc = policydb->bool_val_to_struct[index]->state; out: - read_unlock(&state->ss->policy_rwlock); + rcu_read_unlock(); return rc; } -static int security_preserve_bools(struct selinux_state *state, - struct policydb *policydb) +static int security_preserve_bools(struct selinux_policy *oldpolicy, + struct selinux_policy *newpolicy) { - int rc, nbools = 0, *bvalues = NULL, i; + int rc, *bvalues = NULL; char **bnames = NULL; struct cond_bool_datum *booldatum; - struct cond_node *cur; + u32 i, nbools = 0; - rc = security_get_bools(state, &nbools, &bnames, &bvalues); + rc = security_get_bools(oldpolicy, &nbools, &bnames, &bvalues); if (rc) goto out; for (i = 0; i < nbools; i++) { - booldatum = hashtab_search(policydb->p_bools.table, bnames[i]); + booldatum = symtab_search(&newpolicy->policydb.p_bools, + bnames[i]); if (booldatum) booldatum->state = bvalues[i]; } - for (cur = policydb->cond_list; cur; cur = cur->next) { - rc = evaluate_cond_node(policydb, cur); - if (rc) - goto out; - } + evaluate_cond_nodes(&newpolicy->policydb); out: if (bnames) { @@ -3038,8 +3208,9 @@ out: int security_sid_mls_copy(struct selinux_state *state, u32 sid, u32 mls_sid, u32 *new_sid) { - struct policydb *policydb = &state->ss->policydb; - struct sidtab *sidtab = state->ss->sidtab; + struct selinux_policy *policy; + struct policydb *policydb; + struct sidtab *sidtab; struct context *context1; struct context *context2; struct context newcon; @@ -3047,15 +3218,24 @@ int security_sid_mls_copy(struct selinux_state *state, u32 len; int rc; - rc = 0; - if (!selinux_initialized(state) || !policydb->mls_enabled) { + if (!selinux_initialized(state)) { *new_sid = sid; - goto out; + return 0; } +retry: + rc = 0; context_init(&newcon); - read_lock(&state->ss->policy_rwlock); + rcu_read_lock(); + policy = rcu_dereference(state->policy); + policydb = &policy->policydb; + sidtab = policy->sidtab; + + if (!policydb->mls_enabled) { + *new_sid = sid; + goto out_unlock; + } rc = -EINVAL; context1 = sidtab_search(sidtab, sid); @@ -3082,7 +3262,8 @@ int security_sid_mls_copy(struct selinux_state *state, /* Check the validity of the new context. */ if (!policydb_context_isvalid(policydb, &newcon)) { - rc = convert_context_handle_invalid_context(state, &newcon); + rc = convert_context_handle_invalid_context(state, policydb, + &newcon); if (rc) { if (!context_struct_to_string(policydb, &newcon, &s, &len)) { @@ -3101,19 +3282,25 @@ int security_sid_mls_copy(struct selinux_state *state, goto out_unlock; } } - rc = context_struct_to_sid(state, &newcon, new_sid); + rc = sidtab_context_to_sid(sidtab, &newcon, new_sid); + if (rc == -ESTALE) { + rcu_read_unlock(); + context_destroy(&newcon); + goto retry; + } out_unlock: - read_unlock(&state->ss->policy_rwlock); + rcu_read_unlock(); context_destroy(&newcon); -out: return rc; } /** * security_net_peersid_resolve - Compare and resolve two network peer SIDs + * @state: SELinux state * @nlbl_sid: NetLabel SID * @nlbl_type: NetLabel labeling protocol type * @xfrm_sid: XFRM SID + * @peer_sid: network peer sid * * Description: * Compare the @nlbl_sid and @xfrm_sid values and if the two SIDs can be @@ -3134,8 +3321,9 @@ int security_net_peersid_resolve(struct selinux_state *state, u32 xfrm_sid, u32 *peer_sid) { - struct policydb *policydb = &state->ss->policydb; - struct sidtab *sidtab = state->ss->sidtab; + struct selinux_policy *policy; + struct policydb *policydb; + struct sidtab *sidtab; int rc; struct context *nlbl_ctx; struct context *xfrm_ctx; @@ -3157,15 +3345,23 @@ int security_net_peersid_resolve(struct selinux_state *state, return 0; } + if (!selinux_initialized(state)) + return 0; + + rcu_read_lock(); + policy = rcu_dereference(state->policy); + policydb = &policy->policydb; + sidtab = policy->sidtab; + /* * We don't need to check initialized here since the only way both * nlbl_sid and xfrm_sid are not equal to SECSID_NULL would be if the * security server was initialized and state->initialized was true. */ - if (!policydb->mls_enabled) - return 0; - - read_lock(&state->ss->policy_rwlock); + if (!policydb->mls_enabled) { + rc = 0; + goto out; + } rc = -EINVAL; nlbl_ctx = sidtab_search(sidtab, nlbl_sid); @@ -3192,7 +3388,7 @@ int security_net_peersid_resolve(struct selinux_state *state, * expressive */ *peer_sid = xfrm_sid; out: - read_unlock(&state->ss->policy_rwlock); + rcu_read_unlock(); return rc; } @@ -3209,19 +3405,13 @@ static int get_classes_callback(void *k, void *d, void *args) return 0; } -int security_get_classes(struct selinux_state *state, +int security_get_classes(struct selinux_policy *policy, char ***classes, int *nclasses) { - struct policydb *policydb = &state->ss->policydb; + struct policydb *policydb; int rc; - if (!selinux_initialized(state)) { - *nclasses = 0; - *classes = NULL; - return 0; - } - - read_lock(&state->ss->policy_rwlock); + policydb = &policy->policydb; rc = -ENOMEM; *nclasses = policydb->p_classes.nprim; @@ -3229,8 +3419,8 @@ int security_get_classes(struct selinux_state *state, if (!*classes) goto out; - rc = hashtab_map(policydb->p_classes.table, get_classes_callback, - *classes); + rc = hashtab_map(&policydb->p_classes.table, get_classes_callback, + *classes); if (rc) { int i; for (i = 0; i < *nclasses; i++) @@ -3239,7 +3429,6 @@ int security_get_classes(struct selinux_state *state, } out: - read_unlock(&state->ss->policy_rwlock); return rc; } @@ -3256,17 +3445,17 @@ static int get_permissions_callback(void *k, void *d, void *args) return 0; } -int security_get_permissions(struct selinux_state *state, +int security_get_permissions(struct selinux_policy *policy, char *class, char ***perms, int *nperms) { - struct policydb *policydb = &state->ss->policydb; + struct policydb *policydb; int rc, i; struct class_datum *match; - read_lock(&state->ss->policy_rwlock); + policydb = &policy->policydb; rc = -EINVAL; - match = hashtab_search(policydb->p_classes.table, class); + match = symtab_search(&policydb->p_classes, class); if (!match) { pr_err("SELinux: %s: unrecognized class %s\n", __func__, class); @@ -3280,23 +3469,21 @@ int security_get_permissions(struct selinux_state *state, goto out; if (match->comdatum) { - rc = hashtab_map(match->comdatum->permissions.table, - get_permissions_callback, *perms); + rc = hashtab_map(&match->comdatum->permissions.table, + get_permissions_callback, *perms); if (rc) goto err; } - rc = hashtab_map(match->permissions.table, get_permissions_callback, - *perms); + rc = hashtab_map(&match->permissions.table, get_permissions_callback, + *perms); if (rc) goto err; out: - read_unlock(&state->ss->policy_rwlock); return rc; err: - read_unlock(&state->ss->policy_rwlock); for (i = 0; i < *nperms; i++) kfree((*perms)[i]); kfree(*perms); @@ -3305,16 +3492,37 @@ err: int security_get_reject_unknown(struct selinux_state *state) { - return state->ss->policydb.reject_unknown; + struct selinux_policy *policy; + int value; + + if (!selinux_initialized(state)) + return 0; + + rcu_read_lock(); + policy = rcu_dereference(state->policy); + value = policy->policydb.reject_unknown; + rcu_read_unlock(); + return value; } int security_get_allow_unknown(struct selinux_state *state) { - return state->ss->policydb.allow_unknown; + struct selinux_policy *policy; + int value; + + if (!selinux_initialized(state)) + return 0; + + rcu_read_lock(); + policy = rcu_dereference(state->policy); + value = policy->policydb.allow_unknown; + rcu_read_unlock(); + return value; } /** * security_policycap_supported - Check for a specific policy capability + * @state: SELinux state * @req_cap: capability * * Description: @@ -3326,12 +3534,16 @@ int security_get_allow_unknown(struct selinux_state *state) int security_policycap_supported(struct selinux_state *state, unsigned int req_cap) { - struct policydb *policydb = &state->ss->policydb; + struct selinux_policy *policy; int rc; - read_lock(&state->ss->policy_rwlock); - rc = ebitmap_get_bit(&policydb->policycaps, req_cap); - read_unlock(&state->ss->policy_rwlock); + if (!selinux_initialized(state)) + return 0; + + rcu_read_lock(); + policy = rcu_dereference(state->policy); + rc = ebitmap_get_bit(&policy->policydb.policycaps, req_cap); + rcu_read_unlock(); return rc; } @@ -3354,7 +3566,8 @@ void selinux_audit_rule_free(void *vrule) int selinux_audit_rule_init(u32 field, u32 op, char *rulestr, void **vrule) { struct selinux_state *state = &selinux_state; - struct policydb *policydb = &state->ss->policydb; + struct selinux_policy *policy; + struct policydb *policydb; struct selinux_audit_rule *tmprule; struct role_datum *roledatum; struct type_datum *typedatum; @@ -3397,15 +3610,17 @@ int selinux_audit_rule_init(u32 field, u32 op, char *rulestr, void **vrule) context_init(&tmprule->au_ctxt); - read_lock(&state->ss->policy_rwlock); + rcu_read_lock(); + policy = rcu_dereference(state->policy); + policydb = &policy->policydb; - tmprule->au_seqno = state->ss->latest_granting; + tmprule->au_seqno = policy->latest_granting; switch (field) { case AUDIT_SUBJ_USER: case AUDIT_OBJ_USER: rc = -EINVAL; - userdatum = hashtab_search(policydb->p_users.table, rulestr); + userdatum = symtab_search(&policydb->p_users, rulestr); if (!userdatum) goto out; tmprule->au_ctxt.user = userdatum->value; @@ -3413,7 +3628,7 @@ int selinux_audit_rule_init(u32 field, u32 op, char *rulestr, void **vrule) case AUDIT_SUBJ_ROLE: case AUDIT_OBJ_ROLE: rc = -EINVAL; - roledatum = hashtab_search(policydb->p_roles.table, rulestr); + roledatum = symtab_search(&policydb->p_roles, rulestr); if (!roledatum) goto out; tmprule->au_ctxt.role = roledatum->value; @@ -3421,7 +3636,7 @@ int selinux_audit_rule_init(u32 field, u32 op, char *rulestr, void **vrule) case AUDIT_SUBJ_TYPE: case AUDIT_OBJ_TYPE: rc = -EINVAL; - typedatum = hashtab_search(policydb->p_types.table, rulestr); + typedatum = symtab_search(&policydb->p_types, rulestr); if (!typedatum) goto out; tmprule->au_ctxt.type = typedatum->value; @@ -3438,7 +3653,7 @@ int selinux_audit_rule_init(u32 field, u32 op, char *rulestr, void **vrule) } rc = 0; out: - read_unlock(&state->ss->policy_rwlock); + rcu_read_unlock(); if (rc) { selinux_audit_rule_free(tmprule); @@ -3478,6 +3693,7 @@ int selinux_audit_rule_known(struct audit_krule *rule) int selinux_audit_rule_match(u32 sid, u32 field, u32 op, void *vrule) { struct selinux_state *state = &selinux_state; + struct selinux_policy *policy; struct context *ctxt; struct mls_level *level; struct selinux_audit_rule *rule = vrule; @@ -3488,14 +3704,19 @@ int selinux_audit_rule_match(u32 sid, u32 field, u32 op, void *vrule) return -ENOENT; } - read_lock(&state->ss->policy_rwlock); + if (!selinux_initialized(state)) + return 0; + + rcu_read_lock(); - if (rule->au_seqno < state->ss->latest_granting) { + policy = rcu_dereference(state->policy); + + if (rule->au_seqno < policy->latest_granting) { match = -ESTALE; goto out; } - ctxt = sidtab_search(state->ss->sidtab, sid); + ctxt = sidtab_search(policy->sidtab, sid); if (unlikely(!ctxt)) { WARN_ONCE(1, "selinux_audit_rule_match: unrecognized SID %d\n", sid); @@ -3579,19 +3800,15 @@ int selinux_audit_rule_match(u32 sid, u32 field, u32 op, void *vrule) } out: - read_unlock(&state->ss->policy_rwlock); + rcu_read_unlock(); return match; } -static int (*aurule_callback)(void) = audit_update_lsm_rules; - static int aurule_avc_callback(u32 event) { - int err = 0; - - if (event == AVC_CALLBACK_RESET && aurule_callback) - err = aurule_callback(); - return err; + if (event == AVC_CALLBACK_RESET) + return audit_update_lsm_rules(); + return 0; } static int __init aurule_init(void) @@ -3640,6 +3857,7 @@ static void security_netlbl_cache_add(struct netlbl_lsm_secattr *secattr, /** * security_netlbl_secattr_to_sid - Convert a NetLabel secattr to a SELinux SID + * @state: SELinux state * @secattr: the NetLabel packet security attributes * @sid: the SELinux SID * @@ -3657,8 +3875,9 @@ int security_netlbl_secattr_to_sid(struct selinux_state *state, struct netlbl_lsm_secattr *secattr, u32 *sid) { - struct policydb *policydb = &state->ss->policydb; - struct sidtab *sidtab = state->ss->sidtab; + struct selinux_policy *policy; + struct policydb *policydb; + struct sidtab *sidtab; int rc; struct context *ctx; struct context ctx_new; @@ -3668,7 +3887,12 @@ int security_netlbl_secattr_to_sid(struct selinux_state *state, return 0; } - read_lock(&state->ss->policy_rwlock); +retry: + rc = 0; + rcu_read_lock(); + policy = rcu_dereference(state->policy); + policydb = &policy->policydb; + sidtab = policy->sidtab; if (secattr->flags & NETLBL_SECATTR_CACHE) *sid = *(u32 *)secattr->cache->data; @@ -3691,30 +3915,32 @@ int security_netlbl_secattr_to_sid(struct selinux_state *state, goto out; } rc = -EIDRM; - if (!mls_context_isvalid(policydb, &ctx_new)) - goto out_free; + if (!mls_context_isvalid(policydb, &ctx_new)) { + ebitmap_destroy(&ctx_new.range.level[0].cat); + goto out; + } - rc = context_struct_to_sid(state, &ctx_new, sid); + rc = sidtab_context_to_sid(sidtab, &ctx_new, sid); + ebitmap_destroy(&ctx_new.range.level[0].cat); + if (rc == -ESTALE) { + rcu_read_unlock(); + goto retry; + } if (rc) - goto out_free; + goto out; security_netlbl_cache_add(secattr, *sid); - - ebitmap_destroy(&ctx_new.range.level[0].cat); } else *sid = SECSID_NULL; - read_unlock(&state->ss->policy_rwlock); - return 0; -out_free: - ebitmap_destroy(&ctx_new.range.level[0].cat); out: - read_unlock(&state->ss->policy_rwlock); + rcu_read_unlock(); return rc; } /** * security_netlbl_sid_to_secattr - Convert a SELinux SID to a NetLabel secattr + * @state: SELinux state * @sid: the SELinux SID * @secattr: the NetLabel packet security attributes * @@ -3726,17 +3952,20 @@ out: int security_netlbl_sid_to_secattr(struct selinux_state *state, u32 sid, struct netlbl_lsm_secattr *secattr) { - struct policydb *policydb = &state->ss->policydb; + struct selinux_policy *policy; + struct policydb *policydb; int rc; struct context *ctx; if (!selinux_initialized(state)) return 0; - read_lock(&state->ss->policy_rwlock); + rcu_read_lock(); + policy = rcu_dereference(state->policy); + policydb = &policy->policydb; rc = -ENOENT; - ctx = sidtab_search(state->ss->sidtab, sid); + ctx = sidtab_search(policy->sidtab, sid); if (ctx == NULL) goto out; @@ -3751,13 +3980,38 @@ int security_netlbl_sid_to_secattr(struct selinux_state *state, mls_export_netlbl_lvl(policydb, ctx, secattr); rc = mls_export_netlbl_cat(policydb, ctx, secattr); out: - read_unlock(&state->ss->policy_rwlock); + rcu_read_unlock(); return rc; } #endif /* CONFIG_NETLABEL */ /** + * __security_read_policy - read the policy. + * @policy: SELinux policy + * @data: binary policy data + * @len: length of data in bytes + * + */ +static int __security_read_policy(struct selinux_policy *policy, + void *data, size_t *len) +{ + int rc; + struct policy_file fp; + + fp.data = data; + fp.len = *len; + + rc = policydb_write(&policy->policydb, &fp); + if (rc) + return rc; + + *len = (unsigned long)fp.data - (unsigned long)data; + return 0; +} + +/** * security_read_policy - read the policy. + * @state: selinux_state * @data: binary policy data * @len: length of data in bytes * @@ -3765,30 +4019,54 @@ out: int security_read_policy(struct selinux_state *state, void **data, size_t *len) { - struct policydb *policydb = &state->ss->policydb; - int rc; - struct policy_file fp; + struct selinux_policy *policy; - if (!selinux_initialized(state)) + policy = rcu_dereference_protected( + state->policy, lockdep_is_held(&state->policy_mutex)); + if (!policy) return -EINVAL; - *len = security_policydb_len(state); - + *len = policy->policydb.len; *data = vmalloc_user(*len); if (!*data) return -ENOMEM; - fp.data = *data; - fp.len = *len; + return __security_read_policy(policy, *data, len); +} - read_lock(&state->ss->policy_rwlock); - rc = policydb_write(policydb, &fp); - read_unlock(&state->ss->policy_rwlock); +/** + * security_read_state_kernel - read the policy. + * @state: selinux_state + * @data: binary policy data + * @len: length of data in bytes + * + * Allocates kernel memory for reading SELinux policy. + * This function is for internal use only and should not + * be used for returning data to user space. + * + * This function must be called with policy_mutex held. + */ +int security_read_state_kernel(struct selinux_state *state, + void **data, size_t *len) +{ + int err; + struct selinux_policy *policy; - if (rc) - return rc; + policy = rcu_dereference_protected( + state->policy, lockdep_is_held(&state->policy_mutex)); + if (!policy) + return -EINVAL; - *len = (unsigned long)fp.data - (unsigned long)*data; - return 0; + *len = policy->policydb.len; + *data = vmalloc(*len); + if (!*data) + return -ENOMEM; + err = __security_read_policy(policy, *data, len); + if (err) { + vfree(*data); + *data = NULL; + *len = 0; + } + return err; } diff --git a/security/selinux/ss/services.h b/security/selinux/ss/services.h index c5896f39e8f6..9555ad074303 100644 --- a/security/selinux/ss/services.h +++ b/security/selinux/ss/services.h @@ -8,7 +8,6 @@ #define _SS_SERVICES_H_ #include "policydb.h" -#include "context.h" /* Mapping for a single class */ struct selinux_mapping { @@ -23,14 +22,11 @@ struct selinux_map { u16 size; /* array size of mapping */ }; -struct selinux_ss { +struct selinux_policy { struct sidtab *sidtab; struct policydb policydb; - rwlock_t policy_rwlock; - u32 latest_granting; struct selinux_map map; - struct page *status_page; - struct mutex status_lock; + u32 latest_granting; } __randomize_layout; void services_compute_xperms_drivers(struct extended_perms *xperms, @@ -39,6 +35,4 @@ void services_compute_xperms_drivers(struct extended_perms *xperms, void services_compute_xperms_decision(struct extended_perms_decision *xpermd, struct avtab_node *node); -int context_add_hash(struct policydb *policydb, struct context *context); - #endif /* _SS_SERVICES_H_ */ diff --git a/security/selinux/ss/sidtab.c b/security/selinux/ss/sidtab.c index f511ffccb131..db5cce385bf8 100644 --- a/security/selinux/ss/sidtab.c +++ b/security/selinux/ss/sidtab.c @@ -27,8 +27,8 @@ struct sidtab_str_cache { char str[]; }; -#define index_to_sid(index) (index + SECINITSID_NUM + 1) -#define sid_to_index(sid) (sid - (SECINITSID_NUM + 1)) +#define index_to_sid(index) ((index) + SECINITSID_NUM + 1) +#define sid_to_index(sid) ((sid) - (SECINITSID_NUM + 1)) int sidtab_init(struct sidtab *s) { @@ -39,6 +39,7 @@ int sidtab_init(struct sidtab *s) for (i = 0; i < SECINITSID_NUM; i++) s->isids[i].set = 0; + s->frozen = false; s->count = 0; s->convert = NULL; hash_init(s->context_to_sid); @@ -54,14 +55,15 @@ int sidtab_init(struct sidtab *s) return 0; } -static u32 context_to_sid(struct sidtab *s, struct context *context) +static u32 context_to_sid(struct sidtab *s, struct context *context, u32 hash) { struct sidtab_entry *entry; u32 sid = 0; rcu_read_lock(); - hash_for_each_possible_rcu(s->context_to_sid, entry, list, - context->hash) { + hash_for_each_possible_rcu(s->context_to_sid, entry, list, hash) { + if (entry->hash != hash) + continue; if (context_cmp(&entry->context, context)) { sid = entry->sid; break; @@ -74,6 +76,7 @@ static u32 context_to_sid(struct sidtab *s, struct context *context) int sidtab_set_initial(struct sidtab *s, u32 sid, struct context *context) { struct sidtab_isid_entry *isid; + u32 hash; int rc; if (sid == 0 || sid > SECINITSID_NUM) @@ -90,15 +93,18 @@ int sidtab_set_initial(struct sidtab *s, u32 sid, struct context *context) #endif isid->set = 1; + hash = context_compute_hash(context); + /* * Multiple initial sids may map to the same context. Check that this * context is not already represented in the context_to_sid hashtable * to avoid duplicate entries and long linked lists upon hash * collision. */ - if (!context_to_sid(s, context)) { + if (!context_to_sid(s, context, hash)) { isid->entry.sid = sid; - hash_add(s->context_to_sid, &isid->entry.list, context->hash); + isid->entry.hash = hash; + hash_add(s->context_to_sid, &isid->entry.list, hash); } return 0; @@ -259,12 +265,12 @@ int sidtab_context_to_sid(struct sidtab *s, struct context *context, u32 *sid) { unsigned long flags; - u32 count; + u32 count, hash = context_compute_hash(context); struct sidtab_convert_params *convert; struct sidtab_entry *dst, *dst_convert; int rc; - *sid = context_to_sid(s, context); + *sid = context_to_sid(s, context, hash); if (*sid) return 0; @@ -272,12 +278,20 @@ int sidtab_context_to_sid(struct sidtab *s, struct context *context, spin_lock_irqsave(&s->lock, flags); rc = 0; - *sid = context_to_sid(s, context); + *sid = context_to_sid(s, context, hash); if (*sid) goto out_unlock; - /* read entries only after reading count */ - count = smp_load_acquire(&s->count); + if (unlikely(s->frozen)) { + /* + * This sidtab is now frozen - tell the caller to abort and + * get the new one. + */ + rc = -ESTALE; + goto out_unlock; + } + + count = s->count; convert = s->convert; /* bail out if we already reached max entries */ @@ -292,6 +306,7 @@ int sidtab_context_to_sid(struct sidtab *s, struct context *context, goto out_unlock; dst->sid = index_to_sid(count); + dst->hash = hash; rc = context_cpy(&dst->context, context); if (rc) @@ -310,16 +325,17 @@ int sidtab_context_to_sid(struct sidtab *s, struct context *context, } rc = convert->func(context, &dst_convert->context, - convert->args); + convert->args, GFP_ATOMIC); if (rc) { context_destroy(&dst->context); goto out_unlock; } dst_convert->sid = index_to_sid(count); + dst_convert->hash = context_compute_hash(&dst_convert->context); convert->target->count = count + 1; hash_add_rcu(convert->target->context_to_sid, - &dst_convert->list, dst_convert->context.hash); + &dst_convert->list, dst_convert->hash); } if (context->len) @@ -330,7 +346,7 @@ int sidtab_context_to_sid(struct sidtab *s, struct context *context, /* write entries before updating count */ smp_store_release(&s->count, count + 1); - hash_add_rcu(s->context_to_sid, &dst->list, dst->context.hash); + hash_add_rcu(s->context_to_sid, &dst->list, dst->hash); rc = 0; out_unlock: @@ -346,10 +362,9 @@ static void sidtab_convert_hashtable(struct sidtab *s, u32 count) for (i = 0; i < count; i++) { entry = sidtab_do_lookup(s, i, 0); entry->sid = index_to_sid(i); + entry->hash = context_compute_hash(&entry->context); - hash_add_rcu(s->context_to_sid, &entry->list, - entry->context.hash); - + hash_add_rcu(s->context_to_sid, &entry->list, entry->hash); } } @@ -389,7 +404,7 @@ static int sidtab_convert_tree(union sidtab_entry_inner *edst, while (i < SIDTAB_LEAF_ENTRIES && *pos < count) { rc = convert->func(&esrc->ptr_leaf->entries[i].context, &edst->ptr_leaf->entries[i].context, - convert->args); + convert->args, GFP_KERNEL); if (rc) return rc; (*pos)++; @@ -459,6 +474,27 @@ int sidtab_convert(struct sidtab *s, struct sidtab_convert_params *params) return 0; } +void sidtab_cancel_convert(struct sidtab *s) +{ + unsigned long flags; + + /* cancelling policy load - disable live convert of sidtab */ + spin_lock_irqsave(&s->lock, flags); + s->convert = NULL; + spin_unlock_irqrestore(&s->lock, flags); +} + +void sidtab_freeze_begin(struct sidtab *s, unsigned long *flags) __acquires(&s->lock) +{ + spin_lock_irqsave(&s->lock, *flags); + s->frozen = true; + s->convert = NULL; +} +void sidtab_freeze_end(struct sidtab *s, unsigned long *flags) __releases(&s->lock) +{ + spin_unlock_irqrestore(&s->lock, *flags); +} + static void sidtab_destroy_entry(struct sidtab_entry *entry) { context_destroy(&entry->context); @@ -534,7 +570,7 @@ void sidtab_sid2str_put(struct sidtab *s, struct sidtab_entry *entry, goto out_unlock; } - cache = kmalloc(sizeof(struct sidtab_str_cache) + str_len, GFP_ATOMIC); + cache = kmalloc(struct_size(cache, str, str_len), GFP_ATOMIC); if (!cache) goto out_unlock; diff --git a/security/selinux/ss/sidtab.h b/security/selinux/ss/sidtab.h index 3311d9f236c0..9fce0d553fe2 100644 --- a/security/selinux/ss/sidtab.h +++ b/security/selinux/ss/sidtab.h @@ -19,6 +19,7 @@ struct sidtab_entry { u32 sid; + u32 hash; struct context context; #if CONFIG_SECURITY_SELINUX_SID2STR_CACHE_SIZE > 0 struct sidtab_str_cache __rcu *cache; @@ -64,7 +65,7 @@ struct sidtab_isid_entry { }; struct sidtab_convert_params { - int (*func)(struct context *oldc, struct context *newc, void *args); + int (*func)(struct context *oldc, struct context *newc, void *args, gfp_t gfp_flags); void *args; struct sidtab *target; }; @@ -85,6 +86,7 @@ struct sidtab { u32 count; /* access only under spinlock */ struct sidtab_convert_params *convert; + bool frozen; spinlock_t lock; #if CONFIG_SECURITY_SELINUX_SID2STR_CACHE_SIZE > 0 @@ -122,6 +124,11 @@ static inline struct context *sidtab_search_force(struct sidtab *s, u32 sid) int sidtab_convert(struct sidtab *s, struct sidtab_convert_params *params); +void sidtab_cancel_convert(struct sidtab *s); + +void sidtab_freeze_begin(struct sidtab *s, unsigned long *flags) __acquires(&s->lock); +void sidtab_freeze_end(struct sidtab *s, unsigned long *flags) __releases(&s->lock); + int sidtab_context_to_sid(struct sidtab *s, struct context *context, u32 *sid); void sidtab_destroy(struct sidtab *s); diff --git a/security/selinux/ss/symtab.c b/security/selinux/ss/symtab.c index dc2ce94165d3..c42a6648a07d 100644 --- a/security/selinux/ss/symtab.c +++ b/security/selinux/ss/symtab.c @@ -9,7 +9,7 @@ #include <linux/errno.h> #include "symtab.h" -static unsigned int symhash(struct hashtab *h, const void *key) +static unsigned int symhash(const void *key) { const char *p, *keyp; unsigned int size; @@ -20,10 +20,10 @@ static unsigned int symhash(struct hashtab *h, const void *key) size = strlen(keyp); for (p = keyp; (p - keyp) < size; p++) val = (val << 4 | (val >> (8*sizeof(unsigned int)-4))) ^ (*p); - return val & (h->size - 1); + return val; } -static int symcmp(struct hashtab *h, const void *key1, const void *key2) +static int symcmp(const void *key1, const void *key2) { const char *keyp1, *keyp2; @@ -32,13 +32,23 @@ static int symcmp(struct hashtab *h, const void *key1, const void *key2) return strcmp(keyp1, keyp2); } +static const struct hashtab_key_params symtab_key_params = { + .hash = symhash, + .cmp = symcmp, +}; int symtab_init(struct symtab *s, unsigned int size) { - s->table = hashtab_create(symhash, symcmp, size); - if (!s->table) - return -ENOMEM; s->nprim = 0; - return 0; + return hashtab_init(&s->table, size); } +int symtab_insert(struct symtab *s, char *name, void *datum) +{ + return hashtab_insert(&s->table, name, datum, symtab_key_params); +} + +void *symtab_search(struct symtab *s, const char *name) +{ + return hashtab_search(&s->table, name, symtab_key_params); +} diff --git a/security/selinux/ss/symtab.h b/security/selinux/ss/symtab.h index d75fcafe7281..f2614138d0cd 100644 --- a/security/selinux/ss/symtab.h +++ b/security/selinux/ss/symtab.h @@ -13,12 +13,15 @@ #include "hashtab.h" struct symtab { - struct hashtab *table; /* hash table (keyed on a string) */ + struct hashtab table; /* hash table (keyed on a string) */ u32 nprim; /* number of primary names in table */ }; int symtab_init(struct symtab *s, unsigned int size); +int symtab_insert(struct symtab *s, char *name, void *datum); +void *symtab_search(struct symtab *s, const char *name); + #endif /* _SS_SYMTAB_H_ */ diff --git a/security/selinux/ss/status.c b/security/selinux/status.c index 3c554a442467..4bc8f809934c 100644 --- a/security/selinux/ss/status.c +++ b/security/selinux/status.c @@ -11,7 +11,7 @@ #include <linux/mm.h> #include <linux/mutex.h> #include "avc.h" -#include "services.h" +#include "security.h" /* * The selinux_status_page shall be exposed to userspace applications @@ -44,12 +44,12 @@ struct page *selinux_kernel_status_page(struct selinux_state *state) struct selinux_kernel_status *status; struct page *result = NULL; - mutex_lock(&state->ss->status_lock); - if (!state->ss->status_page) { - state->ss->status_page = alloc_page(GFP_KERNEL|__GFP_ZERO); + mutex_lock(&state->status_lock); + if (!state->status_page) { + state->status_page = alloc_page(GFP_KERNEL|__GFP_ZERO); - if (state->ss->status_page) { - status = page_address(state->ss->status_page); + if (state->status_page) { + status = page_address(state->status_page); status->version = SELINUX_KERNEL_STATUS_VERSION; status->sequence = 0; @@ -65,8 +65,8 @@ struct page *selinux_kernel_status_page(struct selinux_state *state) !security_get_allow_unknown(state); } } - result = state->ss->status_page; - mutex_unlock(&state->ss->status_lock); + result = state->status_page; + mutex_unlock(&state->status_lock); return result; } @@ -81,9 +81,9 @@ void selinux_status_update_setenforce(struct selinux_state *state, { struct selinux_kernel_status *status; - mutex_lock(&state->ss->status_lock); - if (state->ss->status_page) { - status = page_address(state->ss->status_page); + mutex_lock(&state->status_lock); + if (state->status_page) { + status = page_address(state->status_page); status->sequence++; smp_wmb(); @@ -93,7 +93,7 @@ void selinux_status_update_setenforce(struct selinux_state *state, smp_wmb(); status->sequence++; } - mutex_unlock(&state->ss->status_lock); + mutex_unlock(&state->status_lock); } /* @@ -107,9 +107,9 @@ void selinux_status_update_policyload(struct selinux_state *state, { struct selinux_kernel_status *status; - mutex_lock(&state->ss->status_lock); - if (state->ss->status_page) { - status = page_address(state->ss->status_page); + mutex_lock(&state->status_lock); + if (state->status_page) { + status = page_address(state->status_page); status->sequence++; smp_wmb(); @@ -120,5 +120,5 @@ void selinux_status_update_policyload(struct selinux_state *state, smp_wmb(); status->sequence++; } - mutex_unlock(&state->ss->status_lock); + mutex_unlock(&state->status_lock); } diff --git a/security/selinux/xfrm.c b/security/selinux/xfrm.c index 7314196185d1..c576832febc6 100644 --- a/security/selinux/xfrm.c +++ b/security/selinux/xfrm.c @@ -47,7 +47,7 @@ #include "xfrm.h" /* Labeled XFRM instance counter */ -atomic_t selinux_xfrm_refcount = ATOMIC_INIT(0); +atomic_t selinux_xfrm_refcount __read_mostly = ATOMIC_INIT(0); /* * Returns true if the context is an LSM/SELinux context. @@ -89,7 +89,7 @@ static int selinux_xfrm_alloc_user(struct xfrm_sec_ctx **ctxp, if (str_len >= PAGE_SIZE) return -ENOMEM; - ctx = kmalloc(sizeof(*ctx) + str_len + 1, gfp); + ctx = kmalloc(struct_size(ctx, ctx_str, str_len + 1), gfp); if (!ctx) return -ENOMEM; @@ -150,7 +150,7 @@ static int selinux_xfrm_delete(struct xfrm_sec_ctx *ctx) * LSM hook implementation that authorizes that a flow can use a xfrm policy * rule. */ -int selinux_xfrm_policy_lookup(struct xfrm_sec_ctx *ctx, u32 fl_secid, u8 dir) +int selinux_xfrm_policy_lookup(struct xfrm_sec_ctx *ctx, u32 fl_secid) { int rc; @@ -175,9 +175,10 @@ int selinux_xfrm_policy_lookup(struct xfrm_sec_ctx *ctx, u32 fl_secid, u8 dir) */ int selinux_xfrm_state_pol_flow_match(struct xfrm_state *x, struct xfrm_policy *xp, - const struct flowi *fl) + const struct flowi_common *flic) { u32 state_sid; + u32 flic_sid; if (!xp->security) if (x->security) @@ -196,17 +197,17 @@ int selinux_xfrm_state_pol_flow_match(struct xfrm_state *x, return 0; state_sid = x->security->ctx_sid; + flic_sid = flic->flowic_secid; - if (fl->flowi_secid != state_sid) + if (flic_sid != state_sid) return 0; /* We don't need a separate SA Vs. policy polmatch check since the SA * is now of the same label as the flow and a flow Vs. policy polmatch * check had already happened in selinux_xfrm_policy_lookup() above. */ - return (avc_has_perm(&selinux_state, - fl->flowi_secid, state_sid, - SECCLASS_ASSOCIATION, ASSOCIATION__SENDTO, - NULL) ? 0 : 1); + return (avc_has_perm(&selinux_state, flic_sid, state_sid, + SECCLASS_ASSOCIATION, ASSOCIATION__SENDTO, + NULL) ? 0 : 1); } static u32 selinux_xfrm_skb_sid_egress(struct sk_buff *skb) @@ -346,7 +347,7 @@ int selinux_xfrm_state_alloc_acquire(struct xfrm_state *x, int rc; struct xfrm_sec_ctx *ctx; char *ctx_str = NULL; - int str_len; + u32 str_len; if (!polsec) return 0; @@ -359,7 +360,7 @@ int selinux_xfrm_state_alloc_acquire(struct xfrm_state *x, if (rc) return rc; - ctx = kmalloc(sizeof(*ctx) + str_len, GFP_ATOMIC); + ctx = kmalloc(struct_size(ctx, ctx_str, str_len), GFP_ATOMIC); if (!ctx) { rc = -ENOMEM; goto out; |