18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * Pkey table
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci * SELinux must keep a mapping of Infinband PKEYs to labels/SIDs.  This
68c2ecf20Sopenharmony_ci * mapping is maintained as part of the normal policy but a fast cache is
78c2ecf20Sopenharmony_ci * needed to reduce the lookup overhead.
88c2ecf20Sopenharmony_ci *
98c2ecf20Sopenharmony_ci * This code is heavily based on the "netif" and "netport" concept originally
108c2ecf20Sopenharmony_ci * developed by
118c2ecf20Sopenharmony_ci * James Morris <jmorris@redhat.com> and
128c2ecf20Sopenharmony_ci * Paul Moore <paul@paul-moore.com>
138c2ecf20Sopenharmony_ci *   (see security/selinux/netif.c and security/selinux/netport.c for more
148c2ecf20Sopenharmony_ci *   information)
158c2ecf20Sopenharmony_ci */
168c2ecf20Sopenharmony_ci
178c2ecf20Sopenharmony_ci/*
188c2ecf20Sopenharmony_ci * (c) Mellanox Technologies, 2016
198c2ecf20Sopenharmony_ci */
208c2ecf20Sopenharmony_ci
218c2ecf20Sopenharmony_ci#include <linux/types.h>
228c2ecf20Sopenharmony_ci#include <linux/rcupdate.h>
238c2ecf20Sopenharmony_ci#include <linux/list.h>
248c2ecf20Sopenharmony_ci#include <linux/spinlock.h>
258c2ecf20Sopenharmony_ci
268c2ecf20Sopenharmony_ci#include "ibpkey.h"
278c2ecf20Sopenharmony_ci#include "objsec.h"
288c2ecf20Sopenharmony_ci
298c2ecf20Sopenharmony_ci#define SEL_PKEY_HASH_SIZE       256
308c2ecf20Sopenharmony_ci#define SEL_PKEY_HASH_BKT_LIMIT   16
318c2ecf20Sopenharmony_ci
328c2ecf20Sopenharmony_cistruct sel_ib_pkey_bkt {
338c2ecf20Sopenharmony_ci	int size;
348c2ecf20Sopenharmony_ci	struct list_head list;
358c2ecf20Sopenharmony_ci};
368c2ecf20Sopenharmony_ci
378c2ecf20Sopenharmony_cistruct sel_ib_pkey {
388c2ecf20Sopenharmony_ci	struct pkey_security_struct psec;
398c2ecf20Sopenharmony_ci	struct list_head list;
408c2ecf20Sopenharmony_ci	struct rcu_head rcu;
418c2ecf20Sopenharmony_ci};
428c2ecf20Sopenharmony_ci
438c2ecf20Sopenharmony_cistatic LIST_HEAD(sel_ib_pkey_list);
448c2ecf20Sopenharmony_cistatic DEFINE_SPINLOCK(sel_ib_pkey_lock);
458c2ecf20Sopenharmony_cistatic struct sel_ib_pkey_bkt sel_ib_pkey_hash[SEL_PKEY_HASH_SIZE];
468c2ecf20Sopenharmony_ci
478c2ecf20Sopenharmony_ci/**
488c2ecf20Sopenharmony_ci * sel_ib_pkey_hashfn - Hashing function for the pkey table
498c2ecf20Sopenharmony_ci * @pkey: pkey number
508c2ecf20Sopenharmony_ci *
518c2ecf20Sopenharmony_ci * Description:
528c2ecf20Sopenharmony_ci * This is the hashing function for the pkey table, it returns the bucket
538c2ecf20Sopenharmony_ci * number for the given pkey.
548c2ecf20Sopenharmony_ci *
558c2ecf20Sopenharmony_ci */
568c2ecf20Sopenharmony_cistatic unsigned int sel_ib_pkey_hashfn(u16 pkey)
578c2ecf20Sopenharmony_ci{
588c2ecf20Sopenharmony_ci	return (pkey & (SEL_PKEY_HASH_SIZE - 1));
598c2ecf20Sopenharmony_ci}
608c2ecf20Sopenharmony_ci
618c2ecf20Sopenharmony_ci/**
628c2ecf20Sopenharmony_ci * sel_ib_pkey_find - Search for a pkey record
638c2ecf20Sopenharmony_ci * @subnet_prefix: subnet_prefix
648c2ecf20Sopenharmony_ci * @pkey_num: pkey_num
658c2ecf20Sopenharmony_ci *
668c2ecf20Sopenharmony_ci * Description:
678c2ecf20Sopenharmony_ci * Search the pkey table and return the matching record.  If an entry
688c2ecf20Sopenharmony_ci * can not be found in the table return NULL.
698c2ecf20Sopenharmony_ci *
708c2ecf20Sopenharmony_ci */
718c2ecf20Sopenharmony_cistatic struct sel_ib_pkey *sel_ib_pkey_find(u64 subnet_prefix, u16 pkey_num)
728c2ecf20Sopenharmony_ci{
738c2ecf20Sopenharmony_ci	unsigned int idx;
748c2ecf20Sopenharmony_ci	struct sel_ib_pkey *pkey;
758c2ecf20Sopenharmony_ci
768c2ecf20Sopenharmony_ci	idx = sel_ib_pkey_hashfn(pkey_num);
778c2ecf20Sopenharmony_ci	list_for_each_entry_rcu(pkey, &sel_ib_pkey_hash[idx].list, list) {
788c2ecf20Sopenharmony_ci		if (pkey->psec.pkey == pkey_num &&
798c2ecf20Sopenharmony_ci		    pkey->psec.subnet_prefix == subnet_prefix)
808c2ecf20Sopenharmony_ci			return pkey;
818c2ecf20Sopenharmony_ci	}
828c2ecf20Sopenharmony_ci
838c2ecf20Sopenharmony_ci	return NULL;
848c2ecf20Sopenharmony_ci}
858c2ecf20Sopenharmony_ci
868c2ecf20Sopenharmony_ci/**
878c2ecf20Sopenharmony_ci * sel_ib_pkey_insert - Insert a new pkey into the table
888c2ecf20Sopenharmony_ci * @pkey: the new pkey record
898c2ecf20Sopenharmony_ci *
908c2ecf20Sopenharmony_ci * Description:
918c2ecf20Sopenharmony_ci * Add a new pkey record to the hash table.
928c2ecf20Sopenharmony_ci *
938c2ecf20Sopenharmony_ci */
948c2ecf20Sopenharmony_cistatic void sel_ib_pkey_insert(struct sel_ib_pkey *pkey)
958c2ecf20Sopenharmony_ci{
968c2ecf20Sopenharmony_ci	unsigned int idx;
978c2ecf20Sopenharmony_ci
988c2ecf20Sopenharmony_ci	/* we need to impose a limit on the growth of the hash table so check
998c2ecf20Sopenharmony_ci	 * this bucket to make sure it is within the specified bounds
1008c2ecf20Sopenharmony_ci	 */
1018c2ecf20Sopenharmony_ci	idx = sel_ib_pkey_hashfn(pkey->psec.pkey);
1028c2ecf20Sopenharmony_ci	list_add_rcu(&pkey->list, &sel_ib_pkey_hash[idx].list);
1038c2ecf20Sopenharmony_ci	if (sel_ib_pkey_hash[idx].size == SEL_PKEY_HASH_BKT_LIMIT) {
1048c2ecf20Sopenharmony_ci		struct sel_ib_pkey *tail;
1058c2ecf20Sopenharmony_ci
1068c2ecf20Sopenharmony_ci		tail = list_entry(
1078c2ecf20Sopenharmony_ci			rcu_dereference_protected(
1088c2ecf20Sopenharmony_ci				sel_ib_pkey_hash[idx].list.prev,
1098c2ecf20Sopenharmony_ci				lockdep_is_held(&sel_ib_pkey_lock)),
1108c2ecf20Sopenharmony_ci			struct sel_ib_pkey, list);
1118c2ecf20Sopenharmony_ci		list_del_rcu(&tail->list);
1128c2ecf20Sopenharmony_ci		kfree_rcu(tail, rcu);
1138c2ecf20Sopenharmony_ci	} else {
1148c2ecf20Sopenharmony_ci		sel_ib_pkey_hash[idx].size++;
1158c2ecf20Sopenharmony_ci	}
1168c2ecf20Sopenharmony_ci}
1178c2ecf20Sopenharmony_ci
1188c2ecf20Sopenharmony_ci/**
1198c2ecf20Sopenharmony_ci * sel_ib_pkey_sid_slow - Lookup the SID of a pkey using the policy
1208c2ecf20Sopenharmony_ci * @subnet_prefix: subnet prefix
1218c2ecf20Sopenharmony_ci * @pkey_num: pkey number
1228c2ecf20Sopenharmony_ci * @sid: pkey SID
1238c2ecf20Sopenharmony_ci *
1248c2ecf20Sopenharmony_ci * Description:
1258c2ecf20Sopenharmony_ci * This function determines the SID of a pkey by querying the security
1268c2ecf20Sopenharmony_ci * policy.  The result is added to the pkey table to speedup future
1278c2ecf20Sopenharmony_ci * queries.  Returns zero on success, negative values on failure.
1288c2ecf20Sopenharmony_ci *
1298c2ecf20Sopenharmony_ci */
1308c2ecf20Sopenharmony_cistatic int sel_ib_pkey_sid_slow(u64 subnet_prefix, u16 pkey_num, u32 *sid)
1318c2ecf20Sopenharmony_ci{
1328c2ecf20Sopenharmony_ci	int ret;
1338c2ecf20Sopenharmony_ci	struct sel_ib_pkey *pkey;
1348c2ecf20Sopenharmony_ci	struct sel_ib_pkey *new = NULL;
1358c2ecf20Sopenharmony_ci	unsigned long flags;
1368c2ecf20Sopenharmony_ci
1378c2ecf20Sopenharmony_ci	spin_lock_irqsave(&sel_ib_pkey_lock, flags);
1388c2ecf20Sopenharmony_ci	pkey = sel_ib_pkey_find(subnet_prefix, pkey_num);
1398c2ecf20Sopenharmony_ci	if (pkey) {
1408c2ecf20Sopenharmony_ci		*sid = pkey->psec.sid;
1418c2ecf20Sopenharmony_ci		spin_unlock_irqrestore(&sel_ib_pkey_lock, flags);
1428c2ecf20Sopenharmony_ci		return 0;
1438c2ecf20Sopenharmony_ci	}
1448c2ecf20Sopenharmony_ci
1458c2ecf20Sopenharmony_ci	ret = security_ib_pkey_sid(&selinux_state, subnet_prefix, pkey_num,
1468c2ecf20Sopenharmony_ci				   sid);
1478c2ecf20Sopenharmony_ci	if (ret)
1488c2ecf20Sopenharmony_ci		goto out;
1498c2ecf20Sopenharmony_ci
1508c2ecf20Sopenharmony_ci	/* If this memory allocation fails still return 0. The SID
1518c2ecf20Sopenharmony_ci	 * is valid, it just won't be added to the cache.
1528c2ecf20Sopenharmony_ci	 */
1538c2ecf20Sopenharmony_ci	new = kzalloc(sizeof(*new), GFP_ATOMIC);
1548c2ecf20Sopenharmony_ci	if (!new) {
1558c2ecf20Sopenharmony_ci		ret = -ENOMEM;
1568c2ecf20Sopenharmony_ci		goto out;
1578c2ecf20Sopenharmony_ci	}
1588c2ecf20Sopenharmony_ci
1598c2ecf20Sopenharmony_ci	new->psec.subnet_prefix = subnet_prefix;
1608c2ecf20Sopenharmony_ci	new->psec.pkey = pkey_num;
1618c2ecf20Sopenharmony_ci	new->psec.sid = *sid;
1628c2ecf20Sopenharmony_ci	sel_ib_pkey_insert(new);
1638c2ecf20Sopenharmony_ci
1648c2ecf20Sopenharmony_ciout:
1658c2ecf20Sopenharmony_ci	spin_unlock_irqrestore(&sel_ib_pkey_lock, flags);
1668c2ecf20Sopenharmony_ci	return ret;
1678c2ecf20Sopenharmony_ci}
1688c2ecf20Sopenharmony_ci
1698c2ecf20Sopenharmony_ci/**
1708c2ecf20Sopenharmony_ci * sel_ib_pkey_sid - Lookup the SID of a PKEY
1718c2ecf20Sopenharmony_ci * @subnet_prefix: subnet_prefix
1728c2ecf20Sopenharmony_ci * @pkey_num: pkey number
1738c2ecf20Sopenharmony_ci * @sid: pkey SID
1748c2ecf20Sopenharmony_ci *
1758c2ecf20Sopenharmony_ci * Description:
1768c2ecf20Sopenharmony_ci * This function determines the SID of a PKEY using the fastest method
1778c2ecf20Sopenharmony_ci * possible.  First the pkey table is queried, but if an entry can't be found
1788c2ecf20Sopenharmony_ci * then the policy is queried and the result is added to the table to speedup
1798c2ecf20Sopenharmony_ci * future queries.  Returns zero on success, negative values on failure.
1808c2ecf20Sopenharmony_ci *
1818c2ecf20Sopenharmony_ci */
1828c2ecf20Sopenharmony_ciint sel_ib_pkey_sid(u64 subnet_prefix, u16 pkey_num, u32 *sid)
1838c2ecf20Sopenharmony_ci{
1848c2ecf20Sopenharmony_ci	struct sel_ib_pkey *pkey;
1858c2ecf20Sopenharmony_ci
1868c2ecf20Sopenharmony_ci	rcu_read_lock();
1878c2ecf20Sopenharmony_ci	pkey = sel_ib_pkey_find(subnet_prefix, pkey_num);
1888c2ecf20Sopenharmony_ci	if (pkey) {
1898c2ecf20Sopenharmony_ci		*sid = pkey->psec.sid;
1908c2ecf20Sopenharmony_ci		rcu_read_unlock();
1918c2ecf20Sopenharmony_ci		return 0;
1928c2ecf20Sopenharmony_ci	}
1938c2ecf20Sopenharmony_ci	rcu_read_unlock();
1948c2ecf20Sopenharmony_ci
1958c2ecf20Sopenharmony_ci	return sel_ib_pkey_sid_slow(subnet_prefix, pkey_num, sid);
1968c2ecf20Sopenharmony_ci}
1978c2ecf20Sopenharmony_ci
1988c2ecf20Sopenharmony_ci/**
1998c2ecf20Sopenharmony_ci * sel_ib_pkey_flush - Flush the entire pkey table
2008c2ecf20Sopenharmony_ci *
2018c2ecf20Sopenharmony_ci * Description:
2028c2ecf20Sopenharmony_ci * Remove all entries from the pkey table
2038c2ecf20Sopenharmony_ci *
2048c2ecf20Sopenharmony_ci */
2058c2ecf20Sopenharmony_civoid sel_ib_pkey_flush(void)
2068c2ecf20Sopenharmony_ci{
2078c2ecf20Sopenharmony_ci	unsigned int idx;
2088c2ecf20Sopenharmony_ci	struct sel_ib_pkey *pkey, *pkey_tmp;
2098c2ecf20Sopenharmony_ci	unsigned long flags;
2108c2ecf20Sopenharmony_ci
2118c2ecf20Sopenharmony_ci	spin_lock_irqsave(&sel_ib_pkey_lock, flags);
2128c2ecf20Sopenharmony_ci	for (idx = 0; idx < SEL_PKEY_HASH_SIZE; idx++) {
2138c2ecf20Sopenharmony_ci		list_for_each_entry_safe(pkey, pkey_tmp,
2148c2ecf20Sopenharmony_ci					 &sel_ib_pkey_hash[idx].list, list) {
2158c2ecf20Sopenharmony_ci			list_del_rcu(&pkey->list);
2168c2ecf20Sopenharmony_ci			kfree_rcu(pkey, rcu);
2178c2ecf20Sopenharmony_ci		}
2188c2ecf20Sopenharmony_ci		sel_ib_pkey_hash[idx].size = 0;
2198c2ecf20Sopenharmony_ci	}
2208c2ecf20Sopenharmony_ci	spin_unlock_irqrestore(&sel_ib_pkey_lock, flags);
2218c2ecf20Sopenharmony_ci}
2228c2ecf20Sopenharmony_ci
2238c2ecf20Sopenharmony_cistatic __init int sel_ib_pkey_init(void)
2248c2ecf20Sopenharmony_ci{
2258c2ecf20Sopenharmony_ci	int iter;
2268c2ecf20Sopenharmony_ci
2278c2ecf20Sopenharmony_ci	if (!selinux_enabled_boot)
2288c2ecf20Sopenharmony_ci		return 0;
2298c2ecf20Sopenharmony_ci
2308c2ecf20Sopenharmony_ci	for (iter = 0; iter < SEL_PKEY_HASH_SIZE; iter++) {
2318c2ecf20Sopenharmony_ci		INIT_LIST_HEAD(&sel_ib_pkey_hash[iter].list);
2328c2ecf20Sopenharmony_ci		sel_ib_pkey_hash[iter].size = 0;
2338c2ecf20Sopenharmony_ci	}
2348c2ecf20Sopenharmony_ci
2358c2ecf20Sopenharmony_ci	return 0;
2368c2ecf20Sopenharmony_ci}
2378c2ecf20Sopenharmony_ci
2388c2ecf20Sopenharmony_cisubsys_initcall(sel_ib_pkey_init);
239