matchpathcon.c revision 7bfaa63839955b2f743f84f2d873fc13298f5777
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{
297	if (destructor_key_initialized)
298		__selinux_key_delete(destructor_key);
299}
300
301static void matchpathcon_init_once(void)
302{
303	if (__selinux_key_create(&destructor_key, matchpathcon_thread_destructor) == 0)
304		destructor_key_initialized = 1;
305}
306
307int matchpathcon_init_prefix(const char *path, const char *subset)
308{
309	if (!mycanoncon)
310		mycanoncon = default_canoncon;
311
312	__selinux_once(once, matchpathcon_init_once);
313	__selinux_setspecific(destructor_key, (void *)1);
314
315	options[SELABEL_OPT_SUBSET].type = SELABEL_OPT_SUBSET;
316	options[SELABEL_OPT_SUBSET].value = subset;
317	options[SELABEL_OPT_PATH].type = SELABEL_OPT_PATH;
318	options[SELABEL_OPT_PATH].value = path;
319
320	hnd = selabel_open(SELABEL_CTX_FILE, options, SELABEL_NOPT);
321	return hnd ? 0 : -1;
322}
323
324hidden_def(matchpathcon_init_prefix)
325
326int matchpathcon_init(const char *path)
327{
328	return matchpathcon_init_prefix(path, NULL);
329}
330
331void matchpathcon_fini(void)
332{
333	free_array_elts();
334
335	if (hnd) {
336		selabel_close(hnd);
337		hnd = NULL;
338	}
339}
340
341/*
342 * We do not want to resolve a symlink to a real path if it is the final
343 * component of the name.  Thus we split the pathname on the last "/" and
344 * determine a real path component of the first portion.  We then have to
345 * copy the last part back on to get the final real path.  Wheww.
346 */
347static int symlink_realpath(const char *name, char *resolved_path)
348{
349	char *last_component;
350	char *tmp_path, *p;
351	size_t len = 0;
352	int rc = 0;
353
354	tmp_path = strdup(name);
355	if (!tmp_path) {
356		myprintf("symlink_realpath(%s) strdup() failed: %s\n",
357			name, strerror(errno));
358		rc = -1;
359		goto out;
360	}
361
362	last_component = strrchr(tmp_path, '/');
363
364	if (last_component == tmp_path) {
365		last_component++;
366		p = strcpy(resolved_path, "/");
367	} else if (last_component) {
368		*last_component = '\0';
369		last_component++;
370		p = realpath(tmp_path, resolved_path);
371	} else {
372		last_component = tmp_path;
373		p = realpath("./", resolved_path);
374	}
375
376	if (!p) {
377		myprintf("symlink_realpath(%s) realpath() failed: %s\n",
378			name, strerror(errno));
379		rc = -1;
380		goto out;
381	}
382
383	len = strlen(p);
384	if (len + strlen(last_component) + 2 > PATH_MAX) {
385		myprintf("symlink_realpath(%s) failed: Filename too long \n",
386			name);
387		errno=ENAMETOOLONG;
388		rc = -1;
389		goto out;
390	}
391
392	resolved_path += len;
393	strcpy(resolved_path, "/");
394	resolved_path += 1;
395	strcpy(resolved_path, last_component);
396out:
397	free(tmp_path);
398	return rc;
399}
400
401int matchpathcon(const char *path, mode_t mode, security_context_t * con)
402{
403	char stackpath[PATH_MAX + 1];
404	char *p = NULL;
405	if (!hnd && (matchpathcon_init_prefix(NULL, NULL) < 0))
406			return -1;
407
408	if (S_ISLNK(mode)) {
409		if (!symlink_realpath(path, stackpath))
410			path = stackpath;
411	} else {
412		p = realpath(path, stackpath);
413		if (p)
414			path = p;
415	}
416
417	return notrans ?
418		selabel_lookup_raw(hnd, con, path, mode) :
419		selabel_lookup(hnd, con, path, mode);
420}
421
422int matchpathcon_index(const char *name, mode_t mode, security_context_t * con)
423{
424	int i = matchpathcon(name, mode, con);
425
426	if (i < 0)
427		return -1;
428
429	return add_array_elt(*con);
430}
431
432void matchpathcon_checkmatches(char *str __attribute__((unused)))
433{
434	selabel_stats(hnd);
435}
436
437/* Compare two contexts to see if their differences are "significant",
438 * or whether the only difference is in the user. */
439int selinux_file_context_cmp(const security_context_t a,
440			     const security_context_t b)
441{
442	char *rest_a, *rest_b;	/* Rest of the context after the user */
443	if (!a && !b)
444		return 0;
445	if (!a)
446		return -1;
447	if (!b)
448		return 1;
449	rest_a = strchr((char *)a, ':');
450	rest_b = strchr((char *)b, ':');
451	if (!rest_a && !rest_b)
452		return 0;
453	if (!rest_a)
454		return -1;
455	if (!rest_b)
456		return 1;
457	return strcmp(rest_a, rest_b);
458}
459
460int selinux_file_context_verify(const char *path, mode_t mode)
461{
462	security_context_t con = NULL;
463	security_context_t fcontext = NULL;
464	int rc = 0;
465
466	rc = lgetfilecon_raw(path, &con);
467	if (rc == -1) {
468		if (errno != ENOTSUP)
469			return -1;
470		else
471			return 0;
472	}
473
474	if (!hnd && (matchpathcon_init_prefix(NULL, NULL) < 0))
475			return -1;
476
477	if (selabel_lookup_raw(hnd, &fcontext, path, mode) != 0) {
478		if (errno != ENOENT)
479			rc = -1;
480		else
481			rc = 0;
482	} else {
483		/*
484		 * Need to set errno to 0 as it can be set to ENOENT if the
485		 * file_contexts.subs file does not exist (see selabel_open in
486		 * label.c), thus causing confusion if errno is checked on return.
487		 */
488		errno = 0;
489		rc = (selinux_file_context_cmp(fcontext, con) == 0);
490	}
491
492	freecon(con);
493	freecon(fcontext);
494	return rc;
495}
496
497int selinux_lsetfilecon_default(const char *path)
498{
499	struct stat st;
500	int rc = -1;
501	security_context_t scontext = NULL;
502	if (lstat(path, &st) != 0)
503		return rc;
504
505	if (!hnd && (matchpathcon_init_prefix(NULL, NULL) < 0))
506			return -1;
507
508	/* If there's an error determining the context, or it has none,
509	   return to allow default context */
510	if (selabel_lookup_raw(hnd, &scontext, path, st.st_mode)) {
511		if (errno == ENOENT)
512			rc = 0;
513	} else {
514		rc = lsetfilecon_raw(path, scontext);
515		freecon(scontext);
516	}
517	return rc;
518}
519
520int compat_validate(struct selabel_handle *rec,
521		    struct selabel_lookup_rec *contexts,
522		    const char *path, unsigned lineno)
523{
524	int rc;
525	char **ctx = &contexts->ctx_raw;
526
527	if (myinvalidcon)
528		rc = myinvalidcon(path, lineno, *ctx);
529	else if (mycanoncon)
530		rc = mycanoncon(path, lineno, ctx);
531	else {
532		rc = selabel_validate(rec, contexts);
533		if (rc < 0) {
534			COMPAT_LOG(SELINUX_WARNING,
535				    "%s:  line %d has invalid context %s\n",
536				    path, lineno, *ctx);
537		}
538	}
539
540	return rc ? -1 : 0;
541}
542