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
This commit is contained in:
Thiébaud Weksteen 2023-11-17 14:35:33 +11:00
parent 90c0d6546d
commit 7fd89c00f7
3 changed files with 188 additions and 97 deletions

View file

@ -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 /* Similar to seapp_context_reload, but does not implicitly load the default
* context files. It should only be used for unit tests. */ * context files. It should only be used for unit tests. */
int seapp_context_reload_internal(const path_alts_t *context_paths); 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 #ifdef __cplusplus
} }
#endif #endif

View file

@ -675,51 +675,15 @@ void selinux_android_seapp_context_init(void) {
*/ */
#define CAT_MAPPING_MAX_ID (0x1<<16) #define CAT_MAPPING_MAX_ID (0x1<<16)
#define PRIVILEGED_APP_STR ":privapp" #define PRIVILEGED_APP_STR "privapp"
#define ISOLATED_COMPUTE_APP_STR ":isolatedComputeApp" #define ISOLATED_COMPUTE_APP_STR "isolatedComputeApp"
#define APPLY_SDK_SANDBOX_AUDIT_RESTRICTIONS_STR ":isSdkSandboxAudit" #define APPLY_SDK_SANDBOX_AUDIT_RESTRICTIONS_STR "isSdkSandboxAudit"
#define APPLY_SDK_SANDBOX_NEXT_RESTRICTIONS_STR ":isSdkSandboxNext" #define APPLY_SDK_SANDBOX_NEXT_RESTRICTIONS_STR "isSdkSandboxNext"
#define EPHEMERAL_APP_STR ":ephemeralapp" #define EPHEMERAL_APP_STR "ephemeralapp"
#define TARGETSDKVERSION_STR ":targetSdkVersion=" #define TARGETSDKVERSION_STR "targetSdkVersion"
#define PARTITION_STR ":partition=" #define PARTITION_STR "partition"
#define FROM_RUNAS_STR ":fromRunAs" #define FROM_RUNAS_STR "fromRunAs"
static int32_t get_app_targetSdkVersion(const char *seinfo) #define COMPLETE_STR "complete"
{
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;
}
static bool is_preinstalled_app_partition_valid(const char *app_policy, const char *app_partition) { 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. // 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)); 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 */ /* 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) 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; 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 * This code is Android specific, bionic guarantees that
* calls to non-reentrant getpwuid() are thread safe. * calls to non-reentrant getpwuid() are thread safe.
@ -800,35 +824,21 @@ int seapp_context_lookup_internal(enum seapp_kind kind,
int i; int i;
uid_t userid; uid_t userid;
uid_t appid; uid_t appid;
bool isPrivApp = false; struct parsed_seinfo info;
bool isEphemeralApp = false; memset(&info, 0, sizeof(info));
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];
if (seinfo) { 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; goto err;
isPrivApp = strstr(seinfo, PRIVILEGED_APP_STR) ? true : false; }
isEphemeralApp = strstr(seinfo, EPHEMERAL_APP_STR) ? true : false; if (info.targetSdkVersion < 0) {
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) {
selinux_log(SELINUX_ERROR, selinux_log(SELINUX_ERROR,
"%s: Invalid targetSdkVersion passed for app with uid %d, seinfo %s, name %s\n", "%s: Invalid targetSdkVersion passed for app with uid %d, seinfo %s, name %s\n",
__FUNCTION__, uid, seinfo, pkgname); __FUNCTION__, uid, seinfo, pkgname);
goto err; goto err;
} }
seinfo = parsedseinfo;
} }
userid = uid / AID_USER_OFFSET; userid = uid / AID_USER_OFFSET;
@ -858,7 +868,7 @@ int seapp_context_lookup_internal(enum seapp_kind kind,
if (cur->isSystemServer != isSystemServer) if (cur->isSystemServer != isSystemServer)
continue; continue;
if (cur->isEphemeralAppSet && cur->isEphemeralApp != isEphemeralApp) if (cur->isEphemeralAppSet && cur->isEphemeralApp != ((info.is & IS_EPHEMERAL_APP) != 0))
continue; continue;
if (cur->user.str) { if (cur->user.str) {
@ -872,7 +882,7 @@ int seapp_context_lookup_internal(enum seapp_kind kind,
} }
if (cur->seinfo) { if (cur->seinfo) {
if (!seinfo || strcasecmp(seinfo, cur->seinfo)) if (!seinfo || strcasecmp(info.base, cur->seinfo))
continue; 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; continue;
if (cur->minTargetSdkVersion > targetSdkVersion) if (cur->minTargetSdkVersion > info.targetSdkVersion)
continue; continue;
if (cur->fromRunAs != fromRunAs) if (cur->fromRunAs != ((info.is & IS_FROM_RUN_AS) != 0))
continue; continue;
if (cur->isIsolatedComputeApp != isIsolatedComputeApp) if (cur->isIsolatedComputeApp != ((info.is & IS_ISOLATED_COMPUTE_APP) != 0))
continue; continue;
if (cur->isSdkSandboxAudit != isSdkSandboxAudit) if (cur->isSdkSandboxAudit != ((info.is & IS_SDK_SANDBOX_AUDIT) != 0))
continue; continue;
if (cur->isSdkSandboxNext != isSdkSandboxNext) if (cur->isSdkSandboxNext != ((info.is & IS_SDK_SANDBOX_NEXT) != 0))
continue; continue;
if (kind == SEAPP_TYPE && !cur->type) if (kind == SEAPP_TYPE && !cur->type)
@ -930,12 +940,12 @@ int seapp_context_lookup_internal(enum seapp_kind kind,
goto oom; goto oom;
} }
if (isPreinstalledApp if (info.isPreinstalledApp
&& !is_preinstalled_app_partition_valid(cur->partition, partition)) { && !is_preinstalled_app_partition_valid(cur->partition, info.partition)) {
// TODO(b/280547417): make this an error after fixing violations // TODO(b/280547417): make this an error after fixing violations
selinux_log(SELINUX_WARNING, selinux_log(SELINUX_WARNING,
"%s: App %s preinstalled to %s can't be labeled with %s sepolicy", "%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; break;

View file

@ -110,3 +110,65 @@ TEST_F(AndroidSELinuxTest, LoadAndLookupSeAppContext)
EXPECT_STREQ(context_str(ctx), "u:r:app_data_file:s0:c512,c768"); EXPECT_STREQ(context_str(ctx), "u:r:app_data_file:s0:c512,c768");
context_free(ctx); 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);
}