Linux Kernel  3.7.1
 All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Groups Pages
netlabel_domainhash.c
Go to the documentation of this file.
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 <[email protected]>
10  *
11  */
12 
13 /*
14  * (c) Copyright Hewlett-Packard Development Company, L.P., 2006, 2008
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/rculist.h>
34 #include <linux/skbuff.h>
35 #include <linux/spinlock.h>
36 #include <linux/string.h>
37 #include <linux/audit.h>
38 #include <linux/slab.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_addrlist.h"
45 #include "netlabel_domainhash.h"
46 #include "netlabel_user.h"
47 
49  struct list_head *tbl;
51 };
52 
53 /* Domain hash table */
54 /* updates should be so rare that having one spinlock for the entire hash table
55  * should be okay */
56 static DEFINE_SPINLOCK(netlbl_domhsh_lock);
57 #define netlbl_domhsh_rcu_deref(p) \
58  rcu_dereference_check(p, lockdep_is_held(&netlbl_domhsh_lock))
59 static struct netlbl_domhsh_tbl *netlbl_domhsh = NULL;
60 static struct netlbl_dom_map *netlbl_domhsh_def = NULL;
61 
62 /*
63  * Domain Hash Table Helper Functions
64  */
65 
76 static void netlbl_domhsh_free_entry(struct rcu_head *entry)
77 {
78  struct netlbl_dom_map *ptr;
79  struct netlbl_af4list *iter4;
80  struct netlbl_af4list *tmp4;
81 #if IS_ENABLED(CONFIG_IPV6)
82  struct netlbl_af6list *iter6;
83  struct netlbl_af6list *tmp6;
84 #endif /* IPv6 */
85 
86  ptr = container_of(entry, struct netlbl_dom_map, rcu);
87  if (ptr->type == NETLBL_NLTYPE_ADDRSELECT) {
88  netlbl_af4list_foreach_safe(iter4, tmp4,
89  &ptr->type_def.addrsel->list4) {
92  }
93 #if IS_ENABLED(CONFIG_IPV6)
94  netlbl_af6list_foreach_safe(iter6, tmp6,
95  &ptr->type_def.addrsel->list6) {
96  netlbl_af6list_remove_entry(iter6);
98  }
99 #endif /* IPv6 */
100  }
101  kfree(ptr->domain);
102  kfree(ptr);
103 }
104 
116 static u32 netlbl_domhsh_hash(const char *key)
117 {
118  u32 iter;
119  u32 val;
120  u32 len;
121 
122  /* This is taken (with slight modification) from
123  * security/selinux/ss/symtab.c:symhash() */
124 
125  for (iter = 0, val = 0, len = strlen(key); iter < len; iter++)
126  val = (val << 4 | (val >> (8 * sizeof(u32) - 4))) ^ key[iter];
127  return val & (netlbl_domhsh_rcu_deref(netlbl_domhsh)->size - 1);
128 }
129 
141 static struct netlbl_dom_map *netlbl_domhsh_search(const char *domain)
142 {
143  u32 bkt;
144  struct list_head *bkt_list;
145  struct netlbl_dom_map *iter;
146 
147  if (domain != NULL) {
148  bkt = netlbl_domhsh_hash(domain);
149  bkt_list = &netlbl_domhsh_rcu_deref(netlbl_domhsh)->tbl[bkt];
150  list_for_each_entry_rcu(iter, bkt_list, list)
151  if (iter->valid && strcmp(iter->domain, domain) == 0)
152  return iter;
153  }
154 
155  return NULL;
156 }
157 
171 static struct netlbl_dom_map *netlbl_domhsh_search_def(const char *domain)
172 {
173  struct netlbl_dom_map *entry;
174 
175  entry = netlbl_domhsh_search(domain);
176  if (entry == NULL) {
177  entry = netlbl_domhsh_rcu_deref(netlbl_domhsh_def);
178  if (entry != NULL && !entry->valid)
179  entry = NULL;
180  }
181 
182  return entry;
183 }
184 
199 static void netlbl_domhsh_audit_add(struct netlbl_dom_map *entry,
200  struct netlbl_af4list *addr4,
201  struct netlbl_af6list *addr6,
202  int result,
203  struct netlbl_audit *audit_info)
204 {
205  struct audit_buffer *audit_buf;
206  struct cipso_v4_doi *cipsov4 = NULL;
207  u32 type;
208 
209  audit_buf = netlbl_audit_start_common(AUDIT_MAC_MAP_ADD, audit_info);
210  if (audit_buf != NULL) {
211  audit_log_format(audit_buf, " nlbl_domain=%s",
212  entry->domain ? entry->domain : "(default)");
213  if (addr4 != NULL) {
214  struct netlbl_domaddr4_map *map4;
215  map4 = netlbl_domhsh_addr4_entry(addr4);
216  type = map4->type;
217  cipsov4 = map4->type_def.cipsov4;
218  netlbl_af4list_audit_addr(audit_buf, 0, NULL,
219  addr4->addr, addr4->mask);
220 #if IS_ENABLED(CONFIG_IPV6)
221  } else if (addr6 != NULL) {
222  struct netlbl_domaddr6_map *map6;
223  map6 = netlbl_domhsh_addr6_entry(addr6);
224  type = map6->type;
225  netlbl_af6list_audit_addr(audit_buf, 0, NULL,
226  &addr6->addr, &addr6->mask);
227 #endif /* IPv6 */
228  } else {
229  type = entry->type;
230  cipsov4 = entry->type_def.cipsov4;
231  }
232  switch (type) {
234  audit_log_format(audit_buf, " nlbl_protocol=unlbl");
235  break;
237  BUG_ON(cipsov4 == NULL);
238  audit_log_format(audit_buf,
239  " nlbl_protocol=cipsov4 cipso_doi=%u",
240  cipsov4->doi);
241  break;
242  }
243  audit_log_format(audit_buf, " res=%u", result == 0 ? 1 : 0);
244  audit_log_end(audit_buf);
245  }
246 }
247 
248 /*
249  * Domain Hash Table Functions
250  */
251 
263 {
264  u32 iter;
265  struct netlbl_domhsh_tbl *hsh_tbl;
266 
267  if (size == 0)
268  return -EINVAL;
269 
270  hsh_tbl = kmalloc(sizeof(*hsh_tbl), GFP_KERNEL);
271  if (hsh_tbl == NULL)
272  return -ENOMEM;
273  hsh_tbl->size = 1 << size;
274  hsh_tbl->tbl = kcalloc(hsh_tbl->size,
275  sizeof(struct list_head),
276  GFP_KERNEL);
277  if (hsh_tbl->tbl == NULL) {
278  kfree(hsh_tbl);
279  return -ENOMEM;
280  }
281  for (iter = 0; iter < hsh_tbl->size; iter++)
282  INIT_LIST_HEAD(&hsh_tbl->tbl[iter]);
283 
284  spin_lock(&netlbl_domhsh_lock);
285  rcu_assign_pointer(netlbl_domhsh, hsh_tbl);
286  spin_unlock(&netlbl_domhsh_lock);
287 
288  return 0;
289 }
290 
303  struct netlbl_audit *audit_info)
304 {
305  int ret_val = 0;
306  struct netlbl_dom_map *entry_old;
307  struct netlbl_af4list *iter4;
308  struct netlbl_af4list *tmp4;
309 #if IS_ENABLED(CONFIG_IPV6)
310  struct netlbl_af6list *iter6;
311  struct netlbl_af6list *tmp6;
312 #endif /* IPv6 */
313 
314  /* XXX - we can remove this RCU read lock as the spinlock protects the
315  * entire function, but before we do we need to fixup the
316  * netlbl_af[4,6]list RCU functions to do "the right thing" with
317  * respect to rcu_dereference() when only a spinlock is held. */
318  rcu_read_lock();
319  spin_lock(&netlbl_domhsh_lock);
320  if (entry->domain != NULL)
321  entry_old = netlbl_domhsh_search(entry->domain);
322  else
323  entry_old = netlbl_domhsh_search_def(entry->domain);
324  if (entry_old == NULL) {
325  entry->valid = 1;
326 
327  if (entry->domain != NULL) {
328  u32 bkt = netlbl_domhsh_hash(entry->domain);
329  list_add_tail_rcu(&entry->list,
330  &rcu_dereference(netlbl_domhsh)->tbl[bkt]);
331  } else {
332  INIT_LIST_HEAD(&entry->list);
333  rcu_assign_pointer(netlbl_domhsh_def, entry);
334  }
335 
336  if (entry->type == NETLBL_NLTYPE_ADDRSELECT) {
338  &entry->type_def.addrsel->list4)
339  netlbl_domhsh_audit_add(entry, iter4, NULL,
340  ret_val, audit_info);
341 #if IS_ENABLED(CONFIG_IPV6)
342  netlbl_af6list_foreach_rcu(iter6,
343  &entry->type_def.addrsel->list6)
344  netlbl_domhsh_audit_add(entry, NULL, iter6,
345  ret_val, audit_info);
346 #endif /* IPv6 */
347  } else
348  netlbl_domhsh_audit_add(entry, NULL, NULL,
349  ret_val, audit_info);
350  } else if (entry_old->type == NETLBL_NLTYPE_ADDRSELECT &&
351  entry->type == NETLBL_NLTYPE_ADDRSELECT) {
352  struct list_head *old_list4;
353  struct list_head *old_list6;
354 
355  old_list4 = &entry_old->type_def.addrsel->list4;
356  old_list6 = &entry_old->type_def.addrsel->list6;
357 
358  /* we only allow the addition of address selectors if all of
359  * the selectors do not exist in the existing domain map */
361  &entry->type_def.addrsel->list4)
363  iter4->mask,
364  old_list4)) {
365  ret_val = -EEXIST;
366  goto add_return;
367  }
368 #if IS_ENABLED(CONFIG_IPV6)
369  netlbl_af6list_foreach_rcu(iter6,
370  &entry->type_def.addrsel->list6)
371  if (netlbl_af6list_search_exact(&iter6->addr,
372  &iter6->mask,
373  old_list6)) {
374  ret_val = -EEXIST;
375  goto add_return;
376  }
377 #endif /* IPv6 */
378 
379  netlbl_af4list_foreach_safe(iter4, tmp4,
380  &entry->type_def.addrsel->list4) {
382  iter4->valid = 1;
383  ret_val = netlbl_af4list_add(iter4, old_list4);
384  netlbl_domhsh_audit_add(entry_old, iter4, NULL,
385  ret_val, audit_info);
386  if (ret_val != 0)
387  goto add_return;
388  }
389 #if IS_ENABLED(CONFIG_IPV6)
390  netlbl_af6list_foreach_safe(iter6, tmp6,
391  &entry->type_def.addrsel->list6) {
392  netlbl_af6list_remove_entry(iter6);
393  iter6->valid = 1;
394  ret_val = netlbl_af6list_add(iter6, old_list6);
395  netlbl_domhsh_audit_add(entry_old, NULL, iter6,
396  ret_val, audit_info);
397  if (ret_val != 0)
398  goto add_return;
399  }
400 #endif /* IPv6 */
401  } else
402  ret_val = -EINVAL;
403 
404 add_return:
405  spin_unlock(&netlbl_domhsh_lock);
406  rcu_read_unlock();
407  return ret_val;
408 }
409 
422  struct netlbl_audit *audit_info)
423 {
424  return netlbl_domhsh_add(entry, audit_info);
425 }
426 
440  struct netlbl_audit *audit_info)
441 {
442  int ret_val = 0;
443  struct audit_buffer *audit_buf;
444 
445  if (entry == NULL)
446  return -ENOENT;
447 
448  spin_lock(&netlbl_domhsh_lock);
449  if (entry->valid) {
450  entry->valid = 0;
451  if (entry != rcu_dereference(netlbl_domhsh_def))
452  list_del_rcu(&entry->list);
453  else
454  RCU_INIT_POINTER(netlbl_domhsh_def, NULL);
455  } else
456  ret_val = -ENOENT;
457  spin_unlock(&netlbl_domhsh_lock);
458 
459  audit_buf = netlbl_audit_start_common(AUDIT_MAC_MAP_DEL, audit_info);
460  if (audit_buf != NULL) {
461  audit_log_format(audit_buf,
462  " nlbl_domain=%s res=%u",
463  entry->domain ? entry->domain : "(default)",
464  ret_val == 0 ? 1 : 0);
465  audit_log_end(audit_buf);
466  }
467 
468  if (ret_val == 0) {
469  struct netlbl_af4list *iter4;
470  struct netlbl_domaddr4_map *map4;
471 
472  switch (entry->type) {
475  &entry->type_def.addrsel->list4) {
476  map4 = netlbl_domhsh_addr4_entry(iter4);
478  }
479  /* no need to check the IPv6 list since we currently
480  * support only unlabeled protocols for IPv6 */
481  break;
484  break;
485  }
486  call_rcu(&entry->rcu, netlbl_domhsh_free_entry);
487  }
488 
489  return ret_val;
490 }
491 
505 int netlbl_domhsh_remove_af4(const char *domain,
506  const struct in_addr *addr,
507  const struct in_addr *mask,
508  struct netlbl_audit *audit_info)
509 {
510  struct netlbl_dom_map *entry_map;
511  struct netlbl_af4list *entry_addr;
512  struct netlbl_af4list *iter4;
513 #if IS_ENABLED(CONFIG_IPV6)
514  struct netlbl_af6list *iter6;
515 #endif /* IPv6 */
516  struct netlbl_domaddr4_map *entry;
517 
518  rcu_read_lock();
519 
520  if (domain)
521  entry_map = netlbl_domhsh_search(domain);
522  else
523  entry_map = netlbl_domhsh_search_def(domain);
524  if (entry_map == NULL || entry_map->type != NETLBL_NLTYPE_ADDRSELECT)
525  goto remove_af4_failure;
526 
527  spin_lock(&netlbl_domhsh_lock);
528  entry_addr = netlbl_af4list_remove(addr->s_addr, mask->s_addr,
529  &entry_map->type_def.addrsel->list4);
530  spin_unlock(&netlbl_domhsh_lock);
531 
532  if (entry_addr == NULL)
533  goto remove_af4_failure;
534  netlbl_af4list_foreach_rcu(iter4, &entry_map->type_def.addrsel->list4)
535  goto remove_af4_single_addr;
536 #if IS_ENABLED(CONFIG_IPV6)
537  netlbl_af6list_foreach_rcu(iter6, &entry_map->type_def.addrsel->list6)
538  goto remove_af4_single_addr;
539 #endif /* IPv6 */
540  /* the domain mapping is empty so remove it from the mapping table */
541  netlbl_domhsh_remove_entry(entry_map, audit_info);
542 
543 remove_af4_single_addr:
544  rcu_read_unlock();
545  /* yick, we can't use call_rcu here because we don't have a rcu head
546  * pointer but hopefully this should be a rare case so the pause
547  * shouldn't be a problem */
548  synchronize_rcu();
549  entry = netlbl_domhsh_addr4_entry(entry_addr);
551  kfree(entry);
552  return 0;
553 
554 remove_af4_failure:
555  rcu_read_unlock();
556  return -ENOENT;
557 }
558 
570 int netlbl_domhsh_remove(const char *domain, struct netlbl_audit *audit_info)
571 {
572  int ret_val;
573  struct netlbl_dom_map *entry;
574 
575  rcu_read_lock();
576  if (domain)
577  entry = netlbl_domhsh_search(domain);
578  else
579  entry = netlbl_domhsh_search_def(domain);
580  ret_val = netlbl_domhsh_remove_entry(entry, audit_info);
581  rcu_read_unlock();
582 
583  return ret_val;
584 }
585 
597 {
598  return netlbl_domhsh_remove(NULL, audit_info);
599 }
600 
611 struct netlbl_dom_map *netlbl_domhsh_getentry(const char *domain)
612 {
613  return netlbl_domhsh_search_def(domain);
614 }
615 
628  __be32 addr)
629 {
630  struct netlbl_dom_map *dom_iter;
631  struct netlbl_af4list *addr_iter;
632 
633  dom_iter = netlbl_domhsh_search_def(domain);
634  if (dom_iter == NULL)
635  return NULL;
636  if (dom_iter->type != NETLBL_NLTYPE_ADDRSELECT)
637  return NULL;
638 
639  addr_iter = netlbl_af4list_search(addr,
640  &dom_iter->type_def.addrsel->list4);
641  if (addr_iter == NULL)
642  return NULL;
643 
644  return netlbl_domhsh_addr4_entry(addr_iter);
645 }
646 
647 #if IS_ENABLED(CONFIG_IPV6)
648 
659 struct netlbl_domaddr6_map *netlbl_domhsh_getentry_af6(const char *domain,
660  const struct in6_addr *addr)
661 {
662  struct netlbl_dom_map *dom_iter;
663  struct netlbl_af6list *addr_iter;
664 
665  dom_iter = netlbl_domhsh_search_def(domain);
666  if (dom_iter == NULL)
667  return NULL;
668  if (dom_iter->type != NETLBL_NLTYPE_ADDRSELECT)
669  return NULL;
670 
671  addr_iter = netlbl_af6list_search(addr,
672  &dom_iter->type_def.addrsel->list6);
673  if (addr_iter == NULL)
674  return NULL;
675 
676  return netlbl_domhsh_addr6_entry(addr_iter);
677 }
678 #endif /* IPv6 */
679 
695 int netlbl_domhsh_walk(u32 *skip_bkt,
696  u32 *skip_chain,
697  int (*callback) (struct netlbl_dom_map *entry, void *arg),
698  void *cb_arg)
699 {
700  int ret_val = -ENOENT;
701  u32 iter_bkt;
702  struct list_head *iter_list;
703  struct netlbl_dom_map *iter_entry;
704  u32 chain_cnt = 0;
705 
706  rcu_read_lock();
707  for (iter_bkt = *skip_bkt;
708  iter_bkt < rcu_dereference(netlbl_domhsh)->size;
709  iter_bkt++, chain_cnt = 0) {
710  iter_list = &rcu_dereference(netlbl_domhsh)->tbl[iter_bkt];
711  list_for_each_entry_rcu(iter_entry, iter_list, list)
712  if (iter_entry->valid) {
713  if (chain_cnt++ < *skip_chain)
714  continue;
715  ret_val = callback(iter_entry, cb_arg);
716  if (ret_val < 0) {
717  chain_cnt--;
718  goto walk_return;
719  }
720  }
721  }
722 
724  rcu_read_unlock();
725  *skip_bkt = iter_bkt;
726  *skip_chain = chain_cnt;
727  return ret_val;
728 }