#include <sys/types.h> #include <unistd.h> #include <string.h> #include <stdio.h> #include <stdlib.h> #include <ctype.h> #include <errno.h> #include <pwd.h> #include <grp.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <selinux/selinux.h> #include <selinux/context.h> #include <selinux/android.h> #include <selinux/label.h> #include "callbacks.h" #include "selinux_internal.h" /* * XXX Where should this configuration file be located? * Needs to be accessible by zygote and installd when * setting credentials for app processes and setting permissions * on app data directories. */ static char const * const seapp_contexts_file[] = { "/data/system/seapp_contexts", "/seapp_contexts", 0 }; static const struct selinux_opt seopts[] = { { SELABEL_OPT_PATH, "/data/system/file_contexts" }, { SELABEL_OPT_PATH, "/file_contexts" }, { 0, NULL } }; struct seapp_context { /* input selectors */ char isSystemServer; char *user; size_t len; char prefix; char *seinfo; char *name; /* outputs */ char *domain; char *type; char *level; char levelFromUid; }; static int seapp_context_cmp(const void *A, const void *B) { const struct seapp_context **sp1 = A, **sp2 = B; const struct seapp_context *s1 = *sp1, *s2 = *sp2; /* Give precedence to isSystemServer=true. */ if (s1->isSystemServer != s2->isSystemServer) return (s1->isSystemServer ? -1 : 1); /* Give precedence to a specified user= over an unspecified user=. */ if (s1->user && !s2->user) return -1; if (!s1->user && s2->user) return 1; if (s1->user) { /* Give precedence to a fixed user= string over a prefix. */ if (s1->prefix != s2->prefix) return (s2->prefix ? -1 : 1); /* Give precedence to a longer prefix over a shorter prefix. */ if (s1->prefix && s1->len != s2->len) return (s1->len > s2->len) ? -1 : 1; } /* Give precedence to a specified seinfo= over an unspecified seinfo=. */ if (s1->seinfo && !s2->seinfo) return -1; if (!s1->seinfo && s2->seinfo) return 1; /* Give precedence to a specified name= over an unspecified name=. */ if (s1->name && !s2->name) return -1; if (!s1->name && s2->name) return 1; /* Anything else has equal precedence. */ return 0; } static struct seapp_context **seapp_contexts = NULL; static int nspec = 0; int selinux_android_seapp_context_reload(void) { FILE *fp = NULL; char line_buf[BUFSIZ]; char *token; unsigned lineno; struct seapp_context *cur; char *p, *name = NULL, *value = NULL, *saveptr; size_t len; int i = 0, ret; while ((fp==NULL) && seapp_contexts_file[i]) fp = fopen(seapp_contexts_file[i++], "r"); if (!fp) { selinux_log(SELINUX_ERROR, "%s: could not open any seapp_contexts file", __FUNCTION__); return -1; } nspec = 0; while (fgets(line_buf, sizeof line_buf - 1, fp)) { p = line_buf; while (isspace(*p)) p++; if (*p == '#' || *p == 0) continue; nspec++; } seapp_contexts = calloc(nspec, sizeof(struct seapp_context *)); if (!seapp_contexts) goto oom; rewind(fp); nspec = 0; lineno = 1; while (fgets(line_buf, sizeof line_buf - 1, fp)) { len = strlen(line_buf); if (line_buf[len - 1] == '\n') line_buf[len - 1] = 0; p = line_buf; while (isspace(*p)) p++; if (*p == '#' || *p == 0) continue; cur = calloc(1, sizeof(struct seapp_context)); if (!cur) goto oom; token = strtok_r(p, " \t", &saveptr); if (!token) goto err; while (1) { name = token; value = strchr(name, '='); if (!value) goto err; *value++ = 0; if (!strcasecmp(name, "isSystemServer")) { if (!strcasecmp(value, "true")) cur->isSystemServer = 1; else if (!strcasecmp(value, "false")) cur->isSystemServer = 0; else { goto err; } } else if (!strcasecmp(name, "user")) { cur->user = strdup(value); if (!cur->user) goto oom; cur->len = strlen(cur->user); if (cur->user[cur->len-1] == '*') cur->prefix = 1; } else if (!strcasecmp(name, "seinfo")) { cur->seinfo = strdup(value); if (!cur->seinfo) goto oom; } else if (!strcasecmp(name, "name")) { cur->name = strdup(value); if (!cur->name) goto oom; } else if (!strcasecmp(name, "domain")) { cur->domain = strdup(value); if (!cur->domain) goto oom; } else if (!strcasecmp(name, "type")) { cur->type = strdup(value); if (!cur->type) goto oom; } else if (!strcasecmp(name, "levelFromUid")) { if (!strcasecmp(value, "true")) cur->levelFromUid = 1; else if (!strcasecmp(value, "false")) cur->levelFromUid = 0; else { goto err; } } else if (!strcasecmp(name, "level")) { cur->level = strdup(value); if (!cur->level) goto oom; } else goto err; token = strtok_r(NULL, " \t", &saveptr); if (!token) break; } seapp_contexts[nspec] = cur; nspec++; lineno++; } qsort(seapp_contexts, nspec, sizeof(struct seapp_context *), seapp_context_cmp); #if DEBUG { int i; for (i = 0; i < nspec; i++) { cur = seapp_contexts[i]; selinux_log(SELINUX_INFO, "%s: isSystemServer=%s user=%s seinfo=%s name=%s -> domain=%s type=%s level=%s levelFromUid=%s", __FUNCTION__, cur->isSystemServer ? "true" : "false", cur->user, cur->seinfo, cur->name, cur->domain, cur->type, cur->level, cur->levelFromUid ? "true" : "false"); } } #endif ret = 0; out: fclose(fp); return ret; err: selinux_log(SELINUX_ERROR, "%s: Error reading %s, line %u, name %s, value %s\n", __FUNCTION__, seapp_contexts_file[i - 1], lineno, name, value); ret = -1; goto out; oom: selinux_log(SELINUX_ERROR, "%s: Out of memory\n", __FUNCTION__); ret = -1; goto out; } static void seapp_context_init(void) { selinux_android_seapp_context_reload(); } static pthread_once_t once = PTHREAD_ONCE_INIT; int selinux_android_setfilecon(const char *pkgdir, const char *name, uid_t uid) { char *orig_ctx_str = NULL, *ctx_str; context_t ctx = NULL; struct passwd *pw; struct seapp_context *cur; int i, rc; if (is_selinux_enabled() <= 0) return 0; __selinux_once(once, seapp_context_init); rc = getfilecon(pkgdir, &ctx_str); if (rc < 0) goto err; ctx = context_new(ctx_str); orig_ctx_str = ctx_str; if (!ctx) goto oom; pw = getpwuid(uid); if (!pw) goto err; for (i = 0; i < nspec; i++) { cur = seapp_contexts[i]; /* isSystemServer=true is only for app process labeling. */ if (cur->isSystemServer) continue; if (cur->user) { if (cur->prefix) { if (strncasecmp(pw->pw_name, cur->user, cur->len-1)) continue; } else { if (strcasecmp(pw->pw_name, cur->user)) continue; } } /* seinfo= is ignored / not available for file labeling. */ if (cur->name) { if (!name || strcasecmp(name, cur->name)) continue; } if (!cur->type) continue; if (context_type_set(ctx, cur->type)) goto oom; if (cur->levelFromUid && !strncmp(pw->pw_name, "app_", 4)) { char level[255]; unsigned long id; /* Only supported for app UIDs. */ id = strtoul(pw->pw_name + 4, NULL, 10); snprintf(level, sizeof level, "%s:c%lu", context_range_get(ctx), id); if (context_range_set(ctx, level)) goto oom; } else if (cur->level) { if (context_range_set(ctx, cur->level)) goto oom; } break; } ctx_str = context_str(ctx); if (!ctx_str) goto oom; rc = security_check_context(ctx_str); if (rc < 0) goto err; if (strcmp(ctx_str, orig_ctx_str)) { rc = setfilecon(pkgdir, ctx_str); if (rc < 0) goto err; } rc = 0; out: freecon(orig_ctx_str); context_free(ctx); return rc; err: selinux_log(SELINUX_ERROR, "%s: Error setting context for pkgdir %s, uid %d: %s\n", __FUNCTION__, pkgdir, uid, strerror(errno)); rc = -1; goto out; oom: selinux_log(SELINUX_ERROR, "%s: Out of memory\n", __FUNCTION__); rc = -1; goto out; } int selinux_android_setcontext(uid_t uid, int isSystemServer, const char *seinfo, const char *name) { char *orig_ctx_str = NULL, *ctx_str; context_t ctx = NULL; unsigned long id; struct passwd *pw; struct seapp_context *cur; int i, rc; if (is_selinux_enabled() <= 0) return 0; __selinux_once(once, seapp_context_init); rc = getcon(&ctx_str); if (rc) goto err; ctx = context_new(ctx_str); orig_ctx_str = ctx_str; if (!ctx) goto oom; pw = getpwuid(uid); if (!pw) goto err; for (i = 0; i < nspec; i++) { cur = seapp_contexts[i]; if (cur->isSystemServer != isSystemServer) continue; if (cur->user) { if (cur->prefix) { if (strncasecmp(pw->pw_name, cur->user, cur->len-1)) continue; } else { if (strcasecmp(pw->pw_name, cur->user)) continue; } } if (cur->seinfo) { if (!seinfo || strcasecmp(seinfo, cur->seinfo)) continue; } if (cur->name) { if (!name || strcasecmp(name, cur->name)) continue; } if (!cur->domain) continue; if (context_type_set(ctx, cur->domain)) goto oom; if (cur->levelFromUid && !strncmp(pw->pw_name, "app_", 4)) { char level[255]; unsigned long id; /* Only supported for app UIDs. */ id = strtoul(pw->pw_name + 4, NULL, 10); snprintf(level, sizeof level, "%s:c%lu", context_range_get(ctx), id); if (context_range_set(ctx, level)) goto oom; } else if (cur->level) { if (context_range_set(ctx, cur->level)) goto oom; } break; } if (i == nspec) { /* * No match. * Fail to prevent staying in the zygote's context. */ selinux_log(SELINUX_ERROR, "%s: No match for app with uid %d, seinfo %s, name %s\n", __FUNCTION__, uid, seinfo, name); rc = -1; goto out; } ctx_str = context_str(ctx); if (!ctx_str) goto oom; rc = security_check_context(ctx_str); if (rc < 0) goto err; if (strcmp(ctx_str, orig_ctx_str)) { rc = setcon(ctx_str); if (rc < 0) goto err; } rc = 0; out: freecon(orig_ctx_str); context_free(ctx); return rc; err: if (isSystemServer) selinux_log(SELINUX_ERROR, "%s: Error setting context for system server: %s\n", __FUNCTION__, strerror(errno)); else selinux_log(SELINUX_ERROR, "%s: Error setting context for app with uid %d, seinfo %s: %s\n", __FUNCTION__, uid, seinfo, strerror(errno)); rc = -1; goto out; oom: selinux_log(SELINUX_ERROR, "%s: Out of memory\n", __FUNCTION__); rc = -1; goto out; } static struct selabel_handle *sehandle = NULL; static void file_context_init(void) { int i = 0; sehandle = NULL; while ((sehandle == NULL) && seopts[i].value) { sehandle = selabel_open(SELABEL_CTX_FILE, &seopts[i], 1); i++; } if (!sehandle) selinux_log(SELINUX_ERROR,"%s: Error getting sehandle label (%s)\n", __FUNCTION__, strerror(errno)); } static pthread_once_t fc_once = PTHREAD_ONCE_INIT; int selinux_android_restorecon(const char *pathname) { __selinux_once(fc_once, file_context_init); int ret; if (!sehandle) goto bail; struct stat sb; if (lstat(pathname, &sb) < 0) goto err; char *oldcontext, *newcontext; if (lgetfilecon(pathname, &oldcontext) < 0) goto err; if (selabel_lookup(sehandle, &newcontext, pathname, sb.st_mode) < 0) goto err; if (strcmp(newcontext, "<<none>>") && strcmp(oldcontext, newcontext)) if (lsetfilecon(pathname, newcontext) < 0) goto err; ret = 0; out: if (oldcontext) freecon(oldcontext); if (newcontext) freecon(newcontext); return ret; err: selinux_log(SELINUX_ERROR, "%s: Error restoring context for %s (%s)\n", __FUNCTION__, pathname, strerror(errno)); bail: ret = -1; goto out; }