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