* Copyright (c) 2026 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.
*/
* @file spm.c
* @brief SPM (Security Process Manager) module for appspawn
*
* This module provides SPM integration for appspawn:
* 1. Socket message validation and rebuild from kernel SPM
* 2. Reference count management (tokenid/uid) for spawned processes
* 3. Automatic hooks registration on module load
*
* SpawnId is a fixed value per runMode, mapped by RunModeToSpawnId().
* All apps in the same spawn service instance share the same spawnId.
*/
#include "appspawn_hook.h"
#include "appspawn_manager.h"
#include "appspawn_msg.h"
#include "appspawn_utils.h"
#include "cJSON.h"
#include "securec.h"
#include "spm.h"
#include <inttypes.h>
#include "spm_setproc.h"
#include "perm_setproc_c.h"
#include "appspawn_mount_permission.h"
#include "hisysevent_adapter.h"
#include "spm_permission.h"
#include "tlv_builder.h"
* @brief Create a new message structure (internal implementation)
* @return Pointer to new message or NULL on failure
*/
static AppSpawnMsgNode *CreateAppSpawnMsgInternal(void)
{
AppSpawnMsgNode *message = (AppSpawnMsgNode *)calloc(1, sizeof(AppSpawnMsgNode));
if (message == NULL) {
APPSPAWN_LOGE("CreateAppSpawnMsgInternal: Failed to allocate memory");
return NULL;
}
message->buffer = NULL;
message->tlvOffset = NULL;
return message;
}
* @brief Delete a message structure (internal implementation)
* @param msgNode Pointer to message pointer (will be set to NULL)
*/
static void DeleteAppSpawnMsgInternal(AppSpawnMsgNode **msgNode)
{
if (msgNode == NULL || *msgNode == NULL) {
return;
}
if ((*msgNode)->buffer) {
free((*msgNode)->buffer);
(*msgNode)->buffer = NULL;
}
if ((*msgNode)->tlvOffset) {
free((*msgNode)->tlvOffset);
(*msgNode)->tlvOffset = NULL;
}
free(*msgNode);
*msgNode = NULL;
}
static uint32_t g_spawnId = 0;
#define SPAWN_ID_APP 1
#define SPAWN_ID_NWEB 2
#define SPAWN_ID_NATIVE 3
#define SPAWN_ID_CJAPP 4
#define SPAWN_ID_HYBRID 5
#define SPAWN_ID_DEFAULT SPAWN_ID_APP
#define BITS_PER_WORD 32
#define APL_NAME_NORMAL "normal"
#define APL_NAME_SYSTEM_BASIC "system_basic"
#define APL_NAME_SYSTEM_CORE "system_core"
static const char * const APL_NAME_MAP[] = {
APL_NAME_NORMAL,
APL_NAME_NORMAL,
APL_NAME_SYSTEM_BASIC,
APL_NAME_SYSTEM_CORE,
};
#define APL_NAME_MAP_SIZE (sizeof(APL_NAME_MAP) / sizeof(APL_NAME_MAP[0]))
static const char * const DISTRIBUTION_TYPE_MAP[] = {
"none",
"app_gallery",
"enterprise",
"os_integration",
"crowdtesting",
"enterprise_normal",
"enterprise_mdm",
"internaltesting"
};
#define DISTRIBUTION_TYPE_MAP_SIZE (sizeof(DISTRIBUTION_TYPE_MAP) / sizeof(DISTRIBUTION_TYPE_MAP[0]))
typedef struct {
uint64_t accessTokenIdEx;
uint32_t uid;
uint32_t spmHighToken;
uint32_t spmLowToken;
uint32_t spmUid;
} RefcountContext;
uint32_t GetSpawnId(void)
{
return g_spawnId;
}
* @brief Convert runMode to fixed spawnId
*
* Each spawn mode maps to a distinct fixed spawnId value.
*/
static const struct {
uint32_t runMode;
uint32_t spawnId;
} SPAWN_ID_MAP[] = {
{MODE_FOR_APP_SPAWN, SPAWN_ID_APP},
{MODE_FOR_APP_COLD_RUN, SPAWN_ID_APP},
{MODE_FOR_NWEB_SPAWN, SPAWN_ID_NWEB},
{MODE_FOR_NWEB_COLD_RUN, SPAWN_ID_NWEB},
{MODE_FOR_NATIVE_SPAWN, SPAWN_ID_NATIVE},
{MODE_FOR_NATIVE_COLD_RUN, SPAWN_ID_NATIVE},
{MODE_FOR_CJAPP_SPAWN, SPAWN_ID_CJAPP},
{MODE_FOR_CJAPP_COLD_RUN, SPAWN_ID_CJAPP},
{MODE_FOR_HYBRID_SPAWN, SPAWN_ID_HYBRID},
{MODE_FOR_HYBRID_COLD_RUN, SPAWN_ID_HYBRID},
};
#define SPAWN_ID_MAP_SIZE (sizeof(SPAWN_ID_MAP) / sizeof(SPAWN_ID_MAP[0]))
APPSPAWN_STATIC uint32_t RunModeToSpawnId(uint32_t runMode)
{
for (uint32_t i = 0; i < SPAWN_ID_MAP_SIZE; i++) {
if (SPAWN_ID_MAP[i].runMode == runMode) {
return SPAWN_ID_MAP[i].spawnId;
}
}
return SPAWN_ID_DEFAULT;
}
int CleanupStaleSpawns(void)
{
uint32_t spawnId = GetSpawnId();
APPSPAWN_LOGI("CleanupStaleSpawns: spawnId=%{public}u", spawnId);
int ret = SpmClearSpawnidRefCnt(spawnId);
if (ret != 0) {
APPSPAWN_LOGW("CleanupStaleSpawns failed: spawnId=%{public}u, ret=%{public}d", spawnId, ret);
}
return 0;
}
* @brief Collect DAC_INFO TLV descriptor from SPM data and add to list
*
* Note: SPM DAC data is only used when:
* 1. Not in nweb mode (MODE_FOR_NWEB_SPAWN or MODE_FOR_NWEB_COLD_RUN)
* 2. APP_FLAGS_ISOLATED_SANDBOX_TYPE flag is not set
*
* @param mgr AppSpawn manager instance (to check runMode)
* @param ctx AppSpawning context (to check flags)
* @param oldMsg Old message node (to check if DAC_INFO exists)
* @param spmData SPM data from kernel
* @param head List head to append entry to
* @return 1 if entry was added, 0 if skipped, -1 on error
*/
APPSPAWN_STATIC int CollectDacInfoFromSPM(const AppSpawnMgr *mgr, const AppSpawningCtx *ctx,
const AppSpawnMsgNode *oldMsg, const SpmData *spmData, ListNode *head)
{
if (spmData == NULL || head == NULL || mgr == NULL || ctx == NULL) {
return -1;
}
uint32_t runMode = mgr->content.mode;
bool isNWebMode = (runMode == MODE_FOR_NWEB_SPAWN || runMode == MODE_FOR_NWEB_COLD_RUN);
bool hasIsolatedSandboxFlag = CheckAppMsgFlagsSet(ctx, APP_FLAGS_ISOLATED_SANDBOX_TYPE);
if (isNWebMode || hasIsolatedSandboxFlag) {
APPSPAWN_LOGI("CollectDacInfoFromSPM: Skip SPM DAC (isNWeb=%{public}d, hasIsolatedFlag=%{public}d)",
isNWebMode, hasIsolatedSandboxFlag);
if (oldMsg != NULL && oldMsg->tlvOffset[TLV_DAC_INFO] != INVALID_OFFSET) {
return CopyStandardTlv(oldMsg, TLV_DAC_INFO, head);
}
return 0;
}
uint32_t dataLen = sizeof(AppDacInfo);
uint8_t *data = (uint8_t *)calloc(1, dataLen);
APPSPAWN_CHECK(data != NULL, return -1, "Failed to allocate DAC info data");
AppDacInfo *dacInfo = (AppDacInfo *)data;
if (oldMsg != NULL && oldMsg->tlvOffset[TLV_DAC_INFO] != INVALID_OFFSET) {
AppSpawnTlv *oldTlv = (AppSpawnTlv *)(oldMsg->buffer + oldMsg->tlvOffset[TLV_DAC_INFO]);
AppDacInfo *oldDacInfo = (AppDacInfo *)(oldTlv + 1);
int cpRet = memcpy_s(data, sizeof(AppDacInfo), oldDacInfo, sizeof(AppDacInfo));
APPSPAWN_CHECK(cpRet == 0, free(data); return -1,
"Failed to copy DAC info from old message (ret=%{public}d)", cpRet);
}
dacInfo->uid = spmData->uid;
APPSPAWN_LOGI("CollectDacInfoFromSPM: uid=%{public}u, gid=%{public}u, gidCount=%{public}u",
dacInfo->uid, dacInfo->gid, dacInfo->gidCount);
int ret = AddStandardTlv(head, TLV_DAC_INFO, data, dataLen);
APPSPAWN_CHECK(ret == 0, free(data); return -1, "Failed to add DAC_INFO TLV");
return 0;
}
* @brief Compute permission flag indexes from kernel permission bitmap
*
* @param tokenid Token ID to query kernel for permission bitmap
* @param flagIndexes Output array for computed spawn flag indexes
* @param maxCount Output number of bitmap words (flagIndexes array size)
* @return 0 on success, -1 on failure
*/
static int ComputePermissionFlags(const AppSpawnMgr *mgr,
uint64_t tokenid, uint32_t *flagIndexes, uint32_t *maxCount)
{
APPSPAWN_CHECK_ONLY_EXPER(mgr!= NULL && flagIndexes != NULL && maxCount != NULL, return -1);
uint32_t perms[MAX_PERM_BIT_MAP_SIZE];
int32_t ret = GetPermissionsFromKernel(tokenid, perms, MAX_PERM_BIT_MAP_SIZE);
APPSPAWN_CHECK(ret == 0, return -1, "Failed to get permissions from kernel");
APPSPAWN_LOGI("ComputePermissionFlags: tokenid=%{public}" PRIu64 ", bitmap[0-3]=0x%{public}x "
"0x%{public}x 0x%{public}x 0x%{public}x",
tokenid, perms[0], perms[1], perms[2], perms[3]);
const SandboxQueue *permQueue = mgr->content.permissionQueue;
APPSPAWN_CHECK(permQueue != NULL, return -1,
"Failed to get permission queue from AppSpawnContent");
uint32_t queueLen = (uint32_t)OH_ListGetCnt(&permQueue->front);
*maxCount = (queueLen + BITS_PER_WORD - 1) / BITS_PER_WORD;
ret = GetSpawnFlagIndexesFromPermissionBitmap(permQueue, perms, flagIndexes, *maxCount);
APPSPAWN_CHECK(ret == 0, return -1, "Failed to get spawn flag indexes");
APPSPAWN_LOGI("ComputePermissionFlags: queueLen=%{public}u, maxCount=%{public}u",
queueLen, *maxCount);
return 0;
}
* @brief Collect PERMISSION TLV descriptor from SPM kernel data and add to list
*
* @param oldMsg Old message node (to check if PERMISSION exists, for logging)
* @param spmData SPM data from kernel
* @param head List head to append entry to
* @return 1 if entry was added, 0 if skipped, -1 on error
*/
APPSPAWN_STATIC int CollectPermissionFromSPM(const AppSpawnMgr *mgr, const SpmData *spmData, ListNode *head)
{
APPSPAWN_CHECK(spmData != NULL && head != NULL && mgr != NULL, return -1, "invalid param");
uint32_t flagIndexes[MAX_GRANTED_PERMISSIONS] = {0};
uint32_t maxCount = 0;
int ret = ComputePermissionFlags(mgr, spmData->tokenid, flagIndexes, &maxCount);
APPSPAWN_CHECK(ret == 0, return -1, "Failed to compute permission flags");
uint32_t flagsDataLen = sizeof(uint32_t) + maxCount * sizeof(uint32_t);
uint8_t *data = (uint8_t *)calloc(1, flagsDataLen);
APPSPAWN_CHECK(data != NULL, return -1, "Failed to allocate permission data");
*((uint32_t *)data) = (uint32_t)maxCount;
for (uint32_t i = 0; i < maxCount; i++) {
((uint32_t *)(data + sizeof(uint32_t)))[i] = flagIndexes[i];
}
APPSPAWN_LOGV("CollectPermissionFromSPM: %{public}u bitmap words", maxCount);
ret = AddStandardTlv(head, TLV_PERMISSION, data, flagsDataLen);
APPSPAWN_CHECK(ret == 0, free(data); return -1, "Failed to add PERMISSION TLV");
return 0;
}
* @brief Collect BUNDLE_INFO TLV descriptor from SPM data and add to list
*
* @param oldMsg Old message node (to check if BUNDLE_INFO exists)
* @param spmData SPM data from kernel
* @param head List head to append entry to
* @return 1 if entry was added, 0 if skipped, -1 on error
*/
APPSPAWN_STATIC int CollectBundleInfoFromSPM(const AppSpawnMsgNode *oldMsg,
const SpmData *spmData, ListNode *head)
{
if (spmData == NULL || head == NULL || spmData->name.buf == NULL) {
return -1;
}
uint32_t nameLen = strlen(spmData->name.buf);
if (nameLen >= APP_LEN_BUNDLE_NAME) {
nameLen = APP_LEN_BUNDLE_NAME - 1;
}
uint32_t dataLen = sizeof(AppSpawnMsgBundleInfo) + nameLen + 1;
uint8_t *data = (uint8_t *)calloc(1, dataLen);
APPSPAWN_CHECK(data != NULL, return -1, "Failed to allocate bundle info data");
AppSpawnMsgBundleInfo *bundleInfo = (AppSpawnMsgBundleInfo *)data;
bundleInfo->bundleIndex = spmData->index;
char *bundleName = (char *)(bundleInfo + 1);
int ret = memcpy_s(bundleName, dataLen - sizeof(AppSpawnMsgBundleInfo), spmData->name.buf, nameLen);
APPSPAWN_CHECK(ret == 0, free(data); return -1, "Failed to copy bundle name");
APPSPAWN_LOGI("CollectBundleInfoFromSPM: index=%{public}u", bundleInfo->bundleIndex);
ret = AddStandardTlv(head, TLV_BUNDLE_INFO, data, dataLen);
APPSPAWN_CHECK(ret == 0, free(data); return -1, "Failed to add BUNDLE_INFO TLV");
return 0;
}
* @brief Collect DOMAIN_INFO TLV descriptor from SPM data and add to list
*
* @param oldMsg Old message node (to check if DOMAIN_INFO exists and get hapFlags)
* @param spmData SPM data from kernel
* @param head List head to append entry to
* @return 1 if entry was added, 0 if skipped, -1 on error
*/
APPSPAWN_STATIC int CollectDomainInfoFromSPM(const AppSpawnMsgNode *oldMsg,
const SpmData *spmData, ListNode *head)
{
if (spmData == NULL || head == NULL) {
return -1;
}
uint32_t hapFlags = 0;
if (oldMsg != NULL && oldMsg->tlvOffset[TLV_DOMAIN_INFO] != INVALID_OFFSET) {
AppSpawnTlv *oldTlv = (AppSpawnTlv *)(oldMsg->buffer + oldMsg->tlvOffset[TLV_DOMAIN_INFO]);
AppSpawnMsgDomainInfo *oldDomainInfo = (AppSpawnMsgDomainInfo *)(oldTlv + 1);
hapFlags = oldDomainInfo->hapFlags;
}
const char *aplStr = APL_NAME_NORMAL;
APPSPAWN_ONLY_EXPER(spmData->apl < APL_NAME_MAP_SIZE, aplStr = APL_NAME_MAP[spmData->apl]);
uint32_t aplLen = strlen(aplStr);
uint32_t dataLen = sizeof(AppSpawnMsgDomainInfo) + aplLen + 1;
uint8_t *data = (uint8_t *)calloc(1, dataLen);
APPSPAWN_CHECK(data != NULL, return -1, "Failed to allocate domain info data");
AppSpawnMsgDomainInfo *domainInfo = (AppSpawnMsgDomainInfo *)data;
domainInfo->hapFlags = hapFlags;
char *apl = (char *)(domainInfo + 1);
int cpRet = memcpy_s(apl, aplLen + 1, aplStr, aplLen);
APPSPAWN_CHECK(cpRet == 0, free(data); return -1, "Failed to copy apl string");
APPSPAWN_LOGI("CollectDomainInfoFromSPM: apl=%{public}s", apl);
int ret = AddStandardTlv(head, TLV_DOMAIN_INFO, data, dataLen);
APPSPAWN_CHECK(ret == 0, free(data); return -1, "Failed to add DOMAIN_INFO TLV");
return 0;
}
* @brief Collect OWNER_INFO TLV descriptor from SPM data and add to list
*
* @param oldMsg Old message node (to check if OWNER_INFO exists)
* @param spmData SPM data from kernel
* @param head List head to append entry to
* @return 1 if entry was added, 0 if skipped, -1 on error
*/
APPSPAWN_STATIC int CollectOwnerInfoFromSPM(const SpmData *spmData, ListNode *head)
{
if (spmData == NULL || head == NULL) {
return -1;
}
char ownerIdStr[32] = {0};
int ret = snprintf_s(ownerIdStr, sizeof(ownerIdStr), sizeof(ownerIdStr) - 1, "%llu",
(unsigned long long)spmData->ownerid);
APPSPAWN_CHECK(ret > 0, return -1, "Failed to convert ownerid to string");
uint32_t ownerIdLen = strlen(ownerIdStr) + 1;
uint32_t dataLen = sizeof(AppSpawnMsgOwnerId) + ownerIdLen;
uint8_t *data = (uint8_t *)calloc(1, dataLen);
APPSPAWN_CHECK(data != NULL, return -1, "Failed to allocate owner info data");
char *ownerId = (char *)(data + sizeof(AppSpawnMsgOwnerId));
int cpRet = memcpy_s(ownerId, ownerIdLen, ownerIdStr, strlen(ownerIdStr));
APPSPAWN_CHECK(cpRet == 0, free(data); return -1, "Failed to copy ownerId string");
APPSPAWN_LOGI("CollectOwnerInfoFromSPM: ownerid=%{public}s", ownerIdStr);
ret = AddStandardTlv(head, TLV_OWNER_INFO, data, dataLen);
APPSPAWN_CHECK(ret == 0, free(data); return -1, "Failed to add OWNER_INFO TLV");
return 0;
}
* @brief Collect XPM_ID_TYPE extended TLV descriptor from SPM data and add to list
*
* @param spmData SPM data from kernel
* @param head List head to append entry to
* @return 1 if entry was added, 0 if skipped, -1 on error
*/
static int CollectXpmIdTypeFromSPM(const SpmData *spmData, ListNode *head)
{
if (spmData == NULL || head == NULL) {
return -1;
}
uint32_t dataLen = sizeof(uint32_t);
uint8_t *data = (uint8_t *)calloc(1, dataLen);
APPSPAWN_CHECK(data != NULL, return -1, "Failed to allocate xpm id type data");
*((uint32_t *)data) = spmData->idType;
APPSPAWN_LOGI("CollectXpmIdTypeFromSPM: idType=%{public}u", spmData->idType);
int ret = AddExtTlv(head, MSG_EXT_NAME_XPM_ID_TYPE, 0, data, dataLen);
APPSPAWN_CHECK(ret == 0, free(data); return -1, "Failed to add XPM_ID_TYPE TLV");
return 0;
}
* @brief Validate SPM data consistency with original message
*
* @param spmData SPM data from kernel (already fetched)
* @param accessTokenIdEx Access token ID from message (for tokenidAttr validation)
* @param uidFromMsg UID from original message (for UID consistency check)
* @param mgr AppSpawn manager instance (to check runMode for nweb/isolated)
* @param ctx AppSpawning context (to check flags for isolated)
* @return 0 on success, error code on failure
*/
APPSPAWN_STATIC int ValidateSpmDataConsistency(const SpmData *spmData,
uint64_t accessTokenIdEx, uint32_t uidFromMsg, const AppSpawnMgr *mgr,
const AppSpawningCtx *ctx)
{
if (spmData == NULL) {
APPSPAWN_LOGE("ValidateSpmDataConsistency: Invalid parameters");
return SPM_ERROR_INVALID_PARAM;
}
uint32_t tokenidattrFromMsg = (uint32_t)(accessTokenIdEx >> 32);
uint32_t tokenidattrFromSPM = spmData->tokenidAttr;
if (tokenidattrFromMsg != tokenidattrFromSPM) {
APPSPAWN_LOGE("ValidateSpmDataConsistency: TokenidAttr mismatch! msg=0x%{public}x, spm=0x%{public}x",
tokenidattrFromMsg, tokenidattrFromSPM);
return SPM_ERROR_TOKENID_ATTR_MISMATCH;
}
APPSPAWN_LOGI("ValidateSpmDataConsistency: TokenidAttr match: 0x%{public}x", tokenidattrFromMsg);
bool isSpecialProcess = false;
if (mgr != NULL && ctx != NULL) {
uint32_t runMode = mgr->content.mode;
bool isNWebMode = (runMode == MODE_FOR_NWEB_SPAWN || runMode == MODE_FOR_NWEB_COLD_RUN);
bool hasIsolatedSandboxFlag = CheckAppMsgFlagsSet(ctx, APP_FLAGS_ISOLATED_SANDBOX_TYPE);
isSpecialProcess = isNWebMode || hasIsolatedSandboxFlag;
}
if (spmData->uid != uidFromMsg) {
if (!isSpecialProcess) {
APPSPAWN_LOGE("ValidateSpmDataConsistency: UID mismatch! msg_uid=%{public}u, spm_uid=%{public}u. ",
uidFromMsg, spmData->uid);
return SPM_ERROR_UID_MISMATCH;
}
}
APPSPAWN_LOGI("ValidateSpmDataConsistency: SPM data validated: uid=%{public}u, accessTokenId=%{public}u, "
"apl=%{public}u, ownerid=%{public}llu, idType=%{public}u, distributionType=%{public}u",
spmData->uid, spmData->tokenid, spmData->apl,
(unsigned long long)spmData->ownerid, spmData->idType, spmData->distributionType);
return 0;
}
* @brief Get kernel permissions and extended permissions for JIT JSON
*
* @param spmData SPM data from kernel
* @param kernelPerms Output array for kernel permission opcodes
* @param kernelCount Output number of kernel permissions
* @param extPerms Output array for extended permissions (with values)
* @param extPermCount Output number of extended permissions
* @return 0 on success, -1 on failure
*/
static int GetKernelPermissionsForJson(const SpmData *spmData,
uint16_t kernelPerms[], uint32_t *kernelCount, PermsWithValue extPerms[],
uint32_t *extPermCount)
{
if (spmData == NULL || kernelPerms == NULL || kernelCount == NULL ||
extPerms == NULL || extPermCount == NULL) {
APPSPAWN_LOGE("GetKernelPermissionsForJson: Invalid parameters");
return -1;
}
uint32_t perms[MAX_PERM_BIT_MAP_SIZE];
int32_t ret = GetPermissionsFromKernel(spmData->tokenid, perms, MAX_PERM_BIT_MAP_SIZE);
APPSPAWN_CHECK(ret == 0, return -1, "Failed to get permissions from kernel");
*kernelCount = MAX_KERNEL_PERMISSIONS;
ret = FilterKernelPermissions(perms, kernelPerms, kernelCount);
APPSPAWN_CHECK(ret == 0, return -1, "Failed to filter kernel permissions");
*extPermCount = MAX_EXT_PERMISSIONS;
if (strlen(spmData->extendPerms.buf) > 0) {
ret = TransferSpmExternPerms((SpmBlob *)&spmData->extendPerms, extPerms, extPermCount);
APPSPAWN_CHECK(ret == 0, return -1, "Failed to transfer external permissions");
} else {
*extPermCount = 0;
}
return 0;
}
* @brief Add a single permission item to the JSON permissions array
*
* @param permissionsArray cJSON array to add item to
* @param opcode Permission opcode
* @param extPerms Array of extended permissions (may contain values)
* @param extPermCount Number of extended permissions
* @return 0 on success, -1 on failure
*/
static int AddPermissionItemToJson(cJSON *permissionsArray, uint16_t opcode,
const PermsWithValue extPerms[], uint32_t extPermCount)
{
if (permissionsArray == NULL) {
return -1;
}
char permName[256] = {0};
if (!TransferOpCodeToPermission(opcode, permName, sizeof(permName))) {
APPSPAWN_LOGW("AddPermissionItemToJson: failed to get name for opcode %{public}u", opcode);
return -1;
}
cJSON *permItem = cJSON_CreateObject();
if (permItem == NULL) {
APPSPAWN_LOGW("AddPermissionItemToJson: failed to create perm item for %{public}s", permName);
return -1;
}
const char *extraValue = NULL;
for (uint32_t j = 0; j < extPermCount; j++) {
if (extPerms[j].code == opcode) {
extraValue = extPerms[j].value;
break;
}
}
if (extraValue != NULL && strlen(extraValue) > 0) {
cJSON_AddStringToObject(permItem, permName, extraValue);
} else {
cJSON_AddTrueToObject(permItem, permName);
}
cJSON_AddItemToArray(permissionsArray, permItem);
return 0;
}
* @brief 构建 JIT Permissions JSON 字符串
*
* 从 SPM 数据提取权限信息,使用 cJSON 构建符合格式要求的 JSON 字符串
*
* @param spmData SPM 数据
* @param jsonOut 输出 JSON 字符串指针(调用方负责释放,使用 cJSON_free)
* @param jsonLen 输出 JSON 字符串长度
* @return 0 on success, error code on failure
*/
static int BuildJITPermissionsJson(const SpmData *spmData, char **jsonOut,
uint32_t *jsonLen)
{
APPSPAWN_CHECK(spmData != NULL && jsonOut != NULL && jsonLen != NULL, return -1, "Invalid parameters");
uint16_t kernelPerms[MAX_KERNEL_PERMISSIONS];
uint32_t kernelCount = 0;
PermsWithValue extPerms[MAX_EXT_PERMISSIONS];
uint32_t extPermCount = 0;
int ret = GetKernelPermissionsForJson(spmData, kernelPerms, &kernelCount, extPerms, &extPermCount);
APPSPAWN_CHECK(ret == 0, return -1, "Failed to get kernel permissions");
cJSON *root = cJSON_CreateObject();
APPSPAWN_CHECK(root != NULL, return -1, "Failed to create JSON root");
cJSON_AddStringToObject(root, "name", "Permissions");
cJSON_AddNumberToObject(root, "ohos.encaps.count", kernelCount);
cJSON *permissionsArray = cJSON_CreateArray();
APPSPAWN_CHECK(permissionsArray != NULL, cJSON_Delete(root); return -1,
"Failed to create JSON permissionsArray");
cJSON_AddItemToObject(root, "permissions", permissionsArray);
for (uint32_t i = 0; i < kernelCount; i++) {
AddPermissionItemToJson(permissionsArray, kernelPerms[i], extPerms, extPermCount);
}
char *json = cJSON_PrintUnformatted(root);
cJSON_Delete(root);
APPSPAWN_CHECK(json != NULL, return -1, "Failed to print JSON");
*jsonOut = json;
*jsonLen = (uint32_t)strlen(json);
APPSPAWN_LOGI("BuildJITPermissionsJson: built JSON with %{public}u permissions, len=%{public}u",
kernelCount, *jsonLen);
return 0;
}
* @brief Collect JIT_PERMISSIONS extended TLV descriptor from SPM data and add to list
*
* @param spmData SPM data from kernel
* @param head List head to append entry to
* @return 1 if entry was added, 0 if skipped, -1 on error
*/
static int CollectJitPermissionsFromSPM(const SpmData *spmData, ListNode *head)
{
if (spmData == NULL || head == NULL) {
return -1;
}
char *json = NULL;
uint32_t jsonLen = 0;
int32_t ret = BuildJITPermissionsJson(spmData, &json, &jsonLen);
APPSPAWN_CHECK(ret == 0 && json != NULL, return -1, "Failed to build JIT permissions JSON");
APPSPAWN_LOGV("CollectJitPermissionsFromSPM: JSON len=%{public}u JSON content=%{public}s", jsonLen, json);
uint8_t *jsonData = (uint8_t *)calloc(1, jsonLen + 1);
APPSPAWN_CHECK(jsonData != NULL, cJSON_free(json); return -1, "Failed to allocate JSON data");
int cpRet = memcpy_s(jsonData, jsonLen + 1, json, jsonLen);
cJSON_free(json);
APPSPAWN_CHECK(cpRet == 0, free(jsonData); return -1, "Failed to copy JSON string");
int result = AddExtTlv(head, MSG_EXT_NAME_JIT_PERMISSIONS, DATA_TYPE_STRING, jsonData, jsonLen);
APPSPAWN_CHECK(result == 0, free(jsonData); return -1, "Failed to add JIT_PERMISSIONS TLV");
return 0;
}
* @brief Collect a single extended TLV by dispatching on tlvName
*
* Handles extended TLVs (tlvType == TLV_MAX) based on tlvName:
* - JIT_PERMISSIONS / APP_DISTRIBUTION_TYPE: rebuild from SPM data
* - Others: copy from old message
*
* @param tlvExt Pointer to the extended TLV header in old message buffer
* @param spmData SPM data from kernel
* @param head List head to append entry to
* @return 0 on success, -1 on failure
*/
* @brief Rebuild APP_DISTRIBUTION_TYPE extended TLV from SPM distribution type
*
* @param distributionType Distribution type enum value from SPM
* @param head List head to append entry to
* @return 0 on success, -1 on failure
*/
APPSPAWN_STATIC int RebuildDistributionTypeTlv(uint32_t distributionType, ListNode *head)
{
const char *distStr = "none";
if (distributionType < DISTRIBUTION_TYPE_MAP_SIZE) {
distStr = DISTRIBUTION_TYPE_MAP[distributionType];
}
uint32_t distLen = strlen(distStr);
uint32_t allocLen = (distLen > 0) ? distLen : 1;
uint8_t *data = (uint8_t *)calloc(1, allocLen);
APPSPAWN_CHECK(data != NULL, return -1, "Failed to allocate distribution data");
int cpRet = memcpy_s(data, allocLen, distStr, distLen);
APPSPAWN_CHECK(cpRet == 0, free(data); return -1, "Failed to copy distribution string");
APPSPAWN_LOGI("RebuildDistributionTypeTlv: Rebuilt: %{public}s", distStr);
int ret = AddExtTlv(head, MSG_EXT_NAME_APP_DISTRIBUTION_TYPE, DATA_TYPE_STRING, data, distLen);
APPSPAWN_CHECK(ret == 0, free(data); return -1,
"Failed to add APP_DISTRIBUTION_TYPE TLV");
return 0;
}
* @brief Copy other extended TLV as-is from old message
*
* Moved to tlv_builder.c as CopyOtherExtTlv
*/
static int CollectExtTlvByName(const AppSpawnTlvExt *tlvExt,
const SpmData *spmData, ListNode *head)
{
if (tlvExt == NULL || spmData == NULL || head == NULL) {
return -1;
}
if (strcmp(tlvExt->tlvName, MSG_EXT_NAME_JIT_PERMISSIONS) == 0) {
return CollectJitPermissionsFromSPM(spmData, head);
}
if (strcmp(tlvExt->tlvName, MSG_EXT_NAME_APP_DISTRIBUTION_TYPE) == 0) {
return RebuildDistributionTypeTlv(spmData->distributionType, head);
}
return CopyOtherExtTlv(tlvExt, head);
}
* @brief Collect all TLVs by walking the original message buffer sequentially
*
* Reads AppSpawnTlv/AppSpawnTlvExt headers directly from oldMsg->buffer,
* preserving the exact original order. Uses a unified switch on tlvType:
* - Standard sensitive (BUNDLE_INFO, DAC_INFO, etc.): rebuild from SPM data
* - Standard non-sensitive: copy from old message
* - Extended (TLV_MAX): dispatch by tlvName for rebuild or copy
*
* TLV_XPM_ID_TYPE is newly added and not present in original client messages,
* so it is appended after the buffer walk.
*
* @param mgr AppSpawn manager instance
* @param ctx AppSpawning context (for flags checking)
* @param oldMsg Old message node
* @param spmData SPM data from kernel
* @param head List head to append entries to
* @return 0 on success, -1 on failure
*/
static int CollectTlvsInOrder(const AppSpawnMgr *mgr, const AppSpawningCtx *ctx,
const AppSpawnMsgNode *oldMsg, const SpmData *spmData, ListNode *head)
{
APPSPAWN_CHECK(oldMsg != NULL && spmData != NULL && head != NULL, return -1,
"Invalid parameters: oldMsg=%{public}p, spmData=%{public}p, head=%{public}p", oldMsg, spmData, head);
uint32_t bufferLen = oldMsg->msgHeader.msgLen - sizeof(AppSpawnMsg);
uint32_t currLen = 0;
while (currLen < bufferLen) {
APPSPAWN_CHECK(currLen + sizeof(AppSpawnTlv) <= bufferLen, return -1,
"truncated TLV header at offset %{public}u", currLen);
AppSpawnTlv *tlv = (AppSpawnTlv *)(oldMsg->buffer + currLen);
APPSPAWN_CHECK(tlv->tlvLen >= sizeof(AppSpawnTlv) && currLen + tlv->tlvLen <= bufferLen,
return -1, "invalid tlvLen=%{public}u at offset %{public}u", tlv->tlvLen, currLen);
int result = 0;
switch (tlv->tlvType) {
case TLV_BUNDLE_INFO:
result = CollectBundleInfoFromSPM(oldMsg, spmData, head);
break;
case TLV_DAC_INFO:
result = CollectDacInfoFromSPM(mgr, ctx, oldMsg, spmData, head);
break;
case TLV_DOMAIN_INFO:
result = CollectDomainInfoFromSPM(oldMsg, spmData, head);
break;
case TLV_OWNER_INFO:
result = CollectOwnerInfoFromSPM(spmData, head);
break;
case TLV_PERMISSION:
result = CollectPermissionFromSPM(mgr, spmData, head);
break;
case TLV_MAX: {
result = CollectExtTlvByName((AppSpawnTlvExt *)(oldMsg->buffer + currLen), spmData, head);
break;
}
default:
result = CopyStandardTlv(oldMsg, tlv->tlvType, head);
break;
}
APPSPAWN_CHECK(result >= 0, return -1, "Failed to collect TLV %{public}u", tlv->tlvType);
currLen += tlv->tlvLen;
}
int ret = CollectXpmIdTypeFromSPM(spmData, head);
APPSPAWN_CHECK(ret >= 0, return -1, "Failed to collect TLV_XPM_ID_TYPE");
return 0;
}
* @brief Increment SPM reference counts for a context
*
* Increments tokenid and uid refcounts, tracking which ones succeed
* so they can be rolled back precisely on failure.
*
* @param mgr AppSpawn manager instance (for spawn ID)
* @param ctx AppSpawning context (will set spmRefAdded)
* @param refCtx Refcount context containing accessTokenIdEx and uid
* @return 0 on success, error code on failure
*/
APPSPAWN_STATIC int IncreaseRefCounts(AppSpawningCtx *ctx, const RefcountContext *refCtx)
{
APPSPAWN_CHECK(ctx != NULL && refCtx != NULL, return SPM_ERROR_INVALID_PARAM,
"Invalid parameters: ctx=%{public}p, refCtx=%{public}p", ctx, refCtx);
uint32_t spawnId = GetSpawnId();
uint8_t refAdded = SPM_REF_NONE;
int incRet = SpmIncTokenidRefCnt(refCtx->accessTokenIdEx & 0xFFFFFFFF, spawnId);
APPSPAWN_ONLY_EXPER(incRet == EOPNOTSUPP, APPSPAWN_LOGI("not support spm ignore refcount");
return SPM_ERROR_SPM_NOSUPP);
APPSPAWN_CHECK(incRet == 0,
ctx->spmRefAdded = refAdded;
return SPM_ERROR_REF_COUNT_INC_FAILED,
"SpmIncTokenidRefCnt failed (ret=%{public}d), tokenid=%{public}u, spawnId=%{public}u",
incRet, (uint32_t)(refCtx->accessTokenIdEx & 0xFFFFFFFF), spawnId);
refAdded |= SPM_REF_TOKENID;
incRet = SpmIncUidRefCnt(refCtx->uid, spawnId);
APPSPAWN_ONLY_EXPER(incRet == EOPNOTSUPP, APPSPAWN_LOGI("not support spm ignore refcount");
return SPM_ERROR_SPM_NOSUPP);
APPSPAWN_CHECK(incRet == 0,
ctx->spmRefAdded = refAdded;
return SPM_ERROR_REF_COUNT_INC_FAILED,
"SpmIncUidRefCnt failed (ret=%{public}d), uid=%{public}u, spawnId=%{public}u", incRet, refCtx->uid, spawnId);
refAdded |= SPM_REF_UID;
ctx->spmRefAdded = refAdded;
APPSPAWN_LOGI("IncreaseRefCounts: bitmap=0x%{public}02x, tokenid=%{public}" PRIu64
", uid=%{public}u, spawnId=%{public}u", refAdded, refCtx->accessTokenIdEx, refCtx->uid, spawnId);
return 0;
}
APPSPAWN_STATIC int ValidateUidRefCount(const AppSpawnMgr *mgr, const AppSpawningCtx *ctx,
const RefcountContext *refCtx)
{
APPSPAWN_CHECK(mgr != NULL && ctx != NULL && refCtx != NULL, return SPM_ERROR_INVALID_PARAM,
"Invalid parameters");
uint32_t runMode = mgr->content.mode;
bool isNWebMode = (runMode == MODE_FOR_NWEB_SPAWN || runMode == MODE_FOR_NWEB_COLD_RUN);
bool hasIsolatedSandboxFlag = CheckAppMsgFlagsSet(ctx, APP_FLAGS_ISOLATED_SANDBOX_TYPE);
APPSPAWN_ONLY_EXPER((!isNWebMode && !hasIsolatedSandboxFlag) || refCtx->spmUid == 0 ||
(hasIsolatedSandboxFlag && refCtx->uid == refCtx->spmUid), return 0);
uint64_t uidRefCnt = 0;
int getRet = SpmGetUidRefCnt(refCtx->uid, &uidRefCnt);
APPSPAWN_CHECK(getRet == 0 && uidRefCnt == 1, return SPM_ERROR_GET_SPM_FAILED,
"get spm uid ref failed %{public}d %{public}" PRIu64, getRet, uidRefCnt);
return 0;
}
* @brief Get tokenid and UID from message for refcount operations
*
* @param message Message containing ACCESS_TOKEN_INFO and DAC_INFO
* @param refCtx Output refcount context (will be filled with accessTokenIdEx and uid)
* @return 0 on success, error code on failure
*/
static int GetTokenidAndUidFromMsg(const AppSpawnMsgNode *message,
RefcountContext *refCtx)
{
if (message == NULL || refCtx == NULL) {
APPSPAWN_LOGE("GetTokenidAndUidFromMsg: Invalid parameters");
return SPM_ERROR_INVALID_PARAM;
}
const AppSpawnMsgAccessToken *tokenInfo =
(const AppSpawnMsgAccessToken *)GetAppSpawnMsgInfo(message, TLV_ACCESS_TOKEN_INFO);
if (tokenInfo == NULL) {
APPSPAWN_LOGE("GetTokenidAndUidFromMsg: No ACCESS_TOKEN_INFO in message");
return SPM_ERROR_MISSING_MSG_DATA;
}
refCtx->accessTokenIdEx = tokenInfo->accessTokenIdEx;
const AppDacInfo *dacInfo = (const AppDacInfo *)GetAppSpawnMsgInfo(message, TLV_DAC_INFO);
if (dacInfo == NULL) {
APPSPAWN_LOGE("GetTokenidAndUidFromMsg: No DAC_INFO in message");
return SPM_ERROR_MISSING_MSG_DATA;
}
refCtx->uid = dacInfo->uid;
return 0;
}
* @brief Get and validate SPM data from kernel
*
* This function allocates SPM data, retrieves it from kernel, and validates consistency.
* On failure, it returns an error code and the caller should use original message.
*
* @param oldMsg Original message containing ACCESS_TOKEN_INFO
* @param accessTokenIdEx Access token ID (for SpmGetEntry)
* @param uidForRefcount UID from original message (for validation)
* @param mgr AppSpawn manager instance (for process type check)
* @param ctx AppSpawning context (for process type check)
* @param spmOut Output pointer for SPM data (caller owns, must be freed on failure)
* @return 0 on success, error code on failure
*/
static int GetAndValidateSpmData(const AppSpawnMsgNode *oldMsg,
const RefcountContext *refCtx, const AppSpawnMgr *mgr,
const AppSpawningCtx *ctx, SpmData **spmOut)
{
SpmData *spmData = SpmDataNew(MAX_PERM_SIZE, MAX_EXTEND_PERM_SIZE, MAX_BUNDLE_NAME_LEN);
APPSPAWN_CHECK(spmData != NULL, return SPM_ERROR_NO_MEMORY,
"GetAndValidateSpmData: Failed to allocate SpmData");
int ret = SpmGetEntry(refCtx->accessTokenIdEx & 0xFFFFFFFF, spmData);
APPSPAWN_ONLY_EXPER(ret == EOPNOTSUPP, SpmDataFree(spmData);
APPSPAWN_LOGI("not support spm ignore rebuild msg");
return SPM_ERROR_SPM_NOSUPP);
APPSPAWN_CHECK(ret == 0, SpmDataFree(spmData);
return SPM_ERROR_GET_SPM_FAILED,
"Failed to get SPM data for tokenid=%{public}" PRIu64 ", ret=%{public}d", refCtx->accessTokenIdEx, ret);
ret = ValidateSpmDataConsistency(spmData, refCtx->accessTokenIdEx, refCtx->uid, mgr, ctx);
APPSPAWN_CHECK(ret == 0, SpmDataFree(spmData);
return ret, "GetAndValidateSpmData: SPM data validation failed (ret=%{public}d)", ret);
*spmOut = spmData;
return 0;
}
* @brief Phase 2: Calculate buffer size, allocate, and write all TLVs
*
* @param oldMsg Old message (for header copy)
* @param entryList List of TLV descriptors from Phase 1
* @param newMsgOut Output pointer for new message (caller owns)
* @return 0 on success, -1 on failure
*/
static int WriteTlvsToNewMsg(const AppSpawnMsgNode *oldMsg,
ListNode *entryList, AppSpawnMsgNode **newMsgOut)
{
APPSPAWN_CHECK(oldMsg != NULL && entryList != NULL && newMsgOut != NULL, return -1,
"Invalid parameters: oldMsg=%{public}p, entryList=%{public}p, newMsgOut=%{public}p",
oldMsg, entryList, newMsgOut);
uint32_t exactBufSize = 0;
int extTlvCount = 0;
ListNode *node = NULL;
ForEachListEntry(entryList, node) {
RebuildTlvEntry *entry = ListEntry(node, RebuildTlvEntry, node);
exactBufSize += entry->totalLen;
if (entry->entryType == TLV_ENTRY_EXTENDED) {
extTlvCount++;
}
}
APPSPAWN_LOGI("WriteTlvsToNewMsg: Extended TLV count=%{public}d, exact buffer size=%{public}u",
extTlvCount, exactBufSize);
AppSpawnMsgNode *newMsg = CreateAppSpawnMsgInternal();
APPSPAWN_CHECK(newMsg != NULL, return SPM_ERROR_NO_MEMORY, "Failed to create new message");
newMsg->msgHeader = oldMsg->msgHeader;
newMsg->connection = oldMsg->connection;
newMsg->tlvOffset = (uint32_t *)calloc(TLV_MAX + extTlvCount, sizeof(uint32_t));
APPSPAWN_CHECK(newMsg->tlvOffset != NULL, DeleteAppSpawnMsgInternal(&newMsg); return SPM_ERROR_NO_MEMORY,
"Failed to allocate tlvOffset array");
for (int i = 0; i < TLV_MAX + extTlvCount; i++) {
newMsg->tlvOffset[i] = INVALID_OFFSET;
}
if (exactBufSize > 0) {
newMsg->buffer = (uint8_t *)calloc(1, exactBufSize);
APPSPAWN_CHECK(newMsg->buffer != NULL,
free(newMsg->tlvOffset); newMsg->tlvOffset = NULL;
DeleteAppSpawnMsgInternal(&newMsg); return SPM_ERROR_NO_MEMORY,
"Failed to allocate message buffer (%{public}u bytes)", exactBufSize);
}
newMsg->tlvCount = 0;
uint32_t currentOffset = 0;
int ret = WriteTlvEntriesToBuffer(newMsg, entryList, ¤tOffset);
APPSPAWN_CHECK(ret == 0, DeleteAppSpawnMsgInternal(&newMsg); return SPM_ERROR_TLV_BUILD_FAILED,
"Failed to write TLV entries to buffer");
newMsg->msgHeader.msgLen = sizeof(AppSpawnMsg) + currentOffset;
*newMsgOut = newMsg;
return 0;
}
* @brief Build and replace message with TLV-based approach
*
* This function performs the TLV-based message rebuild process. If TLV build fails,
* it returns success (0) to allow using the original message (refcounts already incremented).
*
* @param oldMsg Original message (will be deleted on success)
* @param spmData SPM data containing permissions and extensions
* @param mgr AppSpawn manager instance
* @param ctx AppSpawning context (message pointer will be updated on success)
* @return 0 on success (always returns 0, uses original message on TLV build failure)
*/
static void BuildAndReplaceMessage(AppSpawnMsgNode *oldMsg,
const SpmData *spmData, const AppSpawnMgr *mgr, AppSpawningCtx *ctx)
{
ListNode entryList;
OH_ListInit(&entryList);
int ret = CollectTlvsInOrder(mgr, ctx, oldMsg, spmData, &entryList);
APPSPAWN_CHECK(ret == 0,
FreeTlvEntries(&entryList);
return,
"BuildAndReplaceMessage: Failed to collect TLVs (ret=%{public}d), using original message", ret);
int entryCount = OH_ListGetCnt(&entryList);
APPSPAWN_LOGI("BuildAndReplaceMessage: Phase 1 complete, collected %{public}d TLV entries", entryCount);
AppSpawnMsgNode *newMsg = NULL;
ret = WriteTlvsToNewMsg(oldMsg, &entryList, &newMsg);
APPSPAWN_CHECK(ret == 0, FreeTlvEntries(&entryList);
return, "BuildAndReplaceMessage: Failed to write TLVs (ret=%{public}d), using original message", ret);
DeleteAppSpawnMsgInternal(&oldMsg);
ctx->message = newMsg;
FreeTlvEntries(&entryList);
APPSPAWN_LOGV("BuildAndReplaceMessage: Message successfully replaced with SPM-rebuilt message");
}
* @brief Report SPM rebuild failure via hisysevent key event (no-op when hisysevent is disabled)
*/
static inline void ReportSpmFailEvent(int errorCode)
{
#ifdef APPSPAWN_HISYSEVENT
char eventName[64] = {0};
int ret = snprintf_s(eventName, sizeof(eventName), sizeof(eventName) - 1,
"SPM_REBUILD_FAIL_%d", errorCode);
APPSPAWN_CHECK(ret > 0, return,
"ReportSpmFailEvent: snprintf_s failed (ret=%{public}d)", ret);
ReportKeyEvent(eventName);
#endif
}
* @brief Rebuild message from SPM data (two-phase approach with linked list)
*
* Phase 1 — Collect: Gather all TLV descriptors into a linked list
* Phase 2 — Write: Calculate exact size, allocate buffer, write all TLVs
*
* @param mgr AppSpawnMgr
* @param ctx AppSpawningCtx(包含消息指针)
* @return 0 on success, error code on failure
*/
APPSPAWN_STATIC int RebuildMessageFromSPM(const AppSpawnMgr *mgr, AppSpawningCtx *ctx)
{
APPSPAWN_CHECK(mgr != NULL && ctx != NULL && ctx->message != NULL, return SPM_ERROR_INVALID_PARAM,
"Invalid parameters is null");
AppSpawnMsgNode *oldMsg = ctx->message;
APPSPAWN_LOGI("RebuildMessageFromSPM: Rebuilding message for %{public}s, msgId=%{public}u",
oldMsg->msgHeader.processName, oldMsg->msgHeader.msgId);
RefcountContext refCtx = {0};
int ret = GetTokenidAndUidFromMsg(oldMsg, &refCtx);
APPSPAWN_CHECK(ret == 0, ReportSpmFailEvent(ret);
return 0,
"RebuildMessageFromSPM: Failed to get tokenid/UID (ret=%{public}d), use original message", ret);
ret = IncreaseRefCounts(ctx, &refCtx);
APPSPAWN_ONLY_EXPER(ret == SPM_ERROR_SPM_NOSUPP, return 0);
APPSPAWN_CHECK(ret == 0, ReportSpmFailEvent(ret);
return 0,
"RebuildMessageFromSPM: Failed to increment refcounts (ret=%{public}d), use original message", ret);
SpmData *spmData = NULL;
ret = GetAndValidateSpmData(oldMsg, &refCtx, mgr, ctx, &spmData);
APPSPAWN_ONLY_EXPER(ret == SPM_ERROR_SPM_NOSUPP, return 0);
APPSPAWN_CHECK(ret == 0, ReportSpmFailEvent(ret);
return 0,
"RebuildMessageFromSPM: SPM data failed (ret=%{public}d), use original message", ret);
refCtx.spmUid = spmData->uid;
ret = ValidateUidRefCount(mgr, ctx, &refCtx);
APPSPAWN_CHECK(ret == 0, SpmDataFree(spmData);
ReportSpmFailEvent(ret);
return 0,
"RebuildMessageFromSPM: uidRefCnt validation failed (ret=%{public}d), use original message", ret);
BuildAndReplaceMessage(oldMsg, spmData, mgr, ctx);
SpmDataFree(spmData);
return 0;
}
int OnMessageRebuildFromSPM(AppSpawnMgr *mgr, AppSpawningCtx *ctx)
{
if (mgr == NULL || ctx == NULL) {
APPSPAWN_LOGE("OnMessageRebuildFromSPM: invalid parameters");
return SPM_ERROR_INVALID_PARAM;
}
APPSPAWN_CHECK_LOGV(!mgr->content.isLinux, return 0, "OnMessageRebuildFromSPM: skip on Linux platform");
int ret = RebuildMessageFromSPM(mgr, ctx);
if (ret != 0) {
APPSPAWN_LOGE("OnMessageRebuildFromSPM: Failed to rebuild message from SPM, ret=%{public}d", ret);
return ret;
}
APPSPAWN_LOGI("OnMessageRebuildFromSPM: Message rebuilt successfully for %{public}s",
ctx->message->msgHeader.processName);
return 0;
}
* @brief Hook for spawn abort reference count update
*
* This hook is called when app spawn fails (before app is queued).
* Only decrements refcounts tracked in spmRefAdded bitmap.
* Stage: STAGE_SERVER_SPAWN_ABORT (priority: HOOK_PRIO_HIGHEST to run first)
*/
int OnSpawnAbortUpdateRefCount(AppSpawnMgr *mgr, AppSpawningCtx *ctx)
{
if (mgr == NULL || ctx == NULL) {
return -1;
}
APPSPAWN_CHECK_LOGV(!mgr->content.isLinux, return 0, "OnSpawnAbortUpdateRefCount: skip on Linux platform");
APPSPAWN_CHECK(ctx->spmRefAdded != SPM_REF_NONE, return 0, "spmRefAdded is 0, skip refcount decrement");
AppSpawnMsgNode *msg = ctx->message;
if (msg == NULL) {
APPSPAWN_LOGE("OnSpawnAbortUpdateRefCount: msg is NULL");
return -1;
}
uint32_t spawnId = GetSpawnId();
if (ctx->spmRefAdded & SPM_REF_TOKENID) {
const AppSpawnMsgAccessToken *tokenInfo =
(const AppSpawnMsgAccessToken *)GetAppSpawnMsgInfo(msg, TLV_ACCESS_TOKEN_INFO);
if (tokenInfo != NULL) {
int ret = SpmDecTokenidRefCnt(tokenInfo->accessTokenIdEx & 0xFFFFFFFF, spawnId);
if (ret != 0) {
APPSPAWN_LOGW("OnSpawnAbortUpdateRefCount: SpmDecTokenidRefCnt failed (ret=%{public}d)", ret);
}
}
}
if (ctx->spmRefAdded & SPM_REF_UID) {
const AppDacInfo *dacInfo = (const AppDacInfo *)GetAppSpawnMsgInfo(msg, TLV_DAC_INFO);
if (dacInfo != NULL) {
int ret = SpmDecUidRefCnt(dacInfo->uid, spawnId);
if (ret != 0) {
APPSPAWN_LOGW("OnSpawnAbortUpdateRefCount: SpmDecUidRefCnt failed (ret=%{public}d)", ret);
}
}
}
APPSPAWN_LOGI("OnSpawnAbortUpdateRefCount: bitmap=0x%{public}02x, pid=%{public}d, spawnId=%{public}u",
ctx->spmRefAdded, ctx->pid, spawnId);
ctx->spmRefAdded = SPM_REF_NONE;
return 0;
}
int OnAppExitUpdateRefCount(const AppSpawnMgr *mgr, const AppSpawnedProcess *appInfo)
{
if (mgr == NULL || appInfo == NULL) {
return -1;
}
APPSPAWN_CHECK_LOGV(!mgr->content.isLinux, return 0, "OnAppExitUpdateRefCount: skip on Linux platform");
APPSPAWN_CHECK(appInfo->spmRefAdded != SPM_REF_NONE, return 0, "spmRefAdded is 0, skip refcount decrement");
uint32_t spawnId = GetSpawnId();
AppSpawnedProcess *mutableApp = (AppSpawnedProcess *)appInfo;
if (appInfo->spmRefAdded & SPM_REF_TOKENID) {
int ret = SpmDecTokenidRefCnt(appInfo->tokenid & 0xFFFFFFFF, spawnId);
APPSPAWN_ONLY_EXPER(ret != 0,
APPSPAWN_LOGE("OnAppExitUpdateRefCount: SpmDecTokenidRefCnt failed (ret=%{public}d)", ret));
APPSPAWN_ONLY_EXPER(ret == 0,
mutableApp->spmRefAdded &= ~SPM_REF_TOKENID);
}
if (appInfo->spmRefAdded & SPM_REF_UID) {
int ret = SpmDecUidRefCnt(appInfo->uid, spawnId);
APPSPAWN_ONLY_EXPER(ret != 0,
APPSPAWN_LOGE("OnAppExitUpdateRefCount: SpmDecUidRefCnt failed (ret=%{public}d)", ret));
APPSPAWN_ONLY_EXPER(ret == 0,
mutableApp->spmRefAdded &= ~SPM_REF_UID);
}
APPSPAWN_LOGI("OnAppExitUpdateRefCount: bitmap=0x%{public}02x, tokenid=%{public}" PRIu64
", uid=%{public}u, pid=%{public}d, spawnId=%{public}u",
appInfo->spmRefAdded, appInfo->tokenid, appInfo->uid, appInfo->pid, spawnId);
return 0;
}
APPSPAWN_STATIC int SpmPreloadHook(AppSpawnMgr *mgr)
{
if (mgr == NULL) {
APPSPAWN_LOGE("SpmPreloadHook: invalid mgr");
return -1;
}
APPSPAWN_CHECK_LOGV(!mgr->content.isLinux, return 0, "SpmPreloadHook: skip on Linux platform");
APPSPAWN_LOGI("SPM module initializing...");
uint32_t runMode = mgr->content.mode;
g_spawnId = RunModeToSpawnId(runMode);
APPSPAWN_LOGI("Initialized spawnId: runMode=%{public}u, spawnId=%{public}u",
runMode, g_spawnId);
int ret = SpmClearSpawnidRefCnt(g_spawnId);
if (ret != 0) {
APPSPAWN_LOGW("CleanupStaleSpawns failed (ret=%{public}d)", ret);
}
APPSPAWN_LOGI("SPM module initialized successfully");
return 0;
}
MODULE_CONSTRUCTOR(void)
{
APPSPAWN_LOGI("SPM module loading...");
(void)AddServerStageHook(STAGE_SERVER_PRELOAD, HOOK_PRIO_COMMON, SpmPreloadHook);
(void)AddAppSpawnHook(STAGE_PARENT_MSG_DECODE, HOOK_PRIO_HIGHEST, OnMessageRebuildFromSPM);
(void)AddAppSpawnHook(STAGE_SERVER_SPAWN_ABORT, HOOK_PRIO_HIGHEST, OnSpawnAbortUpdateRefCount);
(void)AddProcessMgrHook(STAGE_SERVER_APP_CLEANUP, HOOK_PRIO_HIGHEST, OnAppExitUpdateRefCount);
APPSPAWN_LOGI("SPM module loaded successfully, hooks registered");
}