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