* Copyright (C) 2025 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 "sandbox_shared.h"
#include <sys/mount.h>
#include "securec.h"
#include "appspawn_sandbox.h"
#include "appspawn_permission.h"
#include "appspawn_utils.h"
#include "parameter.h"
#define USER_ID_SIZE 16
#define DIR_MODE 0711
#define LOCK_STATUS_SIZE 16
#define DATA_GROUP_SOCKET_TYPE "DataGroup"
#define GROUPLIST_KEY_DATAGROUPID "dataGroupId"
#define GROUPLIST_KEY_GID "gid"
#define GROUPLIST_KEY_DIR "dir"
#define GROUPLIST_KEY_UUID "uuid"
static const MountSharedTemplate MOUNT_SHARED_MAP[] = {
{"/data/storage/el2", NULL},
{"/data/storage/el3", NULL},
{"/data/storage/el4", NULL},
{"/data/storage/el5", "ohos.permission.PROTECT_SCREEN_LOCK_DATA"},
};
static const DataGroupSandboxPathTemplate DATA_GROUP_SANDBOX_PATH_MAP[] = {
{"el2", EL2, "/data/storage/el2/group/", NULL},
{"el3", EL3, "/data/storage/el3/group/", NULL},
{"el4", EL4, "/data/storage/el4/group/", NULL},
{"el5", EL5, "/data/storage/el5/group/", "ohos.permission.PROTECT_SCREEN_LOCK_DATA"},
};
bool IsValidDataGroupItem(cJSON *item)
{
cJSON *datagroupId = cJSON_GetObjectItem(item, GROUPLIST_KEY_DATAGROUPID);
cJSON *gid = cJSON_GetObjectItem(item, GROUPLIST_KEY_GID);
cJSON *dir = cJSON_GetObjectItem(item, GROUPLIST_KEY_DIR);
cJSON *uuid = cJSON_GetObjectItem(item, GROUPLIST_KEY_UUID);
if (datagroupId && cJSON_IsString(datagroupId) &&
gid && cJSON_IsString(gid) &&
dir && cJSON_IsString(dir) &&
uuid && cJSON_IsString(uuid)) {
return true;
}
return false;
}
int GetElxInfoFromDir(const char *path)
{
int ret = ELX_MAX;
if (path == NULL) {
return ret;
}
uint32_t count = ARRAY_LENGTH(DATA_GROUP_SANDBOX_PATH_MAP);
for (uint32_t i = 0; i < count; ++i) {
if (strstr(path, DATA_GROUP_SANDBOX_PATH_MAP[i].elxName) != NULL) {
return DATA_GROUP_SANDBOX_PATH_MAP[i].category;
}
}
APPSPAWN_LOGE("Get elx info from dir failed, path %{public}s", path);
return ret;
}
const DataGroupSandboxPathTemplate *GetDataGroupArgTemplate(uint32_t category)
{
uint32_t count = ARRAY_LENGTH(DATA_GROUP_SANDBOX_PATH_MAP);
if (category > count) {
APPSPAWN_LOGE("category %{public}d is out of range", category);
return NULL;
}
for (uint32_t i = 0; i < count; ++i) {
if (DATA_GROUP_SANDBOX_PATH_MAP[i].category == category) {
return &DATA_GROUP_SANDBOX_PATH_MAP[i];
}
}
return NULL;
}
static bool IsUnlockStatus(uint32_t uid)
{
const int userIdBase = UID_BASE;
uid = uid / userIdBase;
if (uid == 0) {
return true;
}
char lockStatusParam[PARAM_BUFFER_SIZE] = {0};
int ret = snprintf_s(lockStatusParam, PARAM_BUFFER_SIZE, PARAM_BUFFER_SIZE - 1,
"startup.appspawn.lockstatus_%u", uid);
APPSPAWN_CHECK(ret > 0, return APPSPAWN_ERROR_UTILS_MEM_FAIL,
"Format lock status param failed, errno: %{public}d", errno);
char userLockStatus[LOCK_STATUS_SIZE] = {0};
ret = GetParameter(lockStatusParam, "1", userLockStatus, sizeof(userLockStatus));
APPSPAWN_LOGI("Get param %{public}s %{public}s", lockStatusParam, userLockStatus);
if (ret > 0 && (strcmp(userLockStatus, "0") == 0)) {
return true;
}
return false;
}
static bool SetSandboxPathShared(const char *sandboxPath)
{
int ret = mount(NULL, sandboxPath, NULL, MS_SHARED, NULL);
if (ret != 0) {
APPSPAWN_LOGW("Need to mount %{public}s to shared, errno %{public}d", sandboxPath, errno);
return false;
}
return true;
}
static int MountWithFileMgr(const AppDacInfo *info)
{
char nosharefsDocsDir[PATH_MAX_LEN] = {0};
int ret = snprintf_s(nosharefsDocsDir, PATH_MAX_LEN, PATH_MAX_LEN - 1, "/mnt/user/%u/nosharefs/docs",
info->uid / UID_BASE);
if (ret <= 0) {
APPSPAWN_LOGE("snprintf nosharefsDocsDir failed, errno %{public}d", errno);
return APPSPAWN_ERROR_UTILS_MEM_FAIL;
}
char storageUserPath[PATH_MAX_LEN] = {0};
ret = snprintf_s(storageUserPath, PATH_MAX_LEN, PATH_MAX_LEN - 1, "/mnt/sandbox/%u/app-root/storage/Users",
info->uid / UID_BASE);
if (ret <= 0) {
APPSPAWN_LOGE("snprintf storageUserPath failed, errno %{public}d", errno);
return APPSPAWN_ERROR_UTILS_MEM_FAIL;
}
if (SetSandboxPathShared(storageUserPath)) {
APPSPAWN_LOGV("shared mountpoint is exist");
return 0;
}
ret = CreateSandboxDir(storageUserPath, DIR_MODE);
if (ret != 0) {
APPSPAWN_LOGE("mkdir %{public}s failed, errno %{public}d", storageUserPath, errno);
return APPSPAWN_SANDBOX_ERROR_MKDIR_FAIL;
}
MountArg arg = {
.originPath = nosharefsDocsDir,
.destinationPath = storageUserPath,
.fsType = NULL,
.mountFlags = MS_BIND | MS_REC,
.options = NULL,
.mountSharedFlag = MS_SHARED
};
ret = SandboxMountPath(&arg);
if (ret != 0) {
APPSPAWN_LOGE("mount %{public}s shared failed, ret %{public}d", storageUserPath, ret);
}
return ret;
}
static int MountWithOther(const AppDacInfo *info)
{
char sharefsDocsDir[PATH_MAX_LEN] = {0};
int ret = snprintf_s(sharefsDocsDir, PATH_MAX_LEN, PATH_MAX_LEN - 1, "/mnt/user/%u/sharefs/docs",
info->uid / UID_BASE);
if (ret <= 0) {
APPSPAWN_LOGE("snprintf sharefsDocsDir failed, errno %{public}d", errno);
return APPSPAWN_ERROR_UTILS_MEM_FAIL;
}
char storageUserPath[PATH_MAX_LEN] = {0};
ret = snprintf_s(storageUserPath, PATH_MAX_LEN, PATH_MAX_LEN - 1, "/mnt/sandbox/%u/app-root/storage/Users",
info->uid / UID_BASE);
if (ret <= 0) {
APPSPAWN_LOGE("snprintf storageUserPath failed, errno %{public}d", errno);
return APPSPAWN_ERROR_UTILS_MEM_FAIL;
}
if (SetSandboxPathShared(storageUserPath)) {
APPSPAWN_LOGV("shared mountpoint is exist");
return 0;
}
ret = CreateSandboxDir(storageUserPath, DIR_MODE);
if (ret != 0) {
APPSPAWN_LOGE("mkdir %{public}s failed, errno %{public}d", storageUserPath, errno);
return APPSPAWN_SANDBOX_ERROR_MKDIR_FAIL;
}
char options[PATH_MAX_LEN] = {0};
ret = snprintf_s(options, PATH_MAX_LEN, PATH_MAX_LEN - 1, "override_support_delete,user_id=%u",
info->uid / UID_BASE);
if (ret <= 0) {
APPSPAWN_LOGE("snprintf options failed, errno %{public}d", errno);
return APPSPAWN_ERROR_UTILS_MEM_FAIL;
}
MountArg arg = {
.originPath = sharefsDocsDir,
.destinationPath = storageUserPath,
.fsType = "sharefs",
.mountFlags = MS_NODEV,
.options = options,
.mountSharedFlag = MS_SHARED
};
ret = SandboxMountPath(&arg);
if (ret != 0) {
APPSPAWN_LOGE("mount %{public}s shared failed, ret %{public}d", storageUserPath, ret);
}
return ret;
}
static void MountStorageUsers(const SandboxContext *context, AppSpawnSandboxCfg *sandbox, const AppDacInfo *info)
{
int ret = 0;
int index = GetPermissionIndexInQueue(&sandbox->permissionQueue, FILE_ACCESS_MANAGER_MODE);
int checkRes = CheckSandboxCtxPermissionFlagSet(context, (uint32_t)index);
if (checkRes == 0) {
ret = MountWithOther(info);
} else {
ret = MountWithFileMgr(info);
}
if (ret != 0) {
APPSPAWN_LOGE("Update %{public}s storage dir failed, ret %{public}d",
checkRes == 0 ? "sharefs dir" : "no sharefs dir", ret);
} else {
APPSPAWN_LOGI("Update %{public}s storage dir success", checkRes == 0 ? "sharefs dir" : "no sharefs dir");
}
}
static int MountSharedMapItem(const char *bundleNamePath, const char *sandboxPathItem)
{
char sandboxPath[PATH_MAX_LEN] = {0};
int ret = snprintf_s(sandboxPath, PATH_MAX_LEN, PATH_MAX_LEN - 1, "%s%s",
bundleNamePath, sandboxPathItem);
if (ret <= 0) {
APPSPAWN_LOGE("snprintf sandboxPath failed, errno %{public}d", errno);
return APPSPAWN_ERROR_UTILS_MEM_FAIL;
}
if (SetSandboxPathShared(sandboxPath)) {
APPSPAWN_LOGV("shared mountpoint is exist");
return 0;
}
ret = CreateSandboxDir(sandboxPath, DIR_MODE);
if (ret != 0) {
APPSPAWN_LOGE("mkdir %{public}s failed, errno %{public}d", sandboxPath, errno);
return APPSPAWN_SANDBOX_ERROR_MKDIR_FAIL;
}
MountArg arg = {
.originPath = sandboxPath,
.destinationPath = sandboxPath,
.fsType = NULL,
.mountFlags = MS_BIND | MS_REC,
.options = NULL,
.mountSharedFlag = MS_SHARED
};
ret = SandboxMountPath(&arg);
if (ret != 0) {
APPSPAWN_LOGE("mount %{public}s shared failed, ret %{public}d", sandboxPath, ret);
}
return ret;
}
static void MountSharedMap(const SandboxContext *context, AppSpawnSandboxCfg *sandbox, const char *bundleNamePath)
{
int length = sizeof(MOUNT_SHARED_MAP) / sizeof(MOUNT_SHARED_MAP[0]);
for (int i = 0; i < length; i++) {
if (MOUNT_SHARED_MAP[i].permission == NULL) {
MountSharedMapItem(bundleNamePath, MOUNT_SHARED_MAP[i].sandboxPath);
} else {
int index = GetPermissionIndexInQueue(&sandbox->permissionQueue, MOUNT_SHARED_MAP[i].permission);
APPSPAWN_LOGV("mount dir on lock mountPermissionFlags %{public}d", index);
if (CheckSandboxCtxPermissionFlagSet(context, (uint32_t)index)) {
MountSharedMapItem(bundleNamePath, MOUNT_SHARED_MAP[i].sandboxPath);
}
}
}
APPSPAWN_LOGI("mount shared map success");
}
static int DataGroupCtxNodeCompare(ListNode *node, void *data)
{
DataGroupCtx *existingNode = (DataGroupCtx *)ListEntry(node, DataGroupCtx, node);
DataGroupCtx *newNode = (DataGroupCtx *)data;
if (existingNode == NULL || newNode == NULL) {
APPSPAWN_LOGE("Invalid param");
return APPSPAWN_ARG_INVALID;
}
bool isSrcPathEqual = (strcmp(existingNode->srcPath.path, newNode->srcPath.path) == 0);
bool isDestPathEqual = (strcmp(existingNode->destPath.path, newNode->destPath.path) == 0);
return (isSrcPathEqual && isDestPathEqual) ? 0 : 1;
}
static int AddDataGroupItemToQueue(AppSpawnMgr *content, const char *srcPath, const char *destPath)
{
DataGroupCtx *dataGroupNode = (DataGroupCtx *)calloc(1, sizeof(DataGroupCtx));
APPSPAWN_CHECK(dataGroupNode != NULL, return APPSPAWN_ERROR_UTILS_MEM_FAIL, "Calloc dataGroupNode failed");
if (strcpy_s(dataGroupNode->srcPath.path, PATH_MAX_LEN - 1, srcPath) != EOK ||
strcpy_s(dataGroupNode->destPath.path, PATH_MAX_LEN - 1, destPath) != EOK) {
APPSPAWN_LOGE("strcpy dataGroupNode path failed");
free(dataGroupNode);
dataGroupNode = NULL;
return APPSPAWN_ERROR_UTILS_MEM_FAIL;
}
dataGroupNode->srcPath.pathLen = strlen(dataGroupNode->srcPath.path);
dataGroupNode->destPath.pathLen = strlen(dataGroupNode->destPath.path);
ListNode *node = OH_ListFind(&content->dataGroupCtxQueue, (void *)dataGroupNode, DataGroupCtxNodeCompare);
if (node != NULL) {
APPSPAWN_LOGI("DataGroupCtxNode %{public}s is exist", dataGroupNode->srcPath.path);
free(dataGroupNode);
dataGroupNode = NULL;
return 0;
}
OH_ListInit(&dataGroupNode->node);
OH_ListAddTail(&content->dataGroupCtxQueue, &dataGroupNode->node);
return 0;
}
static inline cJSON *GetJsonObjFromExtInfo(const SandboxContext *context, const char *name)
{
uint32_t size = 0;
char *extInfo = (char *)(GetAppSpawnMsgExtInfo(context->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;
}
static void DumpDataGroupCtxQueue(const ListNode *front)
{
if (front == NULL) {
return;
}
uint32_t count = 0;
ListNode *node = front->next;
while (node != front && node != NULL) {
DataGroupCtx *dataGroupNode = (DataGroupCtx *)ListEntry(node, DataGroupCtx, node);
count++;
APPSPAWN_LOGV(" ************************************** %{public}d", count);
APPSPAWN_LOGV(" srcPath: %{public}s", dataGroupNode->srcPath.path);
APPSPAWN_LOGV(" destPath: %{public}s", dataGroupNode->destPath.path);
node = node->next;
}
}
static int ParseDataGroupList(AppSpawnMgr *content, SandboxContext *context, const AppSpawnSandboxCfg *appSandbox,
const char *bundleNamePath)
{
int ret = 0;
cJSON *dataGroupList = GetJsonObjFromExtInfo(context, DATA_GROUP_SOCKET_TYPE);
if (dataGroupList == NULL) {
return APPSPAWN_ARG_INVALID;
}
cJSON *item = NULL;
cJSON_ArrayForEach(item, dataGroupList) {
APPSPAWN_CHECK(IsValidDataGroupItem(item), break, "Element is not a valid data group item");
cJSON *dirItem = cJSON_GetObjectItemCaseSensitive(item, "dir");
cJSON *uuidItem = cJSON_GetObjectItemCaseSensitive(item, "uuid");
if (dirItem == NULL || !cJSON_IsString(dirItem) || uuidItem == NULL || !cJSON_IsString(uuidItem)) {
APPSPAWN_LOGE("Data group element is invalid");
break;
}
const char *srcPath = dirItem->valuestring;
APPSPAWN_CHECK(!CheckPath(srcPath), break, "src path %{public}s is invalid", srcPath);
int elxValue = GetElxInfoFromDir(srcPath);
APPSPAWN_CHECK((elxValue >= EL2 && elxValue < ELX_MAX), break, "Get elx value failed");
const DataGroupSandboxPathTemplate *templateItem = GetDataGroupArgTemplate(elxValue);
APPSPAWN_CHECK(templateItem != NULL, break, "Get data group arg template failed");
if (templateItem->permission != NULL) {
int index = GetPermissionIndexInQueue(&appSandbox->permissionQueue, templateItem->permission);
APPSPAWN_LOGV("mount dir no lock mount permission flag %{public}d", index);
if (!CheckSandboxCtxPermissionFlagSet(context, (uint32_t)index)) {
continue;
}
}
char targetPath[PATH_MAX_LEN] = {0};
int len = snprintf_s(targetPath, PATH_MAX_LEN, PATH_MAX_LEN - 1, "%s%s%s",
bundleNamePath, templateItem->sandboxPath, uuidItem->valuestring);
APPSPAWN_CHECK(len > 0, break, "Failed to format targetPath");
ret = AddDataGroupItemToQueue(content, srcPath, targetPath);
if (ret != 0) {
APPSPAWN_LOGE("Add datagroup item to dataGroupCtxQueue failed, el%{public}d", elxValue);
OH_ListRemoveAll(&content->dataGroupCtxQueue, NULL);
break;
}
}
cJSON_Delete(dataGroupList);
DumpDataGroupCtxQueue(&content->dataGroupCtxQueue);
return ret;
}
int UpdateDataGroupDirs(AppSpawnMgr *content)
{
if (content == NULL) {
return APPSPAWN_ARG_INVALID;
}
int ret = 0;
ListNode *node = content->dataGroupCtxQueue.next;
while (node != &content->dataGroupCtxQueue && node != NULL) {
DataGroupCtx *dataGroupNode = (DataGroupCtx *)ListEntry(node, DataGroupCtx, node);
MountArg args = {
.originPath = dataGroupNode->srcPath.path,
.destinationPath = dataGroupNode->destPath.path,
.fsType = NULL,
.mountFlags = MS_BIND | MS_REC,
.options = NULL,
.mountSharedFlag = MS_SHARED
};
ret = SandboxMountPath(&args);
if (ret != 0) {
APPSPAWN_LOGE("Shared mount %{public}s to %{public}s failed, errno %{public}d", args.originPath,
args.destinationPath, ret);
}
node = node->next;
}
OH_ListRemoveAll(&content->dataGroupCtxQueue, NULL);
return 0;
}
static int CreateSharedStamp(AppSpawnMsgDacInfo *info, SandboxContext *context)
{
char lockSbxPathStamp[PATH_MAX_LEN] = {0};
int ret = 0;
if (CheckSandboxCtxMsgFlagSet(context, APP_FLAGS_ISOLATED_SANDBOX_TYPE) != 0) {
ret = snprintf_s(lockSbxPathStamp, PATH_MAX_LEN, PATH_MAX_LEN - 1,
"/mnt/sandbox/%d/isolated/%s_locked", info->uid / UID_BASE, context->bundleName);
} else {
ret = snprintf_s(lockSbxPathStamp, PATH_MAX_LEN, PATH_MAX_LEN - 1,
"/mnt/sandbox/%d/%s_locked", info->uid / UID_BASE, context->bundleName);
}
if (ret <= 0) {
APPSPAWN_LOGE("Failed to format lock sandbox path stamp");
return APPSPAWN_ERROR_UTILS_MEM_FAIL;
}
ret = CreateSandboxDir(lockSbxPathStamp, DIR_MODE);
if (ret != 0) {
APPSPAWN_LOGE("Mkdir %{public}s failed, errno: %{public}d", lockSbxPathStamp, errno);
}
return ret;
}
int MountDirsToShared(AppSpawnMgr *content, SandboxContext *context, AppSpawnSandboxCfg *sandbox)
{
if (content == NULL || context == NULL || sandbox == NULL) {
APPSPAWN_LOGE("Input paramters invalid");
return APPSPAWN_SANDBOX_INVALID;
}
AppSpawnMsgDacInfo *info = (AppSpawnMsgDacInfo *)GetSandboxCtxMsgInfo(context, TLV_DAC_INFO);
AppSpawnMsgBundleInfo *bundleInfo = (AppSpawnMsgBundleInfo *)GetSandboxCtxMsgInfo(context, TLV_BUNDLE_INFO);
if (info == NULL || bundleInfo == NULL) {
APPSPAWN_LOGE("Info or bundleInfo invalid");
return APPSPAWN_SANDBOX_INVALID;
}
if (IsUnlockStatus(info->uid)) {
return 0;
}
char bundleNamePath[PATH_MAX_LEN] = {0};
int ret = snprintf_s(bundleNamePath, PATH_MAX_LEN, PATH_MAX_LEN - 1,
"/mnt/sandbox/%u/%s", info->uid / UID_BASE, bundleInfo->bundleName);
if (ret < 0) {
APPSPAWN_LOGE("Failed to format lock sandbox path stamp");
return APPSPAWN_ERROR_UTILS_MEM_FAIL;
}
MountSharedMap(context, sandbox, bundleNamePath);
MountStorageUsers(context, sandbox, info);
ParseDataGroupList(content, context, sandbox, bundleNamePath);
ret = CreateSharedStamp(info, context);
if (ret != 0) {
APPSPAWN_LOGE("mkdir lockSbxPathStamp failed, ret: %{public}d", ret);
}
return ret;
}
MODULE_CONSTRUCTOR(void)
{
#ifdef APPSPAWN_SANDBOX_NEW
(void)AddServerStageHook(STAGE_SERVER_LOCK, HOOK_PRIO_COMMON, UpdateDataGroupDirs);
#endif
}