1/*
2 * File contexts backend for labeling system
3 *
4 * Author : Eamon Walsh <ewalsh@tycho.nsa.gov>
5 * Author : Stephen Smalley <sds@tycho.nsa.gov>
6 *
7 * This library derived in part from setfiles and the setfiles.pl script
8 * developed by Secure Computing Corporation.
9 */
10
11#include <assert.h>
12#include <fcntl.h>
13#include <stdarg.h>
14#include <string.h>
15#include <stdio.h>
16#include <stdio_ext.h>
17#include <ctype.h>
18#include <errno.h>
19#include <limits.h>
20#include <stdint.h>
21#include <pcre.h>
22
23#include <linux/limits.h>
24
25#include <sys/mman.h>
26#include <sys/types.h>
27#include <sys/stat.h>
28#include <unistd.h>
29#include "callbacks.h"
30#include "label_internal.h"
31#include "label_file.h"
32
33/*
34 * Internals, mostly moved over from matchpathcon.c
35 */
36
37/* return the length of the text that is the stem of a file name */
38static int get_stem_from_file_name(const char *const buf)
39{
40	const char *tmp = strchr(buf + 1, '/');
41
42	if (!tmp)
43		return 0;
44	return tmp - buf;
45}
46
47/* find the stem of a file name, returns the index into stem_arr (or -1 if
48 * there is no match - IE for a file in the root directory or a regex that is
49 * too complex for us).  Makes buf point to the text AFTER the stem. */
50static int find_stem_from_file(struct saved_data *data, const char **buf)
51{
52	int i;
53	int stem_len = get_stem_from_file_name(*buf);
54
55	if (!stem_len)
56		return -1;
57	for (i = 0; i < data->num_stems; i++) {
58		if (stem_len == data->stem_arr[i].len
59		    && !strncmp(*buf, data->stem_arr[i].buf, stem_len)) {
60			*buf += stem_len;
61			return i;
62		}
63	}
64	return -1;
65}
66
67/*
68 * Warn about duplicate specifications.
69 */
70static int nodups_specs(struct saved_data *data, const char *path)
71{
72	int rc = 0;
73	unsigned int ii, jj;
74	struct spec *curr_spec, *spec_arr = data->spec_arr;
75
76	for (ii = 0; ii < data->nspec; ii++) {
77		curr_spec = &spec_arr[ii];
78		for (jj = ii + 1; jj < data->nspec; jj++) {
79			if ((!strcmp(spec_arr[jj].regex_str, curr_spec->regex_str))
80			    && (!spec_arr[jj].mode || !curr_spec->mode
81				|| spec_arr[jj].mode == curr_spec->mode)) {
82				rc = -1;
83				errno = EINVAL;
84				if (strcmp(spec_arr[jj].lr.ctx_raw, curr_spec->lr.ctx_raw)) {
85					COMPAT_LOG
86						(SELINUX_ERROR,
87						 "%s: Multiple different specifications for %s  (%s and %s).\n",
88						 path, curr_spec->regex_str,
89						 spec_arr[jj].lr.ctx_raw,
90						 curr_spec->lr.ctx_raw);
91				} else {
92					COMPAT_LOG
93						(SELINUX_ERROR,
94						 "%s: Multiple same specifications for %s.\n",
95						 path, curr_spec->regex_str);
96				}
97			}
98		}
99	}
100	return rc;
101}
102
103static int compile_regex(struct saved_data *data, struct spec *spec, const char **errbuf)
104{
105	const char *tmperrbuf;
106	char *reg_buf, *anchored_regex, *cp;
107	struct stem *stem_arr = data->stem_arr;
108	size_t len;
109	int erroff;
110
111	if (spec->regcomp)
112		return 0; /* already done */
113
114	/* Skip the fixed stem. */
115	reg_buf = spec->regex_str;
116	if (spec->stem_id >= 0)
117		reg_buf += stem_arr[spec->stem_id].len;
118
119	/* Anchor the regular expression. */
120	len = strlen(reg_buf);
121	cp = anchored_regex = malloc(len + 3);
122	if (!anchored_regex)
123		return -1;
124
125	/* Create ^...$ regexp.  */
126	*cp++ = '^';
127	cp = mempcpy(cp, reg_buf, len);
128	*cp++ = '$';
129	*cp = '\0';
130
131	/* Compile the regular expression. */
132	spec->regex = pcre_compile(anchored_regex, PCRE_DOTALL, &tmperrbuf, &erroff, NULL);
133	free(anchored_regex);
134	if (!spec->regex) {
135		if (errbuf)
136			*errbuf=tmperrbuf;
137		return -1;
138	}
139
140	spec->sd = pcre_study(spec->regex, 0, &tmperrbuf);
141	if (!spec->sd && tmperrbuf) {
142		if (errbuf)
143			*errbuf=tmperrbuf;
144		return -1;
145	}
146
147	/* Done. */
148	spec->regcomp = 1;
149
150	return 0;
151}
152
153static int process_line(struct selabel_handle *rec,
154			const char *path, const char *prefix,
155			char *line_buf, unsigned lineno)
156{
157	int items, len, rc;
158	char *buf_p, *regex, *type, *context;
159	struct saved_data *data = (struct saved_data *)rec->data;
160	struct spec *spec_arr;
161	unsigned int nspec = data->nspec;
162	const char *errbuf = NULL;
163
164	len = strlen(line_buf);
165	if (line_buf[len - 1] == '\n')
166		line_buf[len - 1] = 0;
167	buf_p = line_buf;
168	while (isspace(*buf_p))
169		buf_p++;
170	/* Skip comment lines and empty lines. */
171	if (*buf_p == '#' || *buf_p == 0)
172		return 0;
173	items = sscanf(line_buf, "%ms %ms %ms", &regex, &type, &context);
174	if (items < 2) {
175		COMPAT_LOG(SELINUX_WARNING,
176			    "%s:  line %u is missing fields, skipping\n", path,
177			    lineno);
178		if (items == 1)
179			free(regex);
180		return 0;
181	} else if (items == 2) {
182		/* The type field is optional. */
183		free(context);
184		context = type;
185		type = 0;
186	}
187
188	len = get_stem_from_spec(regex);
189	if (len && prefix && strncmp(prefix, regex, len)) {
190		/* Stem of regex does not match requested prefix, discard. */
191		free(regex);
192		free(type);
193		free(context);
194		return 0;
195	}
196
197	rc = grow_specs(data);
198	if (rc)
199		return rc;
200
201	spec_arr = data->spec_arr;
202
203	/* process and store the specification in spec. */
204	spec_arr[nspec].stem_id = find_stem_from_spec(data, regex);
205	spec_arr[nspec].regex_str = regex;
206	if (rec->validating && compile_regex(data, &spec_arr[nspec], &errbuf)) {
207		COMPAT_LOG(SELINUX_WARNING, "%s:  line %u has invalid regex %s:  %s\n",
208			   path, lineno, regex, (errbuf ? errbuf : "out of memory"));
209	}
210
211	/* Convert the type string to a mode format */
212	spec_arr[nspec].type_str = type;
213	spec_arr[nspec].mode = 0;
214	if (type) {
215		mode_t mode = string_to_mode(type);
216		if (mode == (mode_t)-1) {
217			COMPAT_LOG(SELINUX_WARNING, "%s:  line %u has invalid file type %s\n",
218				   path, lineno, type);
219			mode = 0;
220		}
221		spec_arr[nspec].mode = mode;
222	}
223
224	spec_arr[nspec].lr.ctx_raw = context;
225
226	/* Determine if specification has
227	 * any meta characters in the RE */
228	spec_hasMetaChars(&spec_arr[nspec]);
229
230	if (strcmp(context, "<<none>>") && rec->validating)
231		compat_validate(rec, &spec_arr[nspec].lr, path, lineno);
232
233	data->nspec = ++nspec;
234
235	return 0;
236}
237
238static int load_mmap(struct selabel_handle *rec, const char *path, struct stat *sb)
239{
240	struct saved_data *data = (struct saved_data *)rec->data;
241	char mmap_path[PATH_MAX + 1];
242	int mmapfd;
243	int rc;
244	struct stat mmap_stat;
245	char *addr;
246	size_t len;
247	int stem_map_len, *stem_map;
248	struct mmap_area *mmap_area;
249
250	uint32_t i;
251	uint32_t *magic;
252	uint32_t *section_len;
253	uint32_t *plen;
254
255	rc = snprintf(mmap_path, sizeof(mmap_path), "%s.bin", path);
256	if (rc >= (int)sizeof(mmap_path))
257		return -1;
258
259	mmapfd = open(mmap_path, O_RDONLY | O_CLOEXEC);
260	if (mmapfd < 0)
261		return -1;
262
263	rc = fstat(mmapfd, &mmap_stat);
264	if (rc < 0) {
265		close(mmapfd);
266		return -1;
267	}
268
269	/* if mmap is old, ignore it */
270	if (mmap_stat.st_mtime < sb->st_mtime) {
271		close(mmapfd);
272		return -1;
273	}
274
275	if (mmap_stat.st_mtime == sb->st_mtime &&
276	    mmap_stat.st_mtim.tv_nsec < sb->st_mtim.tv_nsec) {
277		close(mmapfd);
278		return -1;
279	}
280
281	/* ok, read it in... */
282	len = mmap_stat.st_size;
283	len += (sysconf(_SC_PAGE_SIZE) - 1);
284	len &= ~(sysconf(_SC_PAGE_SIZE) - 1);
285
286	mmap_area = malloc(sizeof(*mmap_area));
287	if (!mmap_area) {
288		close(mmapfd);
289		return -1;
290	}
291
292	addr = mmap(NULL, len, PROT_READ, MAP_PRIVATE, mmapfd, 0);
293	close(mmapfd);
294	if (addr == MAP_FAILED) {
295		free(mmap_area);
296		perror("mmap");
297		return -1;
298	}
299
300	/* save where we mmap'd the file to cleanup on close() */
301	mmap_area->addr = addr;
302	mmap_area->len = len;
303	mmap_area->next = data->mmap_areas;
304	data->mmap_areas = mmap_area;
305
306	/* check if this looks like an fcontext file */
307	magic = (uint32_t *)addr;
308	if (*magic != SELINUX_MAGIC_COMPILED_FCONTEXT)
309		return -1;
310	addr += sizeof(uint32_t);
311
312	/* check if this version is higher than we understand */
313	section_len = (uint32_t *)addr;
314	if (*section_len > SELINUX_COMPILED_FCONTEXT_MAX_VERS)
315		return -1;
316	addr += sizeof(uint32_t);
317
318	if (*section_len >= SELINUX_COMPILED_FCONTEXT_PCRE_VERS) {
319		len = strlen(pcre_version());
320		plen = (uint32_t *)addr;
321		if (*plen > mmap_area->len)
322			return -1; /* runs off the end of the map */
323		if (len != *plen)
324			return -1; /* pcre version length mismatch */
325		addr += sizeof(uint32_t);
326		if (memcmp((char *)addr, pcre_version(), len))
327			return -1; /* pcre version content mismatch */
328		if (addr + *plen >= (char *)mmap_area->addr + mmap_area->len)
329			return -1; /* Buffer over-run */
330		addr += *plen;
331	}
332
333	/* allocate the stems_data array */
334	section_len = (uint32_t *)addr;
335	addr += sizeof(uint32_t);
336
337	/*
338	 * map indexed by the stem # in the mmap file and contains the stem
339	 * number in the data stem_arr
340	 */
341	stem_map_len = *section_len;
342	stem_map = calloc(stem_map_len, sizeof(*stem_map));
343	if (!stem_map)
344		return -1;
345
346	for (i = 0; i < *section_len; i++) {
347		char *buf;
348		uint32_t stem_len;
349		int newid;
350
351		/* the length does not inlude the nul */
352		plen = (uint32_t *)addr;
353		addr += sizeof(uint32_t);
354
355		stem_len = *plen;
356		buf = (char *)addr;
357		addr += (stem_len + 1); // +1 is the nul
358
359		/* store the mapping between old and new */
360		newid = find_stem(data, buf, stem_len);
361		if (newid < 0) {
362			newid = store_stem(data, buf, stem_len);
363			if (newid < 0) {
364				rc = newid;
365				goto err;
366			}
367			data->stem_arr[newid].from_mmap = 1;
368		}
369		stem_map[i] = newid;
370	}
371
372	/* allocate the regex array */
373	section_len = (uint32_t *)addr;
374	addr += sizeof(*section_len);
375
376	for (i = 0; i < *section_len; i++) {
377		struct spec *spec;
378		int32_t stem_id;
379
380		rc = grow_specs(data);
381		if (rc < 0)
382			goto err;
383
384		spec = &data->spec_arr[data->nspec];
385		spec->from_mmap = 1;
386		spec->regcomp = 1;
387
388		plen = (uint32_t *)addr;
389		addr += sizeof(uint32_t);
390		rc = -1;
391		spec->lr.ctx_raw = strdup((char *)addr);
392		if (!spec->lr.ctx_raw)
393			goto err;
394
395		if (addr + *plen >= (char *)mmap_area->addr + mmap_area->len)
396			return -1;
397		addr += *plen;
398
399		plen = (uint32_t *)addr;
400		addr += sizeof(uint32_t);
401		spec->regex_str = (char *)addr;
402		if (addr + *plen >= (char *)mmap_area->addr + mmap_area->len)
403			return -1;
404		addr += *plen;
405
406		spec->mode = *(mode_t *)addr;
407		addr += sizeof(mode_t);
408
409		/* map the stem id from the mmap file to the data->stem_arr */
410		stem_id = *(int32_t *)addr;
411		if (stem_id == -1 || stem_id >= stem_map_len)
412			spec->stem_id = -1;
413		else
414			spec->stem_id = stem_map[stem_id];
415		addr += sizeof(int32_t);
416
417		/* retrieve the hasMetaChars bit */
418		spec->hasMetaChars = *(uint32_t *)addr;
419		addr += sizeof(uint32_t);
420
421		plen = (uint32_t *)addr;
422		addr += sizeof(uint32_t);
423		spec->regex = (pcre *)addr;
424		if (addr + *plen >= (char *)mmap_area->addr + mmap_area->len)
425			return -1;
426		addr += *plen;
427
428		plen = (uint32_t *)addr;
429		addr += sizeof(uint32_t);
430		spec->lsd.study_data = (void *)addr;
431		spec->lsd.flags |= PCRE_EXTRA_STUDY_DATA;
432		if (addr + *plen >= (char *)mmap_area->addr + mmap_area->len)
433			return -1;
434		addr += *plen;
435
436		data->nspec++;
437	}
438	/* win */
439	rc = 0;
440err:
441	free(stem_map);
442
443	return rc;
444}
445
446static int process_file(const char *path, const char *suffix, struct selabel_handle *rec, const char *prefix)
447{
448	FILE *fp;
449	struct stat sb;
450	unsigned int lineno;
451	size_t line_len;
452	char *line_buf = NULL;
453	int rc;
454	char stack_path[PATH_MAX + 1];
455
456	/* append the path suffix if we have one */
457	if (suffix) {
458		rc = snprintf(stack_path, sizeof(stack_path), "%s.%s", path, suffix);
459		if (rc >= (int)sizeof(stack_path)) {
460			errno = ENAMETOOLONG;
461			return -1;
462		}
463		path = stack_path;
464	}
465
466	/* Open the specification file. */
467	if ((fp = fopen(path, "r")) == NULL)
468		return -1;
469	__fsetlocking(fp, FSETLOCKING_BYCALLER);
470
471	if (fstat(fileno(fp), &sb) < 0)
472		return -1;
473	if (!S_ISREG(sb.st_mode)) {
474		errno = EINVAL;
475		return -1;
476	}
477
478	rc = load_mmap(rec, path, &sb);
479	if (rc == 0)
480		goto out;
481
482	/*
483	 * The do detailed validation of the input and fill the spec array
484	 */
485	lineno = 0;
486	while (getline(&line_buf, &line_len, fp) > 0) {
487		rc = process_line(rec, path, prefix, line_buf, ++lineno);
488		if (rc)
489			return rc;
490	}
491out:
492	free(line_buf);
493	fclose(fp);
494
495	return 0;
496}
497
498static int init(struct selabel_handle *rec, struct selinux_opt *opts,
499		unsigned n)
500{
501	struct saved_data *data = (struct saved_data *)rec->data;
502	const char *path = NULL;
503	const char *prefix = NULL;
504	char subs_file[PATH_MAX + 1];
505	int status = -1, baseonly = 0;
506
507	/* Process arguments */
508	while (n--)
509		switch(opts[n].type) {
510		case SELABEL_OPT_PATH:
511			path = opts[n].value;
512			break;
513		case SELABEL_OPT_SUBSET:
514			prefix = opts[n].value;
515			break;
516		case SELABEL_OPT_BASEONLY:
517			baseonly = !!opts[n].value;
518			break;
519		}
520
521	/* Process local and distribution substitution files */
522	if (!path) {
523		rec->dist_subs = selabel_subs_init(selinux_file_context_subs_dist_path(), rec->dist_subs);
524		rec->subs = selabel_subs_init(selinux_file_context_subs_path(), rec->subs);
525		path = selinux_file_context_path();
526	} else {
527		snprintf(subs_file, sizeof(subs_file), "%s.subs_dist", path);
528		rec->dist_subs = selabel_subs_init(subs_file, rec->dist_subs);
529		snprintf(subs_file, sizeof(subs_file), "%s.subs", path);
530		rec->subs = selabel_subs_init(subs_file, rec->subs);
531	}
532
533	rec->spec_file = strdup(path);
534
535	/*
536	 * The do detailed validation of the input and fill the spec array
537	 */
538	status = process_file(path, NULL, rec, prefix);
539	if (status)
540		goto finish;
541
542	if (rec->validating) {
543		status = nodups_specs(data, path);
544		if (status)
545			goto finish;
546	}
547
548	if (!baseonly) {
549		status = process_file(path, "homedirs", rec, prefix);
550		if (status && errno != ENOENT)
551			goto finish;
552
553		status = process_file(path, "local", rec, prefix);
554		if (status && errno != ENOENT)
555			goto finish;
556	}
557
558	status = sort_specs(data);
559
560	status = 0;
561finish:
562	if (status)
563		free(data->spec_arr);
564	return status;
565}
566
567/*
568 * Backend interface routines
569 */
570static void closef(struct selabel_handle *rec)
571{
572	struct saved_data *data = (struct saved_data *)rec->data;
573	struct mmap_area *area, *last_area;
574	struct spec *spec;
575	struct stem *stem;
576	unsigned int i;
577
578	for (i = 0; i < data->nspec; i++) {
579		spec = &data->spec_arr[i];
580		free(spec->lr.ctx_trans);
581		free(spec->lr.ctx_raw);
582		if (spec->from_mmap)
583			continue;
584		free(spec->regex_str);
585		free(spec->type_str);
586		if (spec->regcomp) {
587			pcre_free(spec->regex);
588			pcre_free_study(spec->sd);
589		}
590	}
591
592	for (i = 0; i < (unsigned int)data->num_stems; i++) {
593		stem = &data->stem_arr[i];
594		if (stem->from_mmap)
595			continue;
596		free(stem->buf);
597	}
598
599	if (data->spec_arr)
600		free(data->spec_arr);
601	if (data->stem_arr)
602		free(data->stem_arr);
603
604	area = data->mmap_areas;
605	while (area) {
606		munmap(area->addr, area->len);
607		last_area = area;
608		area = area->next;
609		free(last_area);
610	}
611	free(data);
612}
613
614static struct selabel_lookup_rec *lookup(struct selabel_handle *rec,
615					 const char *key, int type)
616{
617	struct saved_data *data = (struct saved_data *)rec->data;
618	struct spec *spec_arr = data->spec_arr;
619	int i, rc, file_stem;
620	mode_t mode = (mode_t)type;
621	const char *buf;
622	struct selabel_lookup_rec *ret = NULL;
623	char *clean_key = NULL;
624	const char *prev_slash, *next_slash;
625	unsigned int sofar = 0;
626
627	if (!data->nspec) {
628		errno = ENOENT;
629		goto finish;
630	}
631
632	/* Remove duplicate slashes */
633	if ((next_slash = strstr(key, "//"))) {
634		clean_key = malloc(strlen(key) + 1);
635		if (!clean_key)
636			goto finish;
637		prev_slash = key;
638		while (next_slash) {
639			memcpy(clean_key + sofar, prev_slash, next_slash - prev_slash);
640			sofar += next_slash - prev_slash;
641			prev_slash = next_slash + 1;
642			next_slash = strstr(prev_slash, "//");
643		}
644		strcpy(clean_key + sofar, prev_slash);
645		key = clean_key;
646	}
647
648	buf = key;
649	file_stem = find_stem_from_file(data, &buf);
650	mode &= S_IFMT;
651
652	/*
653	 * Check for matching specifications in reverse order, so that
654	 * the last matching specification is used.
655	 */
656	for (i = data->nspec - 1; i >= 0; i--) {
657		struct spec *spec = &spec_arr[i];
658		/* if the spec in question matches no stem or has the same
659		 * stem as the file AND if the spec in question has no mode
660		 * specified or if the mode matches the file mode then we do
661		 * a regex check        */
662		if ((spec->stem_id == -1 || spec->stem_id == file_stem) &&
663		    (!mode || !spec->mode || mode == spec->mode)) {
664			if (compile_regex(data, spec, NULL) < 0)
665				goto finish;
666			if (spec->stem_id == -1)
667				rc = pcre_exec(spec->regex, get_pcre_extra(spec), key, strlen(key), 0, 0, NULL, 0);
668			else
669				rc = pcre_exec(spec->regex, get_pcre_extra(spec), buf, strlen(buf), 0, 0, NULL, 0);
670
671			if (rc == 0) {
672				spec->matches++;
673				break;
674			} else if (rc == PCRE_ERROR_NOMATCH)
675				continue;
676
677			errno = ENOENT;
678			/* else it's an error */
679			goto finish;
680		}
681	}
682
683	if (i < 0 || strcmp(spec_arr[i].lr.ctx_raw, "<<none>>") == 0) {
684		/* No matching specification. */
685		errno = ENOENT;
686		goto finish;
687	}
688
689	errno = 0;
690	ret = &spec_arr[i].lr;
691
692finish:
693	free(clean_key);
694	return ret;
695}
696
697static void stats(struct selabel_handle *rec)
698{
699	struct saved_data *data = (struct saved_data *)rec->data;
700	unsigned int i, nspec = data->nspec;
701	struct spec *spec_arr = data->spec_arr;
702
703	for (i = 0; i < nspec; i++) {
704		if (spec_arr[i].matches == 0) {
705			if (spec_arr[i].type_str) {
706				COMPAT_LOG(SELINUX_WARNING,
707				    "Warning!  No matches for (%s, %s, %s)\n",
708				    spec_arr[i].regex_str,
709				    spec_arr[i].type_str,
710				    spec_arr[i].lr.ctx_raw);
711			} else {
712				COMPAT_LOG(SELINUX_WARNING,
713				    "Warning!  No matches for (%s, %s)\n",
714				    spec_arr[i].regex_str,
715				    spec_arr[i].lr.ctx_raw);
716			}
717		}
718	}
719}
720
721int selabel_file_init(struct selabel_handle *rec, struct selinux_opt *opts,
722		      unsigned nopts)
723{
724	struct saved_data *data;
725
726	data = (struct saved_data *)malloc(sizeof(*data));
727	if (!data)
728		return -1;
729	memset(data, 0, sizeof(*data));
730
731	rec->data = data;
732	rec->func_close = &closef;
733	rec->func_stats = &stats;
734	rec->func_lookup = &lookup;
735
736	return init(rec, opts, nopts);
737}
738