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