* Copyright (c) 2024 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include "securec.h"
#include "cJSON.h"
#include "appspawn_adapter.h"
#include "appspawn_hook.h"
#include "appspawn_manager.h"
#include "appspawn_utils.h"
#include "appspawn_encaps.h"
#define APP_ENCAPS "encaps"
#define APP_OHOS_ENCAPS_COUNT_KEY "ohos.encaps.count"
#define APP_OHOS_ENCAPS_FORK_KEY "ohos.encaps.fork.count"
#define APP_OHOS_ENCAPS_PERMISSIONS_KEY "permissions"
#define MSG_EXT_NAME_MAX_DECIMAL 10
#define OH_APP_MAX_PIDS_NUM 512
#define OH_ENCAPS_PROC_TYPE_BASE 0x18
#define OH_ENCAPS_PERMISSION_TYPE_BASE 0x1E
#define HM_ENCAPS_PROC_FLAG_BASE 0x1F
#define OH_ENCAPS_MAGIC 'E'
#define OH_PROC_HAP 4
#define OH_ENCAPS_DEFAULT_FLAG 0
#define OH_ENCAPS_DEFAULT_STR ""
#define CUSTOM_SANDBOX_PROCESS_TYPE (1U << 0)
#define OH_ENCAPS_VALUE_MAX_LEN 512
#define OH_ENCAPS_MAX_COUNT 64
#define SET_ENCAPS_PROC_TYPE_CMD _IOW(OH_ENCAPS_MAGIC, OH_ENCAPS_PROC_TYPE_BASE, uint32_t)
#define SET_ENCAPS_PERMISSION_TYPE_CMD _IOW(OH_ENCAPS_MAGIC, OH_ENCAPS_PERMISSION_TYPE_BASE, UserEncaps)
#define SET_ENCAPS_PROC_FLAG_CMD _IOW(OH_ENCAPS_MAGIC, HM_ENCAPS_PROC_FLAG_BASE, uint32_t)
APPSPAWN_STATIC int OpenEncapsFile(void)
{
int fd = 0;
fd = open("/dev/encaps", O_RDWR);
if (fd < 0) {
APPSPAWN_LOGW("Failed to open encaps file errno: %{public}d", errno);
}
return fd;
}
APPSPAWN_STATIC int WriteEncapsInfo(int fd, AppSpawnEncapsBaseType encapsType, const void *encapsInfo, uint32_t flag)
{
if (encapsInfo == NULL) {
return APPSPAWN_ARG_INVALID;
}
int ret = 0;
switch (encapsType) {
case ENCAPS_PROC_TYPE_MODE:
ret = ioctl(fd, SET_ENCAPS_PROC_TYPE_CMD, &flag);
break;
case ENCAPS_PERMISSION_TYPE_MODE:
ret = ioctl(fd, SET_ENCAPS_PERMISSION_TYPE_CMD, encapsInfo);
break;
default:
ret = APPSPAWN_ARG_INVALID;
break;
}
if (ret != 0) {
APPSPAWN_LOGE("Encaps the setup failed ret: %{public}d errno: %{public}d fd: %{public}d", ret, errno, fd);
return ret;
}
return 0;
}
APPSPAWN_STATIC int EnableEncapsForProc(int encapsFileFd)
{
uint32_t flag = OH_PROC_HAP;
return WriteEncapsInfo(encapsFileFd, ENCAPS_PROC_TYPE_MODE, OH_ENCAPS_DEFAULT_STR, flag);
}
APPSPAWN_STATIC uint32_t SpawnGetMaxPids(AppSpawningCtx *property)
{
uint32_t len = 0;
char *pidMaxStr = GetAppPropertyExt(property, MSG_EXT_NAME_MAX_CHILD_PROCCESS_MAX, &len);
APPSPAWN_CHECK_ONLY_EXPER(pidMaxStr != NULL, return 0);
uint32_t maxNum = 0;
if (len != 0) {
char *endPtr = NULL;
maxNum = strtoul(pidMaxStr, &endPtr, MSG_EXT_NAME_MAX_DECIMAL);
if (endPtr == pidMaxStr || *endPtr != '\0') {
APPSPAWN_LOGW("Failed to convert a character string to a value.(ignore), endPtr: %{public}s", endPtr);
return 0;
}
return maxNum;
}
return 0;
}
static inline cJSON *GetJsonObjFromExtInfo(const AppSpawningCtx *property, const char *name)
{
APPSPAWN_CHECK_LOGV(CheckAppMsgFlagsSet(property, APP_FLAGS_ISOLATED_SANDBOX) == 0, return NULL, "ISOLATED proc");
uint32_t size = 0;
char *extInfo = (char *)(GetAppSpawnMsgExtInfo(property->message, name, &size));
if (size == 0 || extInfo == NULL) {
return NULL;
}
APPSPAWN_LOGV("Get json name %{public}s value %{public}s", name, extInfo);
cJSON *extInfoJson = cJSON_Parse(extInfo);
APPSPAWN_CHECK(extInfoJson != NULL, return NULL, "Invalid ext info %{public}s for %{public}s", extInfo, name);
return extInfoJson;
}
APPSPAWN_STATIC int AddPermissionStrToValue(const char *valueStr, UserEncap *encap)
{
APPSPAWN_CHECK(valueStr != NULL, return APPSPAWN_ARG_INVALID, "Invalid string value");
uint32_t valueLen = strlen(valueStr) + 1;
APPSPAWN_CHECK(valueLen > 1 && valueLen <= OH_ENCAPS_VALUE_MAX_LEN, return APPSPAWN_ARG_INVALID,
"String value len is invalid, len: %{public}u", valueLen);
APPSPAWN_LOGV("valueStr:%{public}s", valueStr);
cJSON *valueJson = cJSON_Parse(valueStr);
APPSPAWN_CHECK(valueJson != NULL, return APPSPAWN_ERROR_UTILS_CREATE_JSON_FAIL, "Invalid valueStr");
cJSON *firstItem = cJSON_GetArrayItem(valueJson, 0);
APPSPAWN_CHECK(firstItem != NULL && cJSON_IsString(firstItem), cJSON_Delete(valueJson);
return APPSPAWN_ARG_INVALID, "get first valueJson element failed");
const char *value = firstItem->valuestring;
valueLen = strlen(value) + 1;
APPSPAWN_LOGV("firstItem->valuestring:%{public}s", value);
encap->value.ptrValue = (void *)strdup(value);
APPSPAWN_CHECK(encap->value.ptrValue != NULL, cJSON_Delete(valueJson);
return APPSPAWN_ERROR_UTILS_MEM_FAIL, "strdup value failed");
encap->valueLen = valueLen;
encap->type = ENCAPS_CHAR_ARRAY;
cJSON_Delete(valueJson);
return 0;
}
APPSPAWN_STATIC int AddPermissionIntArrayToValue(cJSON *arrayItem, UserEncap *encap, uint32_t arraySize)
{
uint32_t valueLen = sizeof(int) * arraySize;
APPSPAWN_CHECK(valueLen <= OH_ENCAPS_VALUE_MAX_LEN, return APPSPAWN_ARG_INVALID,
"Int array len too long, len: %{public}u", valueLen);
int *value = (int *)calloc(1, valueLen);
APPSPAWN_CHECK(value != NULL, return APPSPAWN_SYSTEM_ERROR, "Failed to calloc int array value");
cJSON *arrayItemTemp = arrayItem;
for (uint32_t index = 0; index < arraySize; index++) {
if (arrayItemTemp == NULL || !cJSON_IsNumber(arrayItemTemp)) {
free(value);
APPSPAWN_LOGE("Invalid int array item type");
return APPSPAWN_ARG_INVALID;
}
value[index] = arrayItemTemp->valueint;
arrayItemTemp = arrayItemTemp->next;
}
encap->value.ptrValue = (void *)value;
encap->valueLen = valueLen;
encap->type = ENCAPS_INT_ARRAY;
return 0;
}
APPSPAWN_STATIC int AddPermissionBoolArrayToValue(cJSON *arrayItem, UserEncap *encap, uint32_t arraySize)
{
uint32_t valueLen = sizeof(bool) * arraySize;
APPSPAWN_CHECK(valueLen <= OH_ENCAPS_VALUE_MAX_LEN, return APPSPAWN_ARG_INVALID,
"Bool array len too long, len: %{public}u", valueLen);
bool *value = (bool *)calloc(1, valueLen);
APPSPAWN_CHECK(value != NULL, return APPSPAWN_SYSTEM_ERROR, "Failed to calloc bool array value");
cJSON *arrayItemTemp = arrayItem;
for (uint32_t index = 0; index < arraySize; index++) {
if (arrayItemTemp == NULL || !cJSON_IsBool(arrayItemTemp)) {
free(value);
APPSPAWN_LOGE("Invalid bool array item type");
return APPSPAWN_ARG_INVALID;
}
value[index] = cJSON_IsTrue(arrayItemTemp) ? true : false;
arrayItemTemp = arrayItemTemp->next;
}
encap->value.ptrValue = (void *)value;
encap->valueLen = valueLen;
encap->type = ENCAPS_BOOL_ARRAY;
return 0;
}
APPSPAWN_STATIC int AddPermissionStrArrayToValue(cJSON *arrayItem, UserEncap *encap)
{
uint32_t valueLen = 0;
for (cJSON *arrayItemTemp = arrayItem; arrayItemTemp != NULL; arrayItemTemp = arrayItemTemp->next) {
if (!cJSON_IsString(arrayItemTemp) || arrayItemTemp->valuestring == NULL) {
APPSPAWN_LOGE("Invalid string array item type");
return APPSPAWN_ARG_INVALID;
}
uint32_t tempLen = strlen(arrayItemTemp->valuestring);
APPSPAWN_CHECK(tempLen > 0, return APPSPAWN_ARG_INVALID, "String array value is invalied");
valueLen += tempLen + 1;
}
APPSPAWN_CHECK(valueLen > 0 && valueLen <= OH_ENCAPS_VALUE_MAX_LEN, return APPSPAWN_ARG_INVALID,
"String array len is invalied, len: %{public}u", valueLen);
char *value = (char *)calloc(1, valueLen);
APPSPAWN_CHECK(value != NULL, return APPSPAWN_SYSTEM_ERROR, "Failed to calloc string array value");
char *valuePtr = value;
for (cJSON *arrayItemTemp = arrayItem; arrayItemTemp != NULL; arrayItemTemp = arrayItemTemp->next) {
int len = strlen(arrayItemTemp->valuestring) + 1;
int ret = strcpy_s(valuePtr, len, arrayItemTemp->valuestring);
APPSPAWN_CHECK(ret == EOK, free(value);
return APPSPAWN_SYSTEM_ERROR, "Failed to copy string value");
valuePtr += len;
}
encap->value.ptrValue = (void *)value;
encap->valueLen = valueLen;
encap->type = ENCAPS_CHAR_ARRAY;
return 0;
}
APPSPAWN_STATIC int AddPermissionArrayToValue(cJSON *permissionItemArr, UserEncap *encap)
{
uint32_t arraySize = (uint32_t)cJSON_GetArraySize(permissionItemArr);
if (arraySize == 0) {
return APPSPAWN_ARG_INVALID;
}
cJSON *arrayItem = permissionItemArr->child;
if (cJSON_IsNumber(arrayItem)) {
if (AddPermissionIntArrayToValue(arrayItem, encap, arraySize) != 0) {
return APPSPAWN_ARG_INVALID;
}
} else if (cJSON_IsString(arrayItem)) {
if (AddPermissionStrArrayToValue(arrayItem, encap) != 0) {
return APPSPAWN_ARG_INVALID;
}
} else if (cJSON_IsBool(arrayItem)) {
if (AddPermissionBoolArrayToValue(arrayItem, encap, arraySize) != 0) {
return APPSPAWN_ARG_INVALID;
}
} else {
APPSPAWN_LOGW("Invalid array item type");
return APPSPAWN_ARG_INVALID;
}
return 0;
}
APPSPAWN_STATIC int AddPermissionItemToEncapsInfo(UserEncap *encap, cJSON *permissionItem)
{
char *key = permissionItem->string;
if (key == NULL || strcpy_s(encap->key, OH_ENCAPS_KEY_MAX_LEN, key) != EOK) {
APPSPAWN_LOGE("Failed to copy json key");
return APPSPAWN_SYSTEM_ERROR;
}
if (cJSON_IsNumber(permissionItem)) {
encap->type = ENCAPS_INT;
encap->value.intValue = (uint64_t)permissionItem->valueint;
encap->valueLen = sizeof(permissionItem->valueint);
} else if (cJSON_IsBool(permissionItem)) {
encap->type = ENCAPS_BOOL;
bool value = cJSON_IsTrue(permissionItem) ? true : false;
encap->value.intValue = (uint64_t)value;
encap->valueLen = sizeof(value);
} else if (cJSON_IsString(permissionItem)) {
if (AddPermissionStrToValue(permissionItem->valuestring, encap) != 0) {
return APPSPAWN_ARG_INVALID;
}
} else if (cJSON_IsArray(permissionItem)) {
if (AddPermissionArrayToValue(permissionItem, encap) != 0) {
return APPSPAWN_ARG_INVALID;
}
} else {
APPSPAWN_LOGW("Invalid permission item type");
return APPSPAWN_ARG_INVALID;
}
return 0;
}
APPSPAWN_STATIC cJSON *GetEncapsPermissions(cJSON *extInfoJson, int *count)
{
APPSPAWN_CHECK_LOGV(extInfoJson != NULL, return NULL, "Invalid extInfoJson");
cJSON *countJson = cJSON_GetObjectItem(extInfoJson, APP_OHOS_ENCAPS_COUNT_KEY);
APPSPAWN_CHECK(countJson != NULL && cJSON_IsNumber(countJson), return NULL, "Invalid countJson");
int encapsCount = countJson->valueint;
cJSON *permissionsJson = cJSON_GetObjectItemCaseSensitive(extInfoJson, APP_OHOS_ENCAPS_PERMISSIONS_KEY);
APPSPAWN_CHECK(permissionsJson != NULL && cJSON_IsArray(permissionsJson), return NULL, "Invalid permissionsJson");
*count = cJSON_GetArraySize(permissionsJson);
APPSPAWN_CHECK((*count) > 0 && (*count) <= OH_ENCAPS_MAX_COUNT && encapsCount == (*count), *count = 0;
return NULL, "Invalid args, encaps count: %{public}d, permission count: %{public}d", encapsCount, *count);
return permissionsJson;
}
APPSPAWN_STATIC int AddMembersToEncapsInfo(cJSON *permissionsJson, UserEncaps *encapsInfo, int count)
{
APPSPAWN_CHECK_LOGV(permissionsJson != NULL, return 0, "Encaps not contains valid permissionsJson");
for (int i = 0; i < count; i++) {
cJSON *permission = cJSON_GetArrayItem(permissionsJson, i);
APPSPAWN_CHECK(permission != NULL, return APPSPAWN_ERROR_UTILS_DECODE_JSON_FAIL,
"Encaps get single permission failed index %{public}d", i);
cJSON *permissionItem = permission->child;
APPSPAWN_CHECK(permissionItem != NULL, return APPSPAWN_ERROR_UTILS_DECODE_JSON_FAIL,
"Encaps get permission item failed index %{public}d", i);
if (AddPermissionItemToEncapsInfo(&encapsInfo->encap[i], permissionItem) != 0) {
APPSPAWN_LOGE("Add permission to encap failed index %{public}d", i);
return APPSPAWN_ERROR_UTILS_ADD_JSON_FAIL;
}
encapsInfo->encapsCount++;
}
return 0;
}
APPSPAWN_STATIC void FreeEncapsInfo(UserEncaps *encapsInfo)
{
APPSPAWN_CHECK_ONLY_EXPER(encapsInfo != NULL, return);
if (encapsInfo->encap != NULL) {
for (uint32_t i = 0; i < encapsInfo->encapsCount; i++) {
if (encapsInfo->encap[i].type > ENCAPS_AS_ARRAY) {
free(encapsInfo->encap[i].value.ptrValue);
encapsInfo->encap[i].value.ptrValue = NULL;
}
}
free(encapsInfo->encap);
encapsInfo->encap = NULL;
}
}
static int SpawnSetMaxPids(AppSpawningCtx *property, UserEncaps *encapsInfo)
{
uint32_t maxPidCount = 0;
if (GetAppSpawnMsgType(property) != MSG_SPAWN_NATIVE_PROCESS) {
maxPidCount = SpawnGetMaxPids(property);
}
APPSPAWN_CHECK_LOGV(maxPidCount > 0 && maxPidCount < OH_APP_MAX_PIDS_NUM, return 0,
"Don't need to set pid max count %{public}u. Use default pid max", maxPidCount);
APPSPAWN_CHECK(encapsInfo->encapsCount < OH_ENCAPS_MAX_COUNT,
return APPSPAWN_ARG_INVALID, "Encaps count is more than 64, cannot set permissions");
uint32_t count = encapsInfo->encapsCount;
int ret = strcpy_s(encapsInfo->encap[count].key, OH_ENCAPS_KEY_MAX_LEN, APP_OHOS_ENCAPS_FORK_KEY);
APPSPAWN_CHECK_ONLY_EXPER(ret == EOK, return APPSPAWN_SYSTEM_ERROR);
encapsInfo->encap[count].value.intValue = (uint64_t)maxPidCount;
encapsInfo->encap[count].valueLen = sizeof(maxPidCount);
encapsInfo->encap[count].type = ENCAPS_INT;
encapsInfo->encapsCount++;
APPSPAWN_LOGV("Set max fork count: %{public}u, encapsCount: %{public}u", maxPidCount, encapsInfo->encapsCount);
return 0;
}
APPSPAWN_STATIC int SpawnSetPermissions(AppSpawningCtx *property, UserEncaps *encapsInfo)
{
int count = 0;
cJSON *extInfoJson = GetJsonObjFromExtInfo(property, MSG_EXT_NAME_JIT_PERMISSIONS);
cJSON *permissionsJson = GetEncapsPermissions(extInfoJson, &count);
APPSPAWN_CHECK_LOGW(count >= 0 && count <= OH_ENCAPS_MAX_COUNT, count = 0, "Invalid count: %{public}d", count);
int ret = 0;
do {
encapsInfo->encap = (UserEncap *)calloc(count + 1, sizeof(UserEncap));
APPSPAWN_CHECK(encapsInfo->encap != NULL, ret = APPSPAWN_SYSTEM_ERROR;
break, "Failed to calloc encap");
ret = AddMembersToEncapsInfo(permissionsJson, encapsInfo, count);
APPSPAWN_CHECK_ONLY_LOGW(ret == 0, "Add member to encaps failed, ret: %{public}d", ret);
ret = SpawnSetMaxPids(property, encapsInfo);
APPSPAWN_CHECK(ret == 0, break, "Set max fork count to encaps failed, ret: %{public}d", ret);
} while (0);
APPSPAWN_ONLY_EXPER(extInfoJson != NULL, cJSON_Delete(extInfoJson));
return ret;
}
APPSPAWN_STATIC void SetAllowDumpable(AppSpawningCtx *property, UserEncaps *encapsInfo)
{
#ifdef ALLOW_DUMPABLE
for (int i = 0; i < encapsInfo->encapsCount; i++) {
if (strcmp(encapsInfo->encap[i].key, "ohos.permission.kernel.ALLOW_DEBUG") == 0) {
APPSPAWN_LOGI("App allowDumpable success");
property->allowDumpable = true;
}
}
#endif
}
APPSPAWN_STATIC int SpawnSetEncapsPermissions(AppSpawnMgr *content, AppSpawningCtx *property)
{
if (content == NULL || property == NULL) {
return APPSPAWN_ARG_INVALID;
}
if (!(IsAppSpawnMode(content) || IsHybridSpawnMode(content) || IsNativeSpawnMode(content))) {
return 0;
}
int encapsFileFd = OpenEncapsFile();
if (encapsFileFd <= 0) {
return 0;
}
int ret = EnableEncapsForProc(encapsFileFd);
if (ret != 0) {
close(encapsFileFd);
return 0;
}
if (CheckAppMsgFlagsSet(property, APP_FLAGS_CUSTOM_SANDBOX)) {
APPSPAWN_LOGV("set proc flag for custom sandbox");
int proc_flag = CUSTOM_SANDBOX_PROCESS_TYPE;
ret = ioctl(encapsFileFd, SET_ENCAPS_PROC_FLAG_CMD, &proc_flag);
APPSPAWN_CHECK_ONLY_LOG(ret == 0, "ioctl SET_ENCAPS_PROC_FLAG_CMD failed, errno %{public}d", errno);
}
UserEncaps encapsInfo = {0};
ret = SpawnSetPermissions(property, &encapsInfo);
if (ret != 0) {
close(encapsFileFd);
FreeEncapsInfo(&encapsInfo);
APPSPAWN_LOGV("Build encaps info failed, ret: %{public}d", ret);
return 0;
}
if (encapsInfo.encapsCount > 0) {
(void)WriteEncapsInfo(encapsFileFd, ENCAPS_PERMISSION_TYPE_MODE, &encapsInfo, OH_ENCAPS_DEFAULT_FLAG);
APPSPAWN_LOGV("Set encaps info finish, encapsCount: %{public}u", encapsInfo.encapsCount);
SetAllowDumpable(property, &encapsInfo);
}
FreeEncapsInfo(&encapsInfo);
close(encapsFileFd);
return 0;
}
MODULE_CONSTRUCTOR(void)
{
AddAppSpawnHook(STAGE_CHILD_EXECUTE, HOOK_PRIO_COMMON, SpawnSetEncapsPermissions);
}