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