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