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