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