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