]>
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 | * | |
9 | * Author: Paul Moore <[email protected]> | |
10 | * | |
11 | * This code is heavily based on the "netif" concept originally developed by | |
12 | * James Morris <[email protected]> | |
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 PM |
70 | |
71 | /** | |
72 | * sel_netnode_free - Frees a node entry | |
73 | * @p: the entry's RCU field | |
74 | * | |
75 | * Description: | |
76 | * This function is designed to be used as a callback to the call_rcu() | |
77 | * function so that memory allocated to a hash table node entry can be | |
78 | * released safely. | |
79 | * | |
80 | */ | |
81 | static void sel_netnode_free(struct rcu_head *p) | |
82 | { | |
83 | struct sel_netnode *node = container_of(p, struct sel_netnode, rcu); | |
84 | kfree(node); | |
85 | } | |
86 | ||
87 | /** | |
88 | * sel_netnode_hashfn_ipv4 - IPv4 hashing function for the node table | |
89 | * @addr: IPv4 address | |
90 | * | |
91 | * Description: | |
92 | * This is the IPv4 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_ipv4(__be32 addr) |
224dfbd8 PM |
97 | { |
98 | /* at some point we should determine if the mismatch in byte order | |
99 | * affects the hash function dramatically */ | |
100 | return (addr & (SEL_NETNODE_HASH_SIZE - 1)); | |
101 | } | |
102 | ||
103 | /** | |
104 | * sel_netnode_hashfn_ipv6 - IPv6 hashing function for the node table | |
105 | * @addr: IPv6 address | |
106 | * | |
107 | * Description: | |
108 | * This is the IPv6 hashing function for the node interface table, it returns | |
109 | * the bucket number for the given IP address. | |
110 | * | |
111 | */ | |
a639e7ca | 112 | static unsigned int sel_netnode_hashfn_ipv6(const struct in6_addr *addr) |
224dfbd8 PM |
113 | { |
114 | /* just hash the least significant 32 bits to keep things fast (they | |
115 | * are the most likely to be different anyway), we can revisit this | |
116 | * later if needed */ | |
117 | return (addr->s6_addr32[3] & (SEL_NETNODE_HASH_SIZE - 1)); | |
118 | } | |
119 | ||
120 | /** | |
121 | * sel_netnode_find - Search for a node record | |
122 | * @addr: IP address | |
123 | * @family: address family | |
124 | * | |
125 | * Description: | |
126 | * Search the network node table and return the record matching @addr. If an | |
127 | * entry can not be found in the table return NULL. | |
128 | * | |
129 | */ | |
130 | static struct sel_netnode *sel_netnode_find(const void *addr, u16 family) | |
131 | { | |
a639e7ca | 132 | unsigned int idx; |
224dfbd8 PM |
133 | struct sel_netnode *node; |
134 | ||
135 | switch (family) { | |
136 | case PF_INET: | |
137 | idx = sel_netnode_hashfn_ipv4(*(__be32 *)addr); | |
138 | break; | |
139 | case PF_INET6: | |
140 | idx = sel_netnode_hashfn_ipv6(addr); | |
141 | break; | |
142 | default: | |
143 | BUG(); | |
144 | } | |
145 | ||
a639e7ca | 146 | list_for_each_entry_rcu(node, &sel_netnode_hash[idx].list, list) |
224dfbd8 PM |
147 | if (node->nsec.family == family) |
148 | switch (family) { | |
149 | case PF_INET: | |
150 | if (node->nsec.addr.ipv4 == *(__be32 *)addr) | |
151 | return node; | |
152 | break; | |
153 | case PF_INET6: | |
154 | if (ipv6_addr_equal(&node->nsec.addr.ipv6, | |
155 | addr)) | |
156 | return node; | |
157 | break; | |
158 | } | |
159 | ||
160 | return NULL; | |
161 | } | |
162 | ||
163 | /** | |
164 | * sel_netnode_insert - Insert a new node into the table | |
165 | * @node: the new node record | |
166 | * | |
167 | * Description: | |
a639e7ca | 168 | * Add a new node record to the network address hash table. |
224dfbd8 PM |
169 | * |
170 | */ | |
a639e7ca | 171 | static void sel_netnode_insert(struct sel_netnode *node) |
224dfbd8 | 172 | { |
a639e7ca | 173 | unsigned int idx; |
224dfbd8 PM |
174 | |
175 | switch (node->nsec.family) { | |
176 | case PF_INET: | |
177 | idx = sel_netnode_hashfn_ipv4(node->nsec.addr.ipv4); | |
178 | break; | |
179 | case PF_INET6: | |
180 | idx = sel_netnode_hashfn_ipv6(&node->nsec.addr.ipv6); | |
181 | break; | |
182 | default: | |
183 | BUG(); | |
184 | } | |
a639e7ca PM |
185 | |
186 | INIT_RCU_HEAD(&node->rcu); | |
224dfbd8 PM |
187 | |
188 | /* we need to impose a limit on the growth of the hash table so check | |
189 | * this bucket to make sure it is within the specified bounds */ | |
a639e7ca PM |
190 | list_add_rcu(&node->list, &sel_netnode_hash[idx].list); |
191 | if (sel_netnode_hash[idx].size == SEL_NETNODE_HASH_BKT_LIMIT) { | |
192 | struct sel_netnode *tail; | |
193 | tail = list_entry( | |
194 | rcu_dereference(sel_netnode_hash[idx].list.prev), | |
195 | struct sel_netnode, list); | |
196 | list_del_rcu(&tail->list); | |
197 | call_rcu(&tail->rcu, sel_netnode_free); | |
198 | } else | |
199 | sel_netnode_hash[idx].size++; | |
224dfbd8 PM |
200 | } |
201 | ||
202 | /** | |
203 | * sel_netnode_sid_slow - Lookup the SID of a network address using the policy | |
204 | * @addr: the IP address | |
205 | * @family: the address family | |
206 | * @sid: node SID | |
207 | * | |
208 | * Description: | |
209 | * This function determines the SID of a network address by quering the | |
210 | * security policy. The result is added to the network address table to | |
211 | * speedup future queries. Returns zero on success, negative values on | |
212 | * failure. | |
213 | * | |
214 | */ | |
215 | static int sel_netnode_sid_slow(void *addr, u16 family, u32 *sid) | |
216 | { | |
a639e7ca | 217 | int ret = -ENOMEM; |
224dfbd8 PM |
218 | struct sel_netnode *node; |
219 | struct sel_netnode *new = NULL; | |
220 | ||
221 | spin_lock_bh(&sel_netnode_lock); | |
222 | node = sel_netnode_find(addr, family); | |
223 | if (node != NULL) { | |
224 | *sid = node->nsec.sid; | |
a639e7ca PM |
225 | spin_unlock_bh(&sel_netnode_lock); |
226 | return 0; | |
224dfbd8 PM |
227 | } |
228 | new = kzalloc(sizeof(*new), GFP_ATOMIC); | |
a639e7ca | 229 | if (new == NULL) |
224dfbd8 | 230 | goto out; |
224dfbd8 PM |
231 | switch (family) { |
232 | case PF_INET: | |
233 | ret = security_node_sid(PF_INET, | |
a639e7ca | 234 | addr, sizeof(struct in_addr), sid); |
224dfbd8 PM |
235 | new->nsec.addr.ipv4 = *(__be32 *)addr; |
236 | break; | |
237 | case PF_INET6: | |
238 | ret = security_node_sid(PF_INET6, | |
a639e7ca | 239 | addr, sizeof(struct in6_addr), sid); |
224dfbd8 PM |
240 | ipv6_addr_copy(&new->nsec.addr.ipv6, addr); |
241 | break; | |
242 | default: | |
243 | BUG(); | |
244 | } | |
245 | if (ret != 0) | |
246 | goto out; | |
a639e7ca | 247 | |
224dfbd8 | 248 | new->nsec.family = family; |
a639e7ca PM |
249 | new->nsec.sid = *sid; |
250 | sel_netnode_insert(new); | |
224dfbd8 PM |
251 | |
252 | out: | |
253 | spin_unlock_bh(&sel_netnode_lock); | |
71f1cb05 PM |
254 | if (unlikely(ret)) { |
255 | printk(KERN_WARNING | |
256 | "SELinux: failure in sel_netnode_sid_slow()," | |
257 | " unable to determine network node label\n"); | |
224dfbd8 | 258 | kfree(new); |
71f1cb05 | 259 | } |
224dfbd8 PM |
260 | return ret; |
261 | } | |
262 | ||
263 | /** | |
264 | * sel_netnode_sid - Lookup the SID of a network address | |
265 | * @addr: the IP address | |
266 | * @family: the address family | |
267 | * @sid: node SID | |
268 | * | |
269 | * Description: | |
270 | * This function determines the SID of a network address using the fastest | |
271 | * method possible. First the address table is queried, but if an entry | |
272 | * can't be found then the policy is queried and the result is added to the | |
273 | * table to speedup future queries. Returns zero on success, negative values | |
274 | * on failure. | |
275 | * | |
276 | */ | |
277 | int sel_netnode_sid(void *addr, u16 family, u32 *sid) | |
278 | { | |
279 | struct sel_netnode *node; | |
280 | ||
281 | rcu_read_lock(); | |
282 | node = sel_netnode_find(addr, family); | |
283 | if (node != NULL) { | |
284 | *sid = node->nsec.sid; | |
285 | rcu_read_unlock(); | |
286 | return 0; | |
287 | } | |
288 | rcu_read_unlock(); | |
289 | ||
290 | return sel_netnode_sid_slow(addr, family, sid); | |
291 | } | |
292 | ||
293 | /** | |
294 | * sel_netnode_flush - Flush the entire network address table | |
295 | * | |
296 | * Description: | |
297 | * Remove all entries from the network address table. | |
298 | * | |
299 | */ | |
300 | static void sel_netnode_flush(void) | |
301 | { | |
a639e7ca PM |
302 | unsigned int idx; |
303 | struct sel_netnode *node, *node_tmp; | |
224dfbd8 PM |
304 | |
305 | spin_lock_bh(&sel_netnode_lock); | |
a639e7ca PM |
306 | for (idx = 0; idx < SEL_NETNODE_HASH_SIZE; idx++) { |
307 | list_for_each_entry_safe(node, node_tmp, | |
308 | &sel_netnode_hash[idx].list, list) { | |
309 | list_del_rcu(&node->list); | |
310 | call_rcu(&node->rcu, sel_netnode_free); | |
311 | } | |
312 | sel_netnode_hash[idx].size = 0; | |
313 | } | |
224dfbd8 PM |
314 | spin_unlock_bh(&sel_netnode_lock); |
315 | } | |
316 | ||
317 | static int sel_netnode_avc_callback(u32 event, u32 ssid, u32 tsid, | |
318 | u16 class, u32 perms, u32 *retained) | |
319 | { | |
320 | if (event == AVC_CALLBACK_RESET) { | |
321 | sel_netnode_flush(); | |
322 | synchronize_net(); | |
323 | } | |
324 | return 0; | |
325 | } | |
326 | ||
327 | static __init int sel_netnode_init(void) | |
328 | { | |
329 | int iter; | |
330 | int ret; | |
331 | ||
332 | if (!selinux_enabled) | |
333 | return 0; | |
334 | ||
a639e7ca PM |
335 | for (iter = 0; iter < SEL_NETNODE_HASH_SIZE; iter++) { |
336 | INIT_LIST_HEAD(&sel_netnode_hash[iter].list); | |
337 | sel_netnode_hash[iter].size = 0; | |
338 | } | |
224dfbd8 PM |
339 | |
340 | ret = avc_add_callback(sel_netnode_avc_callback, AVC_CALLBACK_RESET, | |
7b6b239c | 341 | SECSID_NULL, SECSID_NULL, SECCLASS_NULL, 0); |
224dfbd8 PM |
342 | if (ret != 0) |
343 | panic("avc_add_callback() failed, error %d\n", ret); | |
344 | ||
345 | return ret; | |
346 | } | |
347 | ||
348 | __initcall(sel_netnode_init); |