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