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