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