From 7fd89c00f70d9c5d16550ef7e3c49b1e707be0af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thi=C3=A9baud=20Weksteen?= Date: Fri, 17 Nov 2023 14:35:33 +1100 Subject: [PATCH] Refactor the parsing of seinfo The seinfo string contains many attributes provided by the caller to match an seapp_contexts rule. Its usage has evolved organically and now contains multiple fields for various purposes. Refactor the parsing of seinfo, relying on strtok as the string informally follows the convention of using colons between attributes and an equal sign to separate an attribute and its value. For instance, default:privapp:targetSdkVersion=10000:partition=system:complete A new internal structure is introduced to capture the attributes. The new parse_seinfo function replaces seinfo_parse (which only parsed the first attribute, historically the original seinfo), get_partition and get_app_targetSdkVersion. The new function is expected to behave similarly to the previous code. Unknown attributes are now logged, but still ignored. The "complete" attribute is now interpreted (as the last attribute), but not required. Unit tests are added to cover standard and edge cases. Test: boot and verify denial logs Test: atest --host libselinux_test Bug: 307635909 Change-Id: Ia0e3522c42c80e6e631ff1af644e03f53d88da93 --- libselinux/src/android/android_internal.h | 19 ++ libselinux/src/android/android_seapp.c | 204 ++++++++++---------- libselinux/src/android/android_unittest.cpp | 62 ++++++ 3 files changed, 188 insertions(+), 97 deletions(-) diff --git a/libselinux/src/android/android_internal.h b/libselinux/src/android/android_internal.h index 0f11ce8a..e5d69402 100644 --- a/libselinux/src/android/android_internal.h +++ b/libselinux/src/android/android_internal.h @@ -100,6 +100,25 @@ int set_range_from_level(context_t ctx, enum levelFrom levelFrom, uid_t userid, /* Similar to seapp_context_reload, but does not implicitly load the default * context files. It should only be used for unit tests. */ int seapp_context_reload_internal(const path_alts_t *context_paths); + +#define SEINFO_BUFSIZ 256 +/* 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 partition[SEINFO_BUFSIZ]; + int32_t targetSdkVersion; +}; + +/* Parses an seinfo string. Returns -1 if an error occurred. */ +int parse_seinfo(const char* seinfo, struct parsed_seinfo* info); #ifdef __cplusplus } #endif diff --git a/libselinux/src/android/android_seapp.c b/libselinux/src/android/android_seapp.c index 847c9354..d1f50292 100644 --- a/libselinux/src/android/android_seapp.c +++ b/libselinux/src/android/android_seapp.c @@ -675,51 +675,15 @@ 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" -static int32_t get_app_targetSdkVersion(const char *seinfo) -{ - char *substr = strstr(seinfo, TARGETSDKVERSION_STR); - long targetSdkVersion; - char *endptr; - if (substr != NULL) { - substr = substr + strlen(TARGETSDKVERSION_STR); - if (substr != NULL) { - targetSdkVersion = strtol(substr, &endptr, 10); - if (('\0' != *endptr && ':' != *endptr) - || (targetSdkVersion < 0) || (targetSdkVersion > INT32_MAX)) { - return -1; /* malformed targetSdkVersion value in seinfo */ - } else { - return (int32_t) targetSdkVersion; - } - } - } - return 0; /* default to 0 when targetSdkVersion= is not present in seinfo */ -} - -// returns true if found, false if not found or error -static bool get_partition(const char *seinfo, char partition[], size_t size) -{ - if (size == 0) return false; - - const char *substr = strstr(seinfo, PARTITION_STR); - if (substr == NULL) return false; - - const char *src = substr + strlen(PARTITION_STR); - const char *p = strchr(src, ':'); - size_t len = p ? p - src : strlen(src); - if (len > size - 1) return -1; - strncpy(partition, src, len); - partition[len] = '\0'; - - return true; -} +#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" 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. @@ -727,26 +691,6 @@ static bool is_preinstalled_app_partition_valid(const char *app_policy, const ch return !(is_platform(app_partition) && !is_platform(app_policy)); } - -static int seinfo_parse(char *dest, const char *src, size_t size) -{ - size_t len; - char *p; - - if ((p = strchr(src, ':')) != NULL) - len = p - src; - else - len = strlen(src); - - if (len > size - 1) - return -1; - - strncpy(dest, src, len); - dest[len] = '\0'; - - return 0; -} - /* Sets the categories of ctx based on the level request */ int set_range_from_level(context_t ctx, enum levelFrom levelFrom, uid_t userid, uid_t appid) { @@ -781,6 +725,86 @@ int set_range_from_level(context_t ctx, enum levelFrom levelFrom, uid_t userid, return 0; } +int parse_seinfo(const char* seinfo, struct parsed_seinfo* info) { + char local_seinfo[SEINFO_BUFSIZ]; + + memset(info, 0, sizeof(*info)); + + if (strlen(seinfo) >= SEINFO_BUFSIZ) { + selinux_log(SELINUX_ERROR, "%s: seinfo is too large to be parsed: %zu\n", + __FUNCTION__, strlen(seinfo)); + return -1; + } + strncpy(local_seinfo, seinfo, SEINFO_BUFSIZ); + + char *token; + char *saved_colon_ptr = NULL; + char *saved_equal_ptr; + bool first = true; + for (token = strtok_r(local_seinfo, ":", &saved_colon_ptr); token; token = strtok_r(NULL, ":", &saved_colon_ptr)) { + if (first) { + strncpy(info->base, token, SEINFO_BUFSIZ); + first = false; + continue; + } + if (!strcmp(token, PRIVILEGED_APP_STR)) { + info->is |= IS_PRIV_APP; + 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; + continue; + } + if (!strncmp(token, TARGETSDKVERSION_STR, strlen(TARGETSDKVERSION_STR))) { + saved_equal_ptr = NULL; + char *subtoken = strtok_r(token, "=", &saved_equal_ptr); + subtoken = strtok_r(NULL, "=", &saved_equal_ptr); + if (!subtoken) { + selinux_log(SELINUX_ERROR, "%s: Invalid targetSdkVersion: %s in %s\n", + __FUNCTION__, token, seinfo); + return -1; + } + info->targetSdkVersion = strtol(subtoken, NULL, 10); + continue; + } + if (!strncmp(token, PARTITION_STR, strlen(PARTITION_STR))) { + saved_equal_ptr = NULL; + char *subtoken = strtok_r(token, "=", &saved_equal_ptr); + subtoken = strtok_r(NULL, "=", &saved_equal_ptr); + if (!subtoken) { + selinux_log(SELINUX_ERROR, "%s: Invalid partition: %s in %s\n", + __FUNCTION__, token, seinfo); + return -1; + } + info->isPreinstalledApp = true; + strncpy(info->partition, subtoken, strlen(subtoken)); + continue; + } + if (!strcmp(token, COMPLETE_STR)) { + break; + } + selinux_log(SELINUX_WARNING, "%s: Ignoring unknown seinfo field: %s in %s\n", + __FUNCTION__, token, seinfo); + } + return 0; +} + /* * This code is Android specific, bionic guarantees that * calls to non-reentrant getpwuid() are thread safe. @@ -800,35 +824,21 @@ int seapp_context_lookup_internal(enum seapp_kind kind, int i; uid_t userid; uid_t appid; - bool isPrivApp = false; - bool isEphemeralApp = false; - bool isIsolatedComputeApp = false; - bool isSdkSandboxAudit = false; - bool isSdkSandboxNext = false; - int32_t targetSdkVersion = 0; - bool fromRunAs = false; - bool isPreinstalledApp = false; - char partition[BUFSIZ]; - char parsedseinfo[BUFSIZ]; + struct parsed_seinfo info; + memset(&info, 0, sizeof(info)); if (seinfo) { - if (seinfo_parse(parsedseinfo, seinfo, BUFSIZ)) + int ret = parse_seinfo(seinfo, &info); + if (ret) { + selinux_log(SELINUX_ERROR, "%s: Invalid seinfo: %s\n", __FUNCTION__, seinfo); goto err; - isPrivApp = strstr(seinfo, PRIVILEGED_APP_STR) ? true : false; - isEphemeralApp = strstr(seinfo, EPHEMERAL_APP_STR) ? true : false; - isIsolatedComputeApp = strstr(seinfo, ISOLATED_COMPUTE_APP_STR) ? true : false; - isSdkSandboxAudit = strstr(seinfo, APPLY_SDK_SANDBOX_AUDIT_RESTRICTIONS_STR) ? true : false; - isSdkSandboxNext = strstr(seinfo, APPLY_SDK_SANDBOX_NEXT_RESTRICTIONS_STR) ? true : false; - fromRunAs = strstr(seinfo, FROM_RUNAS_STR) ? true : false; - targetSdkVersion = get_app_targetSdkVersion(seinfo); - isPreinstalledApp = get_partition(seinfo, partition, BUFSIZ); - if (targetSdkVersion < 0) { + } + if (info.targetSdkVersion < 0) { selinux_log(SELINUX_ERROR, "%s: Invalid targetSdkVersion passed for app with uid %d, seinfo %s, name %s\n", __FUNCTION__, uid, seinfo, pkgname); goto err; } - seinfo = parsedseinfo; } userid = uid / AID_USER_OFFSET; @@ -858,7 +868,7 @@ int seapp_context_lookup_internal(enum seapp_kind kind, if (cur->isSystemServer != isSystemServer) continue; - if (cur->isEphemeralAppSet && cur->isEphemeralApp != isEphemeralApp) + if (cur->isEphemeralAppSet && cur->isEphemeralApp != ((info.is & IS_EPHEMERAL_APP) != 0)) continue; if (cur->user.str) { @@ -872,7 +882,7 @@ int seapp_context_lookup_internal(enum seapp_kind kind, } if (cur->seinfo) { - if (!seinfo || strcasecmp(seinfo, cur->seinfo)) + if (!seinfo || strcasecmp(info.base, cur->seinfo)) continue; } @@ -889,22 +899,22 @@ int seapp_context_lookup_internal(enum seapp_kind kind, } } - if (cur->isPrivAppSet && cur->isPrivApp != isPrivApp) + if (cur->isPrivAppSet && cur->isPrivApp != ((info.is & IS_PRIV_APP) != 0)) continue; - if (cur->minTargetSdkVersion > targetSdkVersion) + if (cur->minTargetSdkVersion > info.targetSdkVersion) continue; - if (cur->fromRunAs != fromRunAs) + if (cur->fromRunAs != ((info.is & IS_FROM_RUN_AS) != 0)) continue; - if (cur->isIsolatedComputeApp != isIsolatedComputeApp) + if (cur->isIsolatedComputeApp != ((info.is & IS_ISOLATED_COMPUTE_APP) != 0)) continue; - if (cur->isSdkSandboxAudit != isSdkSandboxAudit) + if (cur->isSdkSandboxAudit != ((info.is & IS_SDK_SANDBOX_AUDIT) != 0)) continue; - if (cur->isSdkSandboxNext != isSdkSandboxNext) + if (cur->isSdkSandboxNext != ((info.is & IS_SDK_SANDBOX_NEXT) != 0)) continue; if (kind == SEAPP_TYPE && !cur->type) @@ -930,12 +940,12 @@ int seapp_context_lookup_internal(enum seapp_kind kind, goto oom; } - if (isPreinstalledApp - && !is_preinstalled_app_partition_valid(cur->partition, partition)) { + if (info.isPreinstalledApp + && !is_preinstalled_app_partition_valid(cur->partition, info.partition)) { // TODO(b/280547417): make this an error after fixing violations selinux_log(SELINUX_WARNING, "%s: App %s preinstalled to %s can't be labeled with %s sepolicy", - __FUNCTION__, pkgname, partition, cur->partition); + __FUNCTION__, pkgname, info.partition, cur->partition); } break; diff --git a/libselinux/src/android/android_unittest.cpp b/libselinux/src/android/android_unittest.cpp index 47613928..1eb9056e 100644 --- a/libselinux/src/android/android_unittest.cpp +++ b/libselinux/src/android/android_unittest.cpp @@ -110,3 +110,65 @@ TEST_F(AndroidSELinuxTest, LoadAndLookupSeAppContext) EXPECT_STREQ(context_str(ctx), "u:r:app_data_file:s0:c512,c768"); context_free(ctx); } + +TEST(AndroidSeAppTest, ParseValidSeInfo) +{ + struct parsed_seinfo info; + memset(&info, 0, sizeof(info)); + + string seinfo = "default:privapp:targetSdkVersion=10000:partition=system:complete"; + int ret = parse_seinfo(seinfo.c_str(), &info); + + 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.partition, "system"); + + seinfo = "platform:ephemeralapp:partition=system:complete"; + ret = parse_seinfo(seinfo.c_str(), &info); + + 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.partition, "system"); + + seinfo = "bluetooth"; + ret = parse_seinfo(seinfo.c_str(), &info); + + EXPECT_EQ(ret, 0); + EXPECT_STREQ(info.base, "bluetooth"); + EXPECT_EQ(info.targetSdkVersion, 0); + EXPECT_EQ(info.isPreinstalledApp, false); + EXPECT_EQ(info.is, 0); +} + +TEST(AndroidSeAppTest, ParseInvalidSeInfo) +{ + struct parsed_seinfo info; + + string seinfo = "default:targetSdkVersion:complete"; + int ret = parse_seinfo(seinfo.c_str(), &info); + EXPECT_EQ(ret, -1); + + seinfo = "default:targetSdkVersion=:complete"; + ret = parse_seinfo(seinfo.c_str(), &info); + EXPECT_EQ(ret, -1); +} + +TEST(AndroidSeAppTest, ParseOverflow) +{ + struct parsed_seinfo info; + + string seinfo = std::string(255, 'x'); + int ret = parse_seinfo(seinfo.c_str(), &info); + EXPECT_EQ(ret, 0); + EXPECT_STREQ(info.base, seinfo.c_str()); + + seinfo = std::string(256, 'x'); + ret = parse_seinfo(seinfo.c_str(), &info); + EXPECT_EQ(ret, -1); +}