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