1 #include <sys/stat.h>
2 #include <string.h>
3 #include <errno.h>
4 #include <stdio.h>
5 #include "selinux_internal.h"
6 #include "label_internal.h"
7 #include "callbacks.h"
8 #include <limits.h>
9
10 static __thread struct selabel_handle *hnd;
11
12 /*
13 * An array for mapping integers to contexts
14 */
15 static __thread char **con_array;
16 static __thread int con_array_size;
17 static __thread int con_array_used;
18
19 static pthread_once_t once = PTHREAD_ONCE_INIT;
20 static pthread_key_t destructor_key;
21 static int destructor_key_initialized = 0;
22
add_array_elt(char * con)23 static int add_array_elt(char *con)
24 {
25 if (con_array_size) {
26 while (con_array_used >= con_array_size) {
27 con_array_size *= 2;
28 con_array = (char **)realloc(con_array, sizeof(char*) *
29 con_array_size);
30 if (!con_array) {
31 con_array_size = con_array_used = 0;
32 return -1;
33 }
34 }
35 } else {
36 con_array_size = 1000;
37 con_array = (char **)malloc(sizeof(char*) * con_array_size);
38 if (!con_array) {
39 con_array_size = con_array_used = 0;
40 return -1;
41 }
42 }
43
44 con_array[con_array_used] = strdup(con);
45 if (!con_array[con_array_used])
46 return -1;
47 return con_array_used++;
48 }
49
free_array_elts(void)50 static void free_array_elts(void)
51 {
52 con_array_size = con_array_used = 0;
53 free(con_array);
54 con_array = NULL;
55 }
56
57 static void
58 #ifdef __GNUC__
59 __attribute__ ((format(printf, 1, 2)))
60 #endif
default_printf(const char * fmt,...)61 default_printf(const char *fmt, ...)
62 {
63 va_list ap;
64 va_start(ap, fmt);
65 vfprintf(stderr, fmt, ap);
66 va_end(ap);
67 }
68
69 void
70 #ifdef __GNUC__
71 __attribute__ ((format(printf, 1, 2)))
72 #endif
73 (*myprintf) (const char *fmt,...) = &default_printf;
74 int myprintf_compat = 0;
75
set_matchpathcon_printf(void (* f)(const char * fmt,...))76 void set_matchpathcon_printf(void (*f) (const char *fmt, ...))
77 {
78 myprintf = f ? f : &default_printf;
79 myprintf_compat = 1;
80 }
81
82 static int (*myinvalidcon) (const char *p, unsigned l, char *c) = NULL;
83
set_matchpathcon_invalidcon(int (* f)(const char * p,unsigned l,char * c))84 void set_matchpathcon_invalidcon(int (*f) (const char *p, unsigned l, char *c))
85 {
86 myinvalidcon = f;
87 }
88
default_canoncon(const char * path,unsigned lineno,char ** context)89 static int default_canoncon(const char *path, unsigned lineno, char **context)
90 {
91 char *tmpcon;
92 if (security_canonicalize_context_raw(*context, &tmpcon) < 0) {
93 if (errno == ENOENT)
94 return 0;
95 if (lineno)
96 myprintf("%s: line %u has invalid context %s\n", path,
97 lineno, *context);
98 else
99 myprintf("%s: invalid context %s\n", path, *context);
100 return 1;
101 }
102 free(*context);
103 *context = tmpcon;
104 return 0;
105 }
106
107 static int (*mycanoncon) (const char *p, unsigned l, char **c) =
108 NULL;
109
set_matchpathcon_canoncon(int (* f)(const char * p,unsigned l,char ** c))110 void set_matchpathcon_canoncon(int (*f) (const char *p, unsigned l, char **c))
111 {
112 if (f)
113 mycanoncon = f;
114 else
115 mycanoncon = &default_canoncon;
116 }
117
118 static __thread struct selinux_opt options[SELABEL_NOPT];
119 static __thread int notrans;
120
set_matchpathcon_flags(unsigned int flags)121 void set_matchpathcon_flags(unsigned int flags)
122 {
123 int i;
124 memset(options, 0, sizeof(options));
125 i = SELABEL_OPT_BASEONLY;
126 options[i].type = i;
127 options[i].value = (flags & MATCHPATHCON_BASEONLY) ? (char*)1 : NULL;
128 i = SELABEL_OPT_VALIDATE;
129 options[i].type = i;
130 options[i].value = (flags & MATCHPATHCON_VALIDATE) ? (char*)1 : NULL;
131 notrans = flags & MATCHPATHCON_NOTRANS;
132 }
133
134 /*
135 * An association between an inode and a
136 * specification.
137 */
138 typedef struct file_spec {
139 ino_t ino; /* inode number */
140 int specind; /* index of specification in spec */
141 char *file; /* full pathname for diagnostic messages about conflicts */
142 struct file_spec *next; /* next association in hash bucket chain */
143 } file_spec_t;
144
145 /*
146 * The hash table of associations, hashed by inode number.
147 * Chaining is used for collisions, with elements ordered
148 * by inode number in each bucket. Each hash bucket has a dummy
149 * header.
150 */
151 #define HASH_BITS 16
152 #define HASH_BUCKETS (1 << HASH_BITS)
153 #define HASH_MASK (HASH_BUCKETS-1)
154 static file_spec_t *fl_head;
155
156 /*
157 * Try to add an association between an inode and
158 * a specification. If there is already an association
159 * for the inode and it conflicts with this specification,
160 * then use the specification that occurs later in the
161 * specification array.
162 */
matchpathcon_filespec_add(ino_t ino,int specind,const char * file)163 int matchpathcon_filespec_add(ino_t ino, int specind, const char *file)
164 {
165 file_spec_t *prevfl, *fl;
166 int h, ret;
167 struct stat sb;
168
169 if (!fl_head) {
170 fl_head = malloc(sizeof(file_spec_t) * HASH_BUCKETS);
171 if (!fl_head)
172 goto oom;
173 memset(fl_head, 0, sizeof(file_spec_t) * HASH_BUCKETS);
174 }
175
176 h = (ino + (ino >> HASH_BITS)) & HASH_MASK;
177 for (prevfl = &fl_head[h], fl = fl_head[h].next; fl;
178 prevfl = fl, fl = fl->next) {
179 if (ino == fl->ino) {
180 ret = lstat(fl->file, &sb);
181 if (ret < 0 || sb.st_ino != ino) {
182 fl->specind = specind;
183 free(fl->file);
184 fl->file = malloc(strlen(file) + 1);
185 if (!fl->file)
186 goto oom;
187 strcpy(fl->file, file);
188 return fl->specind;
189
190 }
191
192 if (!strcmp(con_array[fl->specind],
193 con_array[specind]))
194 return fl->specind;
195
196 myprintf
197 ("%s: conflicting specifications for %s and %s, using %s.\n",
198 __FUNCTION__, file, fl->file,
199 con_array[fl->specind]);
200 free(fl->file);
201 fl->file = malloc(strlen(file) + 1);
202 if (!fl->file)
203 goto oom;
204 strcpy(fl->file, file);
205 return fl->specind;
206 }
207
208 if (ino > fl->ino)
209 break;
210 }
211
212 fl = malloc(sizeof(file_spec_t));
213 if (!fl)
214 goto oom;
215 fl->ino = ino;
216 fl->specind = specind;
217 fl->file = malloc(strlen(file) + 1);
218 if (!fl->file)
219 goto oom_freefl;
220 strcpy(fl->file, file);
221 fl->next = prevfl->next;
222 prevfl->next = fl;
223 return fl->specind;
224 oom_freefl:
225 free(fl);
226 oom:
227 myprintf("%s: insufficient memory for file label entry for %s\n",
228 __FUNCTION__, file);
229 return -1;
230 }
231
232 /*
233 * Evaluate the association hash table distribution.
234 */
matchpathcon_filespec_eval(void)235 void matchpathcon_filespec_eval(void)
236 {
237 file_spec_t *fl;
238 int h, used, nel, len, longest;
239
240 if (!fl_head)
241 return;
242
243 used = 0;
244 longest = 0;
245 nel = 0;
246 for (h = 0; h < HASH_BUCKETS; h++) {
247 len = 0;
248 for (fl = fl_head[h].next; fl; fl = fl->next) {
249 len++;
250 }
251 if (len)
252 used++;
253 if (len > longest)
254 longest = len;
255 nel += len;
256 }
257
258 myprintf
259 ("%s: hash table stats: %d elements, %d/%d buckets used, longest chain length %d\n",
260 __FUNCTION__, nel, used, HASH_BUCKETS, longest);
261 }
262
263 /*
264 * Destroy the association hash table.
265 */
matchpathcon_filespec_destroy(void)266 void matchpathcon_filespec_destroy(void)
267 {
268 file_spec_t *fl, *tmp;
269 int h;
270
271 free_array_elts();
272
273 if (!fl_head)
274 return;
275
276 for (h = 0; h < HASH_BUCKETS; h++) {
277 fl = fl_head[h].next;
278 while (fl) {
279 tmp = fl;
280 fl = fl->next;
281 free(tmp->file);
282 free(tmp);
283 }
284 fl_head[h].next = NULL;
285 }
286 free(fl_head);
287 fl_head = NULL;
288 }
289
matchpathcon_thread_destructor(void * ptr)290 static void matchpathcon_thread_destructor(void __attribute__((unused)) *ptr)
291 {
292 matchpathcon_fini();
293 }
294
295 void __attribute__((destructor)) matchpathcon_lib_destructor(void);
296
matchpathcon_lib_destructor(void)297 void hidden __attribute__((destructor)) matchpathcon_lib_destructor(void)
298 {
299 if (destructor_key_initialized)
300 __selinux_key_delete(destructor_key);
301 }
302
matchpathcon_init_once(void)303 static void matchpathcon_init_once(void)
304 {
305 if (__selinux_key_create(&destructor_key, matchpathcon_thread_destructor) == 0)
306 destructor_key_initialized = 1;
307 }
308
matchpathcon_init_prefix(const char * path,const char * subset)309 int matchpathcon_init_prefix(const char *path, const char *subset)
310 {
311 if (!mycanoncon)
312 mycanoncon = default_canoncon;
313
314 __selinux_once(once, matchpathcon_init_once);
315 __selinux_setspecific(destructor_key, (void *)1);
316
317 options[SELABEL_OPT_SUBSET].type = SELABEL_OPT_SUBSET;
318 options[SELABEL_OPT_SUBSET].value = subset;
319 options[SELABEL_OPT_PATH].type = SELABEL_OPT_PATH;
320 options[SELABEL_OPT_PATH].value = path;
321
322 hnd = selabel_open(SELABEL_CTX_FILE, options, SELABEL_NOPT);
323 return hnd ? 0 : -1;
324 }
325
hidden_def(matchpathcon_init_prefix)326 hidden_def(matchpathcon_init_prefix)
327
328 int matchpathcon_init(const char *path)
329 {
330 return matchpathcon_init_prefix(path, NULL);
331 }
332
matchpathcon_fini(void)333 void matchpathcon_fini(void)
334 {
335 free_array_elts();
336
337 if (hnd) {
338 selabel_close(hnd);
339 hnd = NULL;
340 }
341 }
342
343 /*
344 * We do not want to resolve a symlink to a real path if it is the final
345 * component of the name. Thus we split the pathname on the last "/" and
346 * determine a real path component of the first portion. We then have to
347 * copy the last part back on to get the final real path. Wheww.
348 */
realpath_not_final(const char * name,char * resolved_path)349 int realpath_not_final(const char *name, char *resolved_path)
350 {
351 char *last_component;
352 char *tmp_path, *p;
353 size_t len = 0;
354 int rc = 0;
355
356 tmp_path = strdup(name);
357 if (!tmp_path) {
358 myprintf("symlink_realpath(%s) strdup() failed: %s\n",
359 name, strerror(errno));
360 rc = -1;
361 goto out;
362 }
363
364 /* strip leading // */
365 while (tmp_path[len] && tmp_path[len] == '/' &&
366 tmp_path[len+1] && tmp_path[len+1] == '/') {
367 tmp_path++;
368 len++;
369 }
370 last_component = strrchr(tmp_path, '/');
371
372 if (last_component == tmp_path) {
373 last_component++;
374 p = strcpy(resolved_path, "");
375 } else if (last_component) {
376 *last_component = '\0';
377 last_component++;
378 p = realpath(tmp_path, resolved_path);
379 } else {
380 last_component = tmp_path;
381 p = realpath("./", resolved_path);
382 }
383
384 if (!p) {
385 myprintf("symlink_realpath(%s) realpath() failed: %s\n",
386 name, strerror(errno));
387 rc = -1;
388 goto out;
389 }
390
391 len = strlen(p);
392 if (len + strlen(last_component) + 2 > PATH_MAX) {
393 myprintf("symlink_realpath(%s) failed: Filename too long \n",
394 name);
395 errno=ENAMETOOLONG;
396 rc = -1;
397 goto out;
398 }
399
400 resolved_path += len;
401 strcpy(resolved_path, "/");
402 resolved_path += 1;
403 strcpy(resolved_path, last_component);
404 out:
405 free(tmp_path);
406 return rc;
407 }
408
matchpathcon(const char * path,mode_t mode,char ** con)409 int matchpathcon(const char *path, mode_t mode, char ** con)
410 {
411 char stackpath[PATH_MAX + 1];
412 char *p = NULL;
413 if (!hnd && (matchpathcon_init_prefix(NULL, NULL) < 0))
414 return -1;
415
416 if (S_ISLNK(mode)) {
417 if (!realpath_not_final(path, stackpath))
418 path = stackpath;
419 } else {
420 p = realpath(path, stackpath);
421 if (p)
422 path = p;
423 }
424
425 return notrans ?
426 selabel_lookup_raw(hnd, con, path, mode) :
427 selabel_lookup(hnd, con, path, mode);
428 }
429
matchpathcon_index(const char * name,mode_t mode,char ** con)430 int matchpathcon_index(const char *name, mode_t mode, char ** con)
431 {
432 int i = matchpathcon(name, mode, con);
433
434 if (i < 0)
435 return -1;
436
437 return add_array_elt(*con);
438 }
439
matchpathcon_checkmatches(char * str)440 void matchpathcon_checkmatches(char *str __attribute__((unused)))
441 {
442 selabel_stats(hnd);
443 }
444
445 /* Compare two contexts to see if their differences are "significant",
446 * or whether the only difference is in the user. */
selinux_file_context_cmp(const char * a,const char * b)447 int selinux_file_context_cmp(const char * a,
448 const char * b)
449 {
450 char *rest_a, *rest_b; /* Rest of the context after the user */
451 if (!a && !b)
452 return 0;
453 if (!a)
454 return -1;
455 if (!b)
456 return 1;
457 rest_a = strchr((char *)a, ':');
458 rest_b = strchr((char *)b, ':');
459 if (!rest_a && !rest_b)
460 return 0;
461 if (!rest_a)
462 return -1;
463 if (!rest_b)
464 return 1;
465 return strcmp(rest_a, rest_b);
466 }
467
selinux_file_context_verify(const char * path,mode_t mode)468 int selinux_file_context_verify(const char *path, mode_t mode)
469 {
470 char * con = NULL;
471 char * fcontext = NULL;
472 int rc = 0;
473
474 rc = lgetfilecon_raw(path, &con);
475 if (rc == -1) {
476 if (errno != ENOTSUP)
477 return -1;
478 else
479 return 0;
480 }
481
482 if (!hnd && (matchpathcon_init_prefix(NULL, NULL) < 0))
483 return -1;
484
485 if (selabel_lookup_raw(hnd, &fcontext, path, mode) != 0) {
486 if (errno != ENOENT)
487 rc = -1;
488 else
489 rc = 0;
490 } else {
491 /*
492 * Need to set errno to 0 as it can be set to ENOENT if the
493 * file_contexts.subs file does not exist (see selabel_open in
494 * label.c), thus causing confusion if errno is checked on return.
495 */
496 errno = 0;
497 rc = (selinux_file_context_cmp(fcontext, con) == 0);
498 }
499
500 freecon(con);
501 freecon(fcontext);
502 return rc;
503 }
504
selinux_lsetfilecon_default(const char * path)505 int selinux_lsetfilecon_default(const char *path)
506 {
507 struct stat st;
508 int rc = -1;
509 char * scontext = NULL;
510 if (lstat(path, &st) != 0)
511 return rc;
512
513 if (!hnd && (matchpathcon_init_prefix(NULL, NULL) < 0))
514 return -1;
515
516 /* If there's an error determining the context, or it has none,
517 return to allow default context */
518 if (selabel_lookup_raw(hnd, &scontext, path, st.st_mode)) {
519 if (errno == ENOENT)
520 rc = 0;
521 } else {
522 rc = lsetfilecon_raw(path, scontext);
523 freecon(scontext);
524 }
525 return rc;
526 }
527
compat_validate(struct selabel_handle * rec,struct selabel_lookup_rec * contexts,const char * path,unsigned lineno)528 int compat_validate(struct selabel_handle *rec,
529 struct selabel_lookup_rec *contexts,
530 const char *path, unsigned lineno)
531 {
532 int rc;
533 char **ctx = &contexts->ctx_raw;
534
535 if (myinvalidcon)
536 rc = myinvalidcon(path, lineno, *ctx);
537 else if (mycanoncon)
538 rc = mycanoncon(path, lineno, ctx);
539 else {
540 rc = selabel_validate(rec, contexts);
541 if (rc < 0) {
542 if (lineno) {
543 COMPAT_LOG(SELINUX_WARNING,
544 "%s: line %u has invalid context %s\n",
545 path, lineno, *ctx);
546 } else {
547 COMPAT_LOG(SELINUX_WARNING,
548 "%s: has invalid context %s\n", path, *ctx);
549 }
550 }
551 }
552
553 return rc ? -1 : 0;
554 }
555