• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 = strdup(file);
219 				if (!fl->file)
220 					goto oom;
221 				return fl->specind;
222 
223 			}
224 
225 			if (!strcmp(con_array[fl->specind],
226 				    con_array[specind]))
227 				return fl->specind;
228 
229 			myprintf
230 			    ("%s:  conflicting specifications for %s and %s, using %s.\n",
231 			     __FUNCTION__, file, fl->file,
232 			     con_array[fl->specind]);
233 			free(fl->file);
234 			fl->file = strdup(file);
235 			if (!fl->file)
236 				goto oom;
237 			return fl->specind;
238 		}
239 
240 		if (ino > fl->ino)
241 			break;
242 	}
243 
244 	fl = malloc(sizeof(file_spec_t));
245 	if (!fl)
246 		goto oom;
247 	fl->ino = ino;
248 	fl->specind = specind;
249 	fl->file = strdup(file);
250 	if (!fl->file)
251 		goto oom_freefl;
252 	fl->next = prevfl->next;
253 	prevfl->next = fl;
254 	return fl->specind;
255       oom_freefl:
256 	free(fl);
257       oom:
258 	myprintf("%s:  insufficient memory for file label entry for %s\n",
259 		 __FUNCTION__, file);
260 	return -1;
261 }
262 
263 /*
264  * Evaluate the association hash table distribution.
265  */
matchpathcon_filespec_eval(void)266 void matchpathcon_filespec_eval(void)
267 {
268 	file_spec_t *fl;
269 	int h, used, nel, len, longest;
270 
271 	if (!fl_head)
272 		return;
273 
274 	used = 0;
275 	longest = 0;
276 	nel = 0;
277 	for (h = 0; h < HASH_BUCKETS; h++) {
278 		len = 0;
279 		for (fl = fl_head[h].next; fl; fl = fl->next) {
280 			len++;
281 		}
282 		if (len)
283 			used++;
284 		if (len > longest)
285 			longest = len;
286 		nel += len;
287 	}
288 
289 	myprintf
290 	    ("%s:  hash table stats: %d elements, %d/%d buckets used, longest chain length %d\n",
291 	     __FUNCTION__, nel, used, HASH_BUCKETS, longest);
292 }
293 
294 /*
295  * Destroy the association hash table.
296  */
matchpathcon_filespec_destroy(void)297 void matchpathcon_filespec_destroy(void)
298 {
299 	file_spec_t *fl, *tmp;
300 	int h;
301 
302 	free_array_elts();
303 
304 	if (!fl_head)
305 		return;
306 
307 	for (h = 0; h < HASH_BUCKETS; h++) {
308 		fl = fl_head[h].next;
309 		while (fl) {
310 			tmp = fl;
311 			fl = fl->next;
312 			free(tmp->file);
313 			free(tmp);
314 		}
315 		fl_head[h].next = NULL;
316 	}
317 	free(fl_head);
318 	fl_head = NULL;
319 }
320 
matchpathcon_fini_internal(void)321 static void matchpathcon_fini_internal(void)
322 {
323 	free_array_elts();
324 
325 	if (hnd) {
326 		selabel_close(hnd);
327 		hnd = NULL;
328 	}
329 }
330 
matchpathcon_thread_destructor(void * ptr)331 static void matchpathcon_thread_destructor(void __attribute__((unused)) *ptr)
332 {
333 	matchpathcon_fini_internal();
334 }
335 
336 void __attribute__((destructor)) matchpathcon_lib_destructor(void);
337 
matchpathcon_lib_destructor(void)338 void  __attribute__((destructor)) matchpathcon_lib_destructor(void)
339 {
340 	if (destructor_key_initialized)
341 		__selinux_key_delete(destructor_key);
342 }
343 
matchpathcon_init_once(void)344 static void matchpathcon_init_once(void)
345 {
346 	if (__selinux_key_create(&destructor_key, matchpathcon_thread_destructor) == 0)
347 		destructor_key_initialized = 1;
348 }
349 
matchpathcon_init_prefix(const char * path,const char * subset)350 int matchpathcon_init_prefix(const char *path, const char *subset)
351 {
352 	if (!mycanoncon)
353 		mycanoncon = default_canoncon;
354 
355 	__selinux_once(once, matchpathcon_init_once);
356 	__selinux_setspecific(destructor_key, /* some valid address to please GCC */ &selinux_page_size);
357 
358 	options[SELABEL_OPT_SUBSET].type = SELABEL_OPT_SUBSET;
359 	options[SELABEL_OPT_SUBSET].value = subset;
360 	options[SELABEL_OPT_PATH].type = SELABEL_OPT_PATH;
361 	options[SELABEL_OPT_PATH].value = path;
362 
363 	hnd = selabel_open(SELABEL_CTX_FILE, options, SELABEL_NOPT);
364 	return hnd ? 0 : -1;
365 }
366 
367 
matchpathcon_init(const char * path)368 int matchpathcon_init(const char *path)
369 {
370 	return matchpathcon_init_prefix(path, NULL);
371 }
372 
matchpathcon_fini(void)373 void matchpathcon_fini(void)
374 {
375 	matchpathcon_fini_internal();
376 }
377 
378 /*
379  * We do not want to resolve a symlink to a real path if it is the final
380  * component of the name.  Thus we split the pathname on the last "/" and
381  * determine a real path component of the first portion.  We then have to
382  * copy the last part back on to get the final real path.  Wheww.
383  */
realpath_not_final(const char * name,char * resolved_path)384 int realpath_not_final(const char *name, char *resolved_path)
385 {
386 	char *last_component;
387 	char *tmp_path, *p;
388 	size_t len = 0;
389 	int rc = 0;
390 
391 	tmp_path = strdup(name);
392 	if (!tmp_path) {
393 		myprintf("symlink_realpath(%s) strdup() failed: %m\n",
394 			name);
395 		rc = -1;
396 		goto out;
397 	}
398 
399 	last_component = strrchr(tmp_path, '/');
400 
401 	if (last_component == tmp_path) {
402 		last_component++;
403 		p = strcpy(resolved_path, "");
404 	} else if (last_component) {
405 		*last_component = '\0';
406 		last_component++;
407 		p = realpath(tmp_path, resolved_path);
408 	} else {
409 		last_component = tmp_path;
410 		p = realpath("./", resolved_path);
411 	}
412 
413 	if (!p) {
414 		myprintf("symlink_realpath(%s) realpath() failed: %m\n",
415 			name);
416 		rc = -1;
417 		goto out;
418 	}
419 
420 	len = strlen(p);
421 	if (len + strlen(last_component) + 2 > PATH_MAX) {
422 		myprintf("symlink_realpath(%s) failed: Filename too long \n",
423 			name);
424 		errno = ENAMETOOLONG;
425 		rc = -1;
426 		goto out;
427 	}
428 
429 	resolved_path += len;
430 	strcpy(resolved_path, "/");
431 	resolved_path += 1;
432 	strcpy(resolved_path, last_component);
433 out:
434 	free(tmp_path);
435 	return rc;
436 }
437 
matchpathcon_internal(const char * path,mode_t mode,char ** con)438 static int matchpathcon_internal(const char *path, mode_t mode, char ** con)
439 {
440 	char stackpath[PATH_MAX + 1];
441 	char *p = NULL;
442 	if (!hnd && (matchpathcon_init_prefix(NULL, NULL) < 0))
443 			return -1;
444 
445 	if (S_ISLNK(mode)) {
446 		if (!realpath_not_final(path, stackpath))
447 			path = stackpath;
448 	} else {
449 		p = realpath(path, stackpath);
450 		if (p)
451 			path = p;
452 	}
453 
454 	return notrans ?
455 		selabel_lookup_raw(hnd, con, path, mode) :
456 		selabel_lookup(hnd, con, path, mode);
457 }
458 
matchpathcon(const char * path,mode_t mode,char ** con)459 int matchpathcon(const char *path, mode_t mode, char ** con) {
460 	return matchpathcon_internal(path, mode, con);
461 }
462 
matchpathcon_index(const char * name,mode_t mode,char ** con)463 int matchpathcon_index(const char *name, mode_t mode, char ** con)
464 {
465 	int i = matchpathcon_internal(name, mode, con);
466 
467 	if (i < 0)
468 		return -1;
469 
470 	return add_array_elt(*con);
471 }
472 
matchpathcon_checkmatches(char * str)473 void matchpathcon_checkmatches(char *str __attribute__((unused)))
474 {
475 	selabel_stats(hnd);
476 }
477 
478 /* Compare two contexts to see if their differences are "significant",
479  * or whether the only difference is in the user. */
selinux_file_context_cmp(const char * a,const char * b)480 int selinux_file_context_cmp(const char * a,
481 			     const char * b)
482 {
483 	const char *rest_a, *rest_b;	/* Rest of the context after the user */
484 	if (!a && !b)
485 		return 0;
486 	if (!a)
487 		return -1;
488 	if (!b)
489 		return 1;
490 	rest_a = strchr(a, ':');
491 	rest_b = strchr(b, ':');
492 	if (!rest_a && !rest_b)
493 		return 0;
494 	if (!rest_a)
495 		return -1;
496 	if (!rest_b)
497 		return 1;
498 	return strcmp(rest_a, rest_b);
499 }
500 
selinux_file_context_verify(const char * path,mode_t mode)501 int selinux_file_context_verify(const char *path, mode_t mode)
502 {
503 	char * con = NULL;
504 	char * fcontext = NULL;
505 	int rc = 0;
506 	char stackpath[PATH_MAX + 1];
507 	char *p = NULL;
508 
509 	if (S_ISLNK(mode)) {
510 		if (!realpath_not_final(path, stackpath))
511 			path = stackpath;
512 	} else {
513 		p = realpath(path, stackpath);
514 		if (p)
515 			path = p;
516 	}
517 
518 	rc = lgetfilecon_raw(path, &con);
519 	if (rc == -1) {
520 		if (errno != ENOTSUP)
521 			return -1;
522 		else
523 			return 0;
524 	}
525 
526 	if (!hnd && (matchpathcon_init_prefix(NULL, NULL) < 0))
527 			return -1;
528 
529 	if (selabel_lookup_raw(hnd, &fcontext, path, mode) != 0) {
530 		if (errno != ENOENT)
531 			rc = -1;
532 		else
533 			rc = 0;
534 	} else {
535 		/*
536 		 * Need to set errno to 0 as it can be set to ENOENT if the
537 		 * file_contexts.subs file does not exist (see selabel_open in
538 		 * label.c), thus causing confusion if errno is checked on return.
539 		 */
540 		errno = 0;
541 		rc = (selinux_file_context_cmp(fcontext, con) == 0);
542 	}
543 
544 	freecon(con);
545 	freecon(fcontext);
546 	return rc;
547 }
548 
selinux_lsetfilecon_default(const char * path)549 int selinux_lsetfilecon_default(const char *path)
550 {
551 	struct stat st;
552 	int rc = -1;
553 	char * scontext = NULL;
554 	if (lstat(path, &st) != 0)
555 		return rc;
556 
557 	if (!hnd && (matchpathcon_init_prefix(NULL, NULL) < 0))
558 			return -1;
559 
560 	/* If there's an error determining the context, or it has none,
561 	   return to allow default context */
562 	if (selabel_lookup_raw(hnd, &scontext, path, st.st_mode)) {
563 		if (errno == ENOENT)
564 			rc = 0;
565 	} else {
566 		rc = lsetfilecon_raw(path, scontext);
567 		freecon(scontext);
568 	}
569 	return rc;
570 }
571 
572 #endif
573