diff mbox

[v6,3/9] selinux lsm IB/core: Implement LSM notification system

Message ID 1479910651-43246-4-git-send-email-danielj@mellanox.com (mailing list archive)
State New, archived
Headers show

Commit Message

Daniel Jurgens Nov. 23, 2016, 2:17 p.m. UTC
From: Daniel Jurgens <danielj@mellanox.com>

Add a generic notificaiton mechanism in the LSM. Interested consumers
can register a callback with the LSM and security modules can produce
events.

Because access to Infiniband QPs are enforced in the setup phase of a
connection security should be enforced again if the policy changes.
Register infiniband devices for policy change notification and check all
QPs on that device when the notification is received.

Add a call to the notification mechanism from SELinux when the AVC
cache changes or setenforce is cleared.

Signed-off-by: Daniel Jurgens <danielj@mellanox.com>

---
v2:
- new patch that has the generic notification, replaces selinux and
  IB/core patches related to the ib_flush callback. Yuval Shaia and Paul
  Moore

v3:
- use notifier chains. Paul Moore

v4:
- Seperate avc callback for LSM notifier. Paul Moore

v5:
- Fix link error when CONFIG_SECURITY is not set. Build Robot
---
 drivers/infiniband/core/device.c | 53 ++++++++++++++++++++++++++++++++++++++++
 include/linux/security.h         | 23 +++++++++++++++++
 security/security.c              | 20 +++++++++++++++
 security/selinux/hooks.c         | 11 +++++++++
 security/selinux/selinuxfs.c     |  2 ++
 5 files changed, 109 insertions(+)

Comments

Stephen Smalley Dec. 13, 2016, 2:29 p.m. UTC | #1
On 11/23/2016 09:17 AM, Dan Jurgens wrote:
> From: Daniel Jurgens <danielj@mellanox.com>
> 
> Add a generic notificaiton mechanism in the LSM. Interested consumers
> can register a callback with the LSM and security modules can produce
> events.
> 
> Because access to Infiniband QPs are enforced in the setup phase of a
> connection security should be enforced again if the policy changes.
> Register infiniband devices for policy change notification and check all
> QPs on that device when the notification is received.
> 
> Add a call to the notification mechanism from SELinux when the AVC
> cache changes or setenforce is cleared.
> 
> Signed-off-by: Daniel Jurgens <danielj@mellanox.com>
> 
> ---
> v2:
> - new patch that has the generic notification, replaces selinux and
>   IB/core patches related to the ib_flush callback. Yuval Shaia and Paul
>   Moore
> 
> v3:
> - use notifier chains. Paul Moore
> 
> v4:
> - Seperate avc callback for LSM notifier. Paul Moore
> 
> v5:
> - Fix link error when CONFIG_SECURITY is not set. Build Robot
> ---
>  drivers/infiniband/core/device.c | 53 ++++++++++++++++++++++++++++++++++++++++
>  include/linux/security.h         | 23 +++++++++++++++++
>  security/security.c              | 20 +++++++++++++++
>  security/selinux/hooks.c         | 11 +++++++++
>  security/selinux/selinuxfs.c     |  2 ++
>  5 files changed, 109 insertions(+)
> 
> diff --git a/drivers/infiniband/core/device.c b/drivers/infiniband/core/device.c
> index 5b42e83..7b6fd06 100644
> --- a/drivers/infiniband/core/device.c
> +++ b/drivers/infiniband/core/device.c
> @@ -39,6 +39,8 @@
>  #include <linux/init.h>
>  #include <linux/mutex.h>
>  #include <linux/netdevice.h>
> +#include <linux/security.h>
> +#include <linux/notifier.h>
>  #include <rdma/rdma_netlink.h>
>  #include <rdma/ib_addr.h>
>  #include <rdma/ib_cache.h>
> @@ -82,6 +84,14 @@ static LIST_HEAD(client_list);
>  static DEFINE_MUTEX(device_mutex);
>  static DECLARE_RWSEM(lists_rwsem);
>  
> +static int ib_security_change(struct notifier_block *nb, unsigned long event,
> +			      void *lsm_data);
> +static void ib_policy_change_task(struct work_struct *work);
> +static DECLARE_WORK(ib_policy_change_work, ib_policy_change_task);
> +
> +static struct notifier_block ibdev_lsm_nb = {
> +	.notifier_call = ib_security_change,
> +};
>  
>  static int ib_device_check_mandatory(struct ib_device *device)
>  {
> @@ -344,6 +354,40 @@ static int setup_port_pkey_list(struct ib_device *device)
>  	return 0;
>  }
>  
> +static void ib_policy_change_task(struct work_struct *work)
> +{
> +	struct ib_device *dev;
> +
> +	down_read(&lists_rwsem);
> +	list_for_each_entry(dev, &device_list, core_list) {
> +		int i;
> +
> +		for (i = rdma_start_port(dev); i <= rdma_end_port(dev); i++) {
> +			u64 sp;
> +			int ret = ib_get_cached_subnet_prefix(dev,
> +							      i,
> +							      &sp);
> +
> +			WARN_ONCE(ret,
> +				  "ib_get_cached_subnet_prefix err: %d, this should never happen here\n",
> +				  ret);
> +			ib_security_cache_change(dev, i, sp);
> +		}
> +	}
> +	up_read(&lists_rwsem);
> +}
> +
> +static int ib_security_change(struct notifier_block *nb, unsigned long event,
> +			      void *lsm_data)
> +{
> +	if (event != LSM_POLICY_CHANGE)
> +		return NOTIFY_DONE;
> +
> +	schedule_work(&ib_policy_change_work);
> +
> +	return NOTIFY_OK;
> +}
> +
>  /**
>   * ib_register_device - Register an IB device with IB core
>   * @device:Device to register
> @@ -1075,10 +1119,18 @@ static int __init ib_core_init(void)
>  		goto err_sa;
>  	}
>  
> +	ret = register_lsm_notifier(&ibdev_lsm_nb);
> +	if (ret) {
> +		pr_warn("Couldn't register LSM notifier. ret %d\n", ret);
> +		goto err_ibnl_clients;
> +	}
> +
>  	ib_cache_setup();
>  
>  	return 0;
>  
> +err_ibnl_clients:
> +	ib_remove_ibnl_clients();
>  err_sa:
>  	ib_sa_cleanup();
>  err_mad:
> @@ -1098,6 +1150,7 @@ static int __init ib_core_init(void)
>  
>  static void __exit ib_core_cleanup(void)
>  {
> +	unregister_lsm_notifier(&ibdev_lsm_nb);
>  	ib_cache_cleanup();
>  	ib_remove_ibnl_clients();
>  	ib_sa_cleanup();
> diff --git a/include/linux/security.h b/include/linux/security.h
> index 342ca4c..0a5de0c 100644
> --- a/include/linux/security.h
> +++ b/include/linux/security.h
> @@ -69,6 +69,10 @@ struct audit_krule;
>  struct user_namespace;
>  struct timezone;
>  
> +enum lsm_event {
> +	LSM_POLICY_CHANGE,
> +};
> +
>  /* These functions are in security/commoncap.c */
>  extern int cap_capable(const struct cred *cred, struct user_namespace *ns,
>  		       int cap, int audit);
> @@ -161,6 +165,10 @@ struct security_mnt_opts {
>  	int num_mnt_opts;
>  };
>  
> +int call_lsm_notifier(enum lsm_event event, void *data);
> +int register_lsm_notifier(struct notifier_block *nb);
> +int unregister_lsm_notifier(struct notifier_block *nb);
> +
>  static inline void security_init_mnt_opts(struct security_mnt_opts *opts)
>  {
>  	opts->mnt_opts = NULL;
> @@ -377,6 +385,21 @@ int security_inode_getsecctx(struct inode *inode, void **ctx, u32 *ctxlen);
>  struct security_mnt_opts {
>  };
>  
> +static inline int call_lsm_notifier(enum lsm_event event, void *data)
> +{
> +	return 0;
> +}
> +
> +static inline int register_lsm_notifier(struct notifier_block *nb)
> +{
> +	return 0;
> +}
> +
> +static inline  int unregister_lsm_notifier(struct notifier_block *nb)
> +{
> +	return 0;
> +}
> +
>  static inline void security_init_mnt_opts(struct security_mnt_opts *opts)
>  {
>  }
> diff --git a/security/security.c b/security/security.c
> index 7d3bf2f..40326d4 100644
> --- a/security/security.c
> +++ b/security/security.c
> @@ -33,6 +33,8 @@
>  /* Maximum number of letters for an LSM name string */
>  #define SECURITY_NAME_MAX	10
>  
> +static ATOMIC_NOTIFIER_HEAD(lsm_notifier_chain);
> +
>  /* Boot-time LSM user choice */
>  static __initdata char chosen_lsm[SECURITY_NAME_MAX + 1] =
>  	CONFIG_DEFAULT_SECURITY;
> @@ -98,6 +100,24 @@ int __init security_module_enable(const char *module)
>  	return !strcmp(module, chosen_lsm);
>  }
>  
> +int call_lsm_notifier(enum lsm_event event, void *data)
> +{
> +	return atomic_notifier_call_chain(&lsm_notifier_chain, event, data);
> +}
> +EXPORT_SYMBOL(call_lsm_notifier);
> +
> +int register_lsm_notifier(struct notifier_block *nb)
> +{
> +	return atomic_notifier_chain_register(&lsm_notifier_chain, nb);
> +}
> +EXPORT_SYMBOL(register_lsm_notifier);
> +
> +int unregister_lsm_notifier(struct notifier_block *nb)
> +{
> +	return atomic_notifier_chain_unregister(&lsm_notifier_chain, nb);
> +}
> +EXPORT_SYMBOL(unregister_lsm_notifier);
> +
>  /*
>   * Hook list operation macros.
>   *
> diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c
> index 09fd610..2d7a7c1 100644
> --- a/security/selinux/hooks.c
> +++ b/security/selinux/hooks.c
> @@ -170,6 +170,14 @@ static int selinux_netcache_avc_callback(u32 event)
>  	return 0;
>  }
>  
> +static int selinux_lsm_notifier_avc_callback(u32 event)
> +{
> +	if (event == AVC_CALLBACK_RESET)
> +		call_lsm_notifier(LSM_POLICY_CHANGE, NULL);
> +
> +	return 0;
> +}
> +
>  /*
>   * initialise the security for the init task
>   */
> @@ -6325,6 +6333,9 @@ static __init int selinux_init(void)
>  	if (avc_add_callback(selinux_netcache_avc_callback, AVC_CALLBACK_RESET))
>  		panic("SELinux: Unable to register AVC netcache callback\n");
>  
> +	if (avc_add_callback(selinux_lsm_notifier_avc_callback, AVC_CALLBACK_RESET))
> +		panic("SELinux: Unable to register AVC LSM notifier callback\n");
> +
>  	if (selinux_enforcing)
>  		printk(KERN_DEBUG "SELinux:  Starting in enforcing mode\n");
>  	else
> diff --git a/security/selinux/selinuxfs.c b/security/selinux/selinuxfs.c
> index 72c145d..d3f9192 100644
> --- a/security/selinux/selinuxfs.c
> +++ b/security/selinux/selinuxfs.c
> @@ -177,6 +177,8 @@ static ssize_t sel_write_enforce(struct file *file, const char __user *buf,
>  			avc_ss_reset(0);
>  		selnl_notify_setenforce(selinux_enforcing);
>  		selinux_status_update_setenforce(selinux_enforcing);
> +		if (!selinux_enforcing)
> +			call_lsm_notifier(LSM_POLICY_CHANGE, NULL);

Why do you need this notification?  When switching from permissive to
enforcing, you need (and already get) a notification since you may need
to revoke previously granted permissions.  But what action do you need
to take on switching to permissive?

>  	}
>  	length = count;
>  out:
> 

--
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Daniel Jurgens Dec. 13, 2016, 2:38 p.m. UTC | #2
On 12/13/2016 8:26 AM, Stephen Smalley wrote:
> On 11/23/2016 09:17 AM, Dan Jurgens wrote:
>> @@ -177,6 +177,8 @@ static ssize_t sel_write_enforce(struct file *file, const char __user *buf,
>>  			avc_ss_reset(0);
>>  		selnl_notify_setenforce(selinux_enforcing);
>>  		selinux_status_update_setenforce(selinux_enforcing);
>> +		if (!selinux_enforcing)
>> +			call_lsm_notifier(LSM_POLICY_CHANGE, NULL);
> Why do you need this notification?  When switching from permissive to
> enforcing, you need (and already get) a notification since you may need
> to revoke previously granted permissions.  But what action do you need
> to take on switching to permissive?
MAD (management datagram) Agents cache if they are allowed to send and receive subnet management protocol (SMP) datagrams.  Without this notification they will still drop all SMPs in permissive mode if they weren't allowed in enforcing mode.  This is handled in [PATCH v6 4/9] IB/core: Enforce security on management datagrams.

--
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
diff mbox

Patch

diff --git a/drivers/infiniband/core/device.c b/drivers/infiniband/core/device.c
index 5b42e83..7b6fd06 100644
--- a/drivers/infiniband/core/device.c
+++ b/drivers/infiniband/core/device.c
@@ -39,6 +39,8 @@ 
 #include <linux/init.h>
 #include <linux/mutex.h>
 #include <linux/netdevice.h>
+#include <linux/security.h>
+#include <linux/notifier.h>
 #include <rdma/rdma_netlink.h>
 #include <rdma/ib_addr.h>
 #include <rdma/ib_cache.h>
@@ -82,6 +84,14 @@  static LIST_HEAD(client_list);
 static DEFINE_MUTEX(device_mutex);
 static DECLARE_RWSEM(lists_rwsem);
 
+static int ib_security_change(struct notifier_block *nb, unsigned long event,
+			      void *lsm_data);
+static void ib_policy_change_task(struct work_struct *work);
+static DECLARE_WORK(ib_policy_change_work, ib_policy_change_task);
+
+static struct notifier_block ibdev_lsm_nb = {
+	.notifier_call = ib_security_change,
+};
 
 static int ib_device_check_mandatory(struct ib_device *device)
 {
@@ -344,6 +354,40 @@  static int setup_port_pkey_list(struct ib_device *device)
 	return 0;
 }
 
+static void ib_policy_change_task(struct work_struct *work)
+{
+	struct ib_device *dev;
+
+	down_read(&lists_rwsem);
+	list_for_each_entry(dev, &device_list, core_list) {
+		int i;
+
+		for (i = rdma_start_port(dev); i <= rdma_end_port(dev); i++) {
+			u64 sp;
+			int ret = ib_get_cached_subnet_prefix(dev,
+							      i,
+							      &sp);
+
+			WARN_ONCE(ret,
+				  "ib_get_cached_subnet_prefix err: %d, this should never happen here\n",
+				  ret);
+			ib_security_cache_change(dev, i, sp);
+		}
+	}
+	up_read(&lists_rwsem);
+}
+
+static int ib_security_change(struct notifier_block *nb, unsigned long event,
+			      void *lsm_data)
+{
+	if (event != LSM_POLICY_CHANGE)
+		return NOTIFY_DONE;
+
+	schedule_work(&ib_policy_change_work);
+
+	return NOTIFY_OK;
+}
+
 /**
  * ib_register_device - Register an IB device with IB core
  * @device:Device to register
@@ -1075,10 +1119,18 @@  static int __init ib_core_init(void)
 		goto err_sa;
 	}
 
+	ret = register_lsm_notifier(&ibdev_lsm_nb);
+	if (ret) {
+		pr_warn("Couldn't register LSM notifier. ret %d\n", ret);
+		goto err_ibnl_clients;
+	}
+
 	ib_cache_setup();
 
 	return 0;
 
+err_ibnl_clients:
+	ib_remove_ibnl_clients();
 err_sa:
 	ib_sa_cleanup();
 err_mad:
@@ -1098,6 +1150,7 @@  static int __init ib_core_init(void)
 
 static void __exit ib_core_cleanup(void)
 {
+	unregister_lsm_notifier(&ibdev_lsm_nb);
 	ib_cache_cleanup();
 	ib_remove_ibnl_clients();
 	ib_sa_cleanup();
diff --git a/include/linux/security.h b/include/linux/security.h
index 342ca4c..0a5de0c 100644
--- a/include/linux/security.h
+++ b/include/linux/security.h
@@ -69,6 +69,10 @@  struct audit_krule;
 struct user_namespace;
 struct timezone;
 
+enum lsm_event {
+	LSM_POLICY_CHANGE,
+};
+
 /* These functions are in security/commoncap.c */
 extern int cap_capable(const struct cred *cred, struct user_namespace *ns,
 		       int cap, int audit);
@@ -161,6 +165,10 @@  struct security_mnt_opts {
 	int num_mnt_opts;
 };
 
+int call_lsm_notifier(enum lsm_event event, void *data);
+int register_lsm_notifier(struct notifier_block *nb);
+int unregister_lsm_notifier(struct notifier_block *nb);
+
 static inline void security_init_mnt_opts(struct security_mnt_opts *opts)
 {
 	opts->mnt_opts = NULL;
@@ -377,6 +385,21 @@  int security_inode_getsecctx(struct inode *inode, void **ctx, u32 *ctxlen);
 struct security_mnt_opts {
 };
 
+static inline int call_lsm_notifier(enum lsm_event event, void *data)
+{
+	return 0;
+}
+
+static inline int register_lsm_notifier(struct notifier_block *nb)
+{
+	return 0;
+}
+
+static inline  int unregister_lsm_notifier(struct notifier_block *nb)
+{
+	return 0;
+}
+
 static inline void security_init_mnt_opts(struct security_mnt_opts *opts)
 {
 }
diff --git a/security/security.c b/security/security.c
index 7d3bf2f..40326d4 100644
--- a/security/security.c
+++ b/security/security.c
@@ -33,6 +33,8 @@ 
 /* Maximum number of letters for an LSM name string */
 #define SECURITY_NAME_MAX	10
 
+static ATOMIC_NOTIFIER_HEAD(lsm_notifier_chain);
+
 /* Boot-time LSM user choice */
 static __initdata char chosen_lsm[SECURITY_NAME_MAX + 1] =
 	CONFIG_DEFAULT_SECURITY;
@@ -98,6 +100,24 @@  int __init security_module_enable(const char *module)
 	return !strcmp(module, chosen_lsm);
 }
 
+int call_lsm_notifier(enum lsm_event event, void *data)
+{
+	return atomic_notifier_call_chain(&lsm_notifier_chain, event, data);
+}
+EXPORT_SYMBOL(call_lsm_notifier);
+
+int register_lsm_notifier(struct notifier_block *nb)
+{
+	return atomic_notifier_chain_register(&lsm_notifier_chain, nb);
+}
+EXPORT_SYMBOL(register_lsm_notifier);
+
+int unregister_lsm_notifier(struct notifier_block *nb)
+{
+	return atomic_notifier_chain_unregister(&lsm_notifier_chain, nb);
+}
+EXPORT_SYMBOL(unregister_lsm_notifier);
+
 /*
  * Hook list operation macros.
  *
diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c
index 09fd610..2d7a7c1 100644
--- a/security/selinux/hooks.c
+++ b/security/selinux/hooks.c
@@ -170,6 +170,14 @@  static int selinux_netcache_avc_callback(u32 event)
 	return 0;
 }
 
+static int selinux_lsm_notifier_avc_callback(u32 event)
+{
+	if (event == AVC_CALLBACK_RESET)
+		call_lsm_notifier(LSM_POLICY_CHANGE, NULL);
+
+	return 0;
+}
+
 /*
  * initialise the security for the init task
  */
@@ -6325,6 +6333,9 @@  static __init int selinux_init(void)
 	if (avc_add_callback(selinux_netcache_avc_callback, AVC_CALLBACK_RESET))
 		panic("SELinux: Unable to register AVC netcache callback\n");
 
+	if (avc_add_callback(selinux_lsm_notifier_avc_callback, AVC_CALLBACK_RESET))
+		panic("SELinux: Unable to register AVC LSM notifier callback\n");
+
 	if (selinux_enforcing)
 		printk(KERN_DEBUG "SELinux:  Starting in enforcing mode\n");
 	else
diff --git a/security/selinux/selinuxfs.c b/security/selinux/selinuxfs.c
index 72c145d..d3f9192 100644
--- a/security/selinux/selinuxfs.c
+++ b/security/selinux/selinuxfs.c
@@ -177,6 +177,8 @@  static ssize_t sel_write_enforce(struct file *file, const char __user *buf,
 			avc_ss_reset(0);
 		selnl_notify_setenforce(selinux_enforcing);
 		selinux_status_update_setenforce(selinux_enforcing);
+		if (!selinux_enforcing)
+			call_lsm_notifier(LSM_POLICY_CHANGE, NULL);
 	}
 	length = count;
 out: