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