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