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