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