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