matchpathcon.c revision 12e2a0f9fceffca224a2fbe80d144afe237907df
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
10static __thread struct selabel_handle *hnd;
11
12/*
13 * An array for mapping integers to contexts
14 */
15static __thread char **con_array;
16static __thread int con_array_size;
17static __thread int con_array_used;
18
19static pthread_once_t once = PTHREAD_ONCE_INIT;
20static pthread_key_t destructor_key;
21static int destructor_key_initialized = 0;
22
23static 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
50static void free_array_elts(void)
51{
52	con_array_size = con_array_used = 0;
53	free(con_array);
54	con_array = NULL;
55}
56
57static void
58#ifdef __GNUC__
59    __attribute__ ((format(printf, 1, 2)))
60#endif
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
69void
70#ifdef __GNUC__
71    __attribute__ ((format(printf, 1, 2)))
72#endif
73    (*myprintf) (const char *fmt,...) = &default_printf;
74int myprintf_compat = 0;
75
76void set_matchpathcon_printf(void (*f) (const char *fmt, ...))
77{
78	myprintf = f ? f : &default_printf;
79	myprintf_compat = 1;
80}
81
82static int (*myinvalidcon) (const char *p, unsigned l, char *c) = NULL;
83
84void set_matchpathcon_invalidcon(int (*f) (const char *p, unsigned l, char *c))
85{
86	myinvalidcon = f;
87}
88
89static 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
107static int (*mycanoncon) (const char *p, unsigned l, char **c) =
108    NULL;
109
110void 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
118static __thread struct selinux_opt options[SELABEL_NOPT];
119static __thread int notrans;
120
121void 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 */
138typedef 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)
154static 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 */
163int 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 */
235void 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 */
266void 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
290static void matchpathcon_thread_destructor(void __attribute__((unused)) *ptr)
291{
292	matchpathcon_fini();
293}
294
295void __attribute__((destructor)) matchpathcon_lib_destructor(void);
296
297void hidden __attribute__((destructor)) matchpathcon_lib_destructor(void)
298{
299	if (destructor_key_initialized)
300		__selinux_key_delete(destructor_key);
301}
302
303static void matchpathcon_init_once(void)
304{
305	if (__selinux_key_create(&destructor_key, matchpathcon_thread_destructor) == 0)
306		destructor_key_initialized = 1;
307}
308
309int 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
326hidden_def(matchpathcon_init_prefix)
327
328int matchpathcon_init(const char *path)
329{
330	return matchpathcon_init_prefix(path, NULL);
331}
332
333void 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 */
349int 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);
404out:
405	free(tmp_path);
406	return rc;
407}
408
409int matchpathcon(const char *path, mode_t mode, security_context_t * 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
430int matchpathcon_index(const char *name, mode_t mode, security_context_t * 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
440void 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. */
447int selinux_file_context_cmp(const security_context_t a,
448			     const security_context_t 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
468int selinux_file_context_verify(const char *path, mode_t mode)
469{
470	security_context_t con = NULL;
471	security_context_t 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
505int selinux_lsetfilecon_default(const char *path)
506{
507	struct stat st;
508	int rc = -1;
509	security_context_t 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
528int 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 %d 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