Home | History | Annotate | Download | only in src
      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, &regex, &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