NetLabel: Cleanup the LSM domain hash functions
[linux-2.6] / net / netlabel / netlabel_domainhash.c
1 /*
2  * NetLabel Domain Hash Table
3  *
4  * This file manages the domain hash table that NetLabel uses to determine
5  * which network labeling protocol to use for a given domain.  The NetLabel
6  * system manages static and dynamic label mappings for network protocols such
7  * as CIPSO and RIPSO.
8  *
9  * Author: Paul Moore <paul.moore@hp.com>
10  *
11  */
12
13 /*
14  * (c) Copyright Hewlett-Packard Development Company, L.P., 2006
15  *
16  * This program is free software;  you can redistribute it and/or modify
17  * it under the terms of the GNU General Public License as published by
18  * the Free Software Foundation; either version 2 of the License, or
19  * (at your option) any later version.
20  *
21  * This program is distributed in the hope that it will be useful,
22  * but WITHOUT ANY WARRANTY;  without even the implied warranty of
23  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See
24  * the GNU General Public License for more details.
25  *
26  * You should have received a copy of the GNU General Public License
27  * along with this program;  if not, write to the Free Software
28  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
29  *
30  */
31
32 #include <linux/types.h>
33 #include <linux/rcupdate.h>
34 #include <linux/list.h>
35 #include <linux/skbuff.h>
36 #include <linux/spinlock.h>
37 #include <linux/string.h>
38 #include <linux/audit.h>
39 #include <net/netlabel.h>
40 #include <net/cipso_ipv4.h>
41 #include <asm/bug.h>
42
43 #include "netlabel_mgmt.h"
44 #include "netlabel_domainhash.h"
45 #include "netlabel_user.h"
46
47 struct netlbl_domhsh_tbl {
48         struct list_head *tbl;
49         u32 size;
50 };
51
52 /* Domain hash table */
53 /* XXX - updates should be so rare that having one spinlock for the entire
54  * hash table should be okay */
55 static DEFINE_SPINLOCK(netlbl_domhsh_lock);
56 static struct netlbl_domhsh_tbl *netlbl_domhsh = NULL;
57
58 /* Default domain mapping */
59 static DEFINE_SPINLOCK(netlbl_domhsh_def_lock);
60 static struct netlbl_dom_map *netlbl_domhsh_def = NULL;
61
62 /*
63  * Domain Hash Table Helper Functions
64  */
65
66 /**
67  * netlbl_domhsh_free_entry - Frees a domain hash table entry
68  * @entry: the entry's RCU field
69  *
70  * Description:
71  * This function is designed to be used as a callback to the call_rcu()
72  * function so that the memory allocated to a hash table entry can be released
73  * safely.
74  *
75  */
76 static void netlbl_domhsh_free_entry(struct rcu_head *entry)
77 {
78         struct netlbl_dom_map *ptr;
79
80         ptr = container_of(entry, struct netlbl_dom_map, rcu);
81         kfree(ptr->domain);
82         kfree(ptr);
83 }
84
85 /**
86  * netlbl_domhsh_hash - Hashing function for the domain hash table
87  * @domain: the domain name to hash
88  *
89  * Description:
90  * This is the hashing function for the domain hash table, it returns the
91  * correct bucket number for the domain.  The caller is responsibile for
92  * calling the rcu_read_[un]lock() functions.
93  *
94  */
95 static u32 netlbl_domhsh_hash(const char *key)
96 {
97         u32 iter;
98         u32 val;
99         u32 len;
100
101         /* This is taken (with slight modification) from
102          * security/selinux/ss/symtab.c:symhash() */
103
104         for (iter = 0, val = 0, len = strlen(key); iter < len; iter++)
105                 val = (val << 4 | (val >> (8 * sizeof(u32) - 4))) ^ key[iter];
106         return val & (rcu_dereference(netlbl_domhsh)->size - 1);
107 }
108
109 /**
110  * netlbl_domhsh_search - Search for a domain entry
111  * @domain: the domain
112  *
113  * Description:
114  * Searches the domain hash table and returns a pointer to the hash table
115  * entry if found, otherwise NULL is returned.  The caller is responsibile for
116  * the rcu hash table locks (i.e. the caller much call rcu_read_[un]lock()).
117  *
118  */
119 static struct netlbl_dom_map *netlbl_domhsh_search(const char *domain)
120 {
121         u32 bkt;
122         struct netlbl_dom_map *iter;
123
124         if (domain != NULL) {
125                 bkt = netlbl_domhsh_hash(domain);
126                 list_for_each_entry_rcu(iter,
127                                      &rcu_dereference(netlbl_domhsh)->tbl[bkt],
128                                      list)
129                         if (iter->valid && strcmp(iter->domain, domain) == 0)
130                                 return iter;
131         }
132
133         return NULL;
134 }
135
136 /**
137  * netlbl_domhsh_search_def - Search for a domain entry
138  * @domain: the domain
139  * @def: return default if no match is found
140  *
141  * Description:
142  * Searches the domain hash table and returns a pointer to the hash table
143  * entry if an exact match is found, if an exact match is not present in the
144  * hash table then the default entry is returned if valid otherwise NULL is
145  * returned.  The caller is responsibile for the rcu hash table locks
146  * (i.e. the caller much call rcu_read_[un]lock()).
147  *
148  */
149 static struct netlbl_dom_map *netlbl_domhsh_search_def(const char *domain)
150 {
151         struct netlbl_dom_map *entry;
152
153         entry = netlbl_domhsh_search(domain);
154         if (entry == NULL) {
155                 entry = rcu_dereference(netlbl_domhsh_def);
156                 if (entry != NULL && entry->valid)
157                         return entry;
158         }
159
160         return NULL;
161 }
162
163 /*
164  * Domain Hash Table Functions
165  */
166
167 /**
168  * netlbl_domhsh_init - Init for the domain hash
169  * @size: the number of bits to use for the hash buckets
170  *
171  * Description:
172  * Initializes the domain hash table, should be called only by
173  * netlbl_user_init() during initialization.  Returns zero on success, non-zero
174  * values on error.
175  *
176  */
177 int netlbl_domhsh_init(u32 size)
178 {
179         u32 iter;
180         struct netlbl_domhsh_tbl *hsh_tbl;
181
182         if (size == 0)
183                 return -EINVAL;
184
185         hsh_tbl = kmalloc(sizeof(*hsh_tbl), GFP_KERNEL);
186         if (hsh_tbl == NULL)
187                 return -ENOMEM;
188         hsh_tbl->size = 1 << size;
189         hsh_tbl->tbl = kcalloc(hsh_tbl->size,
190                                sizeof(struct list_head),
191                                GFP_KERNEL);
192         if (hsh_tbl->tbl == NULL) {
193                 kfree(hsh_tbl);
194                 return -ENOMEM;
195         }
196         for (iter = 0; iter < hsh_tbl->size; iter++)
197                 INIT_LIST_HEAD(&hsh_tbl->tbl[iter]);
198
199         spin_lock(&netlbl_domhsh_lock);
200         rcu_assign_pointer(netlbl_domhsh, hsh_tbl);
201         spin_unlock(&netlbl_domhsh_lock);
202
203         return 0;
204 }
205
206 /**
207  * netlbl_domhsh_add - Adds a entry to the domain hash table
208  * @entry: the entry to add
209  * @audit_info: NetLabel audit information
210  *
211  * Description:
212  * Adds a new entry to the domain hash table and handles any updates to the
213  * lower level protocol handler (i.e. CIPSO).  Returns zero on success,
214  * negative on failure.
215  *
216  */
217 int netlbl_domhsh_add(struct netlbl_dom_map *entry,
218                       struct netlbl_audit *audit_info)
219 {
220         int ret_val;
221         u32 bkt;
222         struct audit_buffer *audit_buf;
223
224         switch (entry->type) {
225         case NETLBL_NLTYPE_UNLABELED:
226                 ret_val = 0;
227                 break;
228         case NETLBL_NLTYPE_CIPSOV4:
229                 ret_val = cipso_v4_doi_domhsh_add(entry->type_def.cipsov4,
230                                                   entry->domain);
231                 break;
232         default:
233                 return -EINVAL;
234         }
235         if (ret_val != 0)
236                 return ret_val;
237
238         entry->valid = 1;
239         INIT_RCU_HEAD(&entry->rcu);
240
241         rcu_read_lock();
242         if (entry->domain != NULL) {
243                 bkt = netlbl_domhsh_hash(entry->domain);
244                 spin_lock(&netlbl_domhsh_lock);
245                 if (netlbl_domhsh_search(entry->domain) == NULL)
246                         list_add_tail_rcu(&entry->list,
247                                     &rcu_dereference(netlbl_domhsh)->tbl[bkt]);
248                 else
249                         ret_val = -EEXIST;
250                 spin_unlock(&netlbl_domhsh_lock);
251         } else {
252                 INIT_LIST_HEAD(&entry->list);
253                 spin_lock(&netlbl_domhsh_def_lock);
254                 if (rcu_dereference(netlbl_domhsh_def) == NULL)
255                         rcu_assign_pointer(netlbl_domhsh_def, entry);
256                 else
257                         ret_val = -EEXIST;
258                 spin_unlock(&netlbl_domhsh_def_lock);
259         }
260         audit_buf = netlbl_audit_start_common(AUDIT_MAC_MAP_ADD, audit_info);
261         if (audit_buf != NULL) {
262                 audit_log_format(audit_buf,
263                                  " nlbl_domain=%s",
264                                  entry->domain ? entry->domain : "(default)");
265                 switch (entry->type) {
266                 case NETLBL_NLTYPE_UNLABELED:
267                         audit_log_format(audit_buf, " nlbl_protocol=unlbl");
268                         break;
269                 case NETLBL_NLTYPE_CIPSOV4:
270                         audit_log_format(audit_buf,
271                                          " nlbl_protocol=cipsov4 cipso_doi=%u",
272                                          entry->type_def.cipsov4->doi);
273                         break;
274                 }
275                 audit_log_format(audit_buf, " res=%u", ret_val == 0 ? 1 : 0);
276                 audit_log_end(audit_buf);
277         }
278         rcu_read_unlock();
279
280         if (ret_val != 0) {
281                 switch (entry->type) {
282                 case NETLBL_NLTYPE_CIPSOV4:
283                         if (cipso_v4_doi_domhsh_remove(entry->type_def.cipsov4,
284                                                        entry->domain) != 0)
285                                 BUG();
286                         break;
287                 }
288         }
289
290         return ret_val;
291 }
292
293 /**
294  * netlbl_domhsh_add_default - Adds the default entry to the domain hash table
295  * @entry: the entry to add
296  * @audit_info: NetLabel audit information
297  *
298  * Description:
299  * Adds a new default entry to the domain hash table and handles any updates
300  * to the lower level protocol handler (i.e. CIPSO).  Returns zero on success,
301  * negative on failure.
302  *
303  */
304 int netlbl_domhsh_add_default(struct netlbl_dom_map *entry,
305                               struct netlbl_audit *audit_info)
306 {
307         return netlbl_domhsh_add(entry, audit_info);
308 }
309
310 /**
311  * netlbl_domhsh_remove - Removes an entry from the domain hash table
312  * @domain: the domain to remove
313  * @audit_info: NetLabel audit information
314  *
315  * Description:
316  * Removes an entry from the domain hash table and handles any updates to the
317  * lower level protocol handler (i.e. CIPSO).  Returns zero on success,
318  * negative on failure.
319  *
320  */
321 int netlbl_domhsh_remove(const char *domain, struct netlbl_audit *audit_info)
322 {
323         int ret_val = -ENOENT;
324         struct netlbl_dom_map *entry;
325         struct audit_buffer *audit_buf;
326
327         rcu_read_lock();
328         if (domain)
329                 entry = netlbl_domhsh_search(domain);
330         else
331                 entry = netlbl_domhsh_search_def(domain);
332         if (entry == NULL)
333                 goto remove_return;
334         switch (entry->type) {
335         case NETLBL_NLTYPE_CIPSOV4:
336                 cipso_v4_doi_domhsh_remove(entry->type_def.cipsov4,
337                                            entry->domain);
338                 break;
339         }
340         if (entry != rcu_dereference(netlbl_domhsh_def)) {
341                 spin_lock(&netlbl_domhsh_lock);
342                 if (entry->valid) {
343                         entry->valid = 0;
344                         list_del_rcu(&entry->list);
345                         ret_val = 0;
346                 }
347                 spin_unlock(&netlbl_domhsh_lock);
348         } else {
349                 spin_lock(&netlbl_domhsh_def_lock);
350                 if (entry->valid) {
351                         entry->valid = 0;
352                         rcu_assign_pointer(netlbl_domhsh_def, NULL);
353                         ret_val = 0;
354                 }
355                 spin_unlock(&netlbl_domhsh_def_lock);
356         }
357
358         audit_buf = netlbl_audit_start_common(AUDIT_MAC_MAP_DEL, audit_info);
359         if (audit_buf != NULL) {
360                 audit_log_format(audit_buf,
361                                  " nlbl_domain=%s res=%u",
362                                  entry->domain ? entry->domain : "(default)",
363                                  ret_val == 0 ? 1 : 0);
364                 audit_log_end(audit_buf);
365         }
366
367 remove_return:
368         rcu_read_unlock();
369         if (ret_val == 0)
370                 call_rcu(&entry->rcu, netlbl_domhsh_free_entry);
371         return ret_val;
372 }
373
374 /**
375  * netlbl_domhsh_remove_default - Removes the default entry from the table
376  * @audit_info: NetLabel audit information
377  *
378  * Description:
379  * Removes/resets the default entry for the domain hash table and handles any
380  * updates to the lower level protocol handler (i.e. CIPSO).  Returns zero on
381  * success, non-zero on failure.
382  *
383  */
384 int netlbl_domhsh_remove_default(struct netlbl_audit *audit_info)
385 {
386         return netlbl_domhsh_remove(NULL, audit_info);
387 }
388
389 /**
390  * netlbl_domhsh_getentry - Get an entry from the domain hash table
391  * @domain: the domain name to search for
392  *
393  * Description:
394  * Look through the domain hash table searching for an entry to match @domain,
395  * return a pointer to a copy of the entry or NULL.  The caller is responsibile
396  * for ensuring that rcu_read_[un]lock() is called.
397  *
398  */
399 struct netlbl_dom_map *netlbl_domhsh_getentry(const char *domain)
400 {
401         return netlbl_domhsh_search_def(domain);
402 }
403
404 /**
405  * netlbl_domhsh_walk - Iterate through the domain mapping hash table
406  * @skip_bkt: the number of buckets to skip at the start
407  * @skip_chain: the number of entries to skip in the first iterated bucket
408  * @callback: callback for each entry
409  * @cb_arg: argument for the callback function
410  *
411  * Description:
412  * Interate over the domain mapping hash table, skipping the first @skip_bkt
413  * buckets and @skip_chain entries.  For each entry in the table call
414  * @callback, if @callback returns a negative value stop 'walking' through the
415  * table and return.  Updates the values in @skip_bkt and @skip_chain on
416  * return.  Returns zero on succcess, negative values on failure.
417  *
418  */
419 int netlbl_domhsh_walk(u32 *skip_bkt,
420                      u32 *skip_chain,
421                      int (*callback) (struct netlbl_dom_map *entry, void *arg),
422                      void *cb_arg)
423 {
424         int ret_val = -ENOENT;
425         u32 iter_bkt;
426         struct netlbl_dom_map *iter_entry;
427         u32 chain_cnt = 0;
428
429         rcu_read_lock();
430         for (iter_bkt = *skip_bkt;
431              iter_bkt < rcu_dereference(netlbl_domhsh)->size;
432              iter_bkt++, chain_cnt = 0) {
433                 list_for_each_entry_rcu(iter_entry,
434                                 &rcu_dereference(netlbl_domhsh)->tbl[iter_bkt],
435                                 list)
436                         if (iter_entry->valid) {
437                                 if (chain_cnt++ < *skip_chain)
438                                         continue;
439                                 ret_val = callback(iter_entry, cb_arg);
440                                 if (ret_val < 0) {
441                                         chain_cnt--;
442                                         goto walk_return;
443                                 }
444                         }
445         }
446
447 walk_return:
448         rcu_read_unlock();
449         *skip_bkt = iter_bkt;
450         *skip_chain = chain_cnt;
451         return ret_val;
452 }