From 883c71feaf2e810e0331cf780c738cbb09e93b58 Mon Sep 17 00:00:00 2001 From: Daniel Jurgens Date: Fri, 19 May 2017 15:48:51 +0300 Subject: IB/core: IB cache enhancements to support Infiniband security Cache the subnet prefix and add a function to access it. Enforcing security requires frequent queries of the subnet prefix and the pkeys in the pkey table. Signed-off-by: Daniel Jurgens Reviewed-by: Eli Cohen Reviewed-by: Leon Romanovsky Reviewed-by: James Morris Acked-by: Doug Ledford Signed-off-by: Paul Moore --- include/rdma/ib_verbs.h | 1 + 1 file changed, 1 insertion(+) (limited to 'include') diff --git a/include/rdma/ib_verbs.h b/include/rdma/ib_verbs.h index ba8314ec5768..9dc4e7e0aba4 100644 --- a/include/rdma/ib_verbs.h +++ b/include/rdma/ib_verbs.h @@ -1891,6 +1891,7 @@ enum ib_mad_result { }; struct ib_port_cache { + u64 subnet_prefix; struct ib_pkey_cache *pkey; struct ib_gid_table *gid; u8 lmc; -- cgit v1.2.3 From d291f1a6523292d916fe1659c67f6db061fbd1b5 Mon Sep 17 00:00:00 2001 From: Daniel Jurgens Date: Fri, 19 May 2017 15:48:52 +0300 Subject: IB/core: Enforce PKey security on QPs Add new LSM hooks to allocate and free security contexts and check for permission to access a PKey. Allocate and free a security context when creating and destroying a QP. This context is used for controlling access to PKeys. When a request is made to modify a QP that changes the port, PKey index, or alternate path, check that the QP has permission for the PKey in the PKey table index on the subnet prefix of the port. If the QP is shared make sure all handles to the QP also have access. Store which port and PKey index a QP is using. After the reset to init transition the user can modify the port, PKey index and alternate path independently. So port and PKey settings changes can be a merge of the previous settings and the new ones. In order to maintain access control if there are PKey table or subnet prefix change keep a list of all QPs are using each PKey index on each port. If a change occurs all QPs using that device and port must have access enforced for the new cache settings. These changes add a transaction to the QP modify process. Association with the old port and PKey index must be maintained if the modify fails, and must be removed if it succeeds. Association with the new port and PKey index must be established prior to the modify and removed if the modify fails. 1. When a QP is modified to a particular Port, PKey index or alternate path insert that QP into the appropriate lists. 2. Check permission to access the new settings. 3. If step 2 grants access attempt to modify the QP. 4a. If steps 2 and 3 succeed remove any prior associations. 4b. If ether fails remove the new setting associations. If a PKey table or subnet prefix changes walk the list of QPs and check that they have permission. If not send the QP to the error state and raise a fatal error event. If it's a shared QP make sure all the QPs that share the real_qp have permission as well. If the QP that owns a security structure is denied access the security structure is marked as such and the QP is added to an error_list. Once the moving the QP to error is complete the security structure mark is cleared. Maintaining the lists correctly turns QP destroy into a transaction. The hardware driver for the device frees the ib_qp structure, so while the destroy is in progress the ib_qp pointer in the ib_qp_security struct is undefined. When the destroy process begins the ib_qp_security structure is marked as destroying. This prevents any action from being taken on the QP pointer. After the QP is destroyed successfully it could still listed on an error_list wait for it to be processed by that flow before cleaning up the structure. If the destroy fails the QPs port and PKey settings are reinserted into the appropriate lists, the destroying flag is cleared, and access control is enforced, in case there were any cache changes during the destroy flow. To keep the security changes isolated a new file is used to hold security related functionality. Signed-off-by: Daniel Jurgens Acked-by: Doug Ledford [PM: merge fixup in ib_verbs.h and uverbs_cmd.c] Signed-off-by: Paul Moore --- drivers/infiniband/core/Makefile | 3 +- drivers/infiniband/core/cache.c | 21 +- drivers/infiniband/core/core_priv.h | 77 +++++ drivers/infiniband/core/device.c | 33 ++ drivers/infiniband/core/security.c | 613 +++++++++++++++++++++++++++++++++++ drivers/infiniband/core/uverbs_cmd.c | 15 +- drivers/infiniband/core/verbs.c | 27 +- include/linux/lsm_hooks.h | 27 ++ include/linux/security.h | 21 ++ include/rdma/ib_verbs.h | 48 +++ security/Kconfig | 9 + security/security.c | 22 ++ 12 files changed, 907 insertions(+), 9 deletions(-) create mode 100644 drivers/infiniband/core/security.c (limited to 'include') diff --git a/drivers/infiniband/core/Makefile b/drivers/infiniband/core/Makefile index 6ebd9ad95010..e3cdafff8ece 100644 --- a/drivers/infiniband/core/Makefile +++ b/drivers/infiniband/core/Makefile @@ -10,7 +10,8 @@ obj-$(CONFIG_INFINIBAND_USER_ACCESS) += ib_uverbs.o ib_ucm.o \ ib_core-y := packer.o ud_header.o verbs.o cq.o rw.o sysfs.o \ device.o fmr_pool.o cache.o netlink.o \ roce_gid_mgmt.o mr_pool.o addr.o sa_query.o \ - multicast.o mad.o smi.o agent.o mad_rmpp.o + multicast.o mad.o smi.o agent.o mad_rmpp.o \ + security.o ib_core-$(CONFIG_INFINIBAND_USER_MEM) += umem.o ib_core-$(CONFIG_INFINIBAND_ON_DEMAND_PAGING) += umem_odp.o umem_rbtree.o ib_core-$(CONFIG_CGROUP_RDMA) += cgroup.o diff --git a/drivers/infiniband/core/cache.c b/drivers/infiniband/core/cache.c index b9c0066b704e..efc94304dee3 100644 --- a/drivers/infiniband/core/cache.c +++ b/drivers/infiniband/core/cache.c @@ -53,6 +53,7 @@ struct ib_update_work { struct work_struct work; struct ib_device *device; u8 port_num; + bool enforce_security; }; union ib_gid zgid; @@ -1042,7 +1043,8 @@ int ib_get_cached_port_state(struct ib_device *device, EXPORT_SYMBOL(ib_get_cached_port_state); static void ib_cache_update(struct ib_device *device, - u8 port) + u8 port, + bool enforce_security) { struct ib_port_attr *tprops = NULL; struct ib_pkey_cache *pkey_cache = NULL, *old_pkey_cache; @@ -1132,6 +1134,11 @@ static void ib_cache_update(struct ib_device *device, tprops->subnet_prefix; write_unlock_irq(&device->cache.lock); + if (enforce_security) + ib_security_cache_change(device, + port, + tprops->subnet_prefix); + kfree(gid_cache); kfree(old_pkey_cache); kfree(tprops); @@ -1148,7 +1155,9 @@ static void ib_cache_task(struct work_struct *_work) struct ib_update_work *work = container_of(_work, struct ib_update_work, work); - ib_cache_update(work->device, work->port_num); + ib_cache_update(work->device, + work->port_num, + work->enforce_security); kfree(work); } @@ -1169,6 +1178,12 @@ static void ib_cache_event(struct ib_event_handler *handler, INIT_WORK(&work->work, ib_cache_task); work->device = event->device; work->port_num = event->element.port_num; + if (event->event == IB_EVENT_PKEY_CHANGE || + event->event == IB_EVENT_GID_CHANGE) + work->enforce_security = true; + else + work->enforce_security = false; + queue_work(ib_wq, &work->work); } } @@ -1194,7 +1209,7 @@ int ib_cache_setup_one(struct ib_device *device) goto out; for (p = 0; p <= rdma_end_port(device) - rdma_start_port(device); ++p) - ib_cache_update(device, p + rdma_start_port(device)); + ib_cache_update(device, p + rdma_start_port(device), true); INIT_IB_EVENT_HANDLER(&device->cache.event_handler, device, ib_cache_event); diff --git a/drivers/infiniband/core/core_priv.h b/drivers/infiniband/core/core_priv.h index 249e3ed90365..7b63215f80c2 100644 --- a/drivers/infiniband/core/core_priv.h +++ b/drivers/infiniband/core/core_priv.h @@ -39,6 +39,14 @@ #include +struct pkey_index_qp_list { + struct list_head pkey_index_list; + u16 pkey_index; + /* Lock to hold while iterating the qp_list. */ + spinlock_t qp_list_lock; + struct list_head qp_list; +}; + #if IS_ENABLED(CONFIG_INFINIBAND_ADDR_TRANS_CONFIGFS) int cma_configfs_init(void); void cma_configfs_exit(void); @@ -179,4 +187,73 @@ int ib_nl_handle_ip_res_resp(struct sk_buff *skb, int ib_get_cached_subnet_prefix(struct ib_device *device, u8 port_num, u64 *sn_pfx); + +#ifdef CONFIG_SECURITY_INFINIBAND +void ib_security_destroy_port_pkey_list(struct ib_device *device); + +void ib_security_cache_change(struct ib_device *device, + u8 port_num, + u64 subnet_prefix); + +int ib_security_modify_qp(struct ib_qp *qp, + struct ib_qp_attr *qp_attr, + int qp_attr_mask, + struct ib_udata *udata); + +int ib_create_qp_security(struct ib_qp *qp, struct ib_device *dev); +void ib_destroy_qp_security_begin(struct ib_qp_security *sec); +void ib_destroy_qp_security_abort(struct ib_qp_security *sec); +void ib_destroy_qp_security_end(struct ib_qp_security *sec); +int ib_open_shared_qp_security(struct ib_qp *qp, struct ib_device *dev); +void ib_close_shared_qp_security(struct ib_qp_security *sec); +#else +static inline void ib_security_destroy_port_pkey_list(struct ib_device *device) +{ +} + +static inline void ib_security_cache_change(struct ib_device *device, + u8 port_num, + u64 subnet_prefix) +{ +} + +static inline int ib_security_modify_qp(struct ib_qp *qp, + struct ib_qp_attr *qp_attr, + int qp_attr_mask, + struct ib_udata *udata) +{ + return qp->device->modify_qp(qp->real_qp, + qp_attr, + qp_attr_mask, + udata); +} + +static inline int ib_create_qp_security(struct ib_qp *qp, + struct ib_device *dev) +{ + return 0; +} + +static inline void ib_destroy_qp_security_begin(struct ib_qp_security *sec) +{ +} + +static inline void ib_destroy_qp_security_abort(struct ib_qp_security *sec) +{ +} + +static inline void ib_destroy_qp_security_end(struct ib_qp_security *sec) +{ +} + +static inline int ib_open_shared_qp_security(struct ib_qp *qp, + struct ib_device *dev) +{ + return 0; +} + +static inline void ib_close_shared_qp_security(struct ib_qp_security *sec) +{ +} +#endif #endif /* _CORE_PRIV_H */ diff --git a/drivers/infiniband/core/device.c b/drivers/infiniband/core/device.c index 81d447da0048..96e730cc9b81 100644 --- a/drivers/infiniband/core/device.c +++ b/drivers/infiniband/core/device.c @@ -325,6 +325,30 @@ void ib_get_device_fw_str(struct ib_device *dev, char *str, size_t str_len) } EXPORT_SYMBOL(ib_get_device_fw_str); +static int setup_port_pkey_list(struct ib_device *device) +{ + int i; + + /** + * device->port_pkey_list is indexed directly by the port number, + * Therefore it is declared as a 1 based array with potential empty + * slots at the beginning. + */ + device->port_pkey_list = kcalloc(rdma_end_port(device) + 1, + sizeof(*device->port_pkey_list), + GFP_KERNEL); + + if (!device->port_pkey_list) + return -ENOMEM; + + for (i = 0; i < (rdma_end_port(device) + 1); i++) { + spin_lock_init(&device->port_pkey_list[i].list_lock); + INIT_LIST_HEAD(&device->port_pkey_list[i].pkey_list); + } + + return 0; +} + /** * ib_register_device - Register an IB device with IB core * @device:Device to register @@ -385,6 +409,12 @@ int ib_register_device(struct ib_device *device, goto out; } + ret = setup_port_pkey_list(device); + if (ret) { + pr_warn("Couldn't create per port_pkey_list\n"); + goto out; + } + ret = ib_cache_setup_one(device); if (ret) { pr_warn("Couldn't set up InfiniBand P_Key/GID cache\n"); @@ -468,6 +498,9 @@ void ib_unregister_device(struct ib_device *device) ib_device_unregister_sysfs(device); ib_cache_cleanup_one(device); + ib_security_destroy_port_pkey_list(device); + kfree(device->port_pkey_list); + down_write(&lists_rwsem); spin_lock_irqsave(&device->client_data_lock, flags); list_for_each_entry_safe(context, tmp, &device->client_data_list, list) diff --git a/drivers/infiniband/core/security.c b/drivers/infiniband/core/security.c new file mode 100644 index 000000000000..b2f170ddc062 --- /dev/null +++ b/drivers/infiniband/core/security.c @@ -0,0 +1,613 @@ +/* + * Copyright (c) 2016 Mellanox Technologies Ltd. All rights reserved. + * + * This software is available to you under a choice of one of two + * licenses. You may choose to be licensed under the terms of the GNU + * General Public License (GPL) Version 2, available from the file + * COPYING in the main directory of this source tree, or the + * OpenIB.org BSD license below: + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above + * copyright notice, this list of conditions and the following + * disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifdef CONFIG_SECURITY_INFINIBAND + +#include +#include +#include + +#include +#include +#include "core_priv.h" + +static struct pkey_index_qp_list *get_pkey_idx_qp_list(struct ib_port_pkey *pp) +{ + struct pkey_index_qp_list *pkey = NULL; + struct pkey_index_qp_list *tmp_pkey; + struct ib_device *dev = pp->sec->dev; + + spin_lock(&dev->port_pkey_list[pp->port_num].list_lock); + list_for_each_entry(tmp_pkey, + &dev->port_pkey_list[pp->port_num].pkey_list, + pkey_index_list) { + if (tmp_pkey->pkey_index == pp->pkey_index) { + pkey = tmp_pkey; + break; + } + } + spin_unlock(&dev->port_pkey_list[pp->port_num].list_lock); + return pkey; +} + +static int get_pkey_and_subnet_prefix(struct ib_port_pkey *pp, + u16 *pkey, + u64 *subnet_prefix) +{ + struct ib_device *dev = pp->sec->dev; + int ret; + + ret = ib_get_cached_pkey(dev, pp->port_num, pp->pkey_index, pkey); + if (ret) + return ret; + + ret = ib_get_cached_subnet_prefix(dev, pp->port_num, subnet_prefix); + + return ret; +} + +static int enforce_qp_pkey_security(u16 pkey, + u64 subnet_prefix, + struct ib_qp_security *qp_sec) +{ + struct ib_qp_security *shared_qp_sec; + int ret; + + ret = security_ib_pkey_access(qp_sec->security, subnet_prefix, pkey); + if (ret) + return ret; + + if (qp_sec->qp == qp_sec->qp->real_qp) { + list_for_each_entry(shared_qp_sec, + &qp_sec->shared_qp_list, + shared_qp_list) { + ret = security_ib_pkey_access(shared_qp_sec->security, + subnet_prefix, + pkey); + if (ret) + return ret; + } + } + return 0; +} + +/* The caller of this function must hold the QP security + * mutex of the QP of the security structure in *pps. + * + * It takes separate ports_pkeys and security structure + * because in some cases the pps will be for a new settings + * or the pps will be for the real QP and security structure + * will be for a shared QP. + */ +static int check_qp_port_pkey_settings(struct ib_ports_pkeys *pps, + struct ib_qp_security *sec) +{ + u64 subnet_prefix; + u16 pkey; + int ret = 0; + + if (!pps) + return 0; + + if (pps->main.state != IB_PORT_PKEY_NOT_VALID) { + get_pkey_and_subnet_prefix(&pps->main, + &pkey, + &subnet_prefix); + + ret = enforce_qp_pkey_security(pkey, + subnet_prefix, + sec); + } + if (ret) + return ret; + + if (pps->alt.state != IB_PORT_PKEY_NOT_VALID) { + get_pkey_and_subnet_prefix(&pps->alt, + &pkey, + &subnet_prefix); + + ret = enforce_qp_pkey_security(pkey, + subnet_prefix, + sec); + } + + return ret; +} + +/* The caller of this function must hold the QP security + * mutex. + */ +static void qp_to_error(struct ib_qp_security *sec) +{ + struct ib_qp_security *shared_qp_sec; + struct ib_qp_attr attr = { + .qp_state = IB_QPS_ERR + }; + struct ib_event event = { + .event = IB_EVENT_QP_FATAL + }; + + /* If the QP is in the process of being destroyed + * the qp pointer in the security structure is + * undefined. It cannot be modified now. + */ + if (sec->destroying) + return; + + ib_modify_qp(sec->qp, + &attr, + IB_QP_STATE); + + if (sec->qp->event_handler && sec->qp->qp_context) { + event.element.qp = sec->qp; + sec->qp->event_handler(&event, + sec->qp->qp_context); + } + + list_for_each_entry(shared_qp_sec, + &sec->shared_qp_list, + shared_qp_list) { + struct ib_qp *qp = shared_qp_sec->qp; + + if (qp->event_handler && qp->qp_context) { + event.element.qp = qp; + event.device = qp->device; + qp->event_handler(&event, + qp->qp_context); + } + } +} + +static inline void check_pkey_qps(struct pkey_index_qp_list *pkey, + struct ib_device *device, + u8 port_num, + u64 subnet_prefix) +{ + struct ib_port_pkey *pp, *tmp_pp; + bool comp; + LIST_HEAD(to_error_list); + u16 pkey_val; + + if (!ib_get_cached_pkey(device, + port_num, + pkey->pkey_index, + &pkey_val)) { + spin_lock(&pkey->qp_list_lock); + list_for_each_entry(pp, &pkey->qp_list, qp_list) { + if (atomic_read(&pp->sec->error_list_count)) + continue; + + if (enforce_qp_pkey_security(pkey_val, + subnet_prefix, + pp->sec)) { + atomic_inc(&pp->sec->error_list_count); + list_add(&pp->to_error_list, + &to_error_list); + } + } + spin_unlock(&pkey->qp_list_lock); + } + + list_for_each_entry_safe(pp, + tmp_pp, + &to_error_list, + to_error_list) { + mutex_lock(&pp->sec->mutex); + qp_to_error(pp->sec); + list_del(&pp->to_error_list); + atomic_dec(&pp->sec->error_list_count); + comp = pp->sec->destroying; + mutex_unlock(&pp->sec->mutex); + + if (comp) + complete(&pp->sec->error_complete); + } +} + +/* The caller of this function must hold the QP security + * mutex. + */ +static int port_pkey_list_insert(struct ib_port_pkey *pp) +{ + struct pkey_index_qp_list *tmp_pkey; + struct pkey_index_qp_list *pkey; + struct ib_device *dev; + u8 port_num = pp->port_num; + int ret = 0; + + if (pp->state != IB_PORT_PKEY_VALID) + return 0; + + dev = pp->sec->dev; + + pkey = get_pkey_idx_qp_list(pp); + + if (!pkey) { + bool found = false; + + pkey = kzalloc(sizeof(*pkey), GFP_KERNEL); + if (!pkey) + return -ENOMEM; + + spin_lock(&dev->port_pkey_list[port_num].list_lock); + /* Check for the PKey again. A racing process may + * have created it. + */ + list_for_each_entry(tmp_pkey, + &dev->port_pkey_list[port_num].pkey_list, + pkey_index_list) { + if (tmp_pkey->pkey_index == pp->pkey_index) { + kfree(pkey); + pkey = tmp_pkey; + found = true; + break; + } + } + + if (!found) { + pkey->pkey_index = pp->pkey_index; + spin_lock_init(&pkey->qp_list_lock); + INIT_LIST_HEAD(&pkey->qp_list); + list_add(&pkey->pkey_index_list, + &dev->port_pkey_list[port_num].pkey_list); + } + spin_unlock(&dev->port_pkey_list[port_num].list_lock); + } + + spin_lock(&pkey->qp_list_lock); + list_add(&pp->qp_list, &pkey->qp_list); + spin_unlock(&pkey->qp_list_lock); + + pp->state = IB_PORT_PKEY_LISTED; + + return ret; +} + +/* The caller of this function must hold the QP security + * mutex. + */ +static void port_pkey_list_remove(struct ib_port_pkey *pp) +{ + struct pkey_index_qp_list *pkey; + + if (pp->state != IB_PORT_PKEY_LISTED) + return; + + pkey = get_pkey_idx_qp_list(pp); + + spin_lock(&pkey->qp_list_lock); + list_del(&pp->qp_list); + spin_unlock(&pkey->qp_list_lock); + + /* The setting may still be valid, i.e. after + * a destroy has failed for example. + */ + pp->state = IB_PORT_PKEY_VALID; +} + +static void destroy_qp_security(struct ib_qp_security *sec) +{ + security_ib_free_security(sec->security); + kfree(sec->ports_pkeys); + kfree(sec); +} + +/* The caller of this function must hold the QP security + * mutex. + */ +static struct ib_ports_pkeys *get_new_pps(const struct ib_qp *qp, + const struct ib_qp_attr *qp_attr, + int qp_attr_mask) +{ + struct ib_ports_pkeys *new_pps; + struct ib_ports_pkeys *qp_pps = qp->qp_sec->ports_pkeys; + + new_pps = kzalloc(sizeof(*new_pps), GFP_KERNEL); + if (!new_pps) + return NULL; + + if (qp_attr_mask & (IB_QP_PKEY_INDEX | IB_QP_PORT)) { + if (!qp_pps) { + new_pps->main.port_num = qp_attr->port_num; + new_pps->main.pkey_index = qp_attr->pkey_index; + } else { + new_pps->main.port_num = (qp_attr_mask & IB_QP_PORT) ? + qp_attr->port_num : + qp_pps->main.port_num; + + new_pps->main.pkey_index = + (qp_attr_mask & IB_QP_PKEY_INDEX) ? + qp_attr->pkey_index : + qp_pps->main.pkey_index; + } + new_pps->main.state = IB_PORT_PKEY_VALID; + } else if (qp_pps) { + new_pps->main.port_num = qp_pps->main.port_num; + new_pps->main.pkey_index = qp_pps->main.pkey_index; + if (qp_pps->main.state != IB_PORT_PKEY_NOT_VALID) + new_pps->main.state = IB_PORT_PKEY_VALID; + } + + if (qp_attr_mask & IB_QP_ALT_PATH) { + new_pps->alt.port_num = qp_attr->alt_port_num; + new_pps->alt.pkey_index = qp_attr->alt_pkey_index; + new_pps->alt.state = IB_PORT_PKEY_VALID; + } else if (qp_pps) { + new_pps->alt.port_num = qp_pps->alt.port_num; + new_pps->alt.pkey_index = qp_pps->alt.pkey_index; + if (qp_pps->alt.state != IB_PORT_PKEY_NOT_VALID) + new_pps->alt.state = IB_PORT_PKEY_VALID; + } + + new_pps->main.sec = qp->qp_sec; + new_pps->alt.sec = qp->qp_sec; + return new_pps; +} + +int ib_open_shared_qp_security(struct ib_qp *qp, struct ib_device *dev) +{ + struct ib_qp *real_qp = qp->real_qp; + int ret; + + ret = ib_create_qp_security(qp, dev); + + if (ret) + return ret; + + mutex_lock(&real_qp->qp_sec->mutex); + ret = check_qp_port_pkey_settings(real_qp->qp_sec->ports_pkeys, + qp->qp_sec); + + if (ret) + goto ret; + + if (qp != real_qp) + list_add(&qp->qp_sec->shared_qp_list, + &real_qp->qp_sec->shared_qp_list); +ret: + mutex_unlock(&real_qp->qp_sec->mutex); + if (ret) + destroy_qp_security(qp->qp_sec); + + return ret; +} + +void ib_close_shared_qp_security(struct ib_qp_security *sec) +{ + struct ib_qp *real_qp = sec->qp->real_qp; + + mutex_lock(&real_qp->qp_sec->mutex); + list_del(&sec->shared_qp_list); + mutex_unlock(&real_qp->qp_sec->mutex); + + destroy_qp_security(sec); +} + +int ib_create_qp_security(struct ib_qp *qp, struct ib_device *dev) +{ + int ret; + + qp->qp_sec = kzalloc(sizeof(*qp->qp_sec), GFP_KERNEL); + if (!qp->qp_sec) + return -ENOMEM; + + qp->qp_sec->qp = qp; + qp->qp_sec->dev = dev; + mutex_init(&qp->qp_sec->mutex); + INIT_LIST_HEAD(&qp->qp_sec->shared_qp_list); + atomic_set(&qp->qp_sec->error_list_count, 0); + init_completion(&qp->qp_sec->error_complete); + ret = security_ib_alloc_security(&qp->qp_sec->security); + if (ret) + kfree(qp->qp_sec); + + return ret; +} +EXPORT_SYMBOL(ib_create_qp_security); + +void ib_destroy_qp_security_begin(struct ib_qp_security *sec) +{ + mutex_lock(&sec->mutex); + + /* Remove the QP from the lists so it won't get added to + * a to_error_list during the destroy process. + */ + if (sec->ports_pkeys) { + port_pkey_list_remove(&sec->ports_pkeys->main); + port_pkey_list_remove(&sec->ports_pkeys->alt); + } + + /* If the QP is already in one or more of those lists + * the destroying flag will ensure the to error flow + * doesn't operate on an undefined QP. + */ + sec->destroying = true; + + /* Record the error list count to know how many completions + * to wait for. + */ + sec->error_comps_pending = atomic_read(&sec->error_list_count); + + mutex_unlock(&sec->mutex); +} + +void ib_destroy_qp_security_abort(struct ib_qp_security *sec) +{ + int ret; + int i; + + /* If a concurrent cache update is in progress this + * QP security could be marked for an error state + * transition. Wait for this to complete. + */ + for (i = 0; i < sec->error_comps_pending; i++) + wait_for_completion(&sec->error_complete); + + mutex_lock(&sec->mutex); + sec->destroying = false; + + /* Restore the position in the lists and verify + * access is still allowed in case a cache update + * occurred while attempting to destroy. + * + * Because these setting were listed already + * and removed during ib_destroy_qp_security_begin + * we know the pkey_index_qp_list for the PKey + * already exists so port_pkey_list_insert won't fail. + */ + if (sec->ports_pkeys) { + port_pkey_list_insert(&sec->ports_pkeys->main); + port_pkey_list_insert(&sec->ports_pkeys->alt); + } + + ret = check_qp_port_pkey_settings(sec->ports_pkeys, sec); + if (ret) + qp_to_error(sec); + + mutex_unlock(&sec->mutex); +} + +void ib_destroy_qp_security_end(struct ib_qp_security *sec) +{ + int i; + + /* If a concurrent cache update is occurring we must + * wait until this QP security structure is processed + * in the QP to error flow before destroying it because + * the to_error_list is in use. + */ + for (i = 0; i < sec->error_comps_pending; i++) + wait_for_completion(&sec->error_complete); + + destroy_qp_security(sec); +} + +void ib_security_cache_change(struct ib_device *device, + u8 port_num, + u64 subnet_prefix) +{ + struct pkey_index_qp_list *pkey; + + list_for_each_entry(pkey, + &device->port_pkey_list[port_num].pkey_list, + pkey_index_list) { + check_pkey_qps(pkey, + device, + port_num, + subnet_prefix); + } +} + +void ib_security_destroy_port_pkey_list(struct ib_device *device) +{ + struct pkey_index_qp_list *pkey, *tmp_pkey; + int i; + + for (i = rdma_start_port(device); i <= rdma_end_port(device); i++) { + spin_lock(&device->port_pkey_list[i].list_lock); + list_for_each_entry_safe(pkey, + tmp_pkey, + &device->port_pkey_list[i].pkey_list, + pkey_index_list) { + list_del(&pkey->pkey_index_list); + kfree(pkey); + } + spin_unlock(&device->port_pkey_list[i].list_lock); + } +} + +int ib_security_modify_qp(struct ib_qp *qp, + struct ib_qp_attr *qp_attr, + int qp_attr_mask, + struct ib_udata *udata) +{ + int ret = 0; + struct ib_ports_pkeys *tmp_pps; + struct ib_ports_pkeys *new_pps; + bool special_qp = (qp->qp_type == IB_QPT_SMI || + qp->qp_type == IB_QPT_GSI || + qp->qp_type >= IB_QPT_RESERVED1); + bool pps_change = ((qp_attr_mask & (IB_QP_PKEY_INDEX | IB_QP_PORT)) || + (qp_attr_mask & IB_QP_ALT_PATH)); + + if (pps_change && !special_qp) { + mutex_lock(&qp->qp_sec->mutex); + new_pps = get_new_pps(qp, + qp_attr, + qp_attr_mask); + + /* Add this QP to the lists for the new port + * and pkey settings before checking for permission + * in case there is a concurrent cache update + * occurring. Walking the list for a cache change + * doesn't acquire the security mutex unless it's + * sending the QP to error. + */ + ret = port_pkey_list_insert(&new_pps->main); + + if (!ret) + ret = port_pkey_list_insert(&new_pps->alt); + + if (!ret) + ret = check_qp_port_pkey_settings(new_pps, + qp->qp_sec); + } + + if (!ret) + ret = qp->device->modify_qp(qp->real_qp, + qp_attr, + qp_attr_mask, + udata); + + if (pps_change && !special_qp) { + /* Clean up the lists and free the appropriate + * ports_pkeys structure. + */ + if (ret) { + tmp_pps = new_pps; + } else { + tmp_pps = qp->qp_sec->ports_pkeys; + qp->qp_sec->ports_pkeys = new_pps; + } + + if (tmp_pps) { + port_pkey_list_remove(&tmp_pps->main); + port_pkey_list_remove(&tmp_pps->alt); + } + kfree(tmp_pps); + mutex_unlock(&qp->qp_sec->mutex); + } + return ret; +} +EXPORT_SYMBOL(ib_security_modify_qp); + +#endif /* CONFIG_SECURITY_INFINIBAND */ diff --git a/drivers/infiniband/core/uverbs_cmd.c b/drivers/infiniband/core/uverbs_cmd.c index 70b7fb156414..0ad3b05405d8 100644 --- a/drivers/infiniband/core/uverbs_cmd.c +++ b/drivers/infiniband/core/uverbs_cmd.c @@ -1508,6 +1508,10 @@ static int create_qp(struct ib_uverbs_file *file, } if (cmd->qp_type != IB_QPT_XRC_TGT) { + ret = ib_create_qp_security(qp, device); + if (ret) + goto err_cb; + qp->real_qp = qp; qp->device = device; qp->pd = pd; @@ -2002,14 +2006,17 @@ static int modify_qp(struct ib_uverbs_file *file, if (ret) goto release_qp; } - ret = qp->device->modify_qp(qp, attr, + ret = ib_security_modify_qp(qp, + attr, modify_qp_mask(qp->qp_type, cmd->base.attr_mask), udata); } else { - ret = ib_modify_qp(qp, attr, - modify_qp_mask(qp->qp_type, - cmd->base.attr_mask)); + ret = ib_security_modify_qp(qp, + attr, + modify_qp_mask(qp->qp_type, + cmd->base.attr_mask), + NULL); } release_qp: diff --git a/drivers/infiniband/core/verbs.c b/drivers/infiniband/core/verbs.c index 4792f5209ac2..c973a83c898b 100644 --- a/drivers/infiniband/core/verbs.c +++ b/drivers/infiniband/core/verbs.c @@ -44,6 +44,7 @@ #include #include #include +#include #include #include @@ -713,11 +714,19 @@ static struct ib_qp *__ib_open_qp(struct ib_qp *real_qp, { struct ib_qp *qp; unsigned long flags; + int err; qp = kzalloc(sizeof *qp, GFP_KERNEL); if (!qp) return ERR_PTR(-ENOMEM); + qp->real_qp = real_qp; + err = ib_open_shared_qp_security(qp, real_qp->device); + if (err) { + kfree(qp); + return ERR_PTR(err); + } + qp->real_qp = real_qp; atomic_inc(&real_qp->usecnt); qp->device = real_qp->device; @@ -804,6 +813,12 @@ struct ib_qp *ib_create_qp(struct ib_pd *pd, if (IS_ERR(qp)) return qp; + ret = ib_create_qp_security(qp, device); + if (ret) { + ib_destroy_qp(qp); + return ERR_PTR(ret); + } + qp->device = device; qp->real_qp = qp; qp->uobject = NULL; @@ -1266,7 +1281,7 @@ int ib_modify_qp(struct ib_qp *qp, return ret; } - return qp->device->modify_qp(qp->real_qp, qp_attr, qp_attr_mask, NULL); + return ib_security_modify_qp(qp->real_qp, qp_attr, qp_attr_mask, NULL); } EXPORT_SYMBOL(ib_modify_qp); @@ -1295,6 +1310,7 @@ int ib_close_qp(struct ib_qp *qp) spin_unlock_irqrestore(&real_qp->device->event_handler_lock, flags); atomic_dec(&real_qp->usecnt); + ib_close_shared_qp_security(qp->qp_sec); kfree(qp); return 0; @@ -1335,6 +1351,7 @@ int ib_destroy_qp(struct ib_qp *qp) struct ib_cq *scq, *rcq; struct ib_srq *srq; struct ib_rwq_ind_table *ind_tbl; + struct ib_qp_security *sec; int ret; WARN_ON_ONCE(qp->mrs_used > 0); @@ -1350,6 +1367,9 @@ int ib_destroy_qp(struct ib_qp *qp) rcq = qp->recv_cq; srq = qp->srq; ind_tbl = qp->rwq_ind_tbl; + sec = qp->qp_sec; + if (sec) + ib_destroy_qp_security_begin(sec); if (!qp->uobject) rdma_rw_cleanup_mrs(qp); @@ -1366,6 +1386,11 @@ int ib_destroy_qp(struct ib_qp *qp) atomic_dec(&srq->usecnt); if (ind_tbl) atomic_dec(&ind_tbl->usecnt); + if (sec) + ib_destroy_qp_security_end(sec); + } else { + if (sec) + ib_destroy_qp_security_abort(sec); } return ret; diff --git a/include/linux/lsm_hooks.h b/include/linux/lsm_hooks.h index 080f34e66017..6d9f41fffda7 100644 --- a/include/linux/lsm_hooks.h +++ b/include/linux/lsm_hooks.h @@ -8,6 +8,7 @@ * Copyright (C) 2001 Silicon Graphics, Inc. (Trust Technology Group) * Copyright (C) 2015 Intel Corporation. * Copyright (C) 2015 Casey Schaufler + * Copyright (C) 2016 Mellanox Techonologies * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -911,6 +912,21 @@ * associated with the TUN device's security structure. * @security pointer to the TUN devices's security structure. * + * Security hooks for Infiniband + * + * @ib_pkey_access: + * Check permission to access a pkey when modifing a QP. + * @subnet_prefix the subnet prefix of the port being used. + * @pkey the pkey to be accessed. + * @sec pointer to a security structure. + * @ib_alloc_security: + * Allocate a security structure for Infiniband objects. + * @sec pointer to a security structure pointer. + * Returns 0 on success, non-zero on failure + * @ib_free_security: + * Deallocate an Infiniband security structure. + * @sec contains the security structure to be freed. + * * Security hooks for XFRM operations. * * @xfrm_policy_alloc_security: @@ -1620,6 +1636,12 @@ union security_list_options { int (*tun_dev_open)(void *security); #endif /* CONFIG_SECURITY_NETWORK */ +#ifdef CONFIG_SECURITY_INFINIBAND + int (*ib_pkey_access)(void *sec, u64 subnet_prefix, u16 pkey); + int (*ib_alloc_security)(void **sec); + void (*ib_free_security)(void *sec); +#endif /* CONFIG_SECURITY_INFINIBAND */ + #ifdef CONFIG_SECURITY_NETWORK_XFRM int (*xfrm_policy_alloc_security)(struct xfrm_sec_ctx **ctxp, struct xfrm_user_sec_ctx *sec_ctx, @@ -1851,6 +1873,11 @@ struct security_hook_heads { struct list_head tun_dev_attach; struct list_head tun_dev_open; #endif /* CONFIG_SECURITY_NETWORK */ +#ifdef CONFIG_SECURITY_INFINIBAND + struct list_head ib_pkey_access; + struct list_head ib_alloc_security; + struct list_head ib_free_security; +#endif /* CONFIG_SECURITY_INFINIBAND */ #ifdef CONFIG_SECURITY_NETWORK_XFRM struct list_head xfrm_policy_alloc_security; struct list_head xfrm_policy_clone_security; diff --git a/include/linux/security.h b/include/linux/security.h index af675b576645..8c73ee073bab 100644 --- a/include/linux/security.h +++ b/include/linux/security.h @@ -6,6 +6,7 @@ * Copyright (C) 2001 Networks Associates Technology, Inc * Copyright (C) 2001 James Morris * Copyright (C) 2001 Silicon Graphics, Inc. (Trust Technology Group) + * Copyright (C) 2016 Mellanox Techonologies * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -1406,6 +1407,26 @@ static inline int security_tun_dev_open(void *security) } #endif /* CONFIG_SECURITY_NETWORK */ +#ifdef CONFIG_SECURITY_INFINIBAND +int security_ib_pkey_access(void *sec, u64 subnet_prefix, u16 pkey); +int security_ib_alloc_security(void **sec); +void security_ib_free_security(void *sec); +#else /* CONFIG_SECURITY_INFINIBAND */ +static inline int security_ib_pkey_access(void *sec, u64 subnet_prefix, u16 pkey) +{ + return 0; +} + +static inline int security_ib_alloc_security(void **sec) +{ + return 0; +} + +static inline void security_ib_free_security(void *sec) +{ +} +#endif /* CONFIG_SECURITY_INFINIBAND */ + #ifdef CONFIG_SECURITY_NETWORK_XFRM int security_xfrm_policy_alloc(struct xfrm_sec_ctx **ctxp, diff --git a/include/rdma/ib_verbs.h b/include/rdma/ib_verbs.h index 9dc4e7e0aba4..0e480a5630d4 100644 --- a/include/rdma/ib_verbs.h +++ b/include/rdma/ib_verbs.h @@ -1614,6 +1614,45 @@ struct ib_rwq_ind_table_init_attr { struct ib_wq **ind_tbl; }; +enum port_pkey_state { + IB_PORT_PKEY_NOT_VALID = 0, + IB_PORT_PKEY_VALID = 1, + IB_PORT_PKEY_LISTED = 2, +}; + +struct ib_qp_security; + +struct ib_port_pkey { + enum port_pkey_state state; + u16 pkey_index; + u8 port_num; + struct list_head qp_list; + struct list_head to_error_list; + struct ib_qp_security *sec; +}; + +struct ib_ports_pkeys { + struct ib_port_pkey main; + struct ib_port_pkey alt; +}; + +struct ib_qp_security { + struct ib_qp *qp; + struct ib_device *dev; + /* Hold this mutex when changing port and pkey settings. */ + struct mutex mutex; + struct ib_ports_pkeys *ports_pkeys; + /* A list of all open shared QP handles. Required to enforce security + * properly for all users of a shared QP. + */ + struct list_head shared_qp_list; + void *security; + bool destroying; + atomic_t error_list_count; + struct completion error_complete; + int error_comps_pending; +}; + /* * @max_write_sge: Maximum SGE elements per RDMA WRITE request. * @max_read_sge: Maximum SGE elements per RDMA READ request. @@ -1643,6 +1682,7 @@ struct ib_qp { u32 max_read_sge; enum ib_qp_type qp_type; struct ib_rwq_ind_table *rwq_ind_tbl; + struct ib_qp_security *qp_sec; }; struct ib_mr { @@ -1941,6 +1981,12 @@ struct rdma_netdev { union ib_gid *gid, u16 mlid); }; +struct ib_port_pkey_list { + /* Lock to hold while modifying the list. */ + spinlock_t list_lock; + struct list_head pkey_list; +}; + struct ib_device { /* Do not access @dma_device directly from ULP nor from HW drivers. */ struct device *dma_device; @@ -1964,6 +2010,8 @@ struct ib_device { int num_comp_vectors; + struct ib_port_pkey_list *port_pkey_list; + struct iw_cm_verbs *iwcm; /** diff --git a/security/Kconfig b/security/Kconfig index bdcbb92927ab..d540bfe73190 100644 --- a/security/Kconfig +++ b/security/Kconfig @@ -54,6 +54,15 @@ config SECURITY_NETWORK implement socket and networking access controls. If you are unsure how to answer this question, answer N. +config SECURITY_INFINIBAND + bool "Infiniband Security Hooks" + depends on SECURITY && INFINIBAND + help + This enables the Infiniband security hooks. + If enabled, a security module can use these hooks to + implement Infiniband access controls. + If you are unsure how to answer this question, answer N. + config SECURITY_NETWORK_XFRM bool "XFRM (IPSec) Networking Security Hooks" depends on XFRM && SECURITY_NETWORK diff --git a/security/security.c b/security/security.c index 38316bb28b16..2a9d1a7fa1f8 100644 --- a/security/security.c +++ b/security/security.c @@ -4,6 +4,7 @@ * Copyright (C) 2001 WireX Communications, Inc * Copyright (C) 2001-2002 Greg Kroah-Hartman * Copyright (C) 2001 Networks Associates Technology, Inc + * Copyright (C) 2016 Mellanox Technologies * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -1515,6 +1516,27 @@ EXPORT_SYMBOL(security_tun_dev_open); #endif /* CONFIG_SECURITY_NETWORK */ +#ifdef CONFIG_SECURITY_INFINIBAND + +int security_ib_pkey_access(void *sec, u64 subnet_prefix, u16 pkey) +{ + return call_int_hook(ib_pkey_access, 0, sec, subnet_prefix, pkey); +} +EXPORT_SYMBOL(security_ib_pkey_access); + +int security_ib_alloc_security(void **sec) +{ + return call_int_hook(ib_alloc_security, 0, sec); +} +EXPORT_SYMBOL(security_ib_alloc_security); + +void security_ib_free_security(void *sec) +{ + call_void_hook(ib_free_security, sec); +} +EXPORT_SYMBOL(security_ib_free_security); +#endif /* CONFIG_SECURITY_INFINIBAND */ + #ifdef CONFIG_SECURITY_NETWORK_XFRM int security_xfrm_policy_alloc(struct xfrm_sec_ctx **ctxp, -- cgit v1.2.3 From 8f408ab64be6319cb7736cbc6982838dcc362306 Mon Sep 17 00:00:00 2001 From: Daniel Jurgens Date: Fri, 19 May 2017 15:48:53 +0300 Subject: selinux lsm IB/core: Implement LSM notification system 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 Acked-by: James Morris Acked-by: Doug Ledford Signed-off-by: Paul Moore --- 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(+) (limited to 'include') diff --git a/drivers/infiniband/core/device.c b/drivers/infiniband/core/device.c index 96e730cc9b81..631eaa9daf65 100644 --- a/drivers/infiniband/core/device.c +++ b/drivers/infiniband/core/device.c @@ -39,6 +39,8 @@ #include #include #include +#include +#include #include #include #include @@ -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) { @@ -349,6 +359,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 @@ -1115,10 +1159,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: @@ -1138,6 +1190,7 @@ err: 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 8c73ee073bab..f96e333f6042 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); @@ -164,6 +168,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; @@ -382,6 +390,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 2a9d1a7fa1f8..b59be0d6535f 100644 --- a/security/security.c +++ b/security/security.c @@ -35,6 +35,8 @@ #define SECURITY_NAME_MAX 10 struct security_hook_heads security_hook_heads __lsm_ro_after_init; +static ATOMIC_NOTIFIER_HEAD(lsm_notifier_chain); + char *lsm_names; /* Boot-time LSM user choice */ static __initdata char chosen_lsm[SECURITY_NAME_MAX + 1] = @@ -166,6 +168,24 @@ void __init security_add_hooks(struct security_hook_list *hooks, int count, panic("%s - Cannot get early memory.\n", __func__); } +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 cfb7ce339adc..b005acbcc6e9 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -171,6 +171,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 */ @@ -6387,6 +6395,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 82adb78a58f7..9010a3632d6f 100644 --- a/security/selinux/selinuxfs.c +++ b/security/selinux/selinuxfs.c @@ -154,6 +154,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: -- cgit v1.2.3 From 47a2b338fe63200d716d2e24131cdb49f17c77da Mon Sep 17 00:00:00 2001 From: Daniel Jurgens Date: Fri, 19 May 2017 15:48:54 +0300 Subject: IB/core: Enforce security on management datagrams Allocate and free a security context when creating and destroying a MAD agent. This context is used for controlling access to PKeys and sending and receiving SMPs. When sending or receiving a MAD check that the agent has permission to access the PKey for the Subnet Prefix of the port. During MAD and snoop agent registration for SMI QPs check that the calling process has permission to access the manage the subnet and register a callback with the LSM to be notified of policy changes. When notificaiton of a policy change occurs recheck permission and set a flag indicating sending and receiving SMPs is allowed. When sending and receiving MADs check that the agent has access to the SMI if it's on an SMI QP. Because security policy can change it's possible permission was allowed when creating the agent, but no longer is. Signed-off-by: Daniel Jurgens Acked-by: Doug Ledford [PM: remove the LSM hook init code] Signed-off-by: Paul Moore --- drivers/infiniband/core/core_priv.h | 35 ++++++++++++++ drivers/infiniband/core/mad.c | 52 +++++++++++++++++---- drivers/infiniband/core/security.c | 92 +++++++++++++++++++++++++++++++++++++ include/linux/lsm_hooks.h | 8 ++++ include/linux/security.h | 6 +++ include/rdma/ib_mad.h | 4 ++ security/security.c | 6 +++ 7 files changed, 195 insertions(+), 8 deletions(-) (limited to 'include') diff --git a/drivers/infiniband/core/core_priv.h b/drivers/infiniband/core/core_priv.h index 7b63215f80c2..06645272c784 100644 --- a/drivers/infiniband/core/core_priv.h +++ b/drivers/infiniband/core/core_priv.h @@ -38,6 +38,8 @@ #include #include +#include +#include "mad_priv.h" struct pkey_index_qp_list { struct list_head pkey_index_list; @@ -189,6 +191,11 @@ int ib_get_cached_subnet_prefix(struct ib_device *device, u64 *sn_pfx); #ifdef CONFIG_SECURITY_INFINIBAND +int ib_security_pkey_access(struct ib_device *dev, + u8 port_num, + u16 pkey_index, + void *sec); + void ib_security_destroy_port_pkey_list(struct ib_device *device); void ib_security_cache_change(struct ib_device *device, @@ -206,7 +213,19 @@ void ib_destroy_qp_security_abort(struct ib_qp_security *sec); void ib_destroy_qp_security_end(struct ib_qp_security *sec); int ib_open_shared_qp_security(struct ib_qp *qp, struct ib_device *dev); void ib_close_shared_qp_security(struct ib_qp_security *sec); +int ib_mad_agent_security_setup(struct ib_mad_agent *agent, + enum ib_qp_type qp_type); +void ib_mad_agent_security_cleanup(struct ib_mad_agent *agent); +int ib_mad_enforce_security(struct ib_mad_agent_private *map, u16 pkey_index); #else +static inline int ib_security_pkey_access(struct ib_device *dev, + u8 port_num, + u16 pkey_index, + void *sec) +{ + return 0; +} + static inline void ib_security_destroy_port_pkey_list(struct ib_device *device) { } @@ -255,5 +274,21 @@ static inline int ib_open_shared_qp_security(struct ib_qp *qp, static inline void ib_close_shared_qp_security(struct ib_qp_security *sec) { } + +static inline int ib_mad_agent_security_setup(struct ib_mad_agent *agent, + enum ib_qp_type qp_type) +{ + return 0; +} + +static inline void ib_mad_agent_security_cleanup(struct ib_mad_agent *agent) +{ +} + +static inline int ib_mad_enforce_security(struct ib_mad_agent_private *map, + u16 pkey_index) +{ + return 0; +} #endif #endif /* _CORE_PRIV_H */ diff --git a/drivers/infiniband/core/mad.c b/drivers/infiniband/core/mad.c index 192ee3dafb80..f8f53bb90837 100644 --- a/drivers/infiniband/core/mad.c +++ b/drivers/infiniband/core/mad.c @@ -40,9 +40,11 @@ #include #include #include +#include #include #include "mad_priv.h" +#include "core_priv.h" #include "mad_rmpp.h" #include "smi.h" #include "opa_smi.h" @@ -369,6 +371,12 @@ struct ib_mad_agent *ib_register_mad_agent(struct ib_device *device, atomic_set(&mad_agent_priv->refcount, 1); init_completion(&mad_agent_priv->comp); + ret2 = ib_mad_agent_security_setup(&mad_agent_priv->agent, qp_type); + if (ret2) { + ret = ERR_PTR(ret2); + goto error4; + } + spin_lock_irqsave(&port_priv->reg_lock, flags); mad_agent_priv->agent.hi_tid = ++ib_mad_client_id; @@ -386,7 +394,7 @@ struct ib_mad_agent *ib_register_mad_agent(struct ib_device *device, if (method) { if (method_in_use(&method, mad_reg_req)) - goto error4; + goto error5; } } ret2 = add_nonoui_reg_req(mad_reg_req, mad_agent_priv, @@ -402,14 +410,14 @@ struct ib_mad_agent *ib_register_mad_agent(struct ib_device *device, if (is_vendor_method_in_use( vendor_class, mad_reg_req)) - goto error4; + goto error5; } } ret2 = add_oui_reg_req(mad_reg_req, mad_agent_priv); } if (ret2) { ret = ERR_PTR(ret2); - goto error4; + goto error5; } } @@ -418,9 +426,10 @@ struct ib_mad_agent *ib_register_mad_agent(struct ib_device *device, spin_unlock_irqrestore(&port_priv->reg_lock, flags); return &mad_agent_priv->agent; - -error4: +error5: spin_unlock_irqrestore(&port_priv->reg_lock, flags); + ib_mad_agent_security_cleanup(&mad_agent_priv->agent); +error4: kfree(reg_req); error3: kfree(mad_agent_priv); @@ -491,6 +500,7 @@ struct ib_mad_agent *ib_register_mad_snoop(struct ib_device *device, struct ib_mad_agent *ret; struct ib_mad_snoop_private *mad_snoop_priv; int qpn; + int err; /* Validate parameters */ if ((is_snooping_sends(mad_snoop_flags) && !snoop_handler) || @@ -525,17 +535,25 @@ struct ib_mad_agent *ib_register_mad_snoop(struct ib_device *device, mad_snoop_priv->agent.port_num = port_num; mad_snoop_priv->mad_snoop_flags = mad_snoop_flags; init_completion(&mad_snoop_priv->comp); + + err = ib_mad_agent_security_setup(&mad_snoop_priv->agent, qp_type); + if (err) { + ret = ERR_PTR(err); + goto error2; + } + mad_snoop_priv->snoop_index = register_snoop_agent( &port_priv->qp_info[qpn], mad_snoop_priv); if (mad_snoop_priv->snoop_index < 0) { ret = ERR_PTR(mad_snoop_priv->snoop_index); - goto error2; + goto error3; } atomic_set(&mad_snoop_priv->refcount, 1); return &mad_snoop_priv->agent; - +error3: + ib_mad_agent_security_cleanup(&mad_snoop_priv->agent); error2: kfree(mad_snoop_priv); error1: @@ -581,6 +599,8 @@ static void unregister_mad_agent(struct ib_mad_agent_private *mad_agent_priv) deref_mad_agent(mad_agent_priv); wait_for_completion(&mad_agent_priv->comp); + ib_mad_agent_security_cleanup(&mad_agent_priv->agent); + kfree(mad_agent_priv->reg_req); kfree(mad_agent_priv); } @@ -599,6 +619,8 @@ static void unregister_mad_snoop(struct ib_mad_snoop_private *mad_snoop_priv) deref_snoop_agent(mad_snoop_priv); wait_for_completion(&mad_snoop_priv->comp); + ib_mad_agent_security_cleanup(&mad_snoop_priv->agent); + kfree(mad_snoop_priv); } @@ -1215,12 +1237,16 @@ int ib_post_send_mad(struct ib_mad_send_buf *send_buf, /* Walk list of send WRs and post each on send list */ for (; send_buf; send_buf = next_send_buf) { - mad_send_wr = container_of(send_buf, struct ib_mad_send_wr_private, send_buf); mad_agent_priv = mad_send_wr->mad_agent_priv; + ret = ib_mad_enforce_security(mad_agent_priv, + mad_send_wr->send_wr.pkey_index); + if (ret) + goto error; + if (!send_buf->mad_agent->send_handler || (send_buf->timeout_ms && !send_buf->mad_agent->recv_handler)) { @@ -1946,6 +1972,14 @@ static void ib_mad_complete_recv(struct ib_mad_agent_private *mad_agent_priv, struct ib_mad_send_wr_private *mad_send_wr; struct ib_mad_send_wc mad_send_wc; unsigned long flags; + int ret; + + ret = ib_mad_enforce_security(mad_agent_priv, + mad_recv_wc->wc->pkey_index); + if (ret) { + ib_free_recv_mad(mad_recv_wc); + deref_mad_agent(mad_agent_priv); + } INIT_LIST_HEAD(&mad_recv_wc->rmpp_list); list_add(&mad_recv_wc->recv_buf.list, &mad_recv_wc->rmpp_list); @@ -2003,6 +2037,8 @@ static void ib_mad_complete_recv(struct ib_mad_agent_private *mad_agent_priv, mad_recv_wc); deref_mad_agent(mad_agent_priv); } + + return; } static enum smi_action handle_ib_smi(const struct ib_mad_port_private *port_priv, diff --git a/drivers/infiniband/core/security.c b/drivers/infiniband/core/security.c index b2f170ddc062..3e8c38953912 100644 --- a/drivers/infiniband/core/security.c +++ b/drivers/infiniband/core/security.c @@ -39,6 +39,7 @@ #include #include #include "core_priv.h" +#include "mad_priv.h" static struct pkey_index_qp_list *get_pkey_idx_qp_list(struct ib_port_pkey *pp) { @@ -610,4 +611,95 @@ int ib_security_modify_qp(struct ib_qp *qp, } EXPORT_SYMBOL(ib_security_modify_qp); +int ib_security_pkey_access(struct ib_device *dev, + u8 port_num, + u16 pkey_index, + void *sec) +{ + u64 subnet_prefix; + u16 pkey; + int ret; + + ret = ib_get_cached_pkey(dev, port_num, pkey_index, &pkey); + if (ret) + return ret; + + ret = ib_get_cached_subnet_prefix(dev, port_num, &subnet_prefix); + + if (ret) + return ret; + + return security_ib_pkey_access(sec, subnet_prefix, pkey); +} +EXPORT_SYMBOL(ib_security_pkey_access); + +static int ib_mad_agent_security_change(struct notifier_block *nb, + unsigned long event, + void *data) +{ + struct ib_mad_agent *ag = container_of(nb, struct ib_mad_agent, lsm_nb); + + if (event != LSM_POLICY_CHANGE) + return NOTIFY_DONE; + + ag->smp_allowed = !security_ib_endport_manage_subnet(ag->security, + ag->device->name, + ag->port_num); + + return NOTIFY_OK; +} + +int ib_mad_agent_security_setup(struct ib_mad_agent *agent, + enum ib_qp_type qp_type) +{ + int ret; + + ret = security_ib_alloc_security(&agent->security); + if (ret) + return ret; + + if (qp_type != IB_QPT_SMI) + return 0; + + ret = security_ib_endport_manage_subnet(agent->security, + agent->device->name, + agent->port_num); + if (ret) + return ret; + + agent->lsm_nb.notifier_call = ib_mad_agent_security_change; + ret = register_lsm_notifier(&agent->lsm_nb); + if (ret) + return ret; + + agent->smp_allowed = true; + agent->lsm_nb_reg = true; + return 0; +} + +void ib_mad_agent_security_cleanup(struct ib_mad_agent *agent) +{ + security_ib_free_security(agent->security); + if (agent->lsm_nb_reg) + unregister_lsm_notifier(&agent->lsm_nb); +} + +int ib_mad_enforce_security(struct ib_mad_agent_private *map, u16 pkey_index) +{ + int ret; + + if (map->agent.qp->qp_type == IB_QPT_SMI && !map->agent.smp_allowed) + return -EACCES; + + ret = ib_security_pkey_access(map->agent.device, + map->agent.port_num, + pkey_index, + map->agent.security); + + if (ret) + return ret; + + return 0; +} + #endif /* CONFIG_SECURITY_INFINIBAND */ diff --git a/include/linux/lsm_hooks.h b/include/linux/lsm_hooks.h index 6d9f41fffda7..68d91e423bca 100644 --- a/include/linux/lsm_hooks.h +++ b/include/linux/lsm_hooks.h @@ -919,6 +919,11 @@ * @subnet_prefix the subnet prefix of the port being used. * @pkey the pkey to be accessed. * @sec pointer to a security structure. + * @ib_endport_manage_subnet: + * Check permissions to send and receive SMPs on a end port. + * @dev_name the IB device name (i.e. mlx4_0). + * @port_num the port number. + * @sec pointer to a security structure. * @ib_alloc_security: * Allocate a security structure for Infiniband objects. * @sec pointer to a security structure pointer. @@ -1638,6 +1643,8 @@ union security_list_options { #ifdef CONFIG_SECURITY_INFINIBAND int (*ib_pkey_access)(void *sec, u64 subnet_prefix, u16 pkey); + int (*ib_endport_manage_subnet)(void *sec, const char *dev_name, + u8 port_num); int (*ib_alloc_security)(void **sec); void (*ib_free_security)(void *sec); #endif /* CONFIG_SECURITY_INFINIBAND */ @@ -1875,6 +1882,7 @@ struct security_hook_heads { #endif /* CONFIG_SECURITY_NETWORK */ #ifdef CONFIG_SECURITY_INFINIBAND struct list_head ib_pkey_access; + struct list_head ib_endport_manage_subnet; struct list_head ib_alloc_security; struct list_head ib_free_security; #endif /* CONFIG_SECURITY_INFINIBAND */ diff --git a/include/linux/security.h b/include/linux/security.h index f96e333f6042..549cb828a888 100644 --- a/include/linux/security.h +++ b/include/linux/security.h @@ -1432,6 +1432,7 @@ static inline int security_tun_dev_open(void *security) #ifdef CONFIG_SECURITY_INFINIBAND int security_ib_pkey_access(void *sec, u64 subnet_prefix, u16 pkey); +int security_ib_endport_manage_subnet(void *sec, const char *name, u8 port_num); int security_ib_alloc_security(void **sec); void security_ib_free_security(void *sec); #else /* CONFIG_SECURITY_INFINIBAND */ @@ -1440,6 +1441,11 @@ static inline int security_ib_pkey_access(void *sec, u64 subnet_prefix, u16 pkey return 0; } +static inline int security_ib_endport_manage_subnet(void *sec, const char *dev_name, u8 port_num) +{ + return 0; +} + static inline int security_ib_alloc_security(void **sec) { return 0; diff --git a/include/rdma/ib_mad.h b/include/rdma/ib_mad.h index d67b11b72029..2f4f1768ded4 100644 --- a/include/rdma/ib_mad.h +++ b/include/rdma/ib_mad.h @@ -575,6 +575,10 @@ struct ib_mad_agent { u32 flags; u8 port_num; u8 rmpp_version; + void *security; + bool smp_allowed; + bool lsm_nb_reg; + struct notifier_block lsm_nb; }; /** diff --git a/security/security.c b/security/security.c index b59be0d6535f..714433e3e9a2 100644 --- a/security/security.c +++ b/security/security.c @@ -1544,6 +1544,12 @@ int security_ib_pkey_access(void *sec, u64 subnet_prefix, u16 pkey) } EXPORT_SYMBOL(security_ib_pkey_access); +int security_ib_endport_manage_subnet(void *sec, const char *dev_name, u8 port_num) +{ + return call_int_hook(ib_endport_manage_subnet, 0, sec, dev_name, port_num); +} +EXPORT_SYMBOL(security_ib_endport_manage_subnet); + int security_ib_alloc_security(void **sec) { return call_int_hook(ib_alloc_security, 0, sec); -- cgit v1.2.3 From cfc4d882d41780d93471066d57d4630995427b29 Mon Sep 17 00:00:00 2001 From: Daniel Jurgens Date: Fri, 19 May 2017 15:48:57 +0300 Subject: selinux: Implement Infiniband PKey "Access" access vector Add a type and access vector for PKeys. Implement the ib_pkey_access hook to check that the caller has permission to access the PKey on the given subnet prefix. Add an interface to get the PKey SID. Walk the PKey ocontexts to find an entry for the given subnet prefix and pkey. Signed-off-by: Daniel Jurgens Reviewed-by: James Morris Acked-by: Doug Ledford Signed-off-by: Paul Moore --- include/linux/lsm_audit.h | 7 +++++++ security/lsm_audit.c | 11 ++++++++++ security/selinux/hooks.c | 22 ++++++++++++++++++++ security/selinux/include/classmap.h | 2 ++ security/selinux/include/security.h | 2 ++ security/selinux/ss/services.c | 40 +++++++++++++++++++++++++++++++++++++ 6 files changed, 84 insertions(+) (limited to 'include') diff --git a/include/linux/lsm_audit.h b/include/linux/lsm_audit.h index e58e577117b6..0df5639a4ff4 100644 --- a/include/linux/lsm_audit.h +++ b/include/linux/lsm_audit.h @@ -45,6 +45,11 @@ struct lsm_ioctlop_audit { u16 cmd; }; +struct lsm_ibpkey_audit { + u64 subnet_prefix; + u16 pkey; +}; + /* Auxiliary data to use in generating the audit record. */ struct common_audit_data { char type; @@ -60,6 +65,7 @@ struct common_audit_data { #define LSM_AUDIT_DATA_DENTRY 10 #define LSM_AUDIT_DATA_IOCTL_OP 11 #define LSM_AUDIT_DATA_FILE 12 +#define LSM_AUDIT_DATA_IBPKEY 13 union { struct path path; struct dentry *dentry; @@ -77,6 +83,7 @@ struct common_audit_data { char *kmod_name; struct lsm_ioctlop_audit *op; struct file *file; + struct lsm_ibpkey_audit *ibpkey; } u; /* this union contains LSM specific data */ union { diff --git a/security/lsm_audit.c b/security/lsm_audit.c index 37f04dadc8d6..c22c99fae06a 100644 --- a/security/lsm_audit.c +++ b/security/lsm_audit.c @@ -410,6 +410,17 @@ static void dump_common_audit_data(struct audit_buffer *ab, audit_log_format(ab, " kmod="); audit_log_untrustedstring(ab, a->u.kmod_name); break; + case LSM_AUDIT_DATA_IBPKEY: { + struct in6_addr sbn_pfx; + + memset(&sbn_pfx.s6_addr, 0, + sizeof(sbn_pfx.s6_addr)); + memcpy(&sbn_pfx.s6_addr, &a->u.ibpkey->subnet_prefix, + sizeof(a->u.ibpkey->subnet_prefix)); + audit_log_format(ab, " pkey=0x%x subnet_prefix=%pI6c", + a->u.ibpkey->pkey, &sbn_pfx); + break; + } } /* switch (a->type) */ } diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index 062b459b62bf..b59255f86274 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -6148,6 +6148,27 @@ static int selinux_key_getsecurity(struct key *key, char **_buffer) #endif #ifdef CONFIG_SECURITY_INFINIBAND +static int selinux_ib_pkey_access(void *ib_sec, u64 subnet_prefix, u16 pkey_val) +{ + struct common_audit_data ad; + int err; + u32 sid = 0; + struct ib_security_struct *sec = ib_sec; + struct lsm_ibpkey_audit ibpkey; + + err = security_ib_pkey_sid(subnet_prefix, pkey_val, &sid); + if (err) + return err; + + ad.type = LSM_AUDIT_DATA_IBPKEY; + ibpkey.subnet_prefix = subnet_prefix; + ibpkey.pkey = pkey_val; + ad.u.ibpkey = &ibpkey; + return avc_has_perm(sec->sid, sid, + SECCLASS_INFINIBAND_PKEY, + INFINIBAND_PKEY__ACCESS, &ad); +} + static int selinux_ib_alloc_security(void **ib_sec) { struct ib_security_struct *sec; @@ -6352,6 +6373,7 @@ static struct security_hook_list selinux_hooks[] __lsm_ro_after_init = { LSM_HOOK_INIT(tun_dev_attach, selinux_tun_dev_attach), LSM_HOOK_INIT(tun_dev_open, selinux_tun_dev_open), #ifdef CONFIG_SECURITY_INFINIBAND + LSM_HOOK_INIT(ib_pkey_access, selinux_ib_pkey_access), LSM_HOOK_INIT(ib_alloc_security, selinux_ib_alloc_security), LSM_HOOK_INIT(ib_free_security, selinux_ib_free_security), #endif diff --git a/security/selinux/include/classmap.h b/security/selinux/include/classmap.h index 3e49a78f1f46..0fec1c505f84 100644 --- a/security/selinux/include/classmap.h +++ b/security/selinux/include/classmap.h @@ -231,6 +231,8 @@ struct security_class_mapping secclass_map[] = { { COMMON_SOCK_PERMS, NULL } }, { "smc_socket", { COMMON_SOCK_PERMS, NULL } }, + { "infiniband_pkey", + { "access", NULL } }, { NULL } }; diff --git a/security/selinux/include/security.h b/security/selinux/include/security.h index b48a462cf446..592c014e369c 100644 --- a/security/selinux/include/security.h +++ b/security/selinux/include/security.h @@ -181,6 +181,8 @@ int security_get_user_sids(u32 callsid, char *username, int security_port_sid(u8 protocol, u16 port, u32 *out_sid); +int security_ib_pkey_sid(u64 subnet_prefix, u16 pkey_num, u32 *out_sid); + int security_netif_sid(char *name, u32 *if_sid); int security_node_sid(u16 domain, void *addr, u32 addrlen, diff --git a/security/selinux/ss/services.c b/security/selinux/ss/services.c index 2dccba4851f8..02257d90adc9 100644 --- a/security/selinux/ss/services.c +++ b/security/selinux/ss/services.c @@ -2232,6 +2232,46 @@ out: return rc; } +/** + * security_pkey_sid - Obtain the SID for a pkey. + * @subnet_prefix: Subnet Prefix + * @pkey_num: pkey number + * @out_sid: security identifier + */ +int security_ib_pkey_sid(u64 subnet_prefix, u16 pkey_num, u32 *out_sid) +{ + struct ocontext *c; + int rc = 0; + + read_lock(&policy_rwlock); + + c = policydb.ocontexts[OCON_IBPKEY]; + while (c) { + if (c->u.ibpkey.low_pkey <= pkey_num && + c->u.ibpkey.high_pkey >= pkey_num && + c->u.ibpkey.subnet_prefix == subnet_prefix) + break; + + c = c->next; + } + + if (c) { + if (!c->sid[0]) { + rc = sidtab_context_to_sid(&sidtab, + &c->context[0], + &c->sid[0]); + if (rc) + goto out; + } + *out_sid = c->sid[0]; + } else + *out_sid = SECINITSID_UNLABELED; + +out: + read_unlock(&policy_rwlock); + return rc; +} + /** * security_netif_sid - Obtain the SID for a network interface. * @name: interface name -- cgit v1.2.3 From ab861dfca1652aa09b26b7aa2899feb29b33dfd9 Mon Sep 17 00:00:00 2001 From: Daniel Jurgens Date: Fri, 19 May 2017 15:48:58 +0300 Subject: selinux: Add IB Port SMP access vector Add a type for Infiniband ports and an access vector for subnet management packets. Implement the ib_port_smp hook to check that the caller has permission to send and receive SMPs on the end port specified by the device name and port. Add interface to query the SID for a IB port, which walks the IB_PORT ocontexts to find an entry for the given name and port. Signed-off-by: Daniel Jurgens Reviewed-by: James Morris Acked-by: Doug Ledford Signed-off-by: Paul Moore --- include/linux/lsm_audit.h | 8 ++++++++ security/lsm_audit.c | 5 +++++ security/selinux/hooks.c | 25 ++++++++++++++++++++++ security/selinux/include/classmap.h | 2 ++ security/selinux/include/security.h | 2 ++ security/selinux/ss/services.c | 41 +++++++++++++++++++++++++++++++++++++ 6 files changed, 83 insertions(+) (limited to 'include') diff --git a/include/linux/lsm_audit.h b/include/linux/lsm_audit.h index 0df5639a4ff4..22b5d4e687ce 100644 --- a/include/linux/lsm_audit.h +++ b/include/linux/lsm_audit.h @@ -21,6 +21,7 @@ #include #include #include +#include struct lsm_network_audit { int netif; @@ -50,6 +51,11 @@ struct lsm_ibpkey_audit { u16 pkey; }; +struct lsm_ibendport_audit { + char dev_name[IB_DEVICE_NAME_MAX]; + u8 port; +}; + /* Auxiliary data to use in generating the audit record. */ struct common_audit_data { char type; @@ -66,6 +72,7 @@ struct common_audit_data { #define LSM_AUDIT_DATA_IOCTL_OP 11 #define LSM_AUDIT_DATA_FILE 12 #define LSM_AUDIT_DATA_IBPKEY 13 +#define LSM_AUDIT_DATA_IBENDPORT 14 union { struct path path; struct dentry *dentry; @@ -84,6 +91,7 @@ struct common_audit_data { struct lsm_ioctlop_audit *op; struct file *file; struct lsm_ibpkey_audit *ibpkey; + struct lsm_ibendport_audit *ibendport; } u; /* this union contains LSM specific data */ union { diff --git a/security/lsm_audit.c b/security/lsm_audit.c index c22c99fae06a..28d4c3a528ab 100644 --- a/security/lsm_audit.c +++ b/security/lsm_audit.c @@ -421,6 +421,11 @@ static void dump_common_audit_data(struct audit_buffer *ab, a->u.ibpkey->pkey, &sbn_pfx); break; } + case LSM_AUDIT_DATA_IBENDPORT: + audit_log_format(ab, " device=%s port_num=%u", + a->u.ibendport->dev_name, + a->u.ibendport->port); + break; } /* switch (a->type) */ } diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index b59255f86274..91ec46dd34d9 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -6169,6 +6169,29 @@ static int selinux_ib_pkey_access(void *ib_sec, u64 subnet_prefix, u16 pkey_val) INFINIBAND_PKEY__ACCESS, &ad); } +static int selinux_ib_endport_manage_subnet(void *ib_sec, const char *dev_name, + u8 port_num) +{ + struct common_audit_data ad; + int err; + u32 sid = 0; + struct ib_security_struct *sec = ib_sec; + struct lsm_ibendport_audit ibendport; + + err = security_ib_endport_sid(dev_name, port_num, &sid); + + if (err) + return err; + + ad.type = LSM_AUDIT_DATA_IBENDPORT; + strncpy(ibendport.dev_name, dev_name, sizeof(ibendport.dev_name)); + ibendport.port = port_num; + ad.u.ibendport = &ibendport; + return avc_has_perm(sec->sid, sid, + SECCLASS_INFINIBAND_ENDPORT, + INFINIBAND_ENDPORT__MANAGE_SUBNET, &ad); +} + static int selinux_ib_alloc_security(void **ib_sec) { struct ib_security_struct *sec; @@ -6374,6 +6397,8 @@ static struct security_hook_list selinux_hooks[] __lsm_ro_after_init = { LSM_HOOK_INIT(tun_dev_open, selinux_tun_dev_open), #ifdef CONFIG_SECURITY_INFINIBAND LSM_HOOK_INIT(ib_pkey_access, selinux_ib_pkey_access), + LSM_HOOK_INIT(ib_endport_manage_subnet, + selinux_ib_endport_manage_subnet), LSM_HOOK_INIT(ib_alloc_security, selinux_ib_alloc_security), LSM_HOOK_INIT(ib_free_security, selinux_ib_free_security), #endif diff --git a/security/selinux/include/classmap.h b/security/selinux/include/classmap.h index 0fec1c505f84..b9fe3434b036 100644 --- a/security/selinux/include/classmap.h +++ b/security/selinux/include/classmap.h @@ -233,6 +233,8 @@ struct security_class_mapping secclass_map[] = { { COMMON_SOCK_PERMS, NULL } }, { "infiniband_pkey", { "access", NULL } }, + { "infiniband_endport", + { "manage_subnet", NULL } }, { NULL } }; diff --git a/security/selinux/include/security.h b/security/selinux/include/security.h index 592c014e369c..e91f08c16c0b 100644 --- a/security/selinux/include/security.h +++ b/security/selinux/include/security.h @@ -183,6 +183,8 @@ int security_port_sid(u8 protocol, u16 port, u32 *out_sid); int security_ib_pkey_sid(u64 subnet_prefix, u16 pkey_num, u32 *out_sid); +int security_ib_endport_sid(const char *dev_name, u8 port_num, u32 *out_sid); + int security_netif_sid(char *name, u32 *if_sid); int security_node_sid(u16 domain, void *addr, u32 addrlen, diff --git a/security/selinux/ss/services.c b/security/selinux/ss/services.c index 02257d90adc9..202166612b80 100644 --- a/security/selinux/ss/services.c +++ b/security/selinux/ss/services.c @@ -2272,6 +2272,47 @@ out: return rc; } +/** + * security_ib_endport_sid - Obtain the SID for a subnet management interface. + * @dev_name: device name + * @port: port number + * @out_sid: security identifier + */ +int security_ib_endport_sid(const char *dev_name, u8 port_num, u32 *out_sid) +{ + struct ocontext *c; + int rc = 0; + + read_lock(&policy_rwlock); + + c = policydb.ocontexts[OCON_IBENDPORT]; + while (c) { + if (c->u.ibendport.port == port_num && + !strncmp(c->u.ibendport.dev_name, + dev_name, + IB_DEVICE_NAME_MAX)) + break; + + c = c->next; + } + + if (c) { + if (!c->sid[0]) { + rc = sidtab_context_to_sid(&sidtab, + &c->context[0], + &c->sid[0]); + if (rc) + goto out; + } + *out_sid = c->sid[0]; + } else + *out_sid = SECINITSID_UNLABELED; + +out: + read_unlock(&policy_rwlock); + return rc; +} + /** * security_netif_sid - Obtain the SID for a network interface. * @name: interface name -- cgit v1.2.3 From 6623ec7c4dbe18a5a2878e2d888be70d08a91826 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Sun, 7 May 2017 05:53:37 -0700 Subject: securityfs: add the ability to support symlinks Signed-off-by: John Johansen Reviewed-by: Seth Arnold Acked-by: Kees Cook --- include/linux/security.h | 12 ++++ security/inode.c | 144 ++++++++++++++++++++++++++++++++++++++++------- 2 files changed, 135 insertions(+), 21 deletions(-) (limited to 'include') diff --git a/include/linux/security.h b/include/linux/security.h index af675b576645..caf8b64d8b5c 100644 --- a/include/linux/security.h +++ b/include/linux/security.h @@ -1651,6 +1651,10 @@ extern struct dentry *securityfs_create_file(const char *name, umode_t mode, struct dentry *parent, void *data, const struct file_operations *fops); extern struct dentry *securityfs_create_dir(const char *name, struct dentry *parent); +struct dentry *securityfs_create_symlink(const char *name, + struct dentry *parent, + const char *target, + const struct inode_operations *iops); extern void securityfs_remove(struct dentry *dentry); #else /* CONFIG_SECURITYFS */ @@ -1670,6 +1674,14 @@ static inline struct dentry *securityfs_create_file(const char *name, return ERR_PTR(-ENODEV); } +static inline struct dentry *securityfs_create_symlink(const char *name, + struct dentry *parent, + const char *target, + const struct inode_operations *iops) +{ + return ERR_PTR(-ENODEV); +} + static inline void securityfs_remove(struct dentry *dentry) {} diff --git a/security/inode.c b/security/inode.c index eccd58ef2ae8..8dd9ca8848e4 100644 --- a/security/inode.c +++ b/security/inode.c @@ -26,11 +26,31 @@ static struct vfsmount *mount; static int mount_count; +static void securityfs_evict_inode(struct inode *inode) +{ + truncate_inode_pages_final(&inode->i_data); + clear_inode(inode); + if (S_ISLNK(inode->i_mode)) + kfree(inode->i_link); +} + +static const struct super_operations securityfs_super_operations = { + .statfs = simple_statfs, + .evict_inode = securityfs_evict_inode, +}; + static int fill_super(struct super_block *sb, void *data, int silent) { static const struct tree_descr files[] = {{""}}; + int error; + + error = simple_fill_super(sb, SECURITYFS_MAGIC, files); + if (error) + return error; + + sb->s_op = &securityfs_super_operations; - return simple_fill_super(sb, SECURITYFS_MAGIC, files); + return 0; } static struct dentry *get_sb(struct file_system_type *fs_type, @@ -48,7 +68,7 @@ static struct file_system_type fs_type = { }; /** - * securityfs_create_file - create a file in the securityfs filesystem + * securityfs_create_dentry - create a dentry in the securityfs filesystem * * @name: a pointer to a string containing the name of the file to create. * @mode: the permission that the file should have @@ -60,34 +80,35 @@ static struct file_system_type fs_type = { * the open() call. * @fops: a pointer to a struct file_operations that should be used for * this file. + * @iops: a point to a struct of inode_operations that should be used for + * this file/dir * - * This is the basic "create a file" function for securityfs. It allows for a - * wide range of flexibility in creating a file, or a directory (if you - * want to create a directory, the securityfs_create_dir() function is - * recommended to be used instead). + * This is the basic "create a file/dir/symlink" function for + * securityfs. It allows for a wide range of flexibility in creating + * a file, or a directory (if you want to create a directory, the + * securityfs_create_dir() function is recommended to be used + * instead). * * This function returns a pointer to a dentry if it succeeds. This - * pointer must be passed to the securityfs_remove() function when the file is - * to be removed (no automatic cleanup happens if your module is unloaded, - * you are responsible here). If an error occurs, the function will return - * the error value (via ERR_PTR). + * pointer must be passed to the securityfs_remove() function when the + * file is to be removed (no automatic cleanup happens if your module + * is unloaded, you are responsible here). If an error occurs, the + * function will return the error value (via ERR_PTR). * * If securityfs is not enabled in the kernel, the value %-ENODEV is * returned. */ -struct dentry *securityfs_create_file(const char *name, umode_t mode, - struct dentry *parent, void *data, - const struct file_operations *fops) +static struct dentry *securityfs_create_dentry(const char *name, umode_t mode, + struct dentry *parent, void *data, + const struct file_operations *fops, + const struct inode_operations *iops) { struct dentry *dentry; - int is_dir = S_ISDIR(mode); struct inode *dir, *inode; int error; - if (!is_dir) { - BUG_ON(!fops); + if (!(mode & S_IFMT)) mode = (mode & S_IALLUGO) | S_IFREG; - } pr_debug("securityfs: creating file '%s'\n",name); @@ -120,11 +141,14 @@ struct dentry *securityfs_create_file(const char *name, umode_t mode, inode->i_mode = mode; inode->i_atime = inode->i_mtime = inode->i_ctime = current_time(inode); inode->i_private = data; - if (is_dir) { + if (S_ISDIR(mode)) { inode->i_op = &simple_dir_inode_operations; inode->i_fop = &simple_dir_operations; inc_nlink(inode); inc_nlink(dir); + } else if (S_ISLNK(mode)) { + inode->i_op = iops ? iops : &simple_symlink_inode_operations; + inode->i_link = data; } else { inode->i_fop = fops; } @@ -141,6 +165,38 @@ out: simple_release_fs(&mount, &mount_count); return dentry; } + +/** + * securityfs_create_file - create a file in the securityfs filesystem + * + * @name: a pointer to a string containing the name of the file to create. + * @mode: the permission that the file should have + * @parent: a pointer to the parent dentry for this file. This should be a + * directory dentry if set. If this parameter is %NULL, then the + * file will be created in the root of the securityfs filesystem. + * @data: a pointer to something that the caller will want to get to later + * on. The inode.i_private pointer will point to this value on + * the open() call. + * @fops: a pointer to a struct file_operations that should be used for + * this file. + * + * This function creates a file in securityfs with the given @name. + * + * This function returns a pointer to a dentry if it succeeds. This + * pointer must be passed to the securityfs_remove() function when the file is + * to be removed (no automatic cleanup happens if your module is unloaded, + * you are responsible here). If an error occurs, the function will return + * the error value (via ERR_PTR). + * + * If securityfs is not enabled in the kernel, the value %-ENODEV is + * returned. + */ +struct dentry *securityfs_create_file(const char *name, umode_t mode, + struct dentry *parent, void *data, + const struct file_operations *fops) +{ + return securityfs_create_dentry(name, mode, parent, data, fops, NULL); +} EXPORT_SYMBOL_GPL(securityfs_create_file); /** @@ -165,12 +221,58 @@ EXPORT_SYMBOL_GPL(securityfs_create_file); */ struct dentry *securityfs_create_dir(const char *name, struct dentry *parent) { - return securityfs_create_file(name, - S_IFDIR | S_IRWXU | S_IRUGO | S_IXUGO, - parent, NULL, NULL); + return securityfs_create_file(name, S_IFDIR | 0755, parent, NULL, NULL); } EXPORT_SYMBOL_GPL(securityfs_create_dir); +/** + * securityfs_create_symlink - create a symlink in the securityfs filesystem + * + * @name: a pointer to a string containing the name of the symlink to + * create. + * @parent: a pointer to the parent dentry for the symlink. This should be a + * directory dentry if set. If this parameter is %NULL, then the + * directory will be created in the root of the securityfs filesystem. + * @target: a pointer to a string containing the name of the symlink's target. + * If this parameter is %NULL, then the @iops parameter needs to be + * setup to handle .readlink and .get_link inode_operations. + * @iops: a pointer to the struct inode_operations to use for the symlink. If + * this parameter is %NULL, then the default simple_symlink_inode + * operations will be used. + * + * This function creates a symlink in securityfs with the given @name. + * + * This function returns a pointer to a dentry if it succeeds. This + * pointer must be passed to the securityfs_remove() function when the file is + * to be removed (no automatic cleanup happens if your module is unloaded, + * you are responsible here). If an error occurs, the function will return + * the error value (via ERR_PTR). + * + * If securityfs is not enabled in the kernel, the value %-ENODEV is + * returned. + */ +struct dentry *securityfs_create_symlink(const char *name, + struct dentry *parent, + const char *target, + const struct inode_operations *iops) +{ + struct dentry *dent; + char *link = NULL; + + if (target) { + link = kstrdup(target, GFP_KERNEL); + if (!link) + return ERR_PTR(-ENOMEM); + } + dent = securityfs_create_dentry(name, S_IFLNK | 0444, parent, + link, NULL, iops); + if (IS_ERR(dent)) + kfree(link); + + return dent; +} +EXPORT_SYMBOL_GPL(securityfs_create_symlink); + /** * securityfs_remove - removes a file or directory from the securityfs filesystem * -- cgit v1.2.3 From a481f4d917835cad86701fc0d1e620c74bb5cd5f Mon Sep 17 00:00:00 2001 From: John Johansen Date: Thu, 25 May 2017 05:52:56 -0700 Subject: apparmor: add custom apparmorfs that will be used by policy namespace files AppArmor policy needs to be able to be resolved based on the policy namespace a task is confined by. Add a base apparmorfs filesystem that (like nsfs) will exist as a kern mount and be accessed via jump_link through a securityfs file. Setup the base apparmorfs fns and data, but don't use it yet. Signed-off-by: John Johansen Reviewed-by: Seth Arnold Reviewed-by: Kees Cook --- include/uapi/linux/magic.h | 2 + security/apparmor/apparmorfs.c | 353 +++++++++++++++++++++++++++++++++++++++-- 2 files changed, 338 insertions(+), 17 deletions(-) (limited to 'include') diff --git a/include/uapi/linux/magic.h b/include/uapi/linux/magic.h index e230af2e6855..a0908f1d2760 100644 --- a/include/uapi/linux/magic.h +++ b/include/uapi/linux/magic.h @@ -80,6 +80,8 @@ #define BTRFS_TEST_MAGIC 0x73727279 #define NSFS_MAGIC 0x6e736673 #define BPF_FS_MAGIC 0xcafe4a11 +#define AAFS_MAGIC 0x5a3c69f0 + /* Since UDF 2.01 is ISO 13346 based... */ #define UDF_SUPER_MAGIC 0x15013346 #define BALLOON_KVM_MAGIC 0x13661366 diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c index 16680d15d43e..7e4b7f28ee20 100644 --- a/security/apparmor/apparmorfs.c +++ b/security/apparmor/apparmorfs.c @@ -22,8 +22,9 @@ #include #include #include -#include #include +#include +#include #include "include/apparmor.h" #include "include/apparmorfs.h" @@ -74,6 +75,265 @@ static int mangle_name(const char *name, char *target) return t - target; } + +/* + * aafs - core fns and data for the policy tree + */ + +#define AAFS_NAME "apparmorfs" +static struct vfsmount *aafs_mnt; +static int aafs_count; + + +static int aafs_show_path(struct seq_file *seq, struct dentry *dentry) +{ + struct inode *inode = d_inode(dentry); + + seq_printf(seq, "%s:[%lu]", AAFS_NAME, inode->i_ino); + return 0; +} + +static void aafs_evict_inode(struct inode *inode) +{ + truncate_inode_pages_final(&inode->i_data); + clear_inode(inode); + if (S_ISLNK(inode->i_mode)) + kfree(inode->i_link); +} + +static const struct super_operations aafs_super_ops = { + .statfs = simple_statfs, + .evict_inode = aafs_evict_inode, + .show_path = aafs_show_path, +}; + +static int fill_super(struct super_block *sb, void *data, int silent) +{ + static struct tree_descr files[] = { {""} }; + int error; + + error = simple_fill_super(sb, AAFS_MAGIC, files); + if (error) + return error; + sb->s_op = &aafs_super_ops; + + return 0; +} + +static struct dentry *aafs_mount(struct file_system_type *fs_type, + int flags, const char *dev_name, void *data) +{ + return mount_single(fs_type, flags, data, fill_super); +} + +static struct file_system_type aafs_ops = { + .owner = THIS_MODULE, + .name = AAFS_NAME, + .mount = aafs_mount, + .kill_sb = kill_anon_super, +}; + +/** + * __aafs_setup_d_inode - basic inode setup for apparmorfs + * @dir: parent directory for the dentry + * @dentry: dentry we are seting the inode up for + * @mode: permissions the file should have + * @data: data to store on inode.i_private, available in open() + * @link: if symlink, symlink target string + * @fops: struct file_operations that should be used + * @iops: struct of inode_operations that should be used + */ +static int __aafs_setup_d_inode(struct inode *dir, struct dentry *dentry, + umode_t mode, void *data, char *link, + const struct file_operations *fops, + const struct inode_operations *iops) +{ + struct inode *inode = new_inode(dir->i_sb); + + AA_BUG(!dir); + AA_BUG(!dentry); + + if (!inode) + return -ENOMEM; + + inode->i_ino = get_next_ino(); + inode->i_mode = mode; + inode->i_atime = inode->i_mtime = inode->i_ctime = current_time(inode); + inode->i_private = data; + if (S_ISDIR(mode)) { + inode->i_op = iops ? iops : &simple_dir_inode_operations; + inode->i_fop = &simple_dir_operations; + inc_nlink(inode); + inc_nlink(dir); + } else if (S_ISLNK(mode)) { + inode->i_op = iops ? iops : &simple_symlink_inode_operations; + inode->i_link = link; + } else { + inode->i_fop = fops; + } + d_instantiate(dentry, inode); + dget(dentry); + + return 0; +} + +/** + * aafs_create - create a dentry in the apparmorfs filesystem + * + * @name: name of dentry to create + * @mode: permissions the file should have + * @parent: parent directory for this dentry + * @data: data to store on inode.i_private, available in open() + * @link: if symlink, symlink target string + * @fops: struct file_operations that should be used for + * @iops: struct of inode_operations that should be used + * + * This is the basic "create a xxx" function for apparmorfs. + * + * Returns a pointer to a dentry if it succeeds, that must be free with + * aafs_remove(). Will return ERR_PTR on failure. + */ +static struct dentry *aafs_create(const char *name, umode_t mode, + struct dentry *parent, void *data, void *link, + const struct file_operations *fops, + const struct inode_operations *iops) +{ + struct dentry *dentry; + struct inode *dir; + int error; + + AA_BUG(!name); + AA_BUG(!parent); + + if (!(mode & S_IFMT)) + mode = (mode & S_IALLUGO) | S_IFREG; + + error = simple_pin_fs(&aafs_ops, &aafs_mnt, &aafs_count); + if (error) + return ERR_PTR(error); + + dir = d_inode(parent); + + inode_lock(dir); + dentry = lookup_one_len(name, parent, strlen(name)); + if (IS_ERR(dentry)) + goto fail_lock; + + if (d_really_is_positive(dentry)) { + error = -EEXIST; + goto fail_dentry; + } + + error = __aafs_setup_d_inode(dir, dentry, mode, data, link, fops, iops); + if (error) + goto fail_dentry; + inode_unlock(dir); + + return dentry; + +fail_dentry: + dput(dentry); + +fail_lock: + inode_unlock(dir); + simple_release_fs(&aafs_mnt, &aafs_count); + + return ERR_PTR(error); +} + +/** + * aafs_create_file - create a file in the apparmorfs filesystem + * + * @name: name of dentry to create + * @mode: permissions the file should have + * @parent: parent directory for this dentry + * @data: data to store on inode.i_private, available in open() + * @fops: struct file_operations that should be used for + * + * see aafs_create + */ +static struct dentry *aafs_create_file(const char *name, umode_t mode, + struct dentry *parent, void *data, + const struct file_operations *fops) +{ + return aafs_create(name, mode, parent, data, NULL, fops, NULL); +} + +/** + * aafs_create_dir - create a directory in the apparmorfs filesystem + * + * @name: name of dentry to create + * @parent: parent directory for this dentry + * + * see aafs_create + */ +static struct dentry *aafs_create_dir(const char *name, struct dentry *parent) +{ + return aafs_create(name, S_IFDIR | 0755, parent, NULL, NULL, NULL, + NULL); +} + +/** + * aafs_create_symlink - create a symlink in the apparmorfs filesystem + * @name: name of dentry to create + * @parent: parent directory for this dentry + * @target: if symlink, symlink target string + * @iops: struct of inode_operations that should be used + * + * If @target parameter is %NULL, then the @iops parameter needs to be + * setup to handle .readlink and .get_link inode_operations. + */ +static struct dentry *aafs_create_symlink(const char *name, + struct dentry *parent, + const char *target, + const struct inode_operations *iops) +{ + struct dentry *dent; + char *link = NULL; + + if (target) { + link = kstrdup(target, GFP_KERNEL); + if (!link) + return ERR_PTR(-ENOMEM); + } + dent = aafs_create(name, S_IFLNK | 0444, parent, NULL, link, NULL, + iops); + if (IS_ERR(dent)) + kfree(link); + + return dent; +} + +/** + * aafs_remove - removes a file or directory from the apparmorfs filesystem + * + * @dentry: dentry of the file/directory/symlink to removed. + */ +static void aafs_remove(struct dentry *dentry) +{ + struct inode *dir; + + if (!dentry || IS_ERR(dentry)) + return; + + dir = d_inode(dentry->d_parent); + inode_lock(dir); + if (simple_positive(dentry)) { + if (d_is_dir(dentry)) + simple_rmdir(dir, dentry); + else + simple_unlink(dir, dentry); + dput(dentry); + } + inode_unlock(dir); + simple_release_fs(&aafs_mnt, &aafs_count); +} + + +/* + * aa_fs - policy load/replace/remove + */ + /** * aa_simple_write_to_buffer - common routine for getting policy from user * @userbuf: user buffer to copy data from (NOT NULL) @@ -1369,14 +1629,14 @@ static struct aa_fs_entry aa_fs_entry = AA_FS_DIR("apparmor", aa_fs_entry_apparmor); /** - * aafs_create_file - create a file entry in the apparmor securityfs + * entry_create_file - create a file entry in the apparmor securityfs * @fs_file: aa_fs_entry to build an entry for (NOT NULL) * @parent: the parent dentry in the securityfs * - * Use aafs_remove_file to remove entries created with this fn. + * Use entry_remove_file to remove entries created with this fn. */ -static int __init aafs_create_file(struct aa_fs_entry *fs_file, - struct dentry *parent) +static int __init entry_create_file(struct aa_fs_entry *fs_file, + struct dentry *parent) { int error = 0; @@ -1391,15 +1651,15 @@ static int __init aafs_create_file(struct aa_fs_entry *fs_file, return error; } -static void __init aafs_remove_dir(struct aa_fs_entry *fs_dir); +static void __init entry_remove_dir(struct aa_fs_entry *fs_dir); /** - * aafs_create_dir - recursively create a directory entry in the securityfs + * entry_create_dir - recursively create a directory entry in the securityfs * @fs_dir: aa_fs_entry (and all child entries) to build (NOT NULL) * @parent: the parent dentry in the securityfs * - * Use aafs_remove_dir to remove entries created with this fn. + * Use entry_remove_dir to remove entries created with this fn. */ -static int __init aafs_create_dir(struct aa_fs_entry *fs_dir, +static int __init entry_create_dir(struct aa_fs_entry *fs_dir, struct dentry *parent) { struct aa_fs_entry *fs_file; @@ -1413,9 +1673,9 @@ static int __init aafs_create_dir(struct aa_fs_entry *fs_dir, for (fs_file = fs_dir->v.files; fs_file && fs_file->name; ++fs_file) { if (fs_file->v_type == AA_FS_TYPE_DIR) - error = aafs_create_dir(fs_file, fs_dir->dentry); + error = entry_create_dir(fs_file, fs_dir->dentry); else - error = aafs_create_file(fs_file, fs_dir->dentry); + error = entry_create_file(fs_file, fs_dir->dentry); if (error) goto failed; } @@ -1423,7 +1683,7 @@ static int __init aafs_create_dir(struct aa_fs_entry *fs_dir, return 0; failed: - aafs_remove_dir(fs_dir); + entry_remove_dir(fs_dir); return error; } @@ -1442,16 +1702,16 @@ static void __init aafs_remove_file(struct aa_fs_entry *fs_file) } /** - * aafs_remove_dir - recursively drop a directory entry from the securityfs + * entry_remove_dir - recursively drop a directory entry from the securityfs * @fs_dir: aa_fs_entry (and all child entries) to detach (NOT NULL) */ -static void __init aafs_remove_dir(struct aa_fs_entry *fs_dir) +static void __init entry_remove_dir(struct aa_fs_entry *fs_dir) { struct aa_fs_entry *fs_file; for (fs_file = fs_dir->v.files; fs_file && fs_file->name; ++fs_file) { if (fs_file->v_type == AA_FS_TYPE_DIR) - aafs_remove_dir(fs_file); + entry_remove_dir(fs_file); else aafs_remove_file(fs_file); } @@ -1466,7 +1726,7 @@ static void __init aafs_remove_dir(struct aa_fs_entry *fs_dir) */ void __init aa_destroy_aafs(void) { - aafs_remove_dir(&aa_fs_entry); + entry_remove_dir(&aa_fs_entry); } @@ -1515,6 +1775,59 @@ out: return error; } + + +static const char *policy_get_link(struct dentry *dentry, + struct inode *inode, + struct delayed_call *done) +{ + struct aa_ns *ns; + struct path path; + + if (!dentry) + return ERR_PTR(-ECHILD); + ns = aa_get_current_ns(); + path.mnt = mntget(aafs_mnt); + path.dentry = dget(ns_dir(ns)); + nd_jump_link(&path); + aa_put_ns(ns); + + return NULL; +} + +static int ns_get_name(char *buf, size_t size, struct aa_ns *ns, + struct inode *inode) +{ + int res = snprintf(buf, size, "%s:[%lu]", AAFS_NAME, inode->i_ino); + + if (res < 0 || res >= size) + res = -ENOENT; + + return res; +} + +static int policy_readlink(struct dentry *dentry, char __user *buffer, + int buflen) +{ + struct aa_ns *ns; + char name[32]; + int res; + + ns = aa_get_current_ns(); + res = ns_get_name(name, sizeof(name), ns, d_inode(dentry)); + if (res >= 0) + res = readlink_copy(buffer, buflen, name); + aa_put_ns(ns); + + return res; +} + +static const struct inode_operations policy_link_iops = { + .readlink = policy_readlink, + .get_link = policy_get_link, +}; + + /** * aa_create_aafs - create the apparmor security filesystem * @@ -1535,8 +1848,14 @@ static int __init aa_create_aafs(void) return -EEXIST; } + /* setup apparmorfs used to virtualize policy/ */ + aafs_mnt = kern_mount(&aafs_ops); + if (IS_ERR(aafs_mnt)) + panic("can't set apparmorfs up\n"); + aafs_mnt->mnt_sb->s_flags &= ~MS_NOUSER; + /* Populate fs tree. */ - error = aafs_create_dir(&aa_fs_entry, NULL); + error = entry_create_dir(&aa_fs_entry, NULL); if (error) goto error; -- cgit v1.2.3 From 0b4d3452b8b4a5309b4445b900e3cec022cca95a Mon Sep 17 00:00:00 2001 From: Scott Mayhew Date: Mon, 5 Jun 2017 11:45:04 -0400 Subject: security/selinux: allow security_sb_clone_mnt_opts to enable/disable native labeling behavior When an NFSv4 client performs a mount operation, it first mounts the NFSv4 root and then does path walk to the exported path and performs a submount on that, cloning the security mount options from the root's superblock to the submount's superblock in the process. Unless the NFS server has an explicit fsid=0 export with the "security_label" option, the NFSv4 root superblock will not have SBLABEL_MNT set, and neither will the submount superblock after cloning the security mount options. As a result, setxattr's of security labels over NFSv4.2 will fail. In a similar fashion, NFSv4.2 mounts mounted with the context= mount option will not show the correct labels because the nfs_server->caps flags of the cloned superblock will still have NFS_CAP_SECURITY_LABEL set. Allowing the NFSv4 client to enable or disable SECURITY_LSM_NATIVE_LABELS behavior will ensure that the SBLABEL_MNT flag has the correct value when the client traverses from an exported path without the "security_label" option to one with the "security_label" option and vice versa. Similarly, checking to see if SECURITY_LSM_NATIVE_LABELS is set upon return from security_sb_clone_mnt_opts() and clearing NFS_CAP_SECURITY_LABEL if necessary will allow the correct labels to be displayed for NFSv4.2 mounts mounted with the context= mount option. Resolves: https://github.com/SELinuxProject/selinux-kernel/issues/35 Signed-off-by: Scott Mayhew Reviewed-by: Stephen Smalley Tested-by: Stephen Smalley Signed-off-by: Paul Moore --- fs/nfs/super.c | 17 ++++++++++++++++- include/linux/lsm_hooks.h | 4 +++- include/linux/security.h | 8 ++++++-- security/security.c | 7 +++++-- security/selinux/hooks.c | 35 +++++++++++++++++++++++++++++++++-- 5 files changed, 63 insertions(+), 8 deletions(-) (limited to 'include') diff --git a/fs/nfs/super.c b/fs/nfs/super.c index 2f3822a4a7d5..b8e073546507 100644 --- a/fs/nfs/super.c +++ b/fs/nfs/super.c @@ -2544,10 +2544,25 @@ EXPORT_SYMBOL_GPL(nfs_set_sb_security); int nfs_clone_sb_security(struct super_block *s, struct dentry *mntroot, struct nfs_mount_info *mount_info) { + int error; + unsigned long kflags = 0, kflags_out = 0; + /* clone any lsm security options from the parent to the new sb */ if (d_inode(mntroot)->i_op != NFS_SB(s)->nfs_client->rpc_ops->dir_inode_ops) return -ESTALE; - return security_sb_clone_mnt_opts(mount_info->cloned->sb, s); + + if (NFS_SB(s)->caps & NFS_CAP_SECURITY_LABEL) + kflags |= SECURITY_LSM_NATIVE_LABELS; + + error = security_sb_clone_mnt_opts(mount_info->cloned->sb, s, kflags, + &kflags_out); + if (error) + return error; + + if (NFS_SB(s)->caps & NFS_CAP_SECURITY_LABEL && + !(kflags_out & SECURITY_LSM_NATIVE_LABELS)) + NFS_SB(s)->caps &= ~NFS_CAP_SECURITY_LABEL; + return 0; } EXPORT_SYMBOL_GPL(nfs_clone_sb_security); diff --git a/include/linux/lsm_hooks.h b/include/linux/lsm_hooks.h index 68d91e423bca..3cc9d77c7527 100644 --- a/include/linux/lsm_hooks.h +++ b/include/linux/lsm_hooks.h @@ -1409,7 +1409,9 @@ union security_list_options { unsigned long kern_flags, unsigned long *set_kern_flags); int (*sb_clone_mnt_opts)(const struct super_block *oldsb, - struct super_block *newsb); + struct super_block *newsb, + unsigned long kern_flags, + unsigned long *set_kern_flags); int (*sb_parse_opts_str)(char *options, struct security_mnt_opts *opts); int (*dentry_init_security)(struct dentry *dentry, int mode, const struct qstr *name, void **ctx, diff --git a/include/linux/security.h b/include/linux/security.h index 549cb828a888..b44e954815ce 100644 --- a/include/linux/security.h +++ b/include/linux/security.h @@ -249,7 +249,9 @@ int security_sb_set_mnt_opts(struct super_block *sb, unsigned long kern_flags, unsigned long *set_kern_flags); int security_sb_clone_mnt_opts(const struct super_block *oldsb, - struct super_block *newsb); + struct super_block *newsb, + unsigned long kern_flags, + unsigned long *set_kern_flags); int security_sb_parse_opts_str(char *options, struct security_mnt_opts *opts); int security_dentry_init_security(struct dentry *dentry, int mode, const struct qstr *name, void **ctx, @@ -605,7 +607,9 @@ static inline int security_sb_set_mnt_opts(struct super_block *sb, } static inline int security_sb_clone_mnt_opts(const struct super_block *oldsb, - struct super_block *newsb) + struct super_block *newsb, + unsigned long kern_flags, + unsigned long *set_kern_flags) { return 0; } diff --git a/security/security.c b/security/security.c index 714433e3e9a2..30132378d103 100644 --- a/security/security.c +++ b/security/security.c @@ -420,9 +420,12 @@ int security_sb_set_mnt_opts(struct super_block *sb, EXPORT_SYMBOL(security_sb_set_mnt_opts); int security_sb_clone_mnt_opts(const struct super_block *oldsb, - struct super_block *newsb) + struct super_block *newsb, + unsigned long kern_flags, + unsigned long *set_kern_flags) { - return call_int_hook(sb_clone_mnt_opts, 0, oldsb, newsb); + return call_int_hook(sb_clone_mnt_opts, 0, oldsb, newsb, + kern_flags, set_kern_flags); } EXPORT_SYMBOL(security_sb_clone_mnt_opts); diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index 9926adbd50a9..9cc042df10d1 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -525,8 +525,16 @@ static int sb_finish_set_opts(struct super_block *sb) } sbsec->flags |= SE_SBINITIALIZED; + + /* + * Explicitly set or clear SBLABEL_MNT. It's not sufficient to simply + * leave the flag untouched because sb_clone_mnt_opts might be handing + * us a superblock that needs the flag to be cleared. + */ if (selinux_is_sblabel_mnt(sb)) sbsec->flags |= SBLABEL_MNT; + else + sbsec->flags &= ~SBLABEL_MNT; /* Initialize the root inode. */ rc = inode_doinit_with_dentry(root_inode, root); @@ -959,8 +967,11 @@ mismatch: } static int selinux_sb_clone_mnt_opts(const struct super_block *oldsb, - struct super_block *newsb) + struct super_block *newsb, + unsigned long kern_flags, + 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; @@ -975,6 +986,13 @@ static int selinux_sb_clone_mnt_opts(const struct super_block *oldsb, if (!ss_initialized) return 0; + /* + * Specifying internal flags without providing a place to + * place the results is not allowed. + */ + if (kern_flags && !set_kern_flags) + return -EINVAL; + /* how can we clone if the old one wasn't set up?? */ BUG_ON(!(oldsbsec->flags & SE_SBINITIALIZED)); @@ -990,6 +1008,18 @@ static int selinux_sb_clone_mnt_opts(const struct super_block *oldsb, newsbsec->def_sid = oldsbsec->def_sid; newsbsec->behavior = oldsbsec->behavior; + if (newsbsec->behavior == SECURITY_FS_USE_NATIVE && + !(kern_flags & SECURITY_LSM_NATIVE_LABELS) && !set_context) { + rc = security_fs_use(newsb); + if (rc) + goto out; + } + + if (kern_flags & SECURITY_LSM_NATIVE_LABELS && !set_context) { + newsbsec->behavior = SECURITY_FS_USE_NATIVE; + *set_kern_flags |= SECURITY_LSM_NATIVE_LABELS; + } + if (set_context) { u32 sid = oldsbsec->mntpoint_sid; @@ -1009,8 +1039,9 @@ static int selinux_sb_clone_mnt_opts(const struct super_block *oldsb, } sb_finish_set_opts(newsb); +out: mutex_unlock(&newsbsec->lock); - return 0; + return rc; } static int selinux_parse_opts_str(char *options, -- cgit v1.2.3 From be4c9acfe2976b6e024d15656254d2eb207b83a8 Mon Sep 17 00:00:00 2001 From: Stefan Berger Date: Wed, 24 May 2017 17:39:40 -0400 Subject: tpm: vtpm_proxy: Implement request_locality function. Implement the request_locality function. To set the locality on the backend we define vendor-specific TPM 1.2 and TPM 2 ordinals and send a command to the backend to set the locality for the next commands. To avoid recursing into requesting the locality, we set the TPM_TRANSMIT_RAW flag when calling tpm_transmit_cmd. To avoid recursing into TPM 2 space related commands, we set the space parameter to NULL. Signed-off-by: Stefan Berger Reviewed-by: Jarkko Sakkinen Signed-off-by: Jarkko Sakkinen --- drivers/char/tpm/tpm-interface.c | 1 + drivers/char/tpm/tpm_vtpm_proxy.c | 36 ++++++++++++++++++++++++++++++++++++ include/uapi/linux/vtpm_proxy.h | 4 ++++ 3 files changed, 41 insertions(+) (limited to 'include') diff --git a/drivers/char/tpm/tpm-interface.c b/drivers/char/tpm/tpm-interface.c index 8ef5e1723efb..d2b4df6d9894 100644 --- a/drivers/char/tpm/tpm-interface.c +++ b/drivers/char/tpm/tpm-interface.c @@ -538,6 +538,7 @@ ssize_t tpm_transmit_cmd(struct tpm_chip *chip, struct tpm_space *space, return 0; } +EXPORT_SYMBOL_GPL(tpm_transmit_cmd); #define TPM_DIGEST_SIZE 20 #define TPM_RET_CODE_IDX 6 diff --git a/drivers/char/tpm/tpm_vtpm_proxy.c b/drivers/char/tpm/tpm_vtpm_proxy.c index 751059d2140a..66024bf92097 100644 --- a/drivers/char/tpm/tpm_vtpm_proxy.c +++ b/drivers/char/tpm/tpm_vtpm_proxy.c @@ -371,6 +371,41 @@ static bool vtpm_proxy_tpm_req_canceled(struct tpm_chip *chip, u8 status) return ret; } +static int vtpm_proxy_request_locality(struct tpm_chip *chip, int locality) +{ + struct tpm_buf buf; + int rc; + const struct tpm_output_header *header; + + if (chip->flags & TPM_CHIP_FLAG_TPM2) + rc = tpm_buf_init(&buf, TPM2_ST_SESSIONS, + TPM2_CC_SET_LOCALITY); + else + rc = tpm_buf_init(&buf, TPM_TAG_RQU_COMMAND, + TPM_ORD_SET_LOCALITY); + if (rc) + return rc; + tpm_buf_append_u8(&buf, locality); + + rc = tpm_transmit_cmd(chip, NULL, buf.data, tpm_buf_length(&buf), 0, + TPM_TRANSMIT_UNLOCKED | TPM_TRANSMIT_RAW, + "attempting to set locality"); + if (rc < 0) { + locality = rc; + goto out; + } + + header = (const struct tpm_output_header *)buf.data; + rc = be32_to_cpu(header->return_code); + if (rc) + locality = -1; + +out: + tpm_buf_destroy(&buf); + + return locality; +} + static const struct tpm_class_ops vtpm_proxy_tpm_ops = { .flags = TPM_OPS_AUTO_STARTUP, .recv = vtpm_proxy_tpm_op_recv, @@ -380,6 +415,7 @@ static const struct tpm_class_ops vtpm_proxy_tpm_ops = { .req_complete_mask = VTPM_PROXY_REQ_COMPLETE_FLAG, .req_complete_val = VTPM_PROXY_REQ_COMPLETE_FLAG, .req_canceled = vtpm_proxy_tpm_req_canceled, + .request_locality = vtpm_proxy_request_locality, }; /* diff --git a/include/uapi/linux/vtpm_proxy.h b/include/uapi/linux/vtpm_proxy.h index a69e991eb080..58ac73cd38fe 100644 --- a/include/uapi/linux/vtpm_proxy.h +++ b/include/uapi/linux/vtpm_proxy.h @@ -46,4 +46,8 @@ struct vtpm_proxy_new_dev { #define VTPM_PROXY_IOC_NEW_DEV _IOWR(0xa1, 0x00, struct vtpm_proxy_new_dev) +/* vendor specific commands to set locality */ +#define TPM2_CC_SET_LOCALITY 0x20001000 +#define TPM_ORD_SET_LOCALITY 0x20001000 + #endif /* _UAPI_LINUX_VTPM_PROXY_H */ -- cgit v1.2.3 From 6f6723e21589f4594bb72b27ddbb2f75defb33bb Mon Sep 17 00:00:00 2001 From: Mimi Zohar Date: Mon, 24 Apr 2017 22:43:52 -0400 Subject: ima: define is_ima_appraise_enabled() Only return enabled if in enforcing mode, not fix or log modes. Signed-off-by: Mimi Zohar Changes: - Define is_ima_appraise_enabled() as a bool (Thiago Bauermann) --- include/linux/ima.h | 6 ++++++ security/integrity/ima/ima_appraise.c | 10 ++++++++++ 2 files changed, 16 insertions(+) (limited to 'include') diff --git a/include/linux/ima.h b/include/linux/ima.h index 7f6952f8d6aa..0e4647e0eb60 100644 --- a/include/linux/ima.h +++ b/include/linux/ima.h @@ -75,11 +75,17 @@ static inline void ima_add_kexec_buffer(struct kimage *image) #endif #ifdef CONFIG_IMA_APPRAISE +extern bool is_ima_appraise_enabled(void); extern void ima_inode_post_setattr(struct dentry *dentry); extern int ima_inode_setxattr(struct dentry *dentry, const char *xattr_name, const void *xattr_value, size_t xattr_value_len); extern int ima_inode_removexattr(struct dentry *dentry, const char *xattr_name); #else +static inline bool is_ima_appraise_enabled(void) +{ + return 0; +} + static inline void ima_inode_post_setattr(struct dentry *dentry) { return; diff --git a/security/integrity/ima/ima_appraise.c b/security/integrity/ima/ima_appraise.c index ac546df73afc..7fe0566142d8 100644 --- a/security/integrity/ima/ima_appraise.c +++ b/security/integrity/ima/ima_appraise.c @@ -33,6 +33,16 @@ static int __init default_appraise_setup(char *str) __setup("ima_appraise=", default_appraise_setup); +/* + * is_ima_appraise_enabled - return appraise status + * + * Only return enabled, if not in ima_appraise="fix" or "log" modes. + */ +bool is_ima_appraise_enabled(void) +{ + return (ima_appraise & IMA_APPRAISE_ENFORCE) ? 1 : 0; +} + /* * ima_must_appraise - set appraise flag * -- cgit v1.2.3