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 * @def: return default if no match is found 113 * 114 * Description: 115 * Searches the domain hash table and returns a pointer to the hash table 116 * entry if found, otherwise NULL is returned. If @def is non-zero and a 117 * match is not found in the domain hash table the default mapping is returned 118 * if it exists. The caller is responsibile for the rcu hash table locks 119 * (i.e. the caller much call rcu_read_[un]lock()). 120 * 121 */ 122 static struct netlbl_dom_map *netlbl_domhsh_search(const char *domain, u32 def) 123 { 124 u32 bkt; 125 struct netlbl_dom_map *iter; 126 127 if (domain != NULL) { 128 bkt = netlbl_domhsh_hash(domain); 129 list_for_each_entry_rcu(iter, 130 &rcu_dereference(netlbl_domhsh)->tbl[bkt], 131 list) 132 if (iter->valid && strcmp(iter->domain, domain) == 0) 133 return iter; 134 } 135 136 if (def != 0) { 137 iter = rcu_dereference(netlbl_domhsh_def); 138 if (iter != NULL && iter->valid) 139 return iter; 140 } 141 142 return NULL; 143 } 144 145 /* 146 * Domain Hash Table Functions 147 */ 148 149 /** 150 * netlbl_domhsh_init - Init for the domain hash 151 * @size: the number of bits to use for the hash buckets 152 * 153 * Description: 154 * Initializes the domain hash table, should be called only by 155 * netlbl_user_init() during initialization. Returns zero on success, non-zero 156 * values on error. 157 * 158 */ 159 int netlbl_domhsh_init(u32 size) 160 { 161 u32 iter; 162 struct netlbl_domhsh_tbl *hsh_tbl; 163 164 if (size == 0) 165 return -EINVAL; 166 167 hsh_tbl = kmalloc(sizeof(*hsh_tbl), GFP_KERNEL); 168 if (hsh_tbl == NULL) 169 return -ENOMEM; 170 hsh_tbl->size = 1 << size; 171 hsh_tbl->tbl = kcalloc(hsh_tbl->size, 172 sizeof(struct list_head), 173 GFP_KERNEL); 174 if (hsh_tbl->tbl == NULL) { 175 kfree(hsh_tbl); 176 return -ENOMEM; 177 } 178 for (iter = 0; iter < hsh_tbl->size; iter++) 179 INIT_LIST_HEAD(&hsh_tbl->tbl[iter]); 180 181 rcu_read_lock(); 182 spin_lock(&netlbl_domhsh_lock); 183 rcu_assign_pointer(netlbl_domhsh, hsh_tbl); 184 spin_unlock(&netlbl_domhsh_lock); 185 rcu_read_unlock(); 186 187 return 0; 188 } 189 190 /** 191 * netlbl_domhsh_add - Adds a entry to the domain hash table 192 * @entry: the entry to add 193 * @audit_info: NetLabel audit information 194 * 195 * Description: 196 * Adds a new entry to the domain hash table and handles any updates to the 197 * lower level protocol handler (i.e. CIPSO). Returns zero on success, 198 * negative on failure. 199 * 200 */ 201 int netlbl_domhsh_add(struct netlbl_dom_map *entry, 202 struct netlbl_audit *audit_info) 203 { 204 int ret_val; 205 u32 bkt; 206 struct audit_buffer *audit_buf; 207 208 switch (entry->type) { 209 case NETLBL_NLTYPE_UNLABELED: 210 ret_val = 0; 211 break; 212 case NETLBL_NLTYPE_CIPSOV4: 213 ret_val = cipso_v4_doi_domhsh_add(entry->type_def.cipsov4, 214 entry->domain); 215 break; 216 default: 217 return -EINVAL; 218 } 219 if (ret_val != 0) 220 return ret_val; 221 222 entry->valid = 1; 223 INIT_RCU_HEAD(&entry->rcu); 224 225 ret_val = 0; 226 rcu_read_lock(); 227 if (entry->domain != NULL) { 228 bkt = netlbl_domhsh_hash(entry->domain); 229 spin_lock(&netlbl_domhsh_lock); 230 if (netlbl_domhsh_search(entry->domain, 0) == NULL) 231 list_add_tail_rcu(&entry->list, 232 &rcu_dereference(netlbl_domhsh)->tbl[bkt]); 233 else 234 ret_val = -EEXIST; 235 spin_unlock(&netlbl_domhsh_lock); 236 } else if (entry->domain == NULL) { 237 INIT_LIST_HEAD(&entry->list); 238 spin_lock(&netlbl_domhsh_def_lock); 239 if (rcu_dereference(netlbl_domhsh_def) == NULL) 240 rcu_assign_pointer(netlbl_domhsh_def, entry); 241 else 242 ret_val = -EEXIST; 243 spin_unlock(&netlbl_domhsh_def_lock); 244 } else 245 ret_val = -EINVAL; 246 247 audit_buf = netlbl_audit_start_common(AUDIT_MAC_MAP_ADD, audit_info); 248 if (audit_buf != NULL) { 249 audit_log_format(audit_buf, 250 " nlbl_domain=%s", 251 entry->domain ? entry->domain : "(default)"); 252 switch (entry->type) { 253 case NETLBL_NLTYPE_UNLABELED: 254 audit_log_format(audit_buf, " nlbl_protocol=unlbl"); 255 break; 256 case NETLBL_NLTYPE_CIPSOV4: 257 audit_log_format(audit_buf, 258 " nlbl_protocol=cipsov4 cipso_doi=%u", 259 entry->type_def.cipsov4->doi); 260 break; 261 } 262 audit_log_format(audit_buf, " res=%u", ret_val == 0 ? 1 : 0); 263 audit_log_end(audit_buf); 264 } 265 266 rcu_read_unlock(); 267 268 if (ret_val != 0) { 269 switch (entry->type) { 270 case NETLBL_NLTYPE_CIPSOV4: 271 if (cipso_v4_doi_domhsh_remove(entry->type_def.cipsov4, 272 entry->domain) != 0) 273 BUG(); 274 break; 275 } 276 } 277 278 return ret_val; 279 } 280 281 /** 282 * netlbl_domhsh_add_default - Adds the default entry to the domain hash table 283 * @entry: the entry to add 284 * @audit_info: NetLabel audit information 285 * 286 * Description: 287 * Adds a new default entry to the domain hash table and handles any updates 288 * to the lower level protocol handler (i.e. CIPSO). Returns zero on success, 289 * negative on failure. 290 * 291 */ 292 int netlbl_domhsh_add_default(struct netlbl_dom_map *entry, 293 struct netlbl_audit *audit_info) 294 { 295 return netlbl_domhsh_add(entry, audit_info); 296 } 297 298 /** 299 * netlbl_domhsh_remove - Removes an entry from the domain hash table 300 * @domain: the domain to remove 301 * @audit_info: NetLabel audit information 302 * 303 * Description: 304 * Removes an entry from the domain hash table and handles any updates to the 305 * lower level protocol handler (i.e. CIPSO). Returns zero on success, 306 * negative on failure. 307 * 308 */ 309 int netlbl_domhsh_remove(const char *domain, struct netlbl_audit *audit_info) 310 { 311 int ret_val = -ENOENT; 312 struct netlbl_dom_map *entry; 313 struct audit_buffer *audit_buf; 314 315 rcu_read_lock(); 316 if (domain != NULL) 317 entry = netlbl_domhsh_search(domain, 0); 318 else 319 entry = netlbl_domhsh_search(domain, 1); 320 if (entry == NULL) 321 goto remove_return; 322 switch (entry->type) { 323 case NETLBL_NLTYPE_UNLABELED: 324 break; 325 case NETLBL_NLTYPE_CIPSOV4: 326 ret_val = cipso_v4_doi_domhsh_remove(entry->type_def.cipsov4, 327 entry->domain); 328 if (ret_val != 0) 329 goto remove_return; 330 break; 331 } 332 ret_val = 0; 333 if (entry != rcu_dereference(netlbl_domhsh_def)) { 334 spin_lock(&netlbl_domhsh_lock); 335 if (entry->valid) { 336 entry->valid = 0; 337 list_del_rcu(&entry->list); 338 } else 339 ret_val = -ENOENT; 340 spin_unlock(&netlbl_domhsh_lock); 341 } else { 342 spin_lock(&netlbl_domhsh_def_lock); 343 if (entry->valid) { 344 entry->valid = 0; 345 rcu_assign_pointer(netlbl_domhsh_def, NULL); 346 } else 347 ret_val = -ENOENT; 348 spin_unlock(&netlbl_domhsh_def_lock); 349 } 350 351 audit_buf = netlbl_audit_start_common(AUDIT_MAC_MAP_DEL, audit_info); 352 if (audit_buf != NULL) { 353 audit_log_format(audit_buf, 354 " nlbl_domain=%s res=%u", 355 entry->domain ? entry->domain : "(default)", 356 ret_val == 0 ? 1 : 0); 357 audit_log_end(audit_buf); 358 } 359 360 if (ret_val == 0) 361 call_rcu(&entry->rcu, netlbl_domhsh_free_entry); 362 363 remove_return: 364 rcu_read_unlock(); 365 return ret_val; 366 } 367 368 /** 369 * netlbl_domhsh_remove_default - Removes the default entry from the table 370 * @audit_info: NetLabel audit information 371 * 372 * Description: 373 * Removes/resets the default entry for the domain hash table and handles any 374 * updates to the lower level protocol handler (i.e. CIPSO). Returns zero on 375 * success, non-zero on failure. 376 * 377 */ 378 int netlbl_domhsh_remove_default(struct netlbl_audit *audit_info) 379 { 380 return netlbl_domhsh_remove(NULL, audit_info); 381 } 382 383 /** 384 * netlbl_domhsh_getentry - Get an entry from the domain hash table 385 * @domain: the domain name to search for 386 * 387 * Description: 388 * Look through the domain hash table searching for an entry to match @domain, 389 * return a pointer to a copy of the entry or NULL. The caller is responsibile 390 * for ensuring that rcu_read_[un]lock() is called. 391 * 392 */ 393 struct netlbl_dom_map *netlbl_domhsh_getentry(const char *domain) 394 { 395 return netlbl_domhsh_search(domain, 1); 396 } 397 398 /** 399 * netlbl_domhsh_walk - Iterate through the domain mapping hash table 400 * @skip_bkt: the number of buckets to skip at the start 401 * @skip_chain: the number of entries to skip in the first iterated bucket 402 * @callback: callback for each entry 403 * @cb_arg: argument for the callback function 404 * 405 * Description: 406 * Interate over the domain mapping hash table, skipping the first @skip_bkt 407 * buckets and @skip_chain entries. For each entry in the table call 408 * @callback, if @callback returns a negative value stop 'walking' through the 409 * table and return. Updates the values in @skip_bkt and @skip_chain on 410 * return. Returns zero on succcess, negative values on failure. 411 * 412 */ 413 int netlbl_domhsh_walk(u32 *skip_bkt, 414 u32 *skip_chain, 415 int (*callback) (struct netlbl_dom_map *entry, void *arg), 416 void *cb_arg) 417 { 418 int ret_val = -ENOENT; 419 u32 iter_bkt; 420 struct netlbl_dom_map *iter_entry; 421 u32 chain_cnt = 0; 422 423 rcu_read_lock(); 424 for (iter_bkt = *skip_bkt; 425 iter_bkt < rcu_dereference(netlbl_domhsh)->size; 426 iter_bkt++, chain_cnt = 0) { 427 list_for_each_entry_rcu(iter_entry, 428 &rcu_dereference(netlbl_domhsh)->tbl[iter_bkt], 429 list) 430 if (iter_entry->valid) { 431 if (chain_cnt++ < *skip_chain) 432 continue; 433 ret_val = callback(iter_entry, cb_arg); 434 if (ret_val < 0) { 435 chain_cnt--; 436 goto walk_return; 437 } 438 } 439 } 440 441 walk_return: 442 rcu_read_unlock(); 443 *skip_bkt = iter_bkt; 444 *skip_chain = chain_cnt; 445 return ret_val; 446 } 447