| #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/mman.h> |
| #include <sys/mount.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 <selinux/avc.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 } }; |
| |
| static const char *const sepolicy_prefix[] = { |
| "/data/system/sepolicy", |
| "/sepolicy", |
| 0 }; |
| |
| 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 *sebool; |
| 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; |
| |
| /* Give precedence to a specified sebool= over an unspecified sebool=. */ |
| if (s1->sebool && !s2->sebool) |
| return -1; |
| if (!s1->sebool && s2->sebool) |
| 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 if (!strcasecmp(name, "sebool")) { |
| cur->sebool = strdup(value); |
| if (!cur->sebool) |
| 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 sebool=%s -> domain=%s type=%s level=%s levelFromUid=%s", |
| __FUNCTION__, |
| cur->isSystemServer ? "true" : "false", cur->user, |
| cur->seinfo, cur->name, cur->sebool, 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_setfilecon2(const char *pkgdir, |
| const char *pkgname, |
| const char *seinfo, |
| uid_t uid) |
| { |
| const char *username; |
| char *orig_ctx_str = NULL, *ctx_str, *end = NULL; |
| context_t ctx = NULL; |
| struct passwd *pw; |
| struct seapp_context *cur; |
| int i, rc; |
| unsigned long id = 0; |
| |
| 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; |
| username = pw->pw_name; |
| |
| if (!strncmp(username, "app_", 4)) { |
| id = strtoul(username + 4, NULL, 10); |
| if (id >= MLS_CATS) |
| goto err; |
| } else if (username[0] == 'u' && isdigit(username[1])) { |
| unsigned long unused; |
| unused = strtoul(username+1, &end, 10); |
| if (end[0] != '_' || end[1] == 0) |
| goto err; |
| if (end[1] == 'a' && isdigit(end[2])) { |
| id = strtoul(end + 2, NULL, 10); |
| if (id >= MLS_CATS/2) |
| goto err; |
| /* regular app UID */ |
| username = "app_"; |
| } else if (end[1] == 'i' && isdigit(end[2])) { |
| id = strtoul(end + 2, NULL, 10); |
| if (id >= MLS_CATS/2) |
| goto err; |
| /* isolated service */ |
| id += MLS_CATS/2; |
| username = "app_"; |
| } else { |
| username = end + 1; |
| } |
| } |
| |
| 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(username, cur->user, cur->len-1)) |
| continue; |
| } else { |
| if (strcasecmp(username, cur->user)) |
| continue; |
| } |
| } |
| |
| if (cur->seinfo) { |
| if (!seinfo || strcasecmp(seinfo, cur->seinfo)) |
| continue; |
| } |
| |
| if (cur->name) { |
| if (!pkgname || strcasecmp(pkgname, cur->name)) |
| continue; |
| } |
| |
| if (!cur->type) |
| continue; |
| |
| if (cur->sebool) { |
| int value = security_get_boolean_active(cur->sebool); |
| if (value == 0) |
| continue; |
| else if (value == -1) { |
| selinux_log(SELINUX_ERROR, \ |
| "Could not find boolean: %s ", cur->sebool); |
| goto err; |
| } |
| } |
| |
| if (context_type_set(ctx, cur->type)) |
| goto oom; |
| |
| if (cur->levelFromUid) { |
| char level[255]; |
| 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_setfilecon(const char *pkgdir, |
| const char *pkgname, |
| uid_t uid) |
| { |
| return selinux_android_setfilecon2(pkgdir, pkgname, NULL, uid); |
| } |
| |
| int selinux_android_setcontext(uid_t uid, |
| int isSystemServer, |
| const char *seinfo, |
| const char *pkgname) |
| { |
| const char *username; |
| char *orig_ctx_str = NULL, *ctx_str, *end = NULL; |
| context_t ctx = NULL; |
| unsigned long id = 0; |
| 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; |
| username = pw->pw_name; |
| |
| if (!strncmp(username, "app_", 4)) { |
| id = strtoul(username + 4, NULL, 10); |
| if (id >= MLS_CATS) |
| goto err; |
| } else if (username[0] == 'u' && isdigit(username[1])) { |
| unsigned long unused; |
| unused = strtoul(username+1, &end, 10); |
| if (end[0] != '_' || end[1] == 0) |
| goto err; |
| if (end[1] == 'a' && isdigit(end[2])) { |
| id = strtoul(end + 2, NULL, 10); |
| if (id >= MLS_CATS/2) |
| goto err; |
| /* regular app UID */ |
| username = "app_"; |
| } else if (end[1] == 'i' && isdigit(end[2])) { |
| id = strtoul(end + 2, NULL, 10); |
| if (id >= MLS_CATS/2) |
| goto err; |
| /* isolated service */ |
| id += MLS_CATS/2; |
| username = "app_"; |
| } else { |
| username = end + 1; |
| } |
| } |
| |
| for (i = 0; i < nspec; i++) { |
| cur = seapp_contexts[i]; |
| |
| if (cur->isSystemServer != isSystemServer) |
| continue; |
| if (cur->user) { |
| if (cur->prefix) { |
| if (strncasecmp(username, cur->user, cur->len-1)) |
| continue; |
| } else { |
| if (strcasecmp(username, cur->user)) |
| continue; |
| } |
| } |
| if (cur->seinfo) { |
| if (!seinfo || strcasecmp(seinfo, cur->seinfo)) |
| continue; |
| } |
| if (cur->name) { |
| if (!pkgname || strcasecmp(pkgname, cur->name)) |
| continue; |
| } |
| |
| if (!cur->domain) |
| continue; |
| |
| if (cur->sebool) { |
| int value = security_get_boolean_active(cur->sebool); |
| if (value == 0) |
| continue; |
| else if (value == -1) { |
| selinux_log(SELINUX_ERROR, \ |
| "Could not find boolean: %s ", cur->sebool); |
| goto err; |
| } |
| } |
| |
| if (context_type_set(ctx, cur->domain)) |
| goto oom; |
| |
| if (cur->levelFromUid) { |
| char level[255]; |
| 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, pkgname); |
| |
| rc = (security_getenforce() == 0) ? 0 : -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); |
| avc_netlink_close(); |
| 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; |
| } |
| |
| |
| struct selabel_handle* selinux_android_file_context_handle(void) |
| { |
| file_context_init(); |
| |
| return sehandle; |
| } |
| |
| int selinux_android_reload_policy(void) |
| { |
| char path[PATH_MAX]; |
| int fd = -1, rc, vers; |
| struct stat sb; |
| void *map = NULL; |
| int i = 0; |
| |
| vers = security_policyvers(); |
| if (vers <= 0) { |
| selinux_log(SELINUX_ERROR, "SELinux: Unable to read policy version\n"); |
| return -1; |
| } |
| selinux_log(SELINUX_INFO, "SELinux: Maximum supported policy version: %d\n", vers); |
| |
| while (fd < 0 && sepolicy_prefix[i]) { |
| snprintf(path, sizeof(path), "%s.%d", |
| sepolicy_prefix[i], vers); |
| fd = open(path, O_RDONLY); |
| |
| int max_vers = vers; |
| while (fd < 0 && errno == ENOENT && --max_vers) { |
| snprintf(path, sizeof(path), "%s.%d", |
| sepolicy_prefix[i], max_vers); |
| fd = open(path, O_RDONLY); |
| } |
| i++; |
| } |
| if (fd < 0) { |
| selinux_log(SELINUX_ERROR, "SELinux: Could not open sepolicy: %s\n", |
| strerror(errno)); |
| return -1; |
| } |
| if (fstat(fd, &sb) < 0) { |
| selinux_log(SELINUX_ERROR, "SELinux: Could not stat %s: %s\n", |
| path, strerror(errno)); |
| close(fd); |
| return -1; |
| } |
| map = mmap(NULL, sb.st_size, PROT_READ, MAP_PRIVATE, fd, 0); |
| if (map == MAP_FAILED) { |
| selinux_log(SELINUX_ERROR, "SELinux: Could not map %s: %s\n", |
| path, strerror(errno)); |
| close(fd); |
| return -1; |
| } |
| |
| rc = security_load_policy(map, sb.st_size); |
| if (rc < 0) { |
| selinux_log(SELINUX_ERROR, "SELinux: Could not load policy: %s\n", |
| strerror(errno)); |
| munmap(map, sb.st_size); |
| close(fd); |
| return -1; |
| } |
| |
| munmap(map, sb.st_size); |
| close(fd); |
| selinux_log(SELINUX_INFO, "SELinux: Loaded policy from %s\n", path); |
| |
| return 0; |
| } |
| |
| int selinux_android_load_policy(void) |
| { |
| mkdir(SELINUXMNT, 0755); |
| if (mount("selinuxfs", SELINUXMNT, "selinuxfs", 0, NULL)) { |
| if (errno == ENODEV) { |
| /* SELinux not enabled in kernel */ |
| return -1; |
| } |
| selinux_log(SELINUX_ERROR,"SELinux: Could not mount selinuxfs: %s\n", |
| strerror(errno)); |
| return -1; |
| } |
| set_selinuxmnt(SELINUXMNT); |
| |
| return selinux_android_reload_policy(); |
| } |