matchpathcon.c revision 569ce5498553b87dc7af343b2efb4da8d3ecdb4f
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
9static __thread struct selabel_handle *hnd;
10
11/*
12 * An array for mapping integers to contexts
13 */
14static __thread char **con_array;
15static __thread int con_array_size;
16static __thread int con_array_used;
17
18static pthread_once_t once = PTHREAD_ONCE_INIT;
19static pthread_key_t destructor_key;
20
21static int add_array_elt(char *con)
22{
23	if (con_array_size) {
24		while (con_array_used >= con_array_size) {
25			con_array_size *= 2;
26			con_array = (char **)realloc(con_array, sizeof(char*) *
27						     con_array_size);
28			if (!con_array) {
29				con_array_size = con_array_used = 0;
30				return -1;
31			}
32		}
33	} else {
34		con_array_size = 1000;
35		con_array = (char **)malloc(sizeof(char*) * con_array_size);
36		if (!con_array) {
37			con_array_size = con_array_used = 0;
38			return -1;
39		}
40	}
41
42	con_array[con_array_used] = strdup(con);
43	if (!con_array[con_array_used])
44		return -1;
45	return con_array_used++;
46}
47
48static void free_array_elts(void)
49{
50	con_array_size = con_array_used = 0;
51	free(con_array);
52	con_array = NULL;
53}
54
55static void
56#ifdef __GNUC__
57    __attribute__ ((format(printf, 1, 2)))
58#endif
59    default_printf(const char *fmt, ...)
60{
61	va_list ap;
62	va_start(ap, fmt);
63	vfprintf(stderr, fmt, ap);
64	va_end(ap);
65}
66
67void
68#ifdef __GNUC__
69    __attribute__ ((format(printf, 1, 2)))
70#endif
71    (*myprintf) (const char *fmt,...) = &default_printf;
72int myprintf_compat = 0;
73
74void set_matchpathcon_printf(void (*f) (const char *fmt, ...))
75{
76	myprintf = f ? f : &default_printf;
77	myprintf_compat = 1;
78}
79
80static int (*myinvalidcon) (const char *p, unsigned l, char *c) = NULL;
81
82void set_matchpathcon_invalidcon(int (*f) (const char *p, unsigned l, char *c))
83{
84	myinvalidcon = f;
85}
86
87static int default_canoncon(const char *path, unsigned lineno, char **context)
88{
89	char *tmpcon;
90	if (security_canonicalize_context_raw(*context, &tmpcon) < 0) {
91		if (errno == ENOENT)
92			return 0;
93		if (lineno)
94			myprintf("%s:  line %u has invalid context %s\n", path,
95				 lineno, *context);
96		else
97			myprintf("%s:  invalid context %s\n", path, *context);
98		return 1;
99	}
100	free(*context);
101	*context = tmpcon;
102	return 0;
103}
104
105static int (*mycanoncon) (const char *p, unsigned l, char **c) =
106    NULL;
107
108void set_matchpathcon_canoncon(int (*f) (const char *p, unsigned l, char **c))
109{
110	if (f)
111		mycanoncon = f;
112	else
113		mycanoncon = &default_canoncon;
114}
115
116static __thread struct selinux_opt options[SELABEL_NOPT];
117static __thread int notrans;
118
119void set_matchpathcon_flags(unsigned int flags)
120{
121	int i;
122	memset(options, 0, sizeof(options));
123	i = SELABEL_OPT_BASEONLY;
124	options[i].type = i;
125	options[i].value = (flags & MATCHPATHCON_BASEONLY) ? (char*)1 : NULL;
126	i = SELABEL_OPT_VALIDATE;
127	options[i].type = i;
128	options[i].value = (flags & MATCHPATHCON_VALIDATE) ? (char*)1 : NULL;
129	notrans = flags & MATCHPATHCON_NOTRANS;
130}
131
132/*
133 * An association between an inode and a
134 * specification.
135 */
136typedef struct file_spec {
137	ino_t ino;		/* inode number */
138	int specind;		/* index of specification in spec */
139	char *file;		/* full pathname for diagnostic messages about conflicts */
140	struct file_spec *next;	/* next association in hash bucket chain */
141} file_spec_t;
142
143/*
144 * The hash table of associations, hashed by inode number.
145 * Chaining is used for collisions, with elements ordered
146 * by inode number in each bucket.  Each hash bucket has a dummy
147 * header.
148 */
149#define HASH_BITS 16
150#define HASH_BUCKETS (1 << HASH_BITS)
151#define HASH_MASK (HASH_BUCKETS-1)
152static file_spec_t *fl_head;
153
154/*
155 * Try to add an association between an inode and
156 * a specification.  If there is already an association
157 * for the inode and it conflicts with this specification,
158 * then use the specification that occurs later in the
159 * specification array.
160 */
161int matchpathcon_filespec_add(ino_t ino, int specind, const char *file)
162{
163	file_spec_t *prevfl, *fl;
164	int h, ret;
165	struct stat sb;
166
167	if (!fl_head) {
168		fl_head = malloc(sizeof(file_spec_t) * HASH_BUCKETS);
169		if (!fl_head)
170			goto oom;
171		memset(fl_head, 0, sizeof(file_spec_t) * HASH_BUCKETS);
172	}
173
174	h = (ino + (ino >> HASH_BITS)) & HASH_MASK;
175	for (prevfl = &fl_head[h], fl = fl_head[h].next; fl;
176	     prevfl = fl, fl = fl->next) {
177		if (ino == fl->ino) {
178			ret = lstat(fl->file, &sb);
179			if (ret < 0 || sb.st_ino != ino) {
180				fl->specind = specind;
181				free(fl->file);
182				fl->file = malloc(strlen(file) + 1);
183				if (!fl->file)
184					goto oom;
185				strcpy(fl->file, file);
186				return fl->specind;
187
188			}
189
190			if (!strcmp(con_array[fl->specind],
191				    con_array[specind]))
192				return fl->specind;
193
194			myprintf
195			    ("%s:  conflicting specifications for %s and %s, using %s.\n",
196			     __FUNCTION__, file, fl->file,
197			     con_array[fl->specind]);
198			free(fl->file);
199			fl->file = malloc(strlen(file) + 1);
200			if (!fl->file)
201				goto oom;
202			strcpy(fl->file, file);
203			return fl->specind;
204		}
205
206		if (ino > fl->ino)
207			break;
208	}
209
210	fl = malloc(sizeof(file_spec_t));
211	if (!fl)
212		goto oom;
213	fl->ino = ino;
214	fl->specind = specind;
215	fl->file = malloc(strlen(file) + 1);
216	if (!fl->file)
217		goto oom_freefl;
218	strcpy(fl->file, file);
219	fl->next = prevfl->next;
220	prevfl->next = fl;
221	return fl->specind;
222      oom_freefl:
223	free(fl);
224      oom:
225	myprintf("%s:  insufficient memory for file label entry for %s\n",
226		 __FUNCTION__, file);
227	return -1;
228}
229
230/*
231 * Evaluate the association hash table distribution.
232 */
233void matchpathcon_filespec_eval(void)
234{
235	file_spec_t *fl;
236	int h, used, nel, len, longest;
237
238	if (!fl_head)
239		return;
240
241	used = 0;
242	longest = 0;
243	nel = 0;
244	for (h = 0; h < HASH_BUCKETS; h++) {
245		len = 0;
246		for (fl = fl_head[h].next; fl; fl = fl->next) {
247			len++;
248		}
249		if (len)
250			used++;
251		if (len > longest)
252			longest = len;
253		nel += len;
254	}
255
256	myprintf
257	    ("%s:  hash table stats: %d elements, %d/%d buckets used, longest chain length %d\n",
258	     __FUNCTION__, nel, used, HASH_BUCKETS, longest);
259}
260
261/*
262 * Destroy the association hash table.
263 */
264void matchpathcon_filespec_destroy(void)
265{
266	file_spec_t *fl, *tmp;
267	int h;
268
269	free_array_elts();
270
271	if (!fl_head)
272		return;
273
274	for (h = 0; h < HASH_BUCKETS; h++) {
275		fl = fl_head[h].next;
276		while (fl) {
277			tmp = fl;
278			fl = fl->next;
279			free(tmp->file);
280			free(tmp);
281		}
282		fl_head[h].next = NULL;
283	}
284	free(fl_head);
285	fl_head = NULL;
286}
287
288static void matchpathcon_thread_destructor(void __attribute__((unused)) *ptr)
289{
290	matchpathcon_fini();
291}
292
293static void matchpathcon_init_once(void)
294{
295	__selinux_key_create(&destructor_key, matchpathcon_thread_destructor);
296}
297
298int matchpathcon_init_prefix(const char *path, const char *subset)
299{
300	if (!mycanoncon)
301		mycanoncon = default_canoncon;
302
303	__selinux_once(once, matchpathcon_init_once);
304	__selinux_setspecific(destructor_key, (void *)1);
305
306	options[SELABEL_OPT_SUBSET].type = SELABEL_OPT_SUBSET;
307	options[SELABEL_OPT_SUBSET].value = subset;
308	options[SELABEL_OPT_PATH].type = SELABEL_OPT_PATH;
309	options[SELABEL_OPT_PATH].value = path;
310
311	hnd = selabel_open(SELABEL_CTX_FILE, options, SELABEL_NOPT);
312	return hnd ? 0 : -1;
313}
314
315hidden_def(matchpathcon_init_prefix)
316
317int matchpathcon_init(const char *path)
318{
319	return matchpathcon_init_prefix(path, NULL);
320}
321
322void matchpathcon_fini(void)
323{
324	free_array_elts();
325
326	if (hnd) {
327		selabel_close(hnd);
328		hnd = NULL;
329	}
330}
331
332int matchpathcon(const char *name, mode_t mode, security_context_t * con)
333{
334	if (!hnd && (matchpathcon_init_prefix(NULL, NULL) < 0))
335			return -1;
336
337	return notrans ?
338		selabel_lookup_raw(hnd, con, name, mode) :
339		selabel_lookup(hnd, con, name, mode);
340}
341
342int matchpathcon_index(const char *name, mode_t mode, security_context_t * con)
343{
344	int i = matchpathcon(name, mode, con);
345
346	if (i < 0)
347		return -1;
348
349	return add_array_elt(*con);
350}
351
352void matchpathcon_checkmatches(char *str __attribute__((unused)))
353{
354	selabel_stats(hnd);
355}
356
357/* Compare two contexts to see if their differences are "significant",
358 * or whether the only difference is in the user. */
359int selinux_file_context_cmp(const security_context_t a,
360			     const security_context_t b)
361{
362	char *rest_a, *rest_b;	/* Rest of the context after the user */
363	if (!a && !b)
364		return 0;
365	if (!a)
366		return -1;
367	if (!b)
368		return 1;
369	rest_a = strchr((char *)a, ':');
370	rest_b = strchr((char *)b, ':');
371	if (!rest_a && !rest_b)
372		return 0;
373	if (!rest_a)
374		return -1;
375	if (!rest_b)
376		return 1;
377	return strcmp(rest_a, rest_b);
378}
379
380int selinux_file_context_verify(const char *path, mode_t mode)
381{
382	security_context_t con = NULL;
383	security_context_t fcontext = NULL;
384	int rc = 0;
385
386	rc = lgetfilecon_raw(path, &con);
387	if (rc == -1) {
388		if (errno != ENOTSUP)
389			return 1;
390		else
391			return 0;
392	}
393
394	if (!hnd && (matchpathcon_init_prefix(NULL, NULL) < 0))
395			return -1;
396
397	if (selabel_lookup_raw(hnd, &fcontext, path, mode) != 0) {
398		if (errno != ENOENT)
399			rc = 1;
400		else
401			rc = 0;
402	} else
403		rc = (selinux_file_context_cmp(fcontext, con) == 0);
404
405	freecon(con);
406	freecon(fcontext);
407	return rc;
408}
409
410int selinux_lsetfilecon_default(const char *path)
411{
412	struct stat st;
413	int rc = -1;
414	security_context_t scontext = NULL;
415	if (lstat(path, &st) != 0)
416		return rc;
417
418	if (!hnd && (matchpathcon_init_prefix(NULL, NULL) < 0))
419			return -1;
420
421	/* If there's an error determining the context, or it has none,
422	   return to allow default context */
423	if (selabel_lookup_raw(hnd, &scontext, path, st.st_mode)) {
424		if (errno == ENOENT)
425			rc = 0;
426	} else {
427		rc = lsetfilecon_raw(path, scontext);
428		freecon(scontext);
429	}
430	return rc;
431}
432
433int compat_validate(struct selabel_handle *rec,
434		    struct selabel_lookup_rec *contexts,
435		    const char *path, unsigned lineno)
436{
437	int rc;
438	char **ctx = &contexts->ctx_raw;
439
440	if (myinvalidcon)
441		rc = myinvalidcon(path, lineno, *ctx);
442	else if (mycanoncon)
443		rc = mycanoncon(path, lineno, ctx);
444	else {
445		rc = selabel_validate(rec, contexts);
446		if (rc < 0) {
447			COMPAT_LOG(SELINUX_WARNING,
448				    "%s:  line %d has invalid context %s\n",
449				    path, lineno, *ctx);
450		}
451	}
452
453	return rc ? -1 : 0;
454}
455