#include #include #include #include #include "selinux_internal.h" #include #include #include #include #include #include #include #include #include "policy.h" #include "context_internal.h" static void #ifdef __GNUC__ __attribute__ ((format (printf, 1, 2))) #endif default_printf(const char *fmt, ...) { va_list ap; va_start(ap, fmt); vfprintf(stderr, fmt, ap); va_end(ap); } /* If MLS is disabled, strip any MLS level field from the context. This allows file_contexts with MLS levels to be processed on a non-MLS system that otherwise has the same policy. */ static inline int STRIP_LEVEL(char **context, int mls_enabled) { char *str; context_t con; int rc = -1; if (mls_enabled) return 0; con = context_new(*context); if (!con) return rc; if (context_range_set(con,NULL)) goto out; str = context_str(con); if (!str) goto out; str = strdup(str); if (!str) goto out; free(*context); *context = str; rc = 0; out: context_free(con); return rc; } static void (*myprintf)(const char *fmt, ...) = &default_printf; void set_matchpathcon_printf(void (*f)(const char *fmt, ...)) { if (f) myprintf = f; else myprintf = &default_printf; } static int default_invalidcon(const char *path, unsigned lineno, char *context) { if (security_check_context(context) < 0 && errno != ENOENT) { myprintf("%s: line %u has invalid context %s\n", path, lineno, context); return 1; } return 0; } static int (*myinvalidcon)(const char *p, unsigned l, char *c) = &default_invalidcon; void set_matchpathcon_invalidcon(int (*f)(const char *p, unsigned l, char *c)) { if (f) myinvalidcon = f; else myinvalidcon = &default_invalidcon; } static unsigned int myflags; void set_matchpathcon_flags(unsigned int flags) { myflags = flags; } /* * A file security context specification. */ typedef struct spec { char *regex_str; /* regular expession string for diagnostic messages */ char *type_str; /* type string for diagnostic messages */ char *context; /* context string */ regex_t regex; /* compiled regular expression */ mode_t mode; /* mode format value */ int matches; /* number of matching pathnames */ int hasMetaChars; /* indicates whether the RE has any meta characters. 0 = no meta chars 1 = has one or more meta chars */ int stem_id; /* indicates which of the stem-compression * items it matches */ } spec_t; typedef struct stem { char *buf; int len; } stem_t; static stem_t *stem_arr = NULL; static int num_stems = 0; static int alloc_stems = 0; static const char * const regex_chars = ".^$?*+|[({"; /* Return the length of the text that can be considered the stem, returns 0 * if there is no identifiable stem */ static 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(regex_chars, (int)*ind)) return 0; } return tmp - buf; } /* return the length of the text that is the stem of a file name */ static int get_stem_from_file_name(const char * const buf) { const char *tmp = strchr(buf + 1, '/'); if(!tmp) return 0; return tmp - buf; } /* 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). Makes buf * point to the text AFTER the stem. */ static int find_stem_from_spec(const char **buf) { int i; int stem_len = get_stem_from_spec(*buf); if(!stem_len) return -1; for(i = 0; i < num_stems; i++) { if(stem_len == stem_arr[i].len && !strncmp(*buf, stem_arr[i].buf, stem_len)) { *buf += stem_len; return i; } } if(num_stems == alloc_stems) { stem_t *tmp_arr; alloc_stems = alloc_stems * 2 + 16; tmp_arr = realloc(stem_arr, sizeof(stem_t) * alloc_stems); if(!tmp_arr) return -1; stem_arr = tmp_arr; } stem_arr[num_stems].len = stem_len; stem_arr[num_stems].buf = malloc(stem_len + 1); if(!stem_arr[num_stems].buf) return -1; memcpy(stem_arr[num_stems].buf, *buf, stem_len); stem_arr[num_stems].buf[stem_len] = '\0'; num_stems++; *buf += stem_len; return num_stems - 1; } /* find the stem of a file name, returns the index into stem_arr (or -1 if * there is no match - IE for a file in the root directory or a regex that is * too complex for us). Makes buf point to the text AFTER the stem. */ static int find_stem_from_file(const char **buf) { int i; int stem_len = get_stem_from_file_name(*buf); if(!stem_len) return -1; for(i = 0; i < num_stems; i++) { if(stem_len == stem_arr[i].len && !strncmp(*buf, stem_arr[i].buf, stem_len)) { *buf += stem_len; return i; } } return -1; } /* * The array of specifications, initially in the * same order as in the specification file. * Sorting occurs based on hasMetaChars */ static spec_t *spec_arr; static unsigned int nspec; /* * An association between an inode and a * specification. */ typedef struct file_spec { ino_t ino; /* inode number */ int specind; /* index of specification in spec */ char *file; /* full pathname for diagnostic messages about conflicts */ struct file_spec *next; /* next association in hash bucket chain */ } file_spec_t; /* * The hash table of associations, hashed by inode number. * Chaining is used for collisions, with elements ordered * by inode number in each bucket. Each hash bucket has a dummy * header. */ #define HASH_BITS 16 #define HASH_BUCKETS (1 << HASH_BITS) #define HASH_MASK (HASH_BUCKETS-1) static file_spec_t *fl_head; /* * Try to add an association between an inode and * a specification. If there is already an association * for the inode and it conflicts with this specification, * then use the specification that occurs later in the * specification array. */ int matchpathcon_filespec_add(ino_t ino, int specind, const char *file) { file_spec_t *prevfl, *fl; int h, no_conflict, ret; struct stat sb; if (!fl_head) { fl_head = malloc(sizeof(file_spec_t)*HASH_BUCKETS); if (!fl_head) goto oom; memset(fl_head, 0, sizeof(file_spec_t)*HASH_BUCKETS); } h = (ino + (ino >> HASH_BITS)) & HASH_MASK; for (prevfl = &fl_head[h], fl = fl_head[h].next; fl; prevfl = fl, fl = fl->next) { if (ino == fl->ino) { ret = lstat(fl->file, &sb); if (ret < 0 || sb.st_ino != ino) { fl->specind = specind; free(fl->file); fl->file = malloc(strlen(file) + 1); if (!fl->file) goto oom; strcpy(fl->file, file); return fl->specind; } no_conflict = (strcmp(spec_arr[fl->specind].context,spec_arr[specind].context) == 0); if (no_conflict) return fl->specind; myprintf("%s: conflicting specifications for %s and %s, using %s.\n", __FUNCTION__, file, fl->file, ((specind > fl->specind) ? spec_arr[specind]. context : spec_arr[fl->specind].context)); fl->specind = (specind > fl->specind) ? specind : fl->specind; free(fl->file); fl->file = malloc(strlen(file) + 1); if (!fl->file) goto oom; strcpy(fl->file, file); return fl->specind; } if (ino > fl->ino) break; } fl = malloc(sizeof(file_spec_t)); if (!fl) goto oom; fl->ino = ino; fl->specind = specind; fl->file = malloc(strlen(file) + 1); if (!fl->file) goto oom_freefl; strcpy(fl->file, file); fl->next = prevfl->next; prevfl->next = fl; return fl->specind; oom_freefl: free(fl); oom: myprintf("%s: insufficient memory for file label entry for %s\n", __FUNCTION__, file); return -1; } /* * Evaluate the association hash table distribution. */ void matchpathcon_filespec_eval(void) { file_spec_t *fl; int h, used, nel, len, longest; if (!fl_head) return; used = 0; longest = 0; nel = 0; for (h = 0; h < HASH_BUCKETS; h++) { len = 0; for (fl = fl_head[h].next; fl; fl = fl->next) { len++; } if (len) used++; if (len > longest) longest = len; nel += len; } myprintf ("%s: hash table stats: %d elements, %d/%d buckets used, longest chain length %d\n", __FUNCTION__, nel, used, HASH_BUCKETS, longest); } /* * Destroy the association hash table. */ void matchpathcon_filespec_destroy(void) { file_spec_t *fl, *tmp; int h; if (!fl_head) return; for (h = 0; h < HASH_BUCKETS; h++) { fl = fl_head[h].next; while (fl) { tmp = fl; fl = fl->next; free(tmp->file); free(tmp); } fl_head[h].next = NULL; } free(fl_head); fl_head = NULL; } /* * Warn about duplicate specifications. */ static void nodups_specs(const char *path) { unsigned int ii, jj; struct spec *curr_spec; for (ii = 0; ii < nspec; ii++) { curr_spec = &spec_arr[ii]; for (jj = ii + 1; jj < nspec; jj++) { if ((!strcmp(spec_arr[jj].regex_str, curr_spec->regex_str)) && (!spec_arr[jj].mode || !curr_spec->mode || spec_arr[jj].mode == curr_spec->mode)) { if (strcmp(spec_arr[jj].context, curr_spec->context)) { myprintf( "%s: Multiple different specifications for %s (%s and %s).\n", path, curr_spec->regex_str, spec_arr[jj].context, curr_spec->context); } else { myprintf( "%s: Multiple same specifications for %s.\n", path, curr_spec->regex_str); } } } } } /* Determine if the regular expression specification has any meta characters. */ static 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; /* 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; return; case '\\': /* skip the next character */ c++; break; default: break; } c++; } return; } static int process_line( const char *path, char *line_buf, int pass, unsigned lineno, int mls_enabled) { int items, len, regerr; char *buf_p; char *regex, *type, *context; char *anchored_regex; len = strlen(line_buf); if (line_buf[len - 1] == '\n') line_buf[len - 1] = 0; buf_p = line_buf; while (isspace(*buf_p)) buf_p++; /* Skip comment lines and empty lines. */ if (*buf_p == '#' || *buf_p == 0) return 0; items = sscanf(line_buf, "%as %as %as", ®ex, &type, &context); if (items < 2) { myprintf("%s: line %d is missing fields\n, skipping", path, lineno); return 0; } else if (items == 2) { /* The type field is optional. */ free(context); context = type; type = 0; } if (pass == 1) { /* On the second pass, compile and store the specification in spec. */ const char *reg_buf = regex; char *cp; spec_arr[nspec].stem_id = find_stem_from_spec(®_buf); spec_arr[nspec].regex_str = regex; /* Anchor the regular expression. */ len = strlen(reg_buf); cp = anchored_regex = malloc(len + 3); if (!anchored_regex) return -1; /* Create ^...$ regexp. */ *cp++ = '^'; cp = mempcpy(cp, reg_buf, len); *cp++ = '$'; *cp = '\0'; /* Compile the regular expression. */ regerr = regcomp(&spec_arr[nspec].regex, anchored_regex, REG_EXTENDED | REG_NOSUB); if (regerr != 0) { size_t errsz = 0; char *errbuf = NULL; errsz = regerror(regerr, &spec_arr[nspec].regex, errbuf, errsz); if (errsz) errbuf = malloc(errsz); if (errbuf) (void) regerror(regerr, &spec_arr[nspec].regex, errbuf, errsz); myprintf("%s: line %d has invalid regex %s: %s\n", path, lineno, anchored_regex, (errbuf ? errbuf : "out of memory")); free(anchored_regex); return 0; } free(anchored_regex); /* Convert the type string to a mode format */ spec_arr[nspec].type_str = type; spec_arr[nspec].mode = 0; if (!type) goto skip_type; len = strlen(type); if (type[0] != '-' || len != 2) { myprintf("%s: line %d has invalid file type %s\n", path, lineno, type); return 0; } switch (type[1]) { case 'b': spec_arr[nspec].mode = S_IFBLK; break; case 'c': spec_arr[nspec].mode = S_IFCHR; break; case 'd': spec_arr[nspec].mode = S_IFDIR; break; case 'p': spec_arr[nspec].mode = S_IFIFO; break; case 'l': spec_arr[nspec].mode = S_IFLNK; break; case 's': spec_arr[nspec].mode = S_IFSOCK; break; case '-': spec_arr[nspec].mode = S_IFREG; break; default: myprintf("%s: line %d has invalid file type %s\n", path, lineno, type); return 0; } skip_type: if (strcmp(context, "<>")) { if (context_translations) { if (raw_to_trans_context(context, &spec_arr[nspec].context)) { myprintf("%s: line %u has invalid " "context %s\n", path, lineno, context); return 0; } free(context); context = spec_arr[nspec].context; } else { if (STRIP_LEVEL(&context, mls_enabled)) return -1; } if (myinvalidcon(path, lineno, context)) return 0; } spec_arr[nspec].context = context; /* Determine if specification has * any meta characters in the RE */ spec_hasMetaChars(&spec_arr[nspec]); } nspec++; if (pass == 0) { free(regex); if (type) free(type); free(context); } return 0; } int matchpathcon_init(const char *path) { FILE *fp; FILE *localfp = NULL; FILE *homedirfp = NULL; char local_path[PATH_MAX + 1]; char homedir_path[PATH_MAX + 1]; char *line_buf = NULL; size_t line_len = 0; unsigned int lineno, pass, i, j, maxnspec; spec_t *spec_copy=NULL; int status=-1; int mls_enabled=is_selinux_mls_enabled(); /* Open the specification file. */ if (!path) path = selinux_file_context_path(); if ((fp = fopen(path, "r")) == NULL) return -1; __fsetlocking(fp, FSETLOCKING_BYCALLER); if ((myflags & MATCHPATHCON_BASEONLY) == 0) { snprintf(homedir_path, sizeof(homedir_path), "%s.homedirs", path); homedirfp = fopen(homedir_path, "r"); if (homedirfp != NULL) __fsetlocking(homedirfp, FSETLOCKING_BYCALLER); snprintf(local_path, sizeof(local_path), "%s.local", path); localfp = fopen(local_path, "r"); if (localfp != NULL) __fsetlocking(localfp, FSETLOCKING_BYCALLER); } /* * Perform two passes over the specification file. * The first pass counts the number of specifications and * performs simple validation of the input. At the end * of the first pass, the spec array is allocated. * The second pass performs detailed validation of the input * and fills in the spec array. */ maxnspec = UINT_MAX / sizeof(spec_t); for (pass = 0; pass < 2; pass++) { lineno = 0; nspec = 0; while (getline(&line_buf, &line_len, fp) > 0 && nspec < maxnspec) { if (process_line(path, line_buf, pass, ++lineno, mls_enabled) != 0) goto finish; } lineno = 0; if (homedirfp) while (getline(&line_buf, &line_len, homedirfp) > 0 && nspec < maxnspec) { if (process_line(homedir_path, line_buf, pass, ++lineno, mls_enabled) != 0) goto finish; } lineno = 0; if (localfp) while (getline(&line_buf, &line_len, localfp) > 0 && nspec < maxnspec) { if (process_line(local_path, line_buf, pass, ++lineno, mls_enabled) != 0) goto finish; } if (pass == 0) { if (nspec == 0) { status = 0; goto finish; } if ((spec_arr = malloc(sizeof(spec_t) * nspec)) == NULL) goto finish; memset(spec_arr, '\0', sizeof(spec_t) * nspec); maxnspec = nspec; rewind(fp); if (homedirfp) rewind(homedirfp); if (localfp) rewind(localfp); } } free(line_buf); /* Move exact pathname specifications to the end. */ spec_copy = malloc(sizeof(spec_t) * nspec); if (!spec_copy) goto finish; j = 0; for (i = 0; i < nspec; i++) { if (spec_arr[i].hasMetaChars) memcpy(&spec_copy[j++], &spec_arr[i], sizeof(spec_t)); } for (i = 0; i < nspec; i++) { if (!spec_arr[i].hasMetaChars) memcpy(&spec_copy[j++], &spec_arr[i], sizeof(spec_t)); } free(spec_arr); spec_arr = spec_copy; nodups_specs(path); status = 0; finish: fclose(fp); if (spec_arr != spec_copy) free(spec_arr); if (homedirfp) fclose(homedirfp); if (localfp) fclose(localfp); return status; } hidden_def(matchpathcon_init) static int matchpathcon_common(const char *name, mode_t mode) { int i, ret, file_stem; const char *buf = name; if (!nspec) { ret = matchpathcon_init(NULL); if (ret < 0) return ret; if (!nspec) { errno = ENOENT; return -1; } } file_stem = find_stem_from_file(&buf); mode &= S_IFMT; /* * Check for matching specifications in reverse order, so that * the last matching specification is used. */ for (i = nspec - 1; i >= 0; i--) { /* if the spec in question matches no stem or has the same * stem as the file AND if the spec in question has no mode * specified or if the mode matches the file mode then we do * a regex check */ if( (spec_arr[i].stem_id == -1 || spec_arr[i].stem_id == file_stem) && (!mode || !spec_arr[i].mode || ( (mode & S_IFMT) == spec_arr[i].mode ) ) ) { if(spec_arr[i].stem_id == -1) ret = regexec(&spec_arr[i].regex, name, 0, NULL, 0); else ret = regexec(&spec_arr[i].regex, buf, 0, NULL, 0); if (ret == 0) break; if (ret == REG_NOMATCH) continue; /* else it's an error */ return -1; } } if (i < 0) { /* No matching specification. */ errno = ENOENT; return -1; } spec_arr[i].matches++; return i; } int matchpathcon(const char *name, mode_t mode, security_context_t *con) { int i = matchpathcon_common(name, mode); if (i < 0) return -1; if (strcmp(spec_arr[i].context, "<>") == 0) { errno = ENOENT; return -1; } *con = strdup(spec_arr[i].context); if (!(*con)) return -1; return 0; } int matchpathcon_index(const char *name, mode_t mode, security_context_t *con) { int i = matchpathcon_common(name, mode); if (i < 0) return -1; *con = strdup(spec_arr[i].context); if (!(*con)) return -1; return i; } void matchpathcon_checkmatches(char *str) { unsigned int i; for (i = 0; i < nspec; i++) { if (spec_arr[i].matches == 0) { if (spec_arr[i].type_str) { myprintf ("%s: Warning! No matches for (%s, %s, %s)\n", str, spec_arr[i].regex_str, spec_arr[i].type_str, spec_arr[i].context); } else { myprintf ("%s: Warning! No matches for (%s, %s)\n", str, spec_arr[i].regex_str, spec_arr[i].context); } } } }