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