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