Move from static allocation of a single selinux namespace to dynamic allocation. Include necessary support for lifecycle management of the selinux namespace, modeled after the user namespace support. Signed-off-by: Stephen Smalley <sds@xxxxxxxxxxxxx> --- security/selinux/avc.c | 32 ++++++++++---- security/selinux/hooks.c | 68 ++++++++++++++++++++++++++--- security/selinux/include/security.h | 25 ++++++++++- security/selinux/selinuxfs.c | 3 +- security/selinux/ss/services.c | 25 ++++++++--- 5 files changed, 129 insertions(+), 24 deletions(-) diff --git a/security/selinux/avc.c b/security/selinux/avc.c index c53582acd99f..d2cf749b5f19 100644 --- a/security/selinux/avc.c +++ b/security/selinux/avc.c @@ -88,20 +88,34 @@ struct selinux_avc { struct avc_cache avc_cache; }; -static struct selinux_avc selinux_avc; - -void selinux_avc_init(struct selinux_avc **avc) +int selinux_avc_create(struct selinux_avc **avc) { + struct selinux_avc *newavc; int i; - selinux_avc.avc_cache_threshold = AVC_DEF_CACHE_THRESHOLD; + newavc = kzalloc(sizeof(*newavc), GFP_KERNEL); + if (!newavc) + return -ENOMEM; + + newavc->avc_cache_threshold = AVC_DEF_CACHE_THRESHOLD; + for (i = 0; i < AVC_CACHE_SLOTS; i++) { - INIT_HLIST_HEAD(&selinux_avc.avc_cache.slots[i]); - spin_lock_init(&selinux_avc.avc_cache.slots_lock[i]); + INIT_HLIST_HEAD(&newavc->avc_cache.slots[i]); + spin_lock_init(&newavc->avc_cache.slots_lock[i]); } - atomic_set(&selinux_avc.avc_cache.active_nodes, 0); - atomic_set(&selinux_avc.avc_cache.lru_hint, 0); - *avc = &selinux_avc; + atomic_set(&newavc->avc_cache.active_nodes, 0); + atomic_set(&newavc->avc_cache.lru_hint, 0); + + *avc = newavc; + return 0; +} + +static void avc_flush(struct selinux_avc *avc); + +void selinux_avc_free(struct selinux_avc *avc) +{ + avc_flush(avc); + kfree(avc); } unsigned int avc_get_cache_threshold(struct selinux_avc *avc) diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index 6292fdd2b01f..7a4ed553cec0 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -103,7 +103,6 @@ #include "audit.h" #include "avc_ss.h" -static struct selinux_ns init_selinux_ns; struct selinux_ns *current_selinux_ns; /* SECMARK reference count */ @@ -7045,15 +7044,70 @@ static struct security_hook_list selinux_hooks[] __lsm_ro_after_init = { #endif }; +static void selinux_ns_free(struct work_struct *work); + +int selinux_ns_create(struct selinux_ns *parent, struct selinux_ns **ns) +{ + struct selinux_ns *newns; + int rc; + + newns = kzalloc(sizeof(*newns), GFP_KERNEL); + if (!newns) + return -ENOMEM; + + refcount_set(&newns->count, 1); + INIT_WORK(&newns->work, selinux_ns_free); + + rc = selinux_ss_create(&newns->ss); + if (rc) + goto err; + + rc = selinux_avc_create(&newns->avc); + if (rc) + goto err; + + if (parent) + newns->parent = get_selinux_ns(parent); + + *ns = newns; + return 0; +err: + selinux_ss_free(newns->ss); + kfree(newns); + return rc; +} + +static void selinux_ns_free(struct work_struct *work) +{ + struct selinux_ns *parent, *ns = + container_of(work, struct selinux_ns, work); + + do { + parent = ns->parent; + selinux_ss_free(ns->ss); + selinux_avc_free(ns->avc); + kfree(ns); + ns = parent; + } while (ns && refcount_dec_and_test(&ns->count)); +} + +void __put_selinux_ns(struct selinux_ns *ns) +{ + schedule_work(&ns->work); +} + +static struct selinux_ns *init_selinux_ns; + static __init int selinux_init(void) { pr_info("SELinux: Initializing.\n"); - enforcing_set(&init_selinux_ns, selinux_enforcing_boot); - init_selinux_ns.checkreqprot = selinux_checkreqprot_boot; - selinux_ss_init(&init_selinux_ns.ss); - selinux_avc_init(&init_selinux_ns.avc); - current_selinux_ns = &init_selinux_ns; + if (selinux_ns_create(NULL, &init_selinux_ns)) + panic("SELinux: Could not create initial namespace\n"); + + enforcing_set(init_selinux_ns, selinux_enforcing_boot); + init_selinux_ns->checkreqprot = selinux_checkreqprot_boot; + current_selinux_ns = init_selinux_ns; /* Set the security state for the initial task. */ cred_init_security(); @@ -7226,7 +7280,7 @@ int selinux_disable(struct selinux_ns *ns) * within the ns will interpret the absence of a selinuxfs mount * as SELinux being disabled. */ - if (ns != &init_selinux_ns) + if (ns != init_selinux_ns) return 0; pr_info("SELinux: Disabled at runtime.\n"); diff --git a/security/selinux/include/security.h b/security/selinux/include/security.h index e22e281992d8..971fd5f53b6e 100644 --- a/security/selinux/include/security.h +++ b/security/selinux/include/security.h @@ -99,6 +99,8 @@ struct selinux_avc; struct selinux_ss; struct selinux_ns { + refcount_t count; + struct work_struct work; bool disabled; #ifdef CONFIG_SECURITY_SELINUX_DEVELOP bool enforcing; @@ -108,10 +110,29 @@ struct selinux_ns { bool policycap[__POLICYDB_CAPABILITY_MAX]; struct selinux_avc *avc; struct selinux_ss *ss; + struct selinux_ns *parent; }; -void selinux_ss_init(struct selinux_ss **ss); -void selinux_avc_init(struct selinux_avc **avc); +int selinux_ns_create(struct selinux_ns *parent, struct selinux_ns **ns); +void __put_selinux_ns(struct selinux_ns *ns); + +int selinux_ss_create(struct selinux_ss **ss); +void selinux_ss_free(struct selinux_ss *ss); + +int selinux_avc_create(struct selinux_avc **avc); +void selinux_avc_free(struct selinux_avc *avc); + +static inline void put_selinux_ns(struct selinux_ns *ns) +{ + if (ns && refcount_dec_and_test(&ns->count)) + __put_selinux_ns(ns); +} + +static inline struct selinux_ns *get_selinux_ns(struct selinux_ns *ns) +{ + refcount_inc(&ns->count); + return ns; +} extern struct selinux_ns *current_selinux_ns; diff --git a/security/selinux/selinuxfs.c b/security/selinux/selinuxfs.c index a69381f94d37..41270a783cf5 100644 --- a/security/selinux/selinuxfs.c +++ b/security/selinux/selinuxfs.c @@ -90,7 +90,7 @@ static int selinux_fs_info_create(struct super_block *sb) mutex_init(&fsi->mutex); fsi->last_ino = SEL_INO_NEXT - 1; - fsi->ns = current_selinux_ns; + fsi->ns = get_selinux_ns(current_selinux_ns); fsi->sb = sb; sb->s_fs_info = fsi; return 0; @@ -102,6 +102,7 @@ static void selinux_fs_info_free(struct super_block *sb) int i; if (fsi) { + put_selinux_ns(fsi->ns); for (i = 0; i < fsi->bool_num; i++) kfree(fsi->bool_pending_names[i]); kfree(fsi->bool_pending_names); diff --git a/security/selinux/ss/services.c b/security/selinux/ss/services.c index defc70ac9fd1..57d6b8dddc54 100644 --- a/security/selinux/ss/services.c +++ b/security/selinux/ss/services.c @@ -76,13 +76,28 @@ const char *selinux_policycap_names[__POLICYDB_CAPABILITY_MAX] = { "nnp_nosuid_transition" }; -static struct selinux_ss selinux_ss; +int selinux_ss_create(struct selinux_ss **ss) +{ + struct selinux_ss *newss; + + newss = kzalloc(sizeof(*newss), GFP_KERNEL); + if (!newss) + return -ENOMEM; + rwlock_init(&newss->policy_rwlock); + mutex_init(&newss->status_lock); + *ss = newss; + return 0; +} -void selinux_ss_init(struct selinux_ss **ss) +void selinux_ss_free(struct selinux_ss *ss) { - rwlock_init(&selinux_ss.policy_rwlock); - mutex_init(&selinux_ss.status_lock); - *ss = &selinux_ss; + if (ss->sidtab) + sidtab_destroy(ss->sidtab); + policydb_destroy(&ss->policydb); + kfree(ss->map.mapping); + if (ss->status_page) + __free_page(ss->status_page); + kfree(ss); } /* Forward declaration. */ -- 2.21.0