diff --git a/libselinux/src/android/android_internal.h b/libselinux/src/android/android_internal.h index e5d69402..e9405645 100644 --- a/libselinux/src/android/android_internal.h +++ b/libselinux/src/android/android_internal.h @@ -105,14 +105,7 @@ int seapp_context_reload_internal(const path_alts_t *context_paths); /* A parsed seinfo */ struct parsed_seinfo { char base[SEINFO_BUFSIZ]; -#define IS_PRIV_APP (1 << 0) -#define IS_FROM_RUN_AS (1 << 1) -#define IS_EPHEMERAL_APP (1 << 2) -#define IS_ISOLATED_COMPUTE_APP (1 << 3) -#define IS_SDK_SANDBOX_AUDIT (1 << 4) -#define IS_SDK_SANDBOX_NEXT (1 << 5) - int32_t is; - bool isPreinstalledApp; + char isSelector[SEINFO_BUFSIZ]; char partition[SEINFO_BUFSIZ]; int32_t targetSdkVersion; }; diff --git a/libselinux/src/android/android_seapp.c b/libselinux/src/android/android_seapp.c index d1f50292..5df5b338 100644 --- a/libselinux/src/android/android_seapp.c +++ b/libselinux/src/android/android_seapp.c @@ -128,18 +128,14 @@ static void free_prefix_str(struct prefix_str *p) struct seapp_context { /* input selectors */ bool isSystemServer; - bool isEphemeralAppSet; - bool isEphemeralApp; struct prefix_str user; char *seinfo; struct prefix_str name; - bool isPrivAppSet; - bool isPrivApp; int32_t minTargetSdkVersion; - bool fromRunAs; - bool isIsolatedComputeApp; - bool isSdkSandboxAudit; - bool isSdkSandboxNext; + /* name of a boolean selector such as isPrivApp, fromRunAs, + * isIsolatedComputeApp, etc. */ + char *isSelector; + bool isSelectorValue; /* outputs */ char *domain; char *type; @@ -153,6 +149,7 @@ static void free_seapp_context(struct seapp_context *s) if (!s) return; + free(s->isSelector); free_prefix_str(&s->user); free(s->seinfo); free_prefix_str(&s->name); @@ -181,11 +178,6 @@ static int seapp_context_cmp(const void *A, const void *B) if (s1->isSystemServer != s2->isSystemServer) return (s1->isSystemServer ? -1 : 1); - /* Give precedence to a specified isEphemeral= over an - * unspecified isEphemeral=. */ - if (s1->isEphemeralAppSet != s2->isEphemeralAppSet) - return (s1->isEphemeralAppSet ? -1 : 1); - /* Give precedence to a specified user= over an unspecified user=. */ if (s1->user.str && !s2->user.str) return -1; @@ -224,9 +216,12 @@ static int seapp_context_cmp(const void *A, const void *B) return (s1->name.len > s2->name.len) ? -1 : 1; } - /* Give precedence to a specified isPrivApp= over an unspecified isPrivApp=. */ - if (s1->isPrivAppSet != s2->isPrivAppSet) - return (s1->isPrivAppSet ? -1 : 1); + /* Give precedence to a specified isSelector (e.g., isPrivApp) + * over an unspecified isSelector. */ + if(s1->isSelector && !s2->isSelector) + return -1; + if(!s1->isSelector && s2->isSelector) + return 1; /* Give precedence to a higher minTargetSdkVersion= over a lower minTargetSdkVersion=. * If unspecified, minTargetSdkVersion has a default value of 0. @@ -236,10 +231,6 @@ static int seapp_context_cmp(const void *A, const void *B) else if (s1->minTargetSdkVersion < s2->minTargetSdkVersion) return 1; - /* Give precedence to fromRunAs=true. */ - if (s1->fromRunAs != s2->fromRunAs) - return (s1->fromRunAs ? -1 : 1); - /* Give precedence to platform side contexts */ bool isS1Platform = is_platform(s1->partition); bool isS2Platform = is_platform(s2->partition); @@ -375,16 +366,6 @@ int seapp_context_reload_internal(const path_alts_t *context_paths) free_seapp_context(cur); goto err; } - } else if (!strcasecmp(name, "isEphemeralApp")) { - cur->isEphemeralAppSet = true; - if (!strcasecmp(value, "true")) - cur->isEphemeralApp = true; - else if (!strcasecmp(value, "false")) - cur->isEphemeralApp = false; - else { - free_seapp_context(cur); - goto err; - } } else if (!strcasecmp(name, "user")) { if (cur->user.str) { free_seapp_context(cur); @@ -485,55 +466,30 @@ int seapp_context_reload_internal(const path_alts_t *context_paths) free_seapp_context(cur); goto oom; } - } else if (!strcasecmp(name, "isPrivApp")) { - cur->isPrivAppSet = true; - if (!strcasecmp(value, "true")) - cur->isPrivApp = true; - else if (!strcasecmp(value, "false")) - cur->isPrivApp = false; - else { - free_seapp_context(cur); - goto err; - } } else if (!strcasecmp(name, "minTargetSdkVersion")) { cur->minTargetSdkVersion = get_minTargetSdkVersion(value); if (cur->minTargetSdkVersion < 0) { free_seapp_context(cur); goto err; } - } else if (!strcasecmp(name, "fromRunAs")) { - if (!strcasecmp(value, "true")) - cur->fromRunAs = true; - else if (!strcasecmp(value, "false")) - cur->fromRunAs = false; - else { + } else if ((!strncasecmp(name, "is", 2) && strlen(name) > 3) || + !strcasecmp(name, "fromRunAs")) { + if (cur->isSelector) { + selinux_log(SELINUX_ERROR, "%s: isSelector set twice %s", + __FUNCTION__, name); free_seapp_context(cur); goto err; } - } else if (!strcasecmp(name, "isIsolatedComputeApp")) { - if (!strcasecmp(value, "true")) - cur->isIsolatedComputeApp = true; - else if (!strcasecmp(value, "false")) - cur->isIsolatedComputeApp = false; - else { + cur->isSelector = strdup(name); + if (!cur->isSelector) { free_seapp_context(cur); goto err; } - } else if (!strcasecmp(name, "isSdkSandboxAudit")) { - if (!strcasecmp(value, "true")) - cur->isSdkSandboxAudit = true; - else if (!strcasecmp(value, "false")) - cur->isSdkSandboxAudit = false; - else { - free_seapp_context(cur); - goto err; - } - } else if (!strcasecmp(name, "isSdkSandboxNext")) { - if (!strcasecmp(value, "true")) - cur->isSdkSandboxNext = true; - else if (!strcasecmp(value, "false")) - cur->isSdkSandboxNext = false; - else { + if (!strcasecmp(value, "true")) { + cur->isSelectorValue = true; + } else if (!strcasecmp(value, "false")) { + cur->isSelectorValue = false; + } else { free_seapp_context(cur); goto err; } @@ -547,8 +503,9 @@ int seapp_context_reload_internal(const path_alts_t *context_paths) break; } - if (!cur->isPrivApp && cur->name.str && - (!cur->seinfo || !strcmp(cur->seinfo, "default"))) { + if ((!cur->isSelector || strcasecmp(cur->isSelector, "isPrivApp") || !cur->isSelectorValue) && + cur->name.str && + (!cur->seinfo || !strcmp(cur->seinfo, "default"))) { selinux_log(SELINUX_ERROR, "%s: No specific seinfo value specified with name=\"%s\", on line %u: insecure configuration!\n", seapp_contexts_files[i], cur->name.str, lineno); free_seapp_context(cur); @@ -582,11 +539,8 @@ int seapp_context_reload_internal(const path_alts_t *context_paths) bool dup = (!s1->user.str || !strcmp(s1->user.str, s2->user.str)) && (!s1->seinfo || !strcmp(s1->seinfo, s2->seinfo)) && (!s1->name.str || !strcmp(s1->name.str, s2->name.str)) && - (!s1->isPrivAppSet || s1->isPrivApp == s2->isPrivApp) && - (!s1->isEphemeralAppSet || s1->isEphemeralApp == s2->isEphemeralApp) && - (s1->isIsolatedComputeApp == s2->isIsolatedComputeApp) && - (s1->isSdkSandboxAudit == s2->isSdkSandboxAudit) && - (s1->isSdkSandboxNext == s2->isSdkSandboxNext); + (!s1->isSelector || !strcmp(s1->isSelector, s2->isSelector)) && + (s1->isSelectorValue == s2->isSelectorValue); if (dup) { selinux_log(SELINUX_ERROR, "seapp_contexts: Duplicated entry\n"); @@ -608,27 +562,21 @@ int seapp_context_reload_internal(const path_alts_t *context_paths) int i; for (i = 0; i < nspec; i++) { cur = seapp_contexts[i]; - selinux_log(SELINUX_INFO, "%s: isSystemServer=%s isEphemeralApp=%s " - "isIsolatedComputeApp=%s isSdkSandboxAudit=%s isSdkSandboxNext=%s " - "user=%s seinfo=%s name=%s isPrivApp=%s minTargetSdkVersion=%d " - "fromRunAs=%s -> domain=%s type=%s level=%s levelFrom=%s", + selinux_log(SELINUX_INFO, "%s: isSystemServer=%s %s=%s " + "user=%s seinfo=%s name=%s minTargetSdkVersion=%d " + " -> domain=%s type=%s level=%s levelFrom=%s", __FUNCTION__, cur->isSystemServer ? "true" : "false", - cur->isEphemeralAppSet ? (cur->isEphemeralApp ? "true" : "false") : "null", + cur->isSelector ? cur->isSelector : "isX", + cur->isSelector ? (cur->isSelectorValue ? "true" : "false") : "null", cur->user.str, cur->seinfo, cur->name.str, - cur->isPrivAppSet ? (cur->isPrivApp ? "true" : "false") : "null", cur->minTargetSdkVersion, - cur->fromRunAs ? "true" : "false", - cur->isIsolatedComputeApp ? "true" : "false", - cur->isSdkSandboxAudit ? "true" : "false", - cur->isSdkSandboxNext ? "true" : "false", cur->domain, cur->type, cur->level, levelFromName[cur->levelFrom]); } } #endif - ret = 0; out: @@ -675,16 +623,21 @@ void selinux_android_seapp_context_init(void) { */ #define CAT_MAPPING_MAX_ID (0x1<<16) -#define PRIVILEGED_APP_STR "privapp" -#define ISOLATED_COMPUTE_APP_STR "isolatedComputeApp" -#define APPLY_SDK_SANDBOX_AUDIT_RESTRICTIONS_STR "isSdkSandboxAudit" -#define APPLY_SDK_SANDBOX_NEXT_RESTRICTIONS_STR "isSdkSandboxNext" -#define EPHEMERAL_APP_STR "ephemeralapp" #define TARGETSDKVERSION_STR "targetSdkVersion" #define PARTITION_STR "partition" -#define FROM_RUNAS_STR "fromRunAs" #define COMPLETE_STR "complete" +/* Any selector from seapp_contexts starting with "is" is automatically mapped + * to an attribute of seinfo. For historical reasons, there are mappings that do + * not follow this convention. */ +static char* selector_mappings[][2] = { + /* seinfo field, seapp_contexts selector */ + {"privapp", "isPrivApp"}, + {"isolatedComputeApp", "isIsolatedComputeApp"}, + {"ephemeralapp", "isEphemeralApp"}, + {"fromRunAs", "fromRunAs"} +}; + static bool is_preinstalled_app_partition_valid(const char *app_policy, const char *app_partition) { // We forbid system/system_ext/product installed apps from being labeled with vendor sepolicy. // So, either the app shouldn't be platform, or the spec should be platform. @@ -725,6 +678,8 @@ int set_range_from_level(context_t ctx, enum levelFrom levelFrom, uid_t userid, return 0; } +#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) + int parse_seinfo(const char* seinfo, struct parsed_seinfo* info) { char local_seinfo[SEINFO_BUFSIZ]; @@ -747,28 +702,29 @@ int parse_seinfo(const char* seinfo, struct parsed_seinfo* info) { first = false; continue; } - if (!strcmp(token, PRIVILEGED_APP_STR)) { - info->is |= IS_PRIV_APP; + bool matched = false; + for (int i=0; i < ARRAY_SIZE(selector_mappings); i++) { + if (!strcmp(token, selector_mappings[i][0])) { + if(info->isSelector[0]) { + selinux_log(SELINUX_ERROR, "%s: isSelector already populated: %s in %s\n", + __FUNCTION__, token, seinfo); + return -1; + } + strcpy(info->isSelector, selector_mappings[i][1]); + matched = true; + break; + } + } + if (matched) { continue; } - if (!strcmp(token, EPHEMERAL_APP_STR)) { - info->is |= IS_EPHEMERAL_APP; - continue; - } - if (!strcmp(token, ISOLATED_COMPUTE_APP_STR)) { - info->is |= IS_ISOLATED_COMPUTE_APP; - continue; - } - if (!strcmp(token, APPLY_SDK_SANDBOX_AUDIT_RESTRICTIONS_STR)) { - info->is |= IS_SDK_SANDBOX_AUDIT; - continue; - } - if (!strcmp(token, APPLY_SDK_SANDBOX_NEXT_RESTRICTIONS_STR)) { - info->is |= IS_SDK_SANDBOX_NEXT; - continue; - } - if (!strcmp(token, FROM_RUNAS_STR)) { - info->is |= IS_FROM_RUN_AS; + if (!strncmp(token, "is", 2)) { + if(info->isSelector[0]) { + selinux_log(SELINUX_ERROR, "%s: isSelector already populated: %s in %s\n", + __FUNCTION__, token, seinfo); + return -1; + } + strncpy(info->isSelector, token, strlen(token)); continue; } if (!strncmp(token, TARGETSDKVERSION_STR, strlen(TARGETSDKVERSION_STR))) { @@ -792,7 +748,6 @@ int parse_seinfo(const char* seinfo, struct parsed_seinfo* info) { __FUNCTION__, token, seinfo); return -1; } - info->isPreinstalledApp = true; strncpy(info->partition, subtoken, strlen(subtoken)); continue; } @@ -868,9 +823,6 @@ int seapp_context_lookup_internal(enum seapp_kind kind, if (cur->isSystemServer != isSystemServer) continue; - if (cur->isEphemeralAppSet && cur->isEphemeralApp != ((info.is & IS_EPHEMERAL_APP) != 0)) - continue; - if (cur->user.str) { if (cur->user.is_prefix) { if (strncasecmp(username, cur->user.str, cur->user.len-1)) @@ -899,23 +851,16 @@ int seapp_context_lookup_internal(enum seapp_kind kind, } } - if (cur->isPrivAppSet && cur->isPrivApp != ((info.is & IS_PRIV_APP) != 0)) - continue; - if (cur->minTargetSdkVersion > info.targetSdkVersion) continue; - if (cur->fromRunAs != ((info.is & IS_FROM_RUN_AS) != 0)) - continue; - - if (cur->isIsolatedComputeApp != ((info.is & IS_ISOLATED_COMPUTE_APP) != 0)) - continue; - - if (cur->isSdkSandboxAudit != ((info.is & IS_SDK_SANDBOX_AUDIT) != 0)) - continue; - - if (cur->isSdkSandboxNext != ((info.is & IS_SDK_SANDBOX_NEXT) != 0)) - continue; + if (cur->isSelector && cur->isSelectorValue) { + if (!info.isSelector[0]) + continue; + if (strcasecmp(info.isSelector, cur->isSelector)) { + continue; + } + } if (kind == SEAPP_TYPE && !cur->type) continue; @@ -940,7 +885,7 @@ int seapp_context_lookup_internal(enum seapp_kind kind, goto oom; } - if (info.isPreinstalledApp + if (info.partition[0] && !is_preinstalled_app_partition_valid(cur->partition, info.partition)) { // TODO(b/280547417): make this an error after fixing violations selinux_log(SELINUX_WARNING, diff --git a/libselinux/src/android/android_unittest.cpp b/libselinux/src/android/android_unittest.cpp index 1eb9056e..72c67a5e 100644 --- a/libselinux/src/android/android_unittest.cpp +++ b/libselinux/src/android/android_unittest.cpp @@ -11,8 +11,17 @@ using android::base::WriteStringToFile; using std::string; class AndroidSELinuxTest : public ::testing::Test { - protected: + protected: + void SetUp() override { + default_seapp_context_ = StringPrintf("%s/seapp_contexts", tdir_.path); + default_seapp_path_alts_ = { .paths = { + { default_seapp_context_.c_str() } + }}; + } + TemporaryDir tdir_; + string default_seapp_context_; + path_alts_t default_seapp_path_alts_; }; TEST_F(AndroidSELinuxTest, LoadAndLookupServiceContext) @@ -84,19 +93,13 @@ TEST_F(AndroidSELinuxTest, FailLoadingServiceContext) TEST_F(AndroidSELinuxTest, LoadAndLookupSeAppContext) { - string seapp_contexts = - StringPrintf("%s/seapp_contexts", tdir_.path); WriteStringToFile( "# some comment\n" "user=_app seinfo=platform domain=platform_app type=app_data_file levelFrom=user\n", - seapp_contexts); + default_seapp_context_); - const path_alts_t seapp_paths = { .paths = { - { seapp_contexts.c_str() } - }}; - - EXPECT_EQ(seapp_context_reload_internal(&seapp_paths), 0); + EXPECT_EQ(seapp_context_reload_internal(&default_seapp_path_alts_), 0); context_t ctx = context_new("u:r:unknown"); int ret = seapp_context_lookup_internal(SEAPP_DOMAIN, 10001, false, "platform", "com.android.test1", ctx); @@ -111,6 +114,42 @@ TEST_F(AndroidSELinuxTest, LoadAndLookupSeAppContext) context_free(ctx); } +TEST_F(AndroidSELinuxTest, LoadSeAppContextInsecure) +{ + WriteStringToFile( + "user=_app name=com.android.test domain=platform_app type=app_data_file\n", + default_seapp_context_); + + EXPECT_EQ(seapp_context_reload_internal(&default_seapp_path_alts_), -1); + + WriteStringToFile( + "user=_app name=com.android.test isSdkSandbox=true domain=platform_app type=app_data_file\n", + default_seapp_context_); + + EXPECT_EQ(seapp_context_reload_internal(&default_seapp_path_alts_), -1); + + WriteStringToFile( + "user=_app isPrivApp=false name=com.android.test domain=platform_app type=app_data_file\n", + default_seapp_context_); + + EXPECT_EQ(seapp_context_reload_internal(&default_seapp_path_alts_), -1); +} + +TEST_F(AndroidSELinuxTest, LoadSeAppContextInsecureWithSeInfoOrPrivApp) +{ + WriteStringToFile( + "user=_app isPrivApp=true name=com.android.test domain=platform_app type=app_data_file\n", + default_seapp_context_); + + EXPECT_EQ(seapp_context_reload_internal(&default_seapp_path_alts_), 0); + + WriteStringToFile( + "user=_app seinfo=platform name=com.android.test domain=platform_app type=app_data_file\n", + default_seapp_context_); + + EXPECT_EQ(seapp_context_reload_internal(&default_seapp_path_alts_), 0); +} + TEST(AndroidSeAppTest, ParseValidSeInfo) { struct parsed_seinfo info; @@ -122,8 +161,7 @@ TEST(AndroidSeAppTest, ParseValidSeInfo) EXPECT_EQ(ret, 0); EXPECT_STREQ(info.base, "default"); EXPECT_EQ(info.targetSdkVersion, 10000); - EXPECT_EQ(info.is, IS_PRIV_APP); - EXPECT_EQ(info.isPreinstalledApp, true); + EXPECT_STREQ(info.isSelector, "isPrivApp"); EXPECT_STREQ(info.partition, "system"); seinfo = "platform:ephemeralapp:partition=system:complete"; @@ -132,8 +170,7 @@ TEST(AndroidSeAppTest, ParseValidSeInfo) EXPECT_EQ(ret, 0); EXPECT_STREQ(info.base, "platform"); EXPECT_EQ(info.targetSdkVersion, 0); - EXPECT_EQ(info.is, IS_EPHEMERAL_APP); - EXPECT_EQ(info.isPreinstalledApp, true); + EXPECT_STREQ(info.isSelector, "isEphemeralApp"); EXPECT_STREQ(info.partition, "system"); seinfo = "bluetooth"; @@ -142,8 +179,16 @@ TEST(AndroidSeAppTest, ParseValidSeInfo) EXPECT_EQ(ret, 0); EXPECT_STREQ(info.base, "bluetooth"); EXPECT_EQ(info.targetSdkVersion, 0); - EXPECT_EQ(info.isPreinstalledApp, false); - EXPECT_EQ(info.is, 0); + EXPECT_STREQ(info.isSelector, ""); + + seinfo = "default:isSdkSandboxNext:partition=system:complete"; + ret = parse_seinfo(seinfo.c_str(), &info); + + EXPECT_EQ(ret, 0); + EXPECT_STREQ(info.base, "default"); + EXPECT_EQ(info.targetSdkVersion, 0); + EXPECT_STREQ(info.isSelector, "isSdkSandboxNext"); + EXPECT_STREQ(info.partition, "system"); } TEST(AndroidSeAppTest, ParseInvalidSeInfo) @@ -157,6 +202,14 @@ TEST(AndroidSeAppTest, ParseInvalidSeInfo) seinfo = "default:targetSdkVersion=:complete"; ret = parse_seinfo(seinfo.c_str(), &info); EXPECT_EQ(ret, -1); + + seinfo = "default:privapp:ephemeralapp:complete"; + ret = parse_seinfo(seinfo.c_str(), &info); + EXPECT_EQ(ret, -1); + + seinfo = "default:isANewSelector:isAnotherOne:complete"; + ret = parse_seinfo(seinfo.c_str(), &info); + EXPECT_EQ(ret, -1); } TEST(AndroidSeAppTest, ParseOverflow)