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