1#ifndef _SELABEL_FILE_H_ 2#define _SELABEL_FILE_H_ 3 4#include <errno.h> 5#include <pthread.h> 6#include <string.h> 7 8#include <sys/stat.h> 9#include <sys/xattr.h> 10 11/* 12 * regex.h/c were introduced to hold all dependencies on the regular 13 * expression back-end when we started supporting PCRE2. regex.h defines a 14 * minimal interface required by libselinux, so that the remaining code 15 * can be agnostic about the underlying implementation. 16 */ 17#include "regex.h" 18 19#include "callbacks.h" 20#include "label_internal.h" 21#include "selinux_internal.h" 22 23#define SELINUX_MAGIC_COMPILED_FCONTEXT 0xf97cff8a 24 25/* Version specific changes */ 26#define SELINUX_COMPILED_FCONTEXT_NOPCRE_VERS 1 27#define SELINUX_COMPILED_FCONTEXT_PCRE_VERS 2 28#define SELINUX_COMPILED_FCONTEXT_MODE 3 29#define SELINUX_COMPILED_FCONTEXT_PREFIX_LEN 4 30#define SELINUX_COMPILED_FCONTEXT_REGEX_ARCH 5 31 32#define SELINUX_COMPILED_FCONTEXT_MAX_VERS \ 33 SELINUX_COMPILED_FCONTEXT_REGEX_ARCH 34 35/* Required selinux_restorecon and selabel_get_digests_all_partial_matches() */ 36#define RESTORECON_PARTIAL_MATCH_DIGEST "security.sehash" 37 38struct selabel_sub { 39 char *src; 40 int slen; 41 char *dst; 42 struct selabel_sub *next; 43}; 44 45/* A file security context specification. */ 46struct spec { 47 struct selabel_lookup_rec lr; /* holds contexts for lookup result */ 48 char *regex_str; /* regular expression string for diagnostics */ 49 char *type_str; /* type string for diagnostic messages */ 50 struct regex_data * regex; /* backend dependent regular expression data */ 51 bool regex_compiled; /* bool to indicate if the regex is compiled */ 52 pthread_mutex_t regex_lock; /* lock for lazy compilation of regex */ 53 mode_t mode; /* mode format value */ 54 bool any_matches; /* did any pathname match? */ 55 int stem_id; /* indicates which stem-compression item */ 56 char hasMetaChars; /* regular expression has meta-chars */ 57 char from_mmap; /* this spec is from an mmap of the data */ 58 size_t prefix_len; /* length of fixed path prefix */ 59}; 60 61/* A regular expression stem */ 62struct stem { 63 char *buf; 64 int len; 65 char from_mmap; 66}; 67 68/* Where we map the file in during selabel_open() */ 69struct mmap_area { 70 void *addr; /* Start addr + len used to release memory at close */ 71 size_t len; 72 void *next_addr; /* Incremented by next_entry() */ 73 size_t next_len; /* Decremented by next_entry() */ 74 struct mmap_area *next; 75}; 76 77/* Our stored configuration */ 78struct saved_data { 79 /* 80 * The array of specifications, initially in the same order as in 81 * the specification file. Sorting occurs based on hasMetaChars. 82 */ 83 struct spec *spec_arr; 84 unsigned int nspec; 85 unsigned int alloc_specs; 86 87 /* 88 * The array of regular expression stems. 89 */ 90 struct stem *stem_arr; 91 int num_stems; 92 int alloc_stems; 93 struct mmap_area *mmap_areas; 94 95 /* substitution support */ 96 struct selabel_sub *dist_subs; 97 struct selabel_sub *subs; 98}; 99 100static inline mode_t string_to_mode(char *mode) 101{ 102 size_t len; 103 104 if (!mode) 105 return 0; 106 len = strlen(mode); 107 if (mode[0] != '-' || len != 2) 108 return -1; 109 switch (mode[1]) { 110 case 'b': 111 return S_IFBLK; 112 case 'c': 113 return S_IFCHR; 114 case 'd': 115 return S_IFDIR; 116 case 'p': 117 return S_IFIFO; 118 case 'l': 119 return S_IFLNK; 120 case 's': 121 return S_IFSOCK; 122 case '-': 123 return S_IFREG; 124 default: 125 return -1; 126 } 127 /* impossible to get here */ 128 return 0; 129} 130 131static inline int grow_specs(struct saved_data *data) 132{ 133 struct spec *specs; 134 size_t new_specs, total_specs; 135 136 if (data->nspec < data->alloc_specs) 137 return 0; 138 139 new_specs = data->nspec + 16; 140 total_specs = data->nspec + new_specs; 141 142 specs = realloc(data->spec_arr, total_specs * sizeof(*specs)); 143 if (!specs) { 144 perror("realloc"); 145 return -1; 146 } 147 148 /* blank the new entries */ 149 memset(&specs[data->nspec], 0, new_specs * sizeof(*specs)); 150 151 data->spec_arr = specs; 152 data->alloc_specs = total_specs; 153 return 0; 154} 155 156/* Determine if the regular expression specification has any meta characters. */ 157static inline void spec_hasMetaChars(struct spec *spec) 158{ 159 char *c; 160 int len; 161 char *end; 162 163 c = spec->regex_str; 164 len = strlen(spec->regex_str); 165 end = c + len; 166 167 spec->hasMetaChars = 0; 168 spec->prefix_len = len; 169 170 /* Look at each character in the RE specification string for a 171 * meta character. Return when any meta character reached. */ 172 while (c < end) { 173 switch (*c) { 174 case '.': 175 case '^': 176 case '$': 177 case '?': 178 case '*': 179 case '+': 180 case '|': 181 case '[': 182 case '(': 183 case '{': 184 spec->hasMetaChars = 1; 185 spec->prefix_len = c - spec->regex_str; 186 return; 187 case '\\': /* skip the next character */ 188 c++; 189 break; 190 default: 191 break; 192 193 } 194 c++; 195 } 196} 197 198/* Move exact pathname specifications to the end. */ 199static inline int sort_specs(struct saved_data *data) 200{ 201 struct spec *spec_copy; 202 struct spec spec; 203 unsigned int i; 204 int front, back; 205 size_t len = sizeof(*spec_copy); 206 207 spec_copy = malloc(len * data->nspec); 208 if (!spec_copy) 209 return -1; 210 211 /* first move the exact pathnames to the back */ 212 front = 0; 213 back = data->nspec - 1; 214 for (i = 0; i < data->nspec; i++) { 215 if (data->spec_arr[i].hasMetaChars) 216 memcpy(&spec_copy[front++], &data->spec_arr[i], len); 217 else 218 memcpy(&spec_copy[back--], &data->spec_arr[i], len); 219 } 220 221 /* 222 * now the exact pathnames are at the end, but they are in the reverse 223 * order. Since 'front' is now the first of the 'exact' we can run 224 * that part of the array switching the front and back element. 225 */ 226 back = data->nspec - 1; 227 while (front < back) { 228 /* save the front */ 229 memcpy(&spec, &spec_copy[front], len); 230 /* move the back to the front */ 231 memcpy(&spec_copy[front], &spec_copy[back], len); 232 /* put the old front in the back */ 233 memcpy(&spec_copy[back], &spec, len); 234 front++; 235 back--; 236 } 237 238 free(data->spec_arr); 239 data->spec_arr = spec_copy; 240 241 return 0; 242} 243 244/* Return the length of the text that can be considered the stem, returns 0 245 * if there is no identifiable stem */ 246static inline int get_stem_from_spec(const char *const buf) 247{ 248 const char *tmp = strchr(buf + 1, '/'); 249 const char *ind; 250 251 if (!tmp) 252 return 0; 253 254 for (ind = buf; ind < tmp; ind++) { 255 if (strchr(".^$?*+|[({", (int)*ind)) 256 return 0; 257 } 258 return tmp - buf; 259} 260 261/* 262 * return the stemid given a string and a length 263 */ 264static inline int find_stem(struct saved_data *data, const char *buf, 265 int stem_len) 266{ 267 int i; 268 269 for (i = 0; i < data->num_stems; i++) { 270 if (stem_len == data->stem_arr[i].len && 271 !strncmp(buf, data->stem_arr[i].buf, stem_len)) 272 return i; 273 } 274 275 return -1; 276} 277 278/* returns the index of the new stored object */ 279static inline int store_stem(struct saved_data *data, char *buf, int stem_len) 280{ 281 int num = data->num_stems; 282 283 if (data->alloc_stems == num) { 284 struct stem *tmp_arr; 285 int alloc_stems = data->alloc_stems * 2 + 16; 286 tmp_arr = realloc(data->stem_arr, 287 sizeof(*tmp_arr) * alloc_stems); 288 if (!tmp_arr) { 289 return -1; 290 } 291 data->alloc_stems = alloc_stems; 292 data->stem_arr = tmp_arr; 293 } 294 data->stem_arr[num].len = stem_len; 295 data->stem_arr[num].buf = buf; 296 data->stem_arr[num].from_mmap = 0; 297 data->num_stems++; 298 299 return num; 300} 301 302/* find the stem of a file spec, returns the index into stem_arr for a new 303 * or existing stem, (or -1 if there is no possible stem - IE for a file in 304 * the root directory or a regex that is too complex for us). */ 305static inline int find_stem_from_spec(struct saved_data *data, const char *buf) 306{ 307 int stem_len = get_stem_from_spec(buf); 308 int stemid; 309 char *stem; 310 int r; 311 312 if (!stem_len) 313 return -1; 314 315 stemid = find_stem(data, buf, stem_len); 316 if (stemid >= 0) 317 return stemid; 318 319 /* not found, allocate a new one */ 320 stem = strndup(buf, stem_len); 321 if (!stem) 322 return -1; 323 324 r = store_stem(data, stem, stem_len); 325 if (r < 0) 326 free(stem); 327 328 return r; 329} 330 331/* This will always check for buffer over-runs and either read the next entry 332 * if buf != NULL or skip over the entry (as these areas are mapped in the 333 * current buffer). */ 334static inline int next_entry(void *buf, struct mmap_area *fp, size_t bytes) 335{ 336 if (bytes > fp->next_len) 337 return -1; 338 339 if (buf) 340 memcpy(buf, fp->next_addr, bytes); 341 342 fp->next_addr = (char *)fp->next_addr + bytes; 343 fp->next_len -= bytes; 344 return 0; 345} 346 347static inline int compile_regex(struct spec *spec, const char **errbuf) 348{ 349 char *reg_buf, *anchored_regex, *cp; 350 struct regex_error_data error_data; 351 static char regex_error_format_buffer[256]; 352 size_t len; 353 int rc; 354 bool regex_compiled; 355 356 /* We really want pthread_once() here, but since its 357 * init_routine does not take a parameter, it's not possible 358 * to use, so we generate the same effect with atomics and a 359 * mutex */ 360#ifdef __ATOMIC_RELAXED 361 regex_compiled = 362 __atomic_load_n(&spec->regex_compiled, __ATOMIC_ACQUIRE); 363#else 364 /* GCC <4.7 */ 365 __sync_synchronize(); 366 regex_compiled = spec->regex_compiled; 367#endif 368 if (regex_compiled) { 369 return 0; /* already done */ 370 } 371 372 __pthread_mutex_lock(&spec->regex_lock); 373 /* Check if another thread compiled the regex while we waited 374 * on the mutex */ 375#ifdef __ATOMIC_RELAXED 376 regex_compiled = 377 __atomic_load_n(&spec->regex_compiled, __ATOMIC_ACQUIRE); 378#else 379 /* GCC <4.7 */ 380 __sync_synchronize(); 381 regex_compiled = spec->regex_compiled; 382#endif 383 if (regex_compiled) { 384 __pthread_mutex_unlock(&spec->regex_lock); 385 return 0; 386 } 387 388 reg_buf = spec->regex_str; 389 /* Anchor the regular expression. */ 390 len = strlen(reg_buf); 391 cp = anchored_regex = malloc(len + 3); 392 if (!anchored_regex) { 393 if (errbuf) 394 *errbuf = "out of memory"; 395 __pthread_mutex_unlock(&spec->regex_lock); 396 return -1; 397 } 398 399 /* Create ^...$ regexp. */ 400 *cp++ = '^'; 401 memcpy(cp, reg_buf, len); 402 cp += len; 403 *cp++ = '$'; 404 *cp = '\0'; 405 406 /* Compile the regular expression. */ 407 rc = regex_prepare_data(&spec->regex, anchored_regex, &error_data); 408 free(anchored_regex); 409 if (rc < 0) { 410 if (errbuf) { 411 regex_format_error(&error_data, 412 regex_error_format_buffer, 413 sizeof(regex_error_format_buffer)); 414 *errbuf = ®ex_error_format_buffer[0]; 415 } 416 __pthread_mutex_unlock(&spec->regex_lock); 417 return -1; 418 } 419 420 /* Done. */ 421#ifdef __ATOMIC_RELAXED 422 __atomic_store_n(&spec->regex_compiled, true, __ATOMIC_RELEASE); 423#else 424 /* GCC <4.7 */ 425 spec->regex_compiled = true; 426 __sync_synchronize(); 427#endif 428 __pthread_mutex_unlock(&spec->regex_lock); 429 return 0; 430} 431 432/* This service is used by label_file.c process_file() and 433 * utils/sefcontext_compile.c */ 434static inline int process_line(struct selabel_handle *rec, 435 const char *path, const char *prefix, 436 char *line_buf, unsigned lineno) 437{ 438 int items, len, rc; 439 char *regex = NULL, *type = NULL, *context = NULL; 440 struct saved_data *data = (struct saved_data *)rec->data; 441 struct spec *spec_arr; 442 unsigned int nspec = data->nspec; 443 const char *errbuf = NULL; 444 445 items = read_spec_entries(line_buf, &errbuf, 3, ®ex, &type, &context); 446 if (items < 0) { 447 if (errbuf) { 448 selinux_log(SELINUX_ERROR, 449 "%s: line %u error due to: %s\n", path, 450 lineno, errbuf); 451 } else { 452 selinux_log(SELINUX_ERROR, 453 "%s: line %u error due to: %m\n", path, 454 lineno); 455 } 456 return -1; 457 } 458 459 if (items == 0) 460 return items; 461 462 if (items < 2) { 463 COMPAT_LOG(SELINUX_ERROR, 464 "%s: line %u is missing fields\n", path, 465 lineno); 466 if (items == 1) 467 free(regex); 468 errno = EINVAL; 469 return -1; 470 } else if (items == 2) { 471 /* The type field is optional. */ 472 context = type; 473 type = 0; 474 } 475 476 len = get_stem_from_spec(regex); 477 if (len && prefix && strncmp(prefix, regex, len)) { 478 /* Stem of regex does not match requested prefix, discard. */ 479 free(regex); 480 free(type); 481 free(context); 482 return 0; 483 } 484 485 rc = grow_specs(data); 486 if (rc) 487 return rc; 488 489 spec_arr = data->spec_arr; 490 491 /* process and store the specification in spec. */ 492 spec_arr[nspec].stem_id = find_stem_from_spec(data, regex); 493 spec_arr[nspec].regex_str = regex; 494 __pthread_mutex_init(&spec_arr[nspec].regex_lock, NULL); 495 spec_arr[nspec].regex_compiled = false; 496 497 spec_arr[nspec].type_str = type; 498 spec_arr[nspec].mode = 0; 499 500 spec_arr[nspec].lr.ctx_raw = context; 501 spec_arr[nspec].lr.lineno = lineno; 502 503 /* 504 * bump data->nspecs to cause closef() to cover it in its free 505 * but do not bump nspec since it's used below. 506 */ 507 data->nspec++; 508 509 if (rec->validating 510 && compile_regex(&spec_arr[nspec], &errbuf)) { 511 COMPAT_LOG(SELINUX_ERROR, 512 "%s: line %u has invalid regex %s: %s\n", 513 path, lineno, regex, errbuf); 514 errno = EINVAL; 515 return -1; 516 } 517 518 if (type) { 519 mode_t mode = string_to_mode(type); 520 521 if (mode == (mode_t)-1) { 522 COMPAT_LOG(SELINUX_ERROR, 523 "%s: line %u has invalid file type %s\n", 524 path, lineno, type); 525 errno = EINVAL; 526 return -1; 527 } 528 spec_arr[nspec].mode = mode; 529 } 530 531 /* Determine if specification has 532 * any meta characters in the RE */ 533 spec_hasMetaChars(&spec_arr[nspec]); 534 535 if (strcmp(context, "<<none>>") && rec->validating) 536 return compat_validate(rec, &spec_arr[nspec].lr, path, lineno); 537 538 return 0; 539} 540 541#endif /* _SELABEL_FILE_H_ */ 542