• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
string_to_mode(char * mode)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 
grow_specs(struct saved_data * data)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. */
spec_hasMetaChars(struct spec * spec)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. */
sort_specs(struct saved_data * data)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 */
get_stem_from_spec(const char * const buf)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  */
find_stem(struct saved_data * data,const char * buf,int stem_len)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 */
store_stem(struct saved_data * data,char * buf,int stem_len)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 		int alloc_stems = data->alloc_stems * 2 + 16;
282 		tmp_arr = realloc(data->stem_arr,
283 				  sizeof(*tmp_arr) * alloc_stems);
284 		if (!tmp_arr) {
285 			free(buf);
286 			return -1;
287 		}
288 		data->alloc_stems = alloc_stems;
289 		data->stem_arr = tmp_arr;
290 	}
291 	data->stem_arr[num].len = stem_len;
292 	data->stem_arr[num].buf = buf;
293 	data->stem_arr[num].from_mmap = 0;
294 	data->num_stems++;
295 
296 	return num;
297 }
298 
299 /* find the stem of a file spec, returns the index into stem_arr for a new
300  * or existing stem, (or -1 if there is no possible stem - IE for a file in
301  * the root directory or a regex that is too complex for us). */
find_stem_from_spec(struct saved_data * data,const char * buf)302 static inline int find_stem_from_spec(struct saved_data *data, const char *buf)
303 {
304 	int stem_len = get_stem_from_spec(buf);
305 	int stemid;
306 	char *stem;
307 
308 	if (!stem_len)
309 		return -1;
310 
311 	stemid = find_stem(data, buf, stem_len);
312 	if (stemid >= 0)
313 		return stemid;
314 
315 	/* not found, allocate a new one */
316 	stem = strndup(buf, stem_len);
317 	if (!stem)
318 		return -1;
319 
320 	return store_stem(data, stem, stem_len);
321 }
322 
323 /* This will always check for buffer over-runs and either read the next entry
324  * if buf != NULL or skip over the entry (as these areas are mapped in the
325  * current buffer). */
next_entry(void * buf,struct mmap_area * fp,size_t bytes)326 static inline int next_entry(void *buf, struct mmap_area *fp, size_t bytes)
327 {
328 	if (bytes > fp->next_len)
329 		return -1;
330 
331 	if (buf)
332 		memcpy(buf, fp->next_addr, bytes);
333 
334 	fp->next_addr = (char *)fp->next_addr + bytes;
335 	fp->next_len -= bytes;
336 	return 0;
337 }
338 
compile_regex(struct spec * spec,const char ** errbuf)339 static inline int compile_regex(struct spec *spec, const char **errbuf)
340 {
341 	char *reg_buf, *anchored_regex, *cp;
342 	struct regex_error_data error_data;
343 	static char regex_error_format_buffer[256];
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 #ifdef __ATOMIC_RELAXED
353 	regex_compiled =
354 		__atomic_load_n(&spec->regex_compiled, __ATOMIC_ACQUIRE);
355 #else
356 	/* GCC <4.7 */
357 	__sync_synchronize();
358 	regex_compiled = spec->regex_compiled;
359 #endif
360 	if (regex_compiled) {
361 		return 0; /* already done */
362 	}
363 
364 	__pthread_mutex_lock(&spec->regex_lock);
365 	/* Check if another thread compiled the regex while we waited
366 	 * on the mutex */
367 #ifdef __ATOMIC_RELAXED
368 	regex_compiled =
369 		__atomic_load_n(&spec->regex_compiled, __ATOMIC_ACQUIRE);
370 #else
371 	/* GCC <4.7 */
372 	__sync_synchronize();
373 	regex_compiled = spec->regex_compiled;
374 #endif
375 	if (regex_compiled) {
376 		__pthread_mutex_unlock(&spec->regex_lock);
377 		return 0;
378 	}
379 
380 	reg_buf = spec->regex_str;
381 	/* Anchor the regular expression. */
382 	len = strlen(reg_buf);
383 	cp = anchored_regex = malloc(len + 3);
384 	if (!anchored_regex) {
385 		if (errbuf)
386 			*errbuf = "out of memory";
387 		__pthread_mutex_unlock(&spec->regex_lock);
388 		return -1;
389 	}
390 
391 	/* Create ^...$ regexp.  */
392 	*cp++ = '^';
393 	memcpy(cp, reg_buf, len);
394 	cp += len;
395 	*cp++ = '$';
396 	*cp = '\0';
397 
398 	/* Compile the regular expression. */
399 	rc = regex_prepare_data(&spec->regex, anchored_regex, &error_data);
400 	free(anchored_regex);
401 	if (rc < 0) {
402 		if (errbuf) {
403 			regex_format_error(&error_data,
404 					regex_error_format_buffer,
405 					sizeof(regex_error_format_buffer));
406 			*errbuf = &regex_error_format_buffer[0];
407 		}
408 		__pthread_mutex_unlock(&spec->regex_lock);
409 		return -1;
410 	}
411 
412 	/* Done. */
413 #ifdef __ATOMIC_RELAXED
414 	__atomic_store_n(&spec->regex_compiled, true, __ATOMIC_RELEASE);
415 #else
416 	/* GCC <4.7 */
417 	spec->regex_compiled = true;
418 	__sync_synchronize();
419 #endif
420 	__pthread_mutex_unlock(&spec->regex_lock);
421 	return 0;
422 }
423 
424 /* This service is used by label_file.c process_file() and
425  * utils/sefcontext_compile.c */
process_line(struct selabel_handle * rec,const char * path,const char * prefix,char * line_buf,unsigned lineno)426 static inline int process_line(struct selabel_handle *rec,
427 			const char *path, const char *prefix,
428 			char *line_buf, unsigned lineno)
429 {
430 	int items, len, rc;
431 	char *regex = NULL, *type = NULL, *context = NULL;
432 	struct saved_data *data = (struct saved_data *)rec->data;
433 	struct spec *spec_arr;
434 	unsigned int nspec = data->nspec;
435 	const char *errbuf = NULL;
436 
437 	items = read_spec_entries(line_buf, &errbuf, 3, &regex, &type, &context);
438 	if (items < 0) {
439 		rc = errno;
440 		selinux_log(SELINUX_ERROR,
441 			"%s:  line %u error due to: %s\n", path,
442 			lineno, errbuf ?: strerror(errno));
443 		errno = rc;
444 		return -1;
445 	}
446 
447 	if (items == 0)
448 		return items;
449 
450 	if (items < 2) {
451 		COMPAT_LOG(SELINUX_ERROR,
452 			    "%s:  line %u is missing fields\n", path,
453 			    lineno);
454 		if (items == 1)
455 			free(regex);
456 		errno = EINVAL;
457 		return -1;
458 	} else if (items == 2) {
459 		/* The type field is optional. */
460 		context = type;
461 		type = 0;
462 	}
463 
464 	len = get_stem_from_spec(regex);
465 	if (len && prefix && strncmp(prefix, regex, len)) {
466 		/* Stem of regex does not match requested prefix, discard. */
467 		free(regex);
468 		free(type);
469 		free(context);
470 		return 0;
471 	}
472 
473 	rc = grow_specs(data);
474 	if (rc)
475 		return rc;
476 
477 	spec_arr = data->spec_arr;
478 
479 	/* process and store the specification in spec. */
480 	spec_arr[nspec].stem_id = find_stem_from_spec(data, regex);
481 	spec_arr[nspec].regex_str = regex;
482 	__pthread_mutex_init(&spec_arr[nspec].regex_lock, NULL);
483 	spec_arr[nspec].regex_compiled = false;
484 
485 	spec_arr[nspec].type_str = type;
486 	spec_arr[nspec].mode = 0;
487 
488 	spec_arr[nspec].lr.ctx_raw = context;
489 	spec_arr[nspec].lr.lineno = lineno;
490 
491 	/*
492 	 * bump data->nspecs to cause closef() to cover it in its free
493 	 * but do not bump nspec since it's used below.
494 	 */
495 	data->nspec++;
496 
497 	if (rec->validating
498 			&& compile_regex(&spec_arr[nspec], &errbuf)) {
499 		COMPAT_LOG(SELINUX_ERROR,
500 			   "%s:  line %u has invalid regex %s:  %s\n",
501 			   path, lineno, regex, errbuf);
502 		errno = EINVAL;
503 		return -1;
504 	}
505 
506 	if (type) {
507 		mode_t mode = string_to_mode(type);
508 
509 		if (mode == (mode_t)-1) {
510 			COMPAT_LOG(SELINUX_ERROR,
511 				   "%s:  line %u has invalid file type %s\n",
512 				   path, lineno, type);
513 			errno = EINVAL;
514 			return -1;
515 		}
516 		spec_arr[nspec].mode = mode;
517 	}
518 
519 	/* Determine if specification has
520 	 * any meta characters in the RE */
521 	spec_hasMetaChars(&spec_arr[nspec]);
522 
523 	if (strcmp(context, "<<none>>") && rec->validating)
524 		return compat_validate(rec, &spec_arr[nspec].lr, path, lineno);
525 
526 	return 0;
527 }
528 
529 #endif /* _SELABEL_FILE_H_ */
530