#ifndef _SELABEL_FILE_H_ #define _SELABEL_FILE_H_ #include #include #include #include #include /* * regex.h/c were introduced to hold all dependencies on the regular * expression back-end when we started supporting PCRE2. regex.h defines a * minimal interface required by libselinux, so that the remaining code * can be agnostic about the underlying implementation. */ #include "regex.h" #include "callbacks.h" #include "label_internal.h" #include "selinux_internal.h" #define SELINUX_MAGIC_COMPILED_FCONTEXT 0xf97cff8a /* Version specific changes */ #define SELINUX_COMPILED_FCONTEXT_NOPCRE_VERS 1 #define SELINUX_COMPILED_FCONTEXT_PCRE_VERS 2 #define SELINUX_COMPILED_FCONTEXT_MODE 3 #define SELINUX_COMPILED_FCONTEXT_PREFIX_LEN 4 #define SELINUX_COMPILED_FCONTEXT_REGEX_ARCH 5 #define SELINUX_COMPILED_FCONTEXT_MAX_VERS \ SELINUX_COMPILED_FCONTEXT_REGEX_ARCH /* Required selinux_restorecon and selabel_get_digests_all_partial_matches() */ #define RESTORECON_PARTIAL_MATCH_DIGEST "security.sehash" struct selabel_sub { char *src; int slen; char *dst; struct selabel_sub *next; }; /* A file security context specification. */ struct spec { struct selabel_lookup_rec lr; /* holds contexts for lookup result */ char *regex_str; /* regular expression string for diagnostics */ char *type_str; /* type string for diagnostic messages */ struct regex_data * regex; /* backend dependent regular expression data */ bool regex_compiled; /* bool to indicate if the regex is compiled */ pthread_mutex_t regex_lock; /* lock for lazy compilation of regex */ mode_t mode; /* mode format value */ bool any_matches; /* did any pathname match? */ int stem_id; /* indicates which stem-compression item */ char hasMetaChars; /* regular expression has meta-chars */ char from_mmap; /* this spec is from an mmap of the data */ size_t prefix_len; /* length of fixed path prefix */ }; /* A regular expression stem */ struct stem { char *buf; int len; char from_mmap; }; /* Where we map the file in during selabel_open() */ struct mmap_area { void *addr; /* Start addr + len used to release memory at close */ size_t len; void *next_addr; /* Incremented by next_entry() */ size_t next_len; /* Decremented by next_entry() */ struct mmap_area *next; }; /* Our stored configuration */ struct saved_data { /* * The array of specifications, initially in the same order as in * the specification file. Sorting occurs based on hasMetaChars. */ struct spec *spec_arr; unsigned int nspec; unsigned int alloc_specs; /* * The array of regular expression stems. */ struct stem *stem_arr; int num_stems; int alloc_stems; struct mmap_area *mmap_areas; /* substitution support */ struct selabel_sub *dist_subs; struct selabel_sub *subs; }; static inline mode_t string_to_mode(char *mode) { size_t len; if (!mode) return 0; len = strlen(mode); if (mode[0] != '-' || len != 2) return -1; switch (mode[1]) { case 'b': return S_IFBLK; case 'c': return S_IFCHR; case 'd': return S_IFDIR; case 'p': return S_IFIFO; case 'l': return S_IFLNK; case 's': return S_IFSOCK; case '-': return S_IFREG; default: return -1; } /* impossible to get here */ return 0; } static inline int grow_specs(struct saved_data *data) { struct spec *specs; size_t new_specs, total_specs; if (data->nspec < data->alloc_specs) return 0; new_specs = data->nspec + 16; total_specs = data->nspec + new_specs; specs = realloc(data->spec_arr, total_specs * sizeof(*specs)); if (!specs) { perror("realloc"); return -1; } /* blank the new entries */ memset(&specs[data->nspec], 0, new_specs * sizeof(*specs)); data->spec_arr = specs; data->alloc_specs = total_specs; return 0; } /* Determine if the regular expression specification has any meta characters. */ static inline void spec_hasMetaChars(struct spec *spec) { char *c; int len; char *end; c = spec->regex_str; len = strlen(spec->regex_str); end = c + len; spec->hasMetaChars = 0; spec->prefix_len = len; /* Look at each character in the RE specification string for a * meta character. Return when any meta character reached. */ while (c < end) { switch (*c) { case '.': case '^': case '$': case '?': case '*': case '+': case '|': case '[': case '(': case '{': spec->hasMetaChars = 1; spec->prefix_len = c - spec->regex_str; return; case '\\': /* skip the next character */ c++; break; default: break; } c++; } } /* Move exact pathname specifications to the end. */ static inline int sort_specs(struct saved_data *data) { struct spec *spec_copy; struct spec spec; unsigned int i; int front, back; size_t len = sizeof(*spec_copy); spec_copy = malloc(len * data->nspec); if (!spec_copy) return -1; /* first move the exact pathnames to the back */ front = 0; back = data->nspec - 1; for (i = 0; i < data->nspec; i++) { if (data->spec_arr[i].hasMetaChars) memcpy(&spec_copy[front++], &data->spec_arr[i], len); else memcpy(&spec_copy[back--], &data->spec_arr[i], len); } /* * now the exact pathnames are at the end, but they are in the reverse * order. Since 'front' is now the first of the 'exact' we can run * that part of the array switching the front and back element. */ back = data->nspec - 1; while (front < back) { /* save the front */ memcpy(&spec, &spec_copy[front], len); /* move the back to the front */ memcpy(&spec_copy[front], &spec_copy[back], len); /* put the old front in the back */ memcpy(&spec_copy[back], &spec, len); front++; back--; } free(data->spec_arr); data->spec_arr = spec_copy; return 0; } /* Return the length of the text that can be considered the stem, returns 0 * if there is no identifiable stem */ static inline int get_stem_from_spec(const char *const buf) { const char *tmp = strchr(buf + 1, '/'); const char *ind; if (!tmp) return 0; for (ind = buf; ind < tmp; ind++) { if (strchr(".^$?*+|[({", (int)*ind)) return 0; } return tmp - buf; } /* * return the stemid given a string and a length */ static inline int find_stem(struct saved_data *data, const char *buf, int stem_len) { int i; for (i = 0; i < data->num_stems; i++) { if (stem_len == data->stem_arr[i].len && !strncmp(buf, data->stem_arr[i].buf, stem_len)) return i; } return -1; } /* returns the index of the new stored object */ static inline int store_stem(struct saved_data *data, char *buf, int stem_len) { int num = data->num_stems; if (data->alloc_stems == num) { struct stem *tmp_arr; int alloc_stems = data->alloc_stems * 2 + 16; tmp_arr = realloc(data->stem_arr, sizeof(*tmp_arr) * alloc_stems); if (!tmp_arr) { return -1; } data->alloc_stems = alloc_stems; data->stem_arr = tmp_arr; } data->stem_arr[num].len = stem_len; data->stem_arr[num].buf = buf; data->stem_arr[num].from_mmap = 0; data->num_stems++; return num; } /* find the stem of a file spec, returns the index into stem_arr for a new * or existing stem, (or -1 if there is no possible stem - IE for a file in * the root directory or a regex that is too complex for us). */ static inline int find_stem_from_spec(struct saved_data *data, const char *buf) { int stem_len = get_stem_from_spec(buf); int stemid; char *stem; int r; if (!stem_len) return -1; stemid = find_stem(data, buf, stem_len); if (stemid >= 0) return stemid; /* not found, allocate a new one */ stem = strndup(buf, stem_len); if (!stem) return -1; r = store_stem(data, stem, stem_len); if (r < 0) free(stem); return r; } /* This will always check for buffer over-runs and either read the next entry * if buf != NULL or skip over the entry (as these areas are mapped in the * current buffer). */ static inline int next_entry(void *buf, struct mmap_area *fp, size_t bytes) { if (bytes > fp->next_len) return -1; if (buf) memcpy(buf, fp->next_addr, bytes); fp->next_addr = (char *)fp->next_addr + bytes; fp->next_len -= bytes; return 0; } static inline int compile_regex(struct spec *spec, const char **errbuf) { char *reg_buf, *anchored_regex, *cp; struct regex_error_data error_data; static char regex_error_format_buffer[256]; size_t len; int rc; bool regex_compiled; /* We really want pthread_once() here, but since its * init_routine does not take a parameter, it's not possible * to use, so we generate the same effect with atomics and a * mutex */ #ifdef __ATOMIC_RELAXED regex_compiled = __atomic_load_n(&spec->regex_compiled, __ATOMIC_ACQUIRE); #else /* GCC <4.7 */ __sync_synchronize(); regex_compiled = spec->regex_compiled; #endif if (regex_compiled) { return 0; /* already done */ } __pthread_mutex_lock(&spec->regex_lock); /* Check if another thread compiled the regex while we waited * on the mutex */ #ifdef __ATOMIC_RELAXED regex_compiled = __atomic_load_n(&spec->regex_compiled, __ATOMIC_ACQUIRE); #else /* GCC <4.7 */ __sync_synchronize(); regex_compiled = spec->regex_compiled; #endif if (regex_compiled) { __pthread_mutex_unlock(&spec->regex_lock); return 0; } reg_buf = spec->regex_str; /* Anchor the regular expression. */ len = strlen(reg_buf); cp = anchored_regex = malloc(len + 3); if (!anchored_regex) { if (errbuf) *errbuf = "out of memory"; __pthread_mutex_unlock(&spec->regex_lock); return -1; } /* Create ^...$ regexp. */ *cp++ = '^'; memcpy(cp, reg_buf, len); cp += len; *cp++ = '$'; *cp = '\0'; /* Compile the regular expression. */ rc = regex_prepare_data(&spec->regex, anchored_regex, &error_data); free(anchored_regex); if (rc < 0) { if (errbuf) { regex_format_error(&error_data, regex_error_format_buffer, sizeof(regex_error_format_buffer)); *errbuf = ®ex_error_format_buffer[0]; } __pthread_mutex_unlock(&spec->regex_lock); return -1; } /* Done. */ #ifdef __ATOMIC_RELAXED __atomic_store_n(&spec->regex_compiled, true, __ATOMIC_RELEASE); #else /* GCC <4.7 */ spec->regex_compiled = true; __sync_synchronize(); #endif __pthread_mutex_unlock(&spec->regex_lock); return 0; } /* This service is used by label_file.c process_file() and * utils/sefcontext_compile.c */ static inline int process_line(struct selabel_handle *rec, const char *path, const char *prefix, char *line_buf, unsigned lineno) { int items, len, rc; char *regex = NULL, *type = NULL, *context = NULL; struct saved_data *data = (struct saved_data *)rec->data; struct spec *spec_arr; unsigned int nspec = data->nspec; const char *errbuf = NULL; items = read_spec_entries(line_buf, &errbuf, 3, ®ex, &type, &context); if (items < 0) { if (errbuf) { selinux_log(SELINUX_ERROR, "%s: line %u error due to: %s\n", path, lineno, errbuf); } else { selinux_log(SELINUX_ERROR, "%s: line %u error due to: %m\n", path, lineno); } return -1; } if (items == 0) return items; if (items < 2) { COMPAT_LOG(SELINUX_ERROR, "%s: line %u is missing fields\n", path, lineno); if (items == 1) free(regex); errno = EINVAL; return -1; } else if (items == 2) { /* The type field is optional. */ context = type; type = 0; } len = get_stem_from_spec(regex); if (len && prefix && strncmp(prefix, regex, len)) { /* Stem of regex does not match requested prefix, discard. */ free(regex); free(type); free(context); return 0; } rc = grow_specs(data); if (rc) return rc; spec_arr = data->spec_arr; /* process and store the specification in spec. */ spec_arr[nspec].stem_id = find_stem_from_spec(data, regex); spec_arr[nspec].regex_str = regex; __pthread_mutex_init(&spec_arr[nspec].regex_lock, NULL); spec_arr[nspec].regex_compiled = false; spec_arr[nspec].type_str = type; spec_arr[nspec].mode = 0; spec_arr[nspec].lr.ctx_raw = context; spec_arr[nspec].lr.lineno = lineno; /* * bump data->nspecs to cause closef() to cover it in its free * but do not bump nspec since it's used below. */ data->nspec++; if (rec->validating && compile_regex(&spec_arr[nspec], &errbuf)) { COMPAT_LOG(SELINUX_ERROR, "%s: line %u has invalid regex %s: %s\n", path, lineno, regex, errbuf); errno = EINVAL; return -1; } if (type) { mode_t mode = string_to_mode(type); if (mode == (mode_t)-1) { COMPAT_LOG(SELINUX_ERROR, "%s: line %u has invalid file type %s\n", path, lineno, type); errno = EINVAL; return -1; } spec_arr[nspec].mode = mode; } /* Determine if specification has * any meta characters in the RE */ spec_hasMetaChars(&spec_arr[nspec]); if (strcmp(context, "<>") && rec->validating) return compat_validate(rec, &spec_arr[nspec].lr, path, lineno); return 0; } #endif /* _SELABEL_FILE_H_ */