From 66f8e2f03c02e812002f8e9e465681cc62edda5b Mon Sep 17 00:00:00 2001 From: Jeff Vander Stoep Date: Fri, 22 Nov 2019 10:33:06 +0100 Subject: selinux: sidtab reverse lookup hash table This replaces the reverse table lookup and reverse cache with a hashtable which improves cache-miss reverse-lookup times from O(n) to O(1)* and maintains the same performance as a reverse cache hit. This reduces the time needed to add a new sidtab entry from ~500us to 5us on a Pixel 3 when there are ~10,000 sidtab entries. The implementation uses the kernel's generic hashtable API, It uses the context's string represtation as the hash source, and the kernels generic string hashing algorithm full_name_hash() to reduce the string to a 32 bit value. This change also maintains the improvement introduced in commit ee1a84fdfeed ("selinux: overhaul sidtab to fix bug and improve performance") which removed the need to keep the current sidtab locked during policy reload. It does however introduce periodic locking of the target sidtab while converting the hashtable. Sidtab entries are never modified or removed, so the context struct stored in the sid_to_context tree can also be used for the context_to_sid hashtable to reduce memory usage. This bug was reported by: - On the selinux bug tracker. BUG: kernel softlockup due to too many SIDs/contexts #37 https://github.com/SELinuxProject/selinux-kernel/issues/37 - Jovana Knezevic on Android's bugtracker. Bug: 140252993 "During multi-user performance testing, we create and remove users many times. selinux_android_restorecon_pkgdir goes from 1ms to over 20ms after about 200 user creations and removals. Accumulated over ~280 packages, that adds a significant time to user creation, making perf benchmarks unreliable." * Hashtable lookup is only O(1) when n < the number of buckets. Signed-off-by: Jeff Vander Stoep Reported-by: Stephen Smalley Reported-by: Jovana Knezevic Reviewed-by: Stephen Smalley Tested-by: Stephen Smalley [PM: subj tweak, removed changelog from patch description] Signed-off-by: Paul Moore --- security/selinux/include/security.h | 1 + 1 file changed, 1 insertion(+) (limited to 'security/selinux/include/security.h') diff --git a/security/selinux/include/security.h b/security/selinux/include/security.h index ae840634e3c7..8c0dbbd076c6 100644 --- a/security/selinux/include/security.h +++ b/security/selinux/include/security.h @@ -395,5 +395,6 @@ extern int selinux_nlmsg_lookup(u16 sclass, u16 nlmsg_type, u32 *perm); extern void avtab_cache_init(void); extern void ebitmap_cache_init(void); extern void hashtab_cache_init(void); +extern int security_sidtab_hash_stats(struct selinux_state *state, char *page); #endif /* _SELINUX_SECURITY_H_ */ -- cgit From 6c5a682e6497cb1f7a67303ce098462a36bed362 Mon Sep 17 00:00:00 2001 From: Stephen Smalley Date: Tue, 17 Dec 2019 09:15:10 -0500 Subject: selinux: clean up selinux_enabled/disabled/enforcing_boot Rename selinux_enabled to selinux_enabled_boot to make it clear that it only reflects whether SELinux was enabled at boot. Replace the references to it in the MAC_STATUS audit log in sel_write_enforce() with hardcoded "1" values because this code is only reachable if SELinux is enabled and does not change its value, and update the corresponding MAC_STATUS audit log in sel_write_disable(). Stop clearing selinux_enabled in selinux_disable() since it is not used outside of initialization code that runs before selinux_disable() can be reached. Mark both selinux_enabled_boot and selinux_enforcing_boot as __initdata since they are only used in initialization code. Wrap the disabled field in the struct selinux_state with CONFIG_SECURITY_SELINUX_DISABLE since it is only used for runtime disable. Signed-off-by: Stephen Smalley Signed-off-by: Paul Moore --- security/selinux/hooks.c | 12 +++++------- security/selinux/ibpkey.c | 2 +- security/selinux/include/security.h | 4 +++- security/selinux/netif.c | 2 +- security/selinux/netnode.c | 2 +- security/selinux/netport.c | 2 +- security/selinux/selinuxfs.c | 11 +++++------ 7 files changed, 17 insertions(+), 18 deletions(-) (limited to 'security/selinux/include/security.h') diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index 40ec866e48da..659c4a81e897 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -109,7 +109,7 @@ struct selinux_state selinux_state; static atomic_t selinux_secmark_refcount = ATOMIC_INIT(0); #ifdef CONFIG_SECURITY_SELINUX_DEVELOP -static int selinux_enforcing_boot; +static int selinux_enforcing_boot __initdata; static int __init enforcing_setup(char *str) { @@ -123,13 +123,13 @@ __setup("enforcing=", enforcing_setup); #define selinux_enforcing_boot 1 #endif -int selinux_enabled __lsm_ro_after_init = 1; +int selinux_enabled_boot __initdata = 1; #ifdef CONFIG_SECURITY_SELINUX_BOOTPARAM static int __init selinux_enabled_setup(char *str) { unsigned long enabled; if (!kstrtoul(str, 0, &enabled)) - selinux_enabled = enabled ? 1 : 0; + selinux_enabled_boot = enabled ? 1 : 0; return 1; } __setup("selinux=", selinux_enabled_setup); @@ -7202,7 +7202,7 @@ void selinux_complete_init(void) DEFINE_LSM(selinux) = { .name = "selinux", .flags = LSM_FLAG_LEGACY_MAJOR | LSM_FLAG_EXCLUSIVE, - .enabled = &selinux_enabled, + .enabled = &selinux_enabled_boot, .blobs = &selinux_blob_sizes, .init = selinux_init, }; @@ -7271,7 +7271,7 @@ static int __init selinux_nf_ip_init(void) { int err; - if (!selinux_enabled) + if (!selinux_enabled_boot) return 0; pr_debug("SELinux: Registering netfilter hooks\n"); @@ -7318,8 +7318,6 @@ int selinux_disable(struct selinux_state *state) pr_info("SELinux: Disabled at runtime.\n"); - selinux_enabled = 0; - security_delete_hooks(selinux_hooks, ARRAY_SIZE(selinux_hooks)); /* Try to destroy the avc node cache */ diff --git a/security/selinux/ibpkey.c b/security/selinux/ibpkey.c index de92365e4324..f68a7617cfb9 100644 --- a/security/selinux/ibpkey.c +++ b/security/selinux/ibpkey.c @@ -222,7 +222,7 @@ static __init int sel_ib_pkey_init(void) { int iter; - if (!selinux_enabled) + if (!selinux_enabled_boot) return 0; for (iter = 0; iter < SEL_PKEY_HASH_SIZE; iter++) { diff --git a/security/selinux/include/security.h b/security/selinux/include/security.h index 8c0dbbd076c6..af623f03922c 100644 --- a/security/selinux/include/security.h +++ b/security/selinux/include/security.h @@ -69,7 +69,7 @@ struct netlbl_lsm_secattr; -extern int selinux_enabled; +extern int selinux_enabled_boot; /* Policy capabilities */ enum { @@ -99,7 +99,9 @@ struct selinux_avc; struct selinux_ss; struct selinux_state { +#ifdef CONFIG_SECURITY_SELINUX_DISABLE bool disabled; +#endif #ifdef CONFIG_SECURITY_SELINUX_DEVELOP bool enforcing; #endif diff --git a/security/selinux/netif.c b/security/selinux/netif.c index e40fecd73752..15b8c1bcd7d0 100644 --- a/security/selinux/netif.c +++ b/security/selinux/netif.c @@ -266,7 +266,7 @@ static __init int sel_netif_init(void) { int i; - if (!selinux_enabled) + if (!selinux_enabled_boot) return 0; for (i = 0; i < SEL_NETIF_HASH_SIZE; i++) diff --git a/security/selinux/netnode.c b/security/selinux/netnode.c index 9ab84efa46c7..dff587d1e164 100644 --- a/security/selinux/netnode.c +++ b/security/selinux/netnode.c @@ -291,7 +291,7 @@ static __init int sel_netnode_init(void) { int iter; - if (!selinux_enabled) + if (!selinux_enabled_boot) return 0; for (iter = 0; iter < SEL_NETNODE_HASH_SIZE; iter++) { diff --git a/security/selinux/netport.c b/security/selinux/netport.c index 3f8b2c0458c8..de727f7489b7 100644 --- a/security/selinux/netport.c +++ b/security/selinux/netport.c @@ -225,7 +225,7 @@ static __init int sel_netport_init(void) { int iter; - if (!selinux_enabled) + if (!selinux_enabled_boot) return 0; for (iter = 0; iter < SEL_NETPORT_HASH_SIZE; iter++) { diff --git a/security/selinux/selinuxfs.c b/security/selinux/selinuxfs.c index dd7bb1f1dc99..278417e67b4c 100644 --- a/security/selinux/selinuxfs.c +++ b/security/selinux/selinuxfs.c @@ -168,11 +168,10 @@ static ssize_t sel_write_enforce(struct file *file, const char __user *buf, goto out; audit_log(audit_context(), GFP_KERNEL, AUDIT_MAC_STATUS, "enforcing=%d old_enforcing=%d auid=%u ses=%u" - " enabled=%d old-enabled=%d lsm=selinux res=1", + " enabled=1 old-enabled=1 lsm=selinux res=1", new_value, old_value, from_kuid(&init_user_ns, audit_get_loginuid(current)), - audit_get_sessionid(current), - selinux_enabled, selinux_enabled); + audit_get_sessionid(current)); enforcing_set(state, new_value); if (new_value) avc_ss_reset(state->avc, 0); @@ -304,10 +303,10 @@ static ssize_t sel_write_disable(struct file *file, const char __user *buf, goto out; audit_log(audit_context(), GFP_KERNEL, AUDIT_MAC_STATUS, "enforcing=%d old_enforcing=%d auid=%u ses=%u" - " enabled=%d old-enabled=%d lsm=selinux res=1", + " enabled=0 old-enabled=1 lsm=selinux res=1", enforcing, enforcing, from_kuid(&init_user_ns, audit_get_loginuid(current)), - audit_get_sessionid(current), 0, 1); + audit_get_sessionid(current)); } length = count; @@ -2105,7 +2104,7 @@ static int __init init_sel_fs(void) sizeof(NULL_FILE_NAME)-1); int err; - if (!selinux_enabled) + if (!selinux_enabled_boot) return 0; err = sysfs_create_mount_point(fs_kobj, "selinux"); -- cgit From 5c108d4e18f80be01965792726c81b105fbd677a Mon Sep 17 00:00:00 2001 From: Stephen Smalley Date: Fri, 13 Dec 2019 15:28:38 -0500 Subject: selinux: randomize layout of key structures Randomize the layout of key selinux data structures. Initially this is applied to the selinux_state, selinux_ss, policydb, and task_security_struct data structures. NB To test/use this mechanism, one must install the necessary build-time dependencies, e.g. gcc-plugin-devel on Fedora, and enable CONFIG_GCC_PLUGIN_RANDSTRUCT in the kernel configuration. Signed-off-by: Stephen Smalley Reviewed-by: Kees Cook [PM: double semi-colon fixed] Signed-off-by: Paul Moore --- security/selinux/include/objsec.h | 2 +- security/selinux/include/security.h | 2 +- security/selinux/ss/policydb.h | 2 +- security/selinux/ss/services.h | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) (limited to 'security/selinux/include/security.h') diff --git a/security/selinux/include/objsec.h b/security/selinux/include/objsec.h index a4a86cbcfb0a..330b7b6d44e0 100644 --- a/security/selinux/include/objsec.h +++ b/security/selinux/include/objsec.h @@ -35,7 +35,7 @@ struct task_security_struct { u32 create_sid; /* fscreate SID */ u32 keycreate_sid; /* keycreate SID */ u32 sockcreate_sid; /* fscreate SID */ -}; +} __randomize_layout; enum label_initialized { LABEL_INVALID, /* invalid or not initialized */ diff --git a/security/selinux/include/security.h b/security/selinux/include/security.h index af623f03922c..ecdd610e6449 100644 --- a/security/selinux/include/security.h +++ b/security/selinux/include/security.h @@ -110,7 +110,7 @@ struct selinux_state { bool policycap[__POLICYDB_CAPABILITY_MAX]; struct selinux_avc *avc; struct selinux_ss *ss; -}; +} __randomize_layout; void selinux_ss_init(struct selinux_ss **ss); void selinux_avc_init(struct selinux_avc **avc); diff --git a/security/selinux/ss/policydb.h b/security/selinux/ss/policydb.h index bc56b14e2216..69b24191fa38 100644 --- a/security/selinux/ss/policydb.h +++ b/security/selinux/ss/policydb.h @@ -307,7 +307,7 @@ struct policydb { u16 process_class; u32 process_trans_perms; -}; +} __randomize_layout; extern void policydb_destroy(struct policydb *p); extern int policydb_load_isids(struct policydb *p, struct sidtab *s); diff --git a/security/selinux/ss/services.h b/security/selinux/ss/services.h index fc40640a9725..c5896f39e8f6 100644 --- a/security/selinux/ss/services.h +++ b/security/selinux/ss/services.h @@ -31,7 +31,7 @@ struct selinux_ss { struct selinux_map map; struct page *status_page; struct mutex status_lock; -}; +} __randomize_layout; void services_compute_xperms_drivers(struct extended_perms *xperms, struct avtab_node *node); -- cgit From 65cddd50980be8c9c27ad7518a0dc812eccb25d5 Mon Sep 17 00:00:00 2001 From: Ondrej Mosnacek Date: Tue, 7 Jan 2020 14:31:53 +0100 Subject: selinux: treat atomic flags more carefully The disabled/enforcing/initialized flags are all accessed concurrently by threads so use the appropriate accessors that ensure atomicity and document that it is expected. Use smp_load/acquire...() helpers (with memory barriers) for the initialized flag, since it gates access to the rest of the state structures. Note that the disabled flag is currently not used for anything other than avoiding double disable, but it will be used for bailing out of hooks once security_delete_hooks() is removed. Signed-off-by: Ondrej Mosnacek Acked-by: Stephen Smalley Reviewed-by: Kees Cook Reviewed-by: James Morris Signed-off-by: Paul Moore --- security/selinux/hooks.c | 21 ++++++++++---------- security/selinux/include/security.h | 33 ++++++++++++++++++++++++++++++-- security/selinux/ss/services.c | 38 ++++++++++++++++++------------------- 3 files changed, 61 insertions(+), 31 deletions(-) (limited to 'security/selinux/include/security.h') diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index 921283f47862..a81631f8cc5d 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -272,7 +272,7 @@ static int __inode_security_revalidate(struct inode *inode, might_sleep_if(may_sleep); - if (selinux_state.initialized && + if (selinux_initialized(&selinux_state) && isec->initialized != LABEL_INITIALIZED) { if (!may_sleep) return -ECHILD; @@ -659,7 +659,7 @@ static int selinux_set_mnt_opts(struct super_block *sb, mutex_lock(&sbsec->lock); - if (!selinux_state.initialized) { + if (!selinux_initialized(&selinux_state)) { if (!opts) { /* Defer initialization until selinux_complete_init, after the initial policy is loaded and the security @@ -929,7 +929,7 @@ static int selinux_sb_clone_mnt_opts(const struct super_block *oldsb, * if the parent was able to be mounted it clearly had no special lsm * mount options. thus we can safely deal with this superblock later */ - if (!selinux_state.initialized) + if (!selinux_initialized(&selinux_state)) return 0; /* @@ -1104,7 +1104,7 @@ static int selinux_sb_show_options(struct seq_file *m, struct super_block *sb) if (!(sbsec->flags & SE_SBINITIALIZED)) return 0; - if (!selinux_state.initialized) + if (!selinux_initialized(&selinux_state)) return 0; if (sbsec->flags & FSCONTEXT_MNT) { @@ -2921,7 +2921,8 @@ static int selinux_inode_init_security(struct inode *inode, struct inode *dir, isec->initialized = LABEL_INITIALIZED; } - if (!selinux_state.initialized || !(sbsec->flags & SBLABEL_MNT)) + if (!selinux_initialized(&selinux_state) || + !(sbsec->flags & SBLABEL_MNT)) return -EOPNOTSUPP; if (name) @@ -3144,7 +3145,7 @@ static int selinux_inode_setxattr(struct dentry *dentry, const char *name, return dentry_has_perm(current_cred(), dentry, FILE__SETATTR); } - if (!selinux_state.initialized) + if (!selinux_initialized(&selinux_state)) return (inode_owner_or_capable(inode) ? 0 : -EPERM); sbsec = inode->i_sb->s_security; @@ -3230,7 +3231,7 @@ static void selinux_inode_post_setxattr(struct dentry *dentry, const char *name, return; } - if (!selinux_state.initialized) { + if (!selinux_initialized(&selinux_state)) { /* If we haven't even been initialized, then we can't validate * against a policy, so leave the label as invalid. It may * resolve to a valid label on the next revalidation try if @@ -7300,17 +7301,17 @@ static void selinux_nf_ip_exit(void) #ifdef CONFIG_SECURITY_SELINUX_DISABLE int selinux_disable(struct selinux_state *state) { - if (state->initialized) { + if (selinux_initialized(state)) { /* Not permitted after initial policy load. */ return -EINVAL; } - if (state->disabled) { + if (selinux_disabled(state)) { /* Only do this once. */ return -EINVAL; } - state->disabled = 1; + selinux_mark_disabled(state); pr_info("SELinux: Disabled at runtime.\n"); diff --git a/security/selinux/include/security.h b/security/selinux/include/security.h index ecdd610e6449..a39f9565d80b 100644 --- a/security/selinux/include/security.h +++ b/security/selinux/include/security.h @@ -117,15 +117,27 @@ void selinux_avc_init(struct selinux_avc **avc); extern struct selinux_state selinux_state; +static inline bool selinux_initialized(const struct selinux_state *state) +{ + /* do a synchronized load to avoid race conditions */ + return smp_load_acquire(&state->initialized); +} + +static inline void selinux_mark_initialized(struct selinux_state *state) +{ + /* do a synchronized write to avoid race conditions */ + smp_store_release(&state->initialized, true); +} + #ifdef CONFIG_SECURITY_SELINUX_DEVELOP static inline bool enforcing_enabled(struct selinux_state *state) { - return state->enforcing; + return READ_ONCE(state->enforcing); } static inline void enforcing_set(struct selinux_state *state, bool value) { - state->enforcing = value; + WRITE_ONCE(state->enforcing, value); } #else static inline bool enforcing_enabled(struct selinux_state *state) @@ -138,6 +150,23 @@ static inline void enforcing_set(struct selinux_state *state, bool value) } #endif +#ifdef CONFIG_SECURITY_SELINUX_DISABLE +static inline bool selinux_disabled(struct selinux_state *state) +{ + return READ_ONCE(state->disabled); +} + +static inline void selinux_mark_disabled(struct selinux_state *state) +{ + WRITE_ONCE(state->disabled, true); +} +#else +static inline bool selinux_disabled(struct selinux_state *state) +{ + return false; +} +#endif + static inline bool selinux_policycap_netpeer(void) { struct selinux_state *state = &selinux_state; diff --git a/security/selinux/ss/services.c b/security/selinux/ss/services.c index 55cf42945cba..0e8b94e8e156 100644 --- a/security/selinux/ss/services.c +++ b/security/selinux/ss/services.c @@ -767,7 +767,7 @@ static int security_compute_validatetrans(struct selinux_state *state, int rc = 0; - if (!state->initialized) + if (!selinux_initialized(state)) return 0; read_lock(&state->ss->policy_rwlock); @@ -868,7 +868,7 @@ int security_bounded_transition(struct selinux_state *state, int index; int rc; - if (!state->initialized) + if (!selinux_initialized(state)) return 0; read_lock(&state->ss->policy_rwlock); @@ -1027,7 +1027,7 @@ void security_compute_xperms_decision(struct selinux_state *state, memset(xpermd->dontaudit->p, 0, sizeof(xpermd->dontaudit->p)); read_lock(&state->ss->policy_rwlock); - if (!state->initialized) + if (!selinux_initialized(state)) goto allow; policydb = &state->ss->policydb; @@ -1112,7 +1112,7 @@ void security_compute_av(struct selinux_state *state, read_lock(&state->ss->policy_rwlock); avd_init(state, avd); xperms->len = 0; - if (!state->initialized) + if (!selinux_initialized(state)) goto allow; policydb = &state->ss->policydb; @@ -1166,7 +1166,7 @@ void security_compute_av_user(struct selinux_state *state, read_lock(&state->ss->policy_rwlock); avd_init(state, avd); - if (!state->initialized) + if (!selinux_initialized(state)) goto allow; policydb = &state->ss->policydb; @@ -1286,7 +1286,7 @@ int security_sidtab_hash_stats(struct selinux_state *state, char *page) { int rc; - if (!state->initialized) { + if (!selinux_initialized(state)) { pr_err("SELinux: %s: called before initial load_policy\n", __func__); return -EINVAL; @@ -1320,7 +1320,7 @@ static int security_sid_to_context_core(struct selinux_state *state, *scontext = NULL; *scontext_len = 0; - if (!state->initialized) { + if (!selinux_initialized(state)) { if (sid <= SECINITSID_NUM) { char *scontextp; @@ -1549,7 +1549,7 @@ static int security_context_to_sid_core(struct selinux_state *state, if (!scontext2) return -ENOMEM; - if (!state->initialized) { + if (!selinux_initialized(state)) { int i; for (i = 1; i < SECINITSID_NUM; i++) { @@ -1736,7 +1736,7 @@ static int security_compute_sid(struct selinux_state *state, int rc = 0; bool sock; - if (!state->initialized) { + if (!selinux_initialized(state)) { switch (orig_tclass) { case SECCLASS_PROCESS: /* kernel value */ *out_sid = ssid; @@ -2198,7 +2198,7 @@ int security_load_policy(struct selinux_state *state, void *data, size_t len) goto out; } - if (!state->initialized) { + if (!selinux_initialized(state)) { rc = policydb_read(policydb, fp); if (rc) { kfree(newsidtab); @@ -2223,7 +2223,7 @@ int security_load_policy(struct selinux_state *state, void *data, size_t len) state->ss->sidtab = newsidtab; security_load_policycaps(state); - state->initialized = 1; + selinux_mark_initialized(state); seqno = ++state->ss->latest_granting; selinux_complete_init(); avc_ss_reset(state->avc, seqno); @@ -2639,7 +2639,7 @@ int security_get_user_sids(struct selinux_state *state, *sids = NULL; *nel = 0; - if (!state->initialized) + if (!selinux_initialized(state)) goto out; read_lock(&state->ss->policy_rwlock); @@ -2875,7 +2875,7 @@ int security_get_bools(struct selinux_state *state, struct policydb *policydb; int i, rc; - if (!state->initialized) { + if (!selinux_initialized(state)) { *len = 0; *names = NULL; *values = NULL; @@ -3050,7 +3050,7 @@ int security_sid_mls_copy(struct selinux_state *state, int rc; rc = 0; - if (!state->initialized || !policydb->mls_enabled) { + if (!selinux_initialized(state) || !policydb->mls_enabled) { *new_sid = sid; goto out; } @@ -3217,7 +3217,7 @@ int security_get_classes(struct selinux_state *state, struct policydb *policydb = &state->ss->policydb; int rc; - if (!state->initialized) { + if (!selinux_initialized(state)) { *nclasses = 0; *classes = NULL; return 0; @@ -3366,7 +3366,7 @@ int selinux_audit_rule_init(u32 field, u32 op, char *rulestr, void **vrule) *rule = NULL; - if (!state->initialized) + if (!selinux_initialized(state)) return -EOPNOTSUPP; switch (field) { @@ -3665,7 +3665,7 @@ int security_netlbl_secattr_to_sid(struct selinux_state *state, struct context *ctx; struct context ctx_new; - if (!state->initialized) { + if (!selinux_initialized(state)) { *sid = SECSID_NULL; return 0; } @@ -3732,7 +3732,7 @@ int security_netlbl_sid_to_secattr(struct selinux_state *state, int rc; struct context *ctx; - if (!state->initialized) + if (!selinux_initialized(state)) return 0; read_lock(&state->ss->policy_rwlock); @@ -3771,7 +3771,7 @@ int security_read_policy(struct selinux_state *state, int rc; struct policy_file fp; - if (!state->initialized) + if (!selinux_initialized(state)) return -EINVAL; *len = security_policydb_len(state); -- cgit