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