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