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