Path: blob/master/net/netlabel/netlabel_domainhash.c
15109 views
/*1* NetLabel Domain Hash Table2*3* This file manages the domain hash table that NetLabel uses to determine4* which network labeling protocol to use for a given domain. The NetLabel5* system manages static and dynamic label mappings for network protocols such6* as CIPSO and RIPSO.7*8* Author: Paul Moore <[email protected]>9*10*/1112/*13* (c) Copyright Hewlett-Packard Development Company, L.P., 2006, 200814*15* This program is free software; you can redistribute it and/or modify16* it under the terms of the GNU General Public License as published by17* the Free Software Foundation; either version 2 of the License, or18* (at your option) any later version.19*20* This program is distributed in the hope that it will be useful,21* but WITHOUT ANY WARRANTY; without even the implied warranty of22* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See23* the GNU General Public License for more details.24*25* You should have received a copy of the GNU General Public License26* along with this program; if not, write to the Free Software27* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA28*29*/3031#include <linux/types.h>32#include <linux/rculist.h>33#include <linux/skbuff.h>34#include <linux/spinlock.h>35#include <linux/string.h>36#include <linux/audit.h>37#include <linux/slab.h>38#include <net/netlabel.h>39#include <net/cipso_ipv4.h>40#include <asm/bug.h>4142#include "netlabel_mgmt.h"43#include "netlabel_addrlist.h"44#include "netlabel_domainhash.h"45#include "netlabel_user.h"4647struct netlbl_domhsh_tbl {48struct list_head *tbl;49u32 size;50};5152/* Domain hash table */53/* updates should be so rare that having one spinlock for the entire hash table54* should be okay */55static DEFINE_SPINLOCK(netlbl_domhsh_lock);56#define netlbl_domhsh_rcu_deref(p) \57rcu_dereference_check(p, rcu_read_lock_held() || \58lockdep_is_held(&netlbl_domhsh_lock))59static struct netlbl_domhsh_tbl *netlbl_domhsh = NULL;60static struct netlbl_dom_map *netlbl_domhsh_def = NULL;6162/*63* Domain Hash Table Helper Functions64*/6566/**67* netlbl_domhsh_free_entry - Frees a domain hash table entry68* @entry: the entry's RCU field69*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 released73* safely.74*75*/76static void netlbl_domhsh_free_entry(struct rcu_head *entry)77{78struct netlbl_dom_map *ptr;79struct netlbl_af4list *iter4;80struct netlbl_af4list *tmp4;81#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)82struct netlbl_af6list *iter6;83struct netlbl_af6list *tmp6;84#endif /* IPv6 */8586ptr = container_of(entry, struct netlbl_dom_map, rcu);87if (ptr->type == NETLBL_NLTYPE_ADDRSELECT) {88netlbl_af4list_foreach_safe(iter4, tmp4,89&ptr->type_def.addrsel->list4) {90netlbl_af4list_remove_entry(iter4);91kfree(netlbl_domhsh_addr4_entry(iter4));92}93#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)94netlbl_af6list_foreach_safe(iter6, tmp6,95&ptr->type_def.addrsel->list6) {96netlbl_af6list_remove_entry(iter6);97kfree(netlbl_domhsh_addr6_entry(iter6));98}99#endif /* IPv6 */100}101kfree(ptr->domain);102kfree(ptr);103}104105/**106* netlbl_domhsh_hash - Hashing function for the domain hash table107* @domain: the domain name to hash108*109* Description:110* This is the hashing function for the domain hash table, it returns the111* correct bucket number for the domain. The caller is responsible for112* ensuring that the hash table is protected with either a RCU read lock or the113* hash table lock.114*115*/116static u32 netlbl_domhsh_hash(const char *key)117{118u32 iter;119u32 val;120u32 len;121122/* This is taken (with slight modification) from123* security/selinux/ss/symtab.c:symhash() */124125for (iter = 0, val = 0, len = strlen(key); iter < len; iter++)126val = (val << 4 | (val >> (8 * sizeof(u32) - 4))) ^ key[iter];127return val & (netlbl_domhsh_rcu_deref(netlbl_domhsh)->size - 1);128}129130/**131* netlbl_domhsh_search - Search for a domain entry132* @domain: the domain133*134* Description:135* Searches the domain hash table and returns a pointer to the hash table136* entry if found, otherwise NULL is returned. The caller is responsible for137* ensuring that the hash table is protected with either a RCU read lock or the138* hash table lock.139*140*/141static struct netlbl_dom_map *netlbl_domhsh_search(const char *domain)142{143u32 bkt;144struct list_head *bkt_list;145struct netlbl_dom_map *iter;146147if (domain != NULL) {148bkt = netlbl_domhsh_hash(domain);149bkt_list = &netlbl_domhsh_rcu_deref(netlbl_domhsh)->tbl[bkt];150list_for_each_entry_rcu(iter, bkt_list, list)151if (iter->valid && strcmp(iter->domain, domain) == 0)152return iter;153}154155return NULL;156}157158/**159* netlbl_domhsh_search_def - Search for a domain entry160* @domain: the domain161* @def: return default if no match is found162*163* Description:164* Searches the domain hash table and returns a pointer to the hash table165* entry if an exact match is found, if an exact match is not present in the166* hash table then the default entry is returned if valid otherwise NULL is167* returned. The caller is responsible ensuring that the hash table is168* protected with either a RCU read lock or the hash table lock.169*170*/171static struct netlbl_dom_map *netlbl_domhsh_search_def(const char *domain)172{173struct netlbl_dom_map *entry;174175entry = netlbl_domhsh_search(domain);176if (entry == NULL) {177entry = netlbl_domhsh_rcu_deref(netlbl_domhsh_def);178if (entry != NULL && !entry->valid)179entry = NULL;180}181182return entry;183}184185/**186* netlbl_domhsh_audit_add - Generate an audit entry for an add event187* @entry: the entry being added188* @addr4: the IPv4 address information189* @addr6: the IPv6 address information190* @result: the result code191* @audit_info: NetLabel audit information192*193* Description:194* Generate an audit record for adding a new NetLabel/LSM mapping entry with195* the given information. Caller is responsible for holding the necessary196* locks.197*198*/199static void netlbl_domhsh_audit_add(struct netlbl_dom_map *entry,200struct netlbl_af4list *addr4,201struct netlbl_af6list *addr6,202int result,203struct netlbl_audit *audit_info)204{205struct audit_buffer *audit_buf;206struct cipso_v4_doi *cipsov4 = NULL;207u32 type;208209audit_buf = netlbl_audit_start_common(AUDIT_MAC_MAP_ADD, audit_info);210if (audit_buf != NULL) {211audit_log_format(audit_buf, " nlbl_domain=%s",212entry->domain ? entry->domain : "(default)");213if (addr4 != NULL) {214struct netlbl_domaddr4_map *map4;215map4 = netlbl_domhsh_addr4_entry(addr4);216type = map4->type;217cipsov4 = map4->type_def.cipsov4;218netlbl_af4list_audit_addr(audit_buf, 0, NULL,219addr4->addr, addr4->mask);220#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)221} else if (addr6 != NULL) {222struct netlbl_domaddr6_map *map6;223map6 = netlbl_domhsh_addr6_entry(addr6);224type = map6->type;225netlbl_af6list_audit_addr(audit_buf, 0, NULL,226&addr6->addr, &addr6->mask);227#endif /* IPv6 */228} else {229type = entry->type;230cipsov4 = entry->type_def.cipsov4;231}232switch (type) {233case NETLBL_NLTYPE_UNLABELED:234audit_log_format(audit_buf, " nlbl_protocol=unlbl");235break;236case NETLBL_NLTYPE_CIPSOV4:237BUG_ON(cipsov4 == NULL);238audit_log_format(audit_buf,239" nlbl_protocol=cipsov4 cipso_doi=%u",240cipsov4->doi);241break;242}243audit_log_format(audit_buf, " res=%u", result == 0 ? 1 : 0);244audit_log_end(audit_buf);245}246}247248/*249* Domain Hash Table Functions250*/251252/**253* netlbl_domhsh_init - Init for the domain hash254* @size: the number of bits to use for the hash buckets255*256* Description:257* Initializes the domain hash table, should be called only by258* netlbl_user_init() during initialization. Returns zero on success, non-zero259* values on error.260*261*/262int __init netlbl_domhsh_init(u32 size)263{264u32 iter;265struct netlbl_domhsh_tbl *hsh_tbl;266267if (size == 0)268return -EINVAL;269270hsh_tbl = kmalloc(sizeof(*hsh_tbl), GFP_KERNEL);271if (hsh_tbl == NULL)272return -ENOMEM;273hsh_tbl->size = 1 << size;274hsh_tbl->tbl = kcalloc(hsh_tbl->size,275sizeof(struct list_head),276GFP_KERNEL);277if (hsh_tbl->tbl == NULL) {278kfree(hsh_tbl);279return -ENOMEM;280}281for (iter = 0; iter < hsh_tbl->size; iter++)282INIT_LIST_HEAD(&hsh_tbl->tbl[iter]);283284spin_lock(&netlbl_domhsh_lock);285rcu_assign_pointer(netlbl_domhsh, hsh_tbl);286spin_unlock(&netlbl_domhsh_lock);287288return 0;289}290291/**292* netlbl_domhsh_add - Adds a entry to the domain hash table293* @entry: the entry to add294* @audit_info: NetLabel audit information295*296* Description:297* Adds a new entry to the domain hash table and handles any updates to the298* lower level protocol handler (i.e. CIPSO). Returns zero on success,299* negative on failure.300*301*/302int netlbl_domhsh_add(struct netlbl_dom_map *entry,303struct netlbl_audit *audit_info)304{305int ret_val = 0;306struct netlbl_dom_map *entry_old;307struct netlbl_af4list *iter4;308struct netlbl_af4list *tmp4;309#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)310struct netlbl_af6list *iter6;311struct netlbl_af6list *tmp6;312#endif /* IPv6 */313314/* XXX - we can remove this RCU read lock as the spinlock protects the315* entire function, but before we do we need to fixup the316* netlbl_af[4,6]list RCU functions to do "the right thing" with317* respect to rcu_dereference() when only a spinlock is held. */318rcu_read_lock();319spin_lock(&netlbl_domhsh_lock);320if (entry->domain != NULL)321entry_old = netlbl_domhsh_search(entry->domain);322else323entry_old = netlbl_domhsh_search_def(entry->domain);324if (entry_old == NULL) {325entry->valid = 1;326327if (entry->domain != NULL) {328u32 bkt = netlbl_domhsh_hash(entry->domain);329list_add_tail_rcu(&entry->list,330&rcu_dereference(netlbl_domhsh)->tbl[bkt]);331} else {332INIT_LIST_HEAD(&entry->list);333rcu_assign_pointer(netlbl_domhsh_def, entry);334}335336if (entry->type == NETLBL_NLTYPE_ADDRSELECT) {337netlbl_af4list_foreach_rcu(iter4,338&entry->type_def.addrsel->list4)339netlbl_domhsh_audit_add(entry, iter4, NULL,340ret_val, audit_info);341#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)342netlbl_af6list_foreach_rcu(iter6,343&entry->type_def.addrsel->list6)344netlbl_domhsh_audit_add(entry, NULL, iter6,345ret_val, audit_info);346#endif /* IPv6 */347} else348netlbl_domhsh_audit_add(entry, NULL, NULL,349ret_val, audit_info);350} else if (entry_old->type == NETLBL_NLTYPE_ADDRSELECT &&351entry->type == NETLBL_NLTYPE_ADDRSELECT) {352struct list_head *old_list4;353struct list_head *old_list6;354355old_list4 = &entry_old->type_def.addrsel->list4;356old_list6 = &entry_old->type_def.addrsel->list6;357358/* we only allow the addition of address selectors if all of359* the selectors do not exist in the existing domain map */360netlbl_af4list_foreach_rcu(iter4,361&entry->type_def.addrsel->list4)362if (netlbl_af4list_search_exact(iter4->addr,363iter4->mask,364old_list4)) {365ret_val = -EEXIST;366goto add_return;367}368#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)369netlbl_af6list_foreach_rcu(iter6,370&entry->type_def.addrsel->list6)371if (netlbl_af6list_search_exact(&iter6->addr,372&iter6->mask,373old_list6)) {374ret_val = -EEXIST;375goto add_return;376}377#endif /* IPv6 */378379netlbl_af4list_foreach_safe(iter4, tmp4,380&entry->type_def.addrsel->list4) {381netlbl_af4list_remove_entry(iter4);382iter4->valid = 1;383ret_val = netlbl_af4list_add(iter4, old_list4);384netlbl_domhsh_audit_add(entry_old, iter4, NULL,385ret_val, audit_info);386if (ret_val != 0)387goto add_return;388}389#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)390netlbl_af6list_foreach_safe(iter6, tmp6,391&entry->type_def.addrsel->list6) {392netlbl_af6list_remove_entry(iter6);393iter6->valid = 1;394ret_val = netlbl_af6list_add(iter6, old_list6);395netlbl_domhsh_audit_add(entry_old, NULL, iter6,396ret_val, audit_info);397if (ret_val != 0)398goto add_return;399}400#endif /* IPv6 */401} else402ret_val = -EINVAL;403404add_return:405spin_unlock(&netlbl_domhsh_lock);406rcu_read_unlock();407return ret_val;408}409410/**411* netlbl_domhsh_add_default - Adds the default entry to the domain hash table412* @entry: the entry to add413* @audit_info: NetLabel audit information414*415* Description:416* Adds a new default entry to the domain hash table and handles any updates417* to the lower level protocol handler (i.e. CIPSO). Returns zero on success,418* negative on failure.419*420*/421int netlbl_domhsh_add_default(struct netlbl_dom_map *entry,422struct netlbl_audit *audit_info)423{424return netlbl_domhsh_add(entry, audit_info);425}426427/**428* netlbl_domhsh_remove_entry - Removes a given entry from the domain table429* @entry: the entry to remove430* @audit_info: NetLabel audit information431*432* Description:433* Removes an entry from the domain hash table and handles any updates to the434* lower level protocol handler (i.e. CIPSO). Caller is responsible for435* ensuring that the RCU read lock is held. Returns zero on success, negative436* on failure.437*438*/439int netlbl_domhsh_remove_entry(struct netlbl_dom_map *entry,440struct netlbl_audit *audit_info)441{442int ret_val = 0;443struct audit_buffer *audit_buf;444445if (entry == NULL)446return -ENOENT;447448spin_lock(&netlbl_domhsh_lock);449if (entry->valid) {450entry->valid = 0;451if (entry != rcu_dereference(netlbl_domhsh_def))452list_del_rcu(&entry->list);453else454rcu_assign_pointer(netlbl_domhsh_def, NULL);455} else456ret_val = -ENOENT;457spin_unlock(&netlbl_domhsh_lock);458459audit_buf = netlbl_audit_start_common(AUDIT_MAC_MAP_DEL, audit_info);460if (audit_buf != NULL) {461audit_log_format(audit_buf,462" nlbl_domain=%s res=%u",463entry->domain ? entry->domain : "(default)",464ret_val == 0 ? 1 : 0);465audit_log_end(audit_buf);466}467468if (ret_val == 0) {469struct netlbl_af4list *iter4;470struct netlbl_domaddr4_map *map4;471472switch (entry->type) {473case NETLBL_NLTYPE_ADDRSELECT:474netlbl_af4list_foreach_rcu(iter4,475&entry->type_def.addrsel->list4) {476map4 = netlbl_domhsh_addr4_entry(iter4);477cipso_v4_doi_putdef(map4->type_def.cipsov4);478}479/* no need to check the IPv6 list since we currently480* support only unlabeled protocols for IPv6 */481break;482case NETLBL_NLTYPE_CIPSOV4:483cipso_v4_doi_putdef(entry->type_def.cipsov4);484break;485}486call_rcu(&entry->rcu, netlbl_domhsh_free_entry);487}488489return ret_val;490}491492/**493* netlbl_domhsh_remove_af4 - Removes an address selector entry494* @domain: the domain495* @addr: IPv4 address496* @mask: IPv4 address mask497* @audit_info: NetLabel audit information498*499* Description:500* Removes an individual address selector from a domain mapping and potentially501* the entire mapping if it is empty. Returns zero on success, negative values502* on failure.503*504*/505int netlbl_domhsh_remove_af4(const char *domain,506const struct in_addr *addr,507const struct in_addr *mask,508struct netlbl_audit *audit_info)509{510struct netlbl_dom_map *entry_map;511struct netlbl_af4list *entry_addr;512struct netlbl_af4list *iter4;513#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)514struct netlbl_af6list *iter6;515#endif /* IPv6 */516struct netlbl_domaddr4_map *entry;517518rcu_read_lock();519520if (domain)521entry_map = netlbl_domhsh_search(domain);522else523entry_map = netlbl_domhsh_search_def(domain);524if (entry_map == NULL || entry_map->type != NETLBL_NLTYPE_ADDRSELECT)525goto remove_af4_failure;526527spin_lock(&netlbl_domhsh_lock);528entry_addr = netlbl_af4list_remove(addr->s_addr, mask->s_addr,529&entry_map->type_def.addrsel->list4);530spin_unlock(&netlbl_domhsh_lock);531532if (entry_addr == NULL)533goto remove_af4_failure;534netlbl_af4list_foreach_rcu(iter4, &entry_map->type_def.addrsel->list4)535goto remove_af4_single_addr;536#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)537netlbl_af6list_foreach_rcu(iter6, &entry_map->type_def.addrsel->list6)538goto remove_af4_single_addr;539#endif /* IPv6 */540/* the domain mapping is empty so remove it from the mapping table */541netlbl_domhsh_remove_entry(entry_map, audit_info);542543remove_af4_single_addr:544rcu_read_unlock();545/* yick, we can't use call_rcu here because we don't have a rcu head546* pointer but hopefully this should be a rare case so the pause547* shouldn't be a problem */548synchronize_rcu();549entry = netlbl_domhsh_addr4_entry(entry_addr);550cipso_v4_doi_putdef(entry->type_def.cipsov4);551kfree(entry);552return 0;553554remove_af4_failure:555rcu_read_unlock();556return -ENOENT;557}558559/**560* netlbl_domhsh_remove - Removes an entry from the domain hash table561* @domain: the domain to remove562* @audit_info: NetLabel audit information563*564* Description:565* Removes an entry from the domain hash table and handles any updates to the566* lower level protocol handler (i.e. CIPSO). Returns zero on success,567* negative on failure.568*569*/570int netlbl_domhsh_remove(const char *domain, struct netlbl_audit *audit_info)571{572int ret_val;573struct netlbl_dom_map *entry;574575rcu_read_lock();576if (domain)577entry = netlbl_domhsh_search(domain);578else579entry = netlbl_domhsh_search_def(domain);580ret_val = netlbl_domhsh_remove_entry(entry, audit_info);581rcu_read_unlock();582583return ret_val;584}585586/**587* netlbl_domhsh_remove_default - Removes the default entry from the table588* @audit_info: NetLabel audit information589*590* Description:591* Removes/resets the default entry for the domain hash table and handles any592* updates to the lower level protocol handler (i.e. CIPSO). Returns zero on593* success, non-zero on failure.594*595*/596int netlbl_domhsh_remove_default(struct netlbl_audit *audit_info)597{598return netlbl_domhsh_remove(NULL, audit_info);599}600601/**602* netlbl_domhsh_getentry - Get an entry from the domain hash table603* @domain: the domain name to search for604*605* Description:606* Look through the domain hash table searching for an entry to match @domain,607* return a pointer to a copy of the entry or NULL. The caller is responsible608* for ensuring that rcu_read_[un]lock() is called.609*610*/611struct netlbl_dom_map *netlbl_domhsh_getentry(const char *domain)612{613return netlbl_domhsh_search_def(domain);614}615616/**617* netlbl_domhsh_getentry_af4 - Get an entry from the domain hash table618* @domain: the domain name to search for619* @addr: the IP address to search for620*621* Description:622* Look through the domain hash table searching for an entry to match @domain623* and @addr, return a pointer to a copy of the entry or NULL. The caller is624* responsible for ensuring that rcu_read_[un]lock() is called.625*626*/627struct netlbl_domaddr4_map *netlbl_domhsh_getentry_af4(const char *domain,628__be32 addr)629{630struct netlbl_dom_map *dom_iter;631struct netlbl_af4list *addr_iter;632633dom_iter = netlbl_domhsh_search_def(domain);634if (dom_iter == NULL)635return NULL;636if (dom_iter->type != NETLBL_NLTYPE_ADDRSELECT)637return NULL;638639addr_iter = netlbl_af4list_search(addr,640&dom_iter->type_def.addrsel->list4);641if (addr_iter == NULL)642return NULL;643644return netlbl_domhsh_addr4_entry(addr_iter);645}646647#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)648/**649* netlbl_domhsh_getentry_af6 - Get an entry from the domain hash table650* @domain: the domain name to search for651* @addr: the IP address to search for652*653* Description:654* Look through the domain hash table searching for an entry to match @domain655* and @addr, return a pointer to a copy of the entry or NULL. The caller is656* responsible for ensuring that rcu_read_[un]lock() is called.657*658*/659struct netlbl_domaddr6_map *netlbl_domhsh_getentry_af6(const char *domain,660const struct in6_addr *addr)661{662struct netlbl_dom_map *dom_iter;663struct netlbl_af6list *addr_iter;664665dom_iter = netlbl_domhsh_search_def(domain);666if (dom_iter == NULL)667return NULL;668if (dom_iter->type != NETLBL_NLTYPE_ADDRSELECT)669return NULL;670671addr_iter = netlbl_af6list_search(addr,672&dom_iter->type_def.addrsel->list6);673if (addr_iter == NULL)674return NULL;675676return netlbl_domhsh_addr6_entry(addr_iter);677}678#endif /* IPv6 */679680/**681* netlbl_domhsh_walk - Iterate through the domain mapping hash table682* @skip_bkt: the number of buckets to skip at the start683* @skip_chain: the number of entries to skip in the first iterated bucket684* @callback: callback for each entry685* @cb_arg: argument for the callback function686*687* Description:688* Interate over the domain mapping hash table, skipping the first @skip_bkt689* buckets and @skip_chain entries. For each entry in the table call690* @callback, if @callback returns a negative value stop 'walking' through the691* table and return. Updates the values in @skip_bkt and @skip_chain on692* return. Returns zero on success, negative values on failure.693*694*/695int netlbl_domhsh_walk(u32 *skip_bkt,696u32 *skip_chain,697int (*callback) (struct netlbl_dom_map *entry, void *arg),698void *cb_arg)699{700int ret_val = -ENOENT;701u32 iter_bkt;702struct list_head *iter_list;703struct netlbl_dom_map *iter_entry;704u32 chain_cnt = 0;705706rcu_read_lock();707for (iter_bkt = *skip_bkt;708iter_bkt < rcu_dereference(netlbl_domhsh)->size;709iter_bkt++, chain_cnt = 0) {710iter_list = &rcu_dereference(netlbl_domhsh)->tbl[iter_bkt];711list_for_each_entry_rcu(iter_entry, iter_list, list)712if (iter_entry->valid) {713if (chain_cnt++ < *skip_chain)714continue;715ret_val = callback(iter_entry, cb_arg);716if (ret_val < 0) {717chain_cnt--;718goto walk_return;719}720}721}722723walk_return:724rcu_read_unlock();725*skip_bkt = iter_bkt;726*skip_chain = chain_cnt;727return ret_val;728}729730731