| 1 | // SPDX-License-Identifier: GPL-2.0-only | 
|---|
| 2 | /* | 
|---|
| 3 | * Network node table | 
|---|
| 4 | * | 
|---|
| 5 | * SELinux must keep a mapping of network nodes to labels/SIDs.  This | 
|---|
| 6 | * mapping is maintained as part of the normal policy but a fast cache is | 
|---|
| 7 | * needed to reduce the lookup overhead since most of these queries happen on | 
|---|
| 8 | * a per-packet basis. | 
|---|
| 9 | * | 
|---|
| 10 | * Author: Paul Moore <paul@paul-moore.com> | 
|---|
| 11 | * | 
|---|
| 12 | * This code is heavily based on the "netif" concept originally developed by | 
|---|
| 13 | * James Morris <jmorris@redhat.com> | 
|---|
| 14 | *   (see security/selinux/netif.c for more information) | 
|---|
| 15 | */ | 
|---|
| 16 |  | 
|---|
| 17 | /* | 
|---|
| 18 | * (c) Copyright Hewlett-Packard Development Company, L.P., 2007 | 
|---|
| 19 | */ | 
|---|
| 20 |  | 
|---|
| 21 | #include <linux/types.h> | 
|---|
| 22 | #include <linux/rcupdate.h> | 
|---|
| 23 | #include <linux/list.h> | 
|---|
| 24 | #include <linux/slab.h> | 
|---|
| 25 | #include <linux/spinlock.h> | 
|---|
| 26 | #include <linux/in.h> | 
|---|
| 27 | #include <linux/in6.h> | 
|---|
| 28 | #include <linux/ip.h> | 
|---|
| 29 | #include <linux/ipv6.h> | 
|---|
| 30 | #include <net/ip.h> | 
|---|
| 31 | #include <net/ipv6.h> | 
|---|
| 32 |  | 
|---|
| 33 | #include "netnode.h" | 
|---|
| 34 | #include "objsec.h" | 
|---|
| 35 |  | 
|---|
| 36 | #define SEL_NETNODE_HASH_SIZE       256 | 
|---|
| 37 | #define SEL_NETNODE_HASH_BKT_LIMIT   16 | 
|---|
| 38 |  | 
|---|
| 39 | struct sel_netnode_bkt { | 
|---|
| 40 | unsigned int size; | 
|---|
| 41 | struct list_head list; | 
|---|
| 42 | }; | 
|---|
| 43 |  | 
|---|
| 44 | struct sel_netnode { | 
|---|
| 45 | struct netnode_security_struct nsec; | 
|---|
| 46 |  | 
|---|
| 47 | struct list_head list; | 
|---|
| 48 | struct rcu_head rcu; | 
|---|
| 49 | }; | 
|---|
| 50 |  | 
|---|
| 51 | /* NOTE: we are using a combined hash table for both IPv4 and IPv6, the reason | 
|---|
| 52 | * for this is that I suspect most users will not make heavy use of both | 
|---|
| 53 | * address families at the same time so one table will usually end up wasted, | 
|---|
| 54 | * if this becomes a problem we can always add a hash table for each address | 
|---|
| 55 | * family later */ | 
|---|
| 56 |  | 
|---|
| 57 | static DEFINE_SPINLOCK(sel_netnode_lock); | 
|---|
| 58 | static struct sel_netnode_bkt sel_netnode_hash[SEL_NETNODE_HASH_SIZE]; | 
|---|
| 59 |  | 
|---|
| 60 | /** | 
|---|
| 61 | * sel_netnode_hashfn_ipv4 - IPv4 hashing function for the node table | 
|---|
| 62 | * @addr: IPv4 address | 
|---|
| 63 | * | 
|---|
| 64 | * Description: | 
|---|
| 65 | * This is the IPv4 hashing function for the node interface table, it returns | 
|---|
| 66 | * the bucket number for the given IP address. | 
|---|
| 67 | * | 
|---|
| 68 | */ | 
|---|
| 69 | static unsigned int sel_netnode_hashfn_ipv4(__be32 addr) | 
|---|
| 70 | { | 
|---|
| 71 | /* at some point we should determine if the mismatch in byte order | 
|---|
| 72 | * affects the hash function dramatically */ | 
|---|
| 73 | return (addr & (SEL_NETNODE_HASH_SIZE - 1)); | 
|---|
| 74 | } | 
|---|
| 75 |  | 
|---|
| 76 | /** | 
|---|
| 77 | * sel_netnode_hashfn_ipv6 - IPv6 hashing function for the node table | 
|---|
| 78 | * @addr: IPv6 address | 
|---|
| 79 | * | 
|---|
| 80 | * Description: | 
|---|
| 81 | * This is the IPv6 hashing function for the node interface table, it returns | 
|---|
| 82 | * the bucket number for the given IP address. | 
|---|
| 83 | * | 
|---|
| 84 | */ | 
|---|
| 85 | static unsigned int sel_netnode_hashfn_ipv6(const struct in6_addr *addr) | 
|---|
| 86 | { | 
|---|
| 87 | /* just hash the least significant 32 bits to keep things fast (they | 
|---|
| 88 | * are the most likely to be different anyway), we can revisit this | 
|---|
| 89 | * later if needed */ | 
|---|
| 90 | return (addr->s6_addr32[3] & (SEL_NETNODE_HASH_SIZE - 1)); | 
|---|
| 91 | } | 
|---|
| 92 |  | 
|---|
| 93 | /** | 
|---|
| 94 | * sel_netnode_find - Search for a node record | 
|---|
| 95 | * @addr: IP address | 
|---|
| 96 | * @family: address family | 
|---|
| 97 | * | 
|---|
| 98 | * Description: | 
|---|
| 99 | * Search the network node table and return the record matching @addr.  If an | 
|---|
| 100 | * entry can not be found in the table return NULL. | 
|---|
| 101 | * | 
|---|
| 102 | */ | 
|---|
| 103 | static struct sel_netnode *sel_netnode_find(const void *addr, u16 family) | 
|---|
| 104 | { | 
|---|
| 105 | unsigned int idx; | 
|---|
| 106 | struct sel_netnode *node; | 
|---|
| 107 |  | 
|---|
| 108 | switch (family) { | 
|---|
| 109 | case PF_INET: | 
|---|
| 110 | idx = sel_netnode_hashfn_ipv4(addr: *(const __be32 *)addr); | 
|---|
| 111 | break; | 
|---|
| 112 | case PF_INET6: | 
|---|
| 113 | idx = sel_netnode_hashfn_ipv6(addr); | 
|---|
| 114 | break; | 
|---|
| 115 | default: | 
|---|
| 116 | BUG(); | 
|---|
| 117 | return NULL; | 
|---|
| 118 | } | 
|---|
| 119 |  | 
|---|
| 120 | list_for_each_entry_rcu(node, &sel_netnode_hash[idx].list, list) | 
|---|
| 121 | if (node->nsec.family == family) | 
|---|
| 122 | switch (family) { | 
|---|
| 123 | case PF_INET: | 
|---|
| 124 | if (node->nsec.addr.ipv4 == *(const __be32 *)addr) | 
|---|
| 125 | return node; | 
|---|
| 126 | break; | 
|---|
| 127 | case PF_INET6: | 
|---|
| 128 | if (ipv6_addr_equal(a1: &node->nsec.addr.ipv6, | 
|---|
| 129 | a2: addr)) | 
|---|
| 130 | return node; | 
|---|
| 131 | break; | 
|---|
| 132 | } | 
|---|
| 133 |  | 
|---|
| 134 | return NULL; | 
|---|
| 135 | } | 
|---|
| 136 |  | 
|---|
| 137 | /** | 
|---|
| 138 | * sel_netnode_insert - Insert a new node into the table | 
|---|
| 139 | * @node: the new node record | 
|---|
| 140 | * | 
|---|
| 141 | * Description: | 
|---|
| 142 | * Add a new node record to the network address hash table. | 
|---|
| 143 | * | 
|---|
| 144 | */ | 
|---|
| 145 | static void sel_netnode_insert(struct sel_netnode *node) | 
|---|
| 146 | { | 
|---|
| 147 | unsigned int idx; | 
|---|
| 148 |  | 
|---|
| 149 | switch (node->nsec.family) { | 
|---|
| 150 | case PF_INET: | 
|---|
| 151 | idx = sel_netnode_hashfn_ipv4(addr: node->nsec.addr.ipv4); | 
|---|
| 152 | break; | 
|---|
| 153 | case PF_INET6: | 
|---|
| 154 | idx = sel_netnode_hashfn_ipv6(addr: &node->nsec.addr.ipv6); | 
|---|
| 155 | break; | 
|---|
| 156 | default: | 
|---|
| 157 | BUG(); | 
|---|
| 158 | return; | 
|---|
| 159 | } | 
|---|
| 160 |  | 
|---|
| 161 | /* we need to impose a limit on the growth of the hash table so check | 
|---|
| 162 | * this bucket to make sure it is within the specified bounds */ | 
|---|
| 163 | list_add_rcu(new: &node->list, head: &sel_netnode_hash[idx].list); | 
|---|
| 164 | if (sel_netnode_hash[idx].size == SEL_NETNODE_HASH_BKT_LIMIT) { | 
|---|
| 165 | struct sel_netnode *tail; | 
|---|
| 166 | tail = list_entry( | 
|---|
| 167 | rcu_dereference_protected( | 
|---|
| 168 | list_tail_rcu(&sel_netnode_hash[idx].list), | 
|---|
| 169 | lockdep_is_held(&sel_netnode_lock)), | 
|---|
| 170 | struct sel_netnode, list); | 
|---|
| 171 | list_del_rcu(entry: &tail->list); | 
|---|
| 172 | kfree_rcu(tail, rcu); | 
|---|
| 173 | } else | 
|---|
| 174 | sel_netnode_hash[idx].size++; | 
|---|
| 175 | } | 
|---|
| 176 |  | 
|---|
| 177 | /** | 
|---|
| 178 | * sel_netnode_sid_slow - Lookup the SID of a network address using the policy | 
|---|
| 179 | * @addr: the IP address | 
|---|
| 180 | * @family: the address family | 
|---|
| 181 | * @sid: node SID | 
|---|
| 182 | * | 
|---|
| 183 | * Description: | 
|---|
| 184 | * This function determines the SID of a network address by querying the | 
|---|
| 185 | * security policy.  The result is added to the network address table to | 
|---|
| 186 | * speedup future queries.  Returns zero on success, negative values on | 
|---|
| 187 | * failure. | 
|---|
| 188 | * | 
|---|
| 189 | */ | 
|---|
| 190 | static int sel_netnode_sid_slow(const void *addr, u16 family, u32 *sid) | 
|---|
| 191 | { | 
|---|
| 192 | int ret; | 
|---|
| 193 | struct sel_netnode *node; | 
|---|
| 194 | struct sel_netnode *new; | 
|---|
| 195 |  | 
|---|
| 196 | spin_lock_bh(lock: &sel_netnode_lock); | 
|---|
| 197 | node = sel_netnode_find(addr, family); | 
|---|
| 198 | if (node != NULL) { | 
|---|
| 199 | *sid = node->nsec.sid; | 
|---|
| 200 | spin_unlock_bh(lock: &sel_netnode_lock); | 
|---|
| 201 | return 0; | 
|---|
| 202 | } | 
|---|
| 203 |  | 
|---|
| 204 | /* If this memory allocation fails still return 0. The SID | 
|---|
| 205 | * is valid, it just won't be added to the cache. | 
|---|
| 206 | */ | 
|---|
| 207 | new = kmalloc(sizeof(*new), GFP_ATOMIC); | 
|---|
| 208 | switch (family) { | 
|---|
| 209 | case PF_INET: | 
|---|
| 210 | ret = security_node_sid(PF_INET, | 
|---|
| 211 | addr, addrlen: sizeof(struct in_addr), out_sid: sid); | 
|---|
| 212 | if (new) | 
|---|
| 213 | new->nsec.addr.ipv4 = *(const __be32 *)addr; | 
|---|
| 214 | break; | 
|---|
| 215 | case PF_INET6: | 
|---|
| 216 | ret = security_node_sid(PF_INET6, | 
|---|
| 217 | addr, addrlen: sizeof(struct in6_addr), out_sid: sid); | 
|---|
| 218 | if (new) | 
|---|
| 219 | new->nsec.addr.ipv6 = *(const struct in6_addr *)addr; | 
|---|
| 220 | break; | 
|---|
| 221 | default: | 
|---|
| 222 | BUG(); | 
|---|
| 223 | ret = -EINVAL; | 
|---|
| 224 | } | 
|---|
| 225 | if (ret == 0 && new) { | 
|---|
| 226 | new->nsec.family = family; | 
|---|
| 227 | new->nsec.sid = *sid; | 
|---|
| 228 | sel_netnode_insert(node: new); | 
|---|
| 229 | } else | 
|---|
| 230 | kfree(objp: new); | 
|---|
| 231 |  | 
|---|
| 232 | spin_unlock_bh(lock: &sel_netnode_lock); | 
|---|
| 233 | if (unlikely(ret)) | 
|---|
| 234 | pr_warn( "SELinux: failure in %s(), unable to determine network node label\n", | 
|---|
| 235 | __func__); | 
|---|
| 236 | return ret; | 
|---|
| 237 | } | 
|---|
| 238 |  | 
|---|
| 239 | /** | 
|---|
| 240 | * sel_netnode_sid - Lookup the SID of a network address | 
|---|
| 241 | * @addr: the IP address | 
|---|
| 242 | * @family: the address family | 
|---|
| 243 | * @sid: node SID | 
|---|
| 244 | * | 
|---|
| 245 | * Description: | 
|---|
| 246 | * This function determines the SID of a network address using the fastest | 
|---|
| 247 | * method possible.  First the address table is queried, but if an entry | 
|---|
| 248 | * can't be found then the policy is queried and the result is added to the | 
|---|
| 249 | * table to speedup future queries.  Returns zero on success, negative values | 
|---|
| 250 | * on failure. | 
|---|
| 251 | * | 
|---|
| 252 | */ | 
|---|
| 253 | int sel_netnode_sid(const void *addr, u16 family, u32 *sid) | 
|---|
| 254 | { | 
|---|
| 255 | struct sel_netnode *node; | 
|---|
| 256 |  | 
|---|
| 257 | rcu_read_lock(); | 
|---|
| 258 | node = sel_netnode_find(addr, family); | 
|---|
| 259 | if (likely(node != NULL)) { | 
|---|
| 260 | *sid = node->nsec.sid; | 
|---|
| 261 | rcu_read_unlock(); | 
|---|
| 262 | return 0; | 
|---|
| 263 | } | 
|---|
| 264 | rcu_read_unlock(); | 
|---|
| 265 |  | 
|---|
| 266 | return sel_netnode_sid_slow(addr, family, sid); | 
|---|
| 267 | } | 
|---|
| 268 |  | 
|---|
| 269 | /** | 
|---|
| 270 | * sel_netnode_flush - Flush the entire network address table | 
|---|
| 271 | * | 
|---|
| 272 | * Description: | 
|---|
| 273 | * Remove all entries from the network address table. | 
|---|
| 274 | * | 
|---|
| 275 | */ | 
|---|
| 276 | void sel_netnode_flush(void) | 
|---|
| 277 | { | 
|---|
| 278 | unsigned int idx; | 
|---|
| 279 | struct sel_netnode *node, *node_tmp; | 
|---|
| 280 |  | 
|---|
| 281 | spin_lock_bh(lock: &sel_netnode_lock); | 
|---|
| 282 | for (idx = 0; idx < SEL_NETNODE_HASH_SIZE; idx++) { | 
|---|
| 283 | list_for_each_entry_safe(node, node_tmp, | 
|---|
| 284 | &sel_netnode_hash[idx].list, list) { | 
|---|
| 285 | list_del_rcu(entry: &node->list); | 
|---|
| 286 | kfree_rcu(node, rcu); | 
|---|
| 287 | } | 
|---|
| 288 | sel_netnode_hash[idx].size = 0; | 
|---|
| 289 | } | 
|---|
| 290 | spin_unlock_bh(lock: &sel_netnode_lock); | 
|---|
| 291 | } | 
|---|
| 292 |  | 
|---|
| 293 | static __init int sel_netnode_init(void) | 
|---|
| 294 | { | 
|---|
| 295 | int iter; | 
|---|
| 296 |  | 
|---|
| 297 | if (!selinux_enabled_boot) | 
|---|
| 298 | return 0; | 
|---|
| 299 |  | 
|---|
| 300 | for (iter = 0; iter < SEL_NETNODE_HASH_SIZE; iter++) { | 
|---|
| 301 | INIT_LIST_HEAD(list: &sel_netnode_hash[iter].list); | 
|---|
| 302 | sel_netnode_hash[iter].size = 0; | 
|---|
| 303 | } | 
|---|
| 304 |  | 
|---|
| 305 | return 0; | 
|---|
| 306 | } | 
|---|
| 307 |  | 
|---|
| 308 | __initcall(sel_netnode_init); | 
|---|
| 309 |  | 
|---|