| #include <sys/stat.h> |
| #include <string.h> |
| #include <errno.h> |
| #include <stdio.h> |
| #include "selinux_internal.h" |
| #include "label_internal.h" |
| #include "callbacks.h" |
| #include <limits.h> |
| |
| static int (*myinvalidcon) (const char *p, unsigned l, char *c) = NULL; |
| static int (*mycanoncon) (const char *p, unsigned l, char **c) = NULL; |
| |
| static void |
| #ifdef __GNUC__ |
| __attribute__ ((format(printf, 1, 2))) |
| #endif |
| default_printf(const char *fmt, ...) |
| { |
| va_list ap; |
| va_start(ap, fmt); |
| vfprintf(stderr, fmt, ap); |
| va_end(ap); |
| } |
| |
| void |
| #ifdef __GNUC__ |
| __attribute__ ((format(printf, 1, 2))) |
| #endif |
| (*myprintf) (const char *fmt,...) = &default_printf; |
| int myprintf_compat = 0; |
| |
| void set_matchpathcon_printf(void (*f) (const char *fmt, ...)) |
| { |
| myprintf = f ? f : &default_printf; |
| myprintf_compat = 1; |
| } |
| |
| int compat_validate(struct selabel_handle *rec, |
| struct selabel_lookup_rec *contexts, |
| const char *path, unsigned lineno) |
| { |
| int rc; |
| char **ctx = &contexts->ctx_raw; |
| |
| if (myinvalidcon) |
| rc = myinvalidcon(path, lineno, *ctx); |
| else if (mycanoncon) |
| rc = mycanoncon(path, lineno, ctx); |
| else { |
| rc = selabel_validate(rec, contexts); |
| if (rc < 0) { |
| if (lineno) { |
| COMPAT_LOG(SELINUX_WARNING, |
| "%s: line %u has invalid context %s\n", |
| path, lineno, *ctx); |
| } else { |
| COMPAT_LOG(SELINUX_WARNING, |
| "%s: has invalid context %s\n", path, *ctx); |
| } |
| } |
| } |
| |
| return rc ? -1 : 0; |
| } |
| |
| #ifndef BUILD_HOST |
| |
| static __thread struct selabel_handle *hnd; |
| |
| /* |
| * An array for mapping integers to contexts |
| */ |
| static __thread char **con_array; |
| static __thread int con_array_size; |
| static __thread int con_array_used; |
| |
| static pthread_once_t once = PTHREAD_ONCE_INIT; |
| static pthread_key_t destructor_key; |
| static int destructor_key_initialized = 0; |
| |
| static void free_array_elts(void) |
| { |
| int i; |
| for (i = 0; i < con_array_used; i++) |
| free(con_array[i]); |
| free(con_array); |
| |
| con_array_size = con_array_used = 0; |
| con_array = NULL; |
| } |
| |
| static int add_array_elt(char *con) |
| { |
| char **tmp; |
| if (con_array_size) { |
| while (con_array_used >= con_array_size) { |
| con_array_size *= 2; |
| tmp = (char **)realloc(con_array, sizeof(char*) * |
| con_array_size); |
| if (!tmp) { |
| free_array_elts(); |
| return -1; |
| } |
| con_array = tmp; |
| } |
| } else { |
| con_array_size = 1000; |
| con_array = (char **)malloc(sizeof(char*) * con_array_size); |
| if (!con_array) { |
| con_array_size = con_array_used = 0; |
| return -1; |
| } |
| } |
| |
| con_array[con_array_used] = strdup(con); |
| if (!con_array[con_array_used]) |
| return -1; |
| return con_array_used++; |
| } |
| |
| void set_matchpathcon_invalidcon(int (*f) (const char *p, unsigned l, char *c)) |
| { |
| myinvalidcon = f; |
| } |
| |
| static int default_canoncon(const char *path, unsigned lineno, char **context) |
| { |
| char *tmpcon; |
| if (security_canonicalize_context_raw(*context, &tmpcon) < 0) { |
| if (errno == ENOENT) |
| return 0; |
| if (lineno) |
| myprintf("%s: line %u has invalid context %s\n", path, |
| lineno, *context); |
| else |
| myprintf("%s: invalid context %s\n", path, *context); |
| return 1; |
| } |
| free(*context); |
| *context = tmpcon; |
| return 0; |
| } |
| |
| void set_matchpathcon_canoncon(int (*f) (const char *p, unsigned l, char **c)) |
| { |
| if (f) |
| mycanoncon = f; |
| else |
| mycanoncon = &default_canoncon; |
| } |
| |
| static __thread struct selinux_opt options[SELABEL_NOPT]; |
| static __thread int notrans; |
| |
| void set_matchpathcon_flags(unsigned int flags) |
| { |
| int i; |
| memset(options, 0, sizeof(options)); |
| i = SELABEL_OPT_BASEONLY; |
| options[i].type = i; |
| options[i].value = (flags & MATCHPATHCON_BASEONLY) ? (char*)1 : NULL; |
| i = SELABEL_OPT_VALIDATE; |
| options[i].type = i; |
| options[i].value = (flags & MATCHPATHCON_VALIDATE) ? (char*)1 : NULL; |
| notrans = flags & MATCHPATHCON_NOTRANS; |
| } |
| |
| /* |
| * An association between an inode and a |
| * specification. |
| */ |
| typedef struct file_spec { |
| ino_t ino; /* inode number */ |
| int specind; /* index of specification in spec */ |
| char *file; /* full pathname for diagnostic messages about conflicts */ |
| struct file_spec *next; /* next association in hash bucket chain */ |
| } file_spec_t; |
| |
| /* |
| * The hash table of associations, hashed by inode number. |
| * Chaining is used for collisions, with elements ordered |
| * by inode number in each bucket. Each hash bucket has a dummy |
| * header. |
| */ |
| #define HASH_BITS 16 |
| #define HASH_BUCKETS (1 << HASH_BITS) |
| #define HASH_MASK (HASH_BUCKETS-1) |
| static file_spec_t *fl_head; |
| |
| /* |
| * Try to add an association between an inode and |
| * a specification. If there is already an association |
| * for the inode and it conflicts with this specification, |
| * then use the specification that occurs later in the |
| * specification array. |
| */ |
| int matchpathcon_filespec_add(ino_t ino, int specind, const char *file) |
| { |
| file_spec_t *prevfl, *fl; |
| int h, ret; |
| struct stat sb; |
| |
| if (!fl_head) { |
| fl_head = malloc(sizeof(file_spec_t) * HASH_BUCKETS); |
| if (!fl_head) |
| goto oom; |
| memset(fl_head, 0, sizeof(file_spec_t) * HASH_BUCKETS); |
| } |
| |
| h = (ino + (ino >> HASH_BITS)) & HASH_MASK; |
| for (prevfl = &fl_head[h], fl = fl_head[h].next; fl; |
| prevfl = fl, fl = fl->next) { |
| if (ino == fl->ino) { |
| ret = lstat(fl->file, &sb); |
| if (ret < 0 || sb.st_ino != ino) { |
| fl->specind = specind; |
| free(fl->file); |
| fl->file = strdup(file); |
| if (!fl->file) |
| goto oom; |
| return fl->specind; |
| |
| } |
| |
| if (!strcmp(con_array[fl->specind], |
| con_array[specind])) |
| return fl->specind; |
| |
| myprintf |
| ("%s: conflicting specifications for %s and %s, using %s.\n", |
| __FUNCTION__, file, fl->file, |
| con_array[fl->specind]); |
| free(fl->file); |
| fl->file = strdup(file); |
| if (!fl->file) |
| goto oom; |
| return fl->specind; |
| } |
| |
| if (ino > fl->ino) |
| break; |
| } |
| |
| fl = malloc(sizeof(file_spec_t)); |
| if (!fl) |
| goto oom; |
| fl->ino = ino; |
| fl->specind = specind; |
| fl->file = strdup(file); |
| if (!fl->file) |
| goto oom_freefl; |
| fl->next = prevfl->next; |
| prevfl->next = fl; |
| return fl->specind; |
| oom_freefl: |
| free(fl); |
| oom: |
| myprintf("%s: insufficient memory for file label entry for %s\n", |
| __FUNCTION__, file); |
| return -1; |
| } |
| |
| /* |
| * Evaluate the association hash table distribution. |
| */ |
| void matchpathcon_filespec_eval(void) |
| { |
| file_spec_t *fl; |
| int h, used, nel, len, longest; |
| |
| if (!fl_head) |
| return; |
| |
| used = 0; |
| longest = 0; |
| nel = 0; |
| for (h = 0; h < HASH_BUCKETS; h++) { |
| len = 0; |
| for (fl = fl_head[h].next; fl; fl = fl->next) { |
| len++; |
| } |
| if (len) |
| used++; |
| if (len > longest) |
| longest = len; |
| nel += len; |
| } |
| |
| myprintf |
| ("%s: hash table stats: %d elements, %d/%d buckets used, longest chain length %d\n", |
| __FUNCTION__, nel, used, HASH_BUCKETS, longest); |
| } |
| |
| /* |
| * Destroy the association hash table. |
| */ |
| void matchpathcon_filespec_destroy(void) |
| { |
| file_spec_t *fl, *tmp; |
| int h; |
| |
| free_array_elts(); |
| |
| if (!fl_head) |
| return; |
| |
| for (h = 0; h < HASH_BUCKETS; h++) { |
| fl = fl_head[h].next; |
| while (fl) { |
| tmp = fl; |
| fl = fl->next; |
| free(tmp->file); |
| free(tmp); |
| } |
| fl_head[h].next = NULL; |
| } |
| free(fl_head); |
| fl_head = NULL; |
| } |
| |
| static void matchpathcon_fini_internal(void) |
| { |
| free_array_elts(); |
| |
| if (hnd) { |
| selabel_close(hnd); |
| hnd = NULL; |
| } |
| } |
| |
| static void matchpathcon_thread_destructor(void __attribute__((unused)) *ptr) |
| { |
| matchpathcon_fini_internal(); |
| } |
| |
| void __attribute__((destructor)) matchpathcon_lib_destructor(void); |
| |
| void __attribute__((destructor)) matchpathcon_lib_destructor(void) |
| { |
| if (destructor_key_initialized) |
| __selinux_key_delete(destructor_key); |
| } |
| |
| static void matchpathcon_init_once(void) |
| { |
| if (__selinux_key_create(&destructor_key, matchpathcon_thread_destructor) == 0) |
| destructor_key_initialized = 1; |
| } |
| |
| int matchpathcon_init_prefix(const char *path, const char *subset) |
| { |
| if (!mycanoncon) |
| mycanoncon = default_canoncon; |
| |
| __selinux_once(once, matchpathcon_init_once); |
| __selinux_setspecific(destructor_key, /* some valid address to please GCC */ &selinux_page_size); |
| |
| options[SELABEL_OPT_SUBSET].type = SELABEL_OPT_SUBSET; |
| options[SELABEL_OPT_SUBSET].value = subset; |
| options[SELABEL_OPT_PATH].type = SELABEL_OPT_PATH; |
| options[SELABEL_OPT_PATH].value = path; |
| |
| hnd = selabel_open(SELABEL_CTX_FILE, options, SELABEL_NOPT); |
| return hnd ? 0 : -1; |
| } |
| |
| |
| int matchpathcon_init(const char *path) |
| { |
| return matchpathcon_init_prefix(path, NULL); |
| } |
| |
| void matchpathcon_fini(void) |
| { |
| matchpathcon_fini_internal(); |
| } |
| |
| /* |
| * We do not want to resolve a symlink to a real path if it is the final |
| * component of the name. Thus we split the pathname on the last "/" and |
| * determine a real path component of the first portion. We then have to |
| * copy the last part back on to get the final real path. Wheww. |
| */ |
| int realpath_not_final(const char *name, char *resolved_path) |
| { |
| char *last_component; |
| char *tmp_path, *p; |
| size_t len = 0; |
| int rc = 0; |
| |
| tmp_path = strdup(name); |
| if (!tmp_path) { |
| myprintf("symlink_realpath(%s) strdup() failed: %m\n", |
| name); |
| rc = -1; |
| goto out; |
| } |
| |
| last_component = strrchr(tmp_path, '/'); |
| |
| if (last_component == tmp_path) { |
| last_component++; |
| p = strcpy(resolved_path, ""); |
| } else if (last_component) { |
| *last_component = '\0'; |
| last_component++; |
| p = realpath(tmp_path, resolved_path); |
| } else { |
| last_component = tmp_path; |
| p = realpath("./", resolved_path); |
| } |
| |
| if (!p) { |
| myprintf("symlink_realpath(%s) realpath() failed: %m\n", |
| name); |
| rc = -1; |
| goto out; |
| } |
| |
| len = strlen(p); |
| if (len + strlen(last_component) + 2 > PATH_MAX) { |
| myprintf("symlink_realpath(%s) failed: Filename too long \n", |
| name); |
| errno = ENAMETOOLONG; |
| rc = -1; |
| goto out; |
| } |
| |
| resolved_path += len; |
| strcpy(resolved_path, "/"); |
| resolved_path += 1; |
| strcpy(resolved_path, last_component); |
| out: |
| free(tmp_path); |
| return rc; |
| } |
| |
| static int matchpathcon_internal(const char *path, mode_t mode, char ** con) |
| { |
| char stackpath[PATH_MAX + 1]; |
| char *p = NULL; |
| if (!hnd && (matchpathcon_init_prefix(NULL, NULL) < 0)) |
| return -1; |
| |
| if (S_ISLNK(mode)) { |
| if (!realpath_not_final(path, stackpath)) |
| path = stackpath; |
| } else { |
| p = realpath(path, stackpath); |
| if (p) |
| path = p; |
| } |
| |
| return notrans ? |
| selabel_lookup_raw(hnd, con, path, mode) : |
| selabel_lookup(hnd, con, path, mode); |
| } |
| |
| int matchpathcon(const char *path, mode_t mode, char ** con) { |
| return matchpathcon_internal(path, mode, con); |
| } |
| |
| int matchpathcon_index(const char *name, mode_t mode, char ** con) |
| { |
| int i = matchpathcon_internal(name, mode, con); |
| |
| if (i < 0) |
| return -1; |
| |
| return add_array_elt(*con); |
| } |
| |
| void matchpathcon_checkmatches(char *str __attribute__((unused))) |
| { |
| selabel_stats(hnd); |
| } |
| |
| /* Compare two contexts to see if their differences are "significant", |
| * or whether the only difference is in the user. */ |
| int selinux_file_context_cmp(const char * a, |
| const char * b) |
| { |
| const char *rest_a, *rest_b; /* Rest of the context after the user */ |
| if (!a && !b) |
| return 0; |
| if (!a) |
| return -1; |
| if (!b) |
| return 1; |
| rest_a = strchr(a, ':'); |
| rest_b = strchr(b, ':'); |
| if (!rest_a && !rest_b) |
| return 0; |
| if (!rest_a) |
| return -1; |
| if (!rest_b) |
| return 1; |
| return strcmp(rest_a, rest_b); |
| } |
| |
| int selinux_file_context_verify(const char *path, mode_t mode) |
| { |
| char * con = NULL; |
| char * fcontext = NULL; |
| int rc = 0; |
| char stackpath[PATH_MAX + 1]; |
| char *p = NULL; |
| |
| if (S_ISLNK(mode)) { |
| if (!realpath_not_final(path, stackpath)) |
| path = stackpath; |
| } else { |
| p = realpath(path, stackpath); |
| if (p) |
| path = p; |
| } |
| |
| rc = lgetfilecon_raw(path, &con); |
| if (rc == -1) { |
| if (errno != ENOTSUP) |
| return -1; |
| else |
| return 0; |
| } |
| |
| if (!hnd && (matchpathcon_init_prefix(NULL, NULL) < 0)) |
| return -1; |
| |
| if (selabel_lookup_raw(hnd, &fcontext, path, mode) != 0) { |
| if (errno != ENOENT) |
| rc = -1; |
| else |
| rc = 0; |
| } else { |
| /* |
| * Need to set errno to 0 as it can be set to ENOENT if the |
| * file_contexts.subs file does not exist (see selabel_open in |
| * label.c), thus causing confusion if errno is checked on return. |
| */ |
| errno = 0; |
| rc = (selinux_file_context_cmp(fcontext, con) == 0); |
| } |
| |
| freecon(con); |
| freecon(fcontext); |
| return rc; |
| } |
| |
| int selinux_lsetfilecon_default(const char *path) |
| { |
| struct stat st; |
| int rc = -1; |
| char * scontext = NULL; |
| if (lstat(path, &st) != 0) |
| return rc; |
| |
| if (!hnd && (matchpathcon_init_prefix(NULL, NULL) < 0)) |
| return -1; |
| |
| /* If there's an error determining the context, or it has none, |
| return to allow default context */ |
| if (selabel_lookup_raw(hnd, &scontext, path, st.st_mode)) { |
| if (errno == ENOENT) |
| rc = 0; |
| } else { |
| rc = lsetfilecon_raw(path, scontext); |
| freecon(scontext); |
| } |
| return rc; |
| } |
| |
| #endif |