| #ifndef _SELABEL_FILE_H_ |
| #define _SELABEL_FILE_H_ |
| |
| #include <errno.h> |
| #include <string.h> |
| |
| #include <sys/stat.h> |
| |
| /* |
| * Android: regex.h/c was 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" |
| |
| #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_MAX_VERS SELINUX_COMPILED_FCONTEXT_PREFIX_LEN |
| |
| /* A file security context specification. */ |
| struct spec { |
| struct selabel_lookup_rec lr; /* holds contexts for lookup result */ |
| char *regex_str; /* regular expession string for diagnostics */ |
| char *type_str; /* type string for diagnostic messages */ |
| struct regex_data * regex; /* backend dependent regular expression data */ |
| mode_t mode; /* mode format value */ |
| int matches; /* number of matching pathnames */ |
| int stem_id; /* indicates which stem-compression item */ |
| char hasMetaChars; /* regular expression has meta-chars */ |
| char regcomp; /* regex_str has been compiled to regex */ |
| 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; |
| }; |
| |
| 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; |
| |
| data->alloc_stems = data->alloc_stems * 2 + 16; |
| tmp_arr = realloc(data->stem_arr, |
| sizeof(*tmp_arr) * data->alloc_stems); |
| if (!tmp_arr) |
| return -1; |
| 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; |
| |
| 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; |
| |
| return store_stem(data, stem, stem_len); |
| } |
| |
| /* 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 saved_data *data, struct spec *spec, |
| struct regex_error_data * error_data) |
| { |
| char *reg_buf, *anchored_regex, *cp; |
| struct stem *stem_arr = data->stem_arr; |
| size_t len; |
| int rc; |
| |
| if (spec->regcomp) |
| return 0; /* already done */ |
| |
| /* Skip the fixed stem. */ |
| reg_buf = spec->regex_str; |
| if (spec->stem_id >= 0) |
| reg_buf += stem_arr[spec->stem_id].len; |
| |
| /* Anchor the regular expression. */ |
| len = strlen(reg_buf); |
| cp = anchored_regex = malloc(len + 3); |
| if (!anchored_regex) |
| 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) { |
| return -1; |
| } |
| |
| /* Done. */ |
| spec->regcomp = 1; |
| |
| 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; |
| char const *errbuf; |
| struct regex_error_data error_data; |
| |
| items = read_spec_entries(line_buf, &errbuf, 3, ®ex, &type, &context); |
| if (items < 0) { |
| rc = errno; |
| selinux_log(SELINUX_ERROR, |
| "%s: line %u error due to: %s\n", path, |
| lineno, errbuf ?: strerror(errno)); |
| errno = rc; |
| return -1; |
| } |
| |
| if (items == 0) |
| return items; |
| |
| if (items < 2) { |
| selinux_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; |
| |
| spec_arr[nspec].type_str = type; |
| spec_arr[nspec].mode = 0; |
| |
| spec_arr[nspec].lr.ctx_raw = context; |
| |
| /* |
| * 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(data, &spec_arr[nspec], &error_data)) { |
| selinux_log(SELINUX_ERROR, |
| "%s: line %u has invalid regex %s: %s\n", |
| path, lineno, regex, |
| (errbuf ? errbuf : "out of memory")); |
| errno = EINVAL; |
| return -1; |
| } |
| |
| if (type) { |
| mode_t mode = string_to_mode(type); |
| |
| if (mode == (mode_t)-1) { |
| selinux_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, "<<none>>") && rec->validating) { |
| if (selabel_validate(rec, &spec_arr[nspec].lr) < 0) { |
| selinux_log(SELINUX_ERROR, |
| "%s: line %u has invalid context %s\n", |
| path, lineno, spec_arr[nspec].lr.ctx_raw); |
| errno = EINVAL; |
| return -1; |
| } |
| } |
| |
| return 0; |
| } |
| |
| #endif /* _SELABEL_FILE_H_ */ |