diff mbox series

[RFC,03/10] selinux: dynamically allocate selinux namespace

Message ID 20191015132528.13519-4-sds@tycho.nsa.gov (mailing list archive)
State Accepted
Headers show
Series SELinux namespace series, re-based | expand

Commit Message

Stephen Smalley Oct. 15, 2019, 1:25 p.m. UTC
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@tycho.nsa.gov>
---
 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 mbox series

Patch

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. */