/* * 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 #include #include #include #include #include #include #include "appspawn_hook.h" #include "appspawn_manager.h" #include "appspawn_utils.h" #include "securec.h" #ifdef WITH_SELINUX #include "selinux/selinux.h" #endif #define PID_NS_INIT_UID 100000 // reserved for pid_ns_init process, avoid app, render proc, etc. #define PID_NS_INIT_GID 100000 typedef struct { AppSpawnExtData extData; int nsSelfPidFd; // ns pid fd of appspawn int nsInitPidFd; // ns pid fd of pid_ns_init } AppSpawnNamespace; APPSPAWN_STATIC pid_t GetPidByName(const char *name); static int AppSpawnExtDataCompareDataId(ListNode *node, void *data) { AppSpawnExtData *extData = (AppSpawnExtData *)ListEntry(node, AppSpawnExtData, node); return extData->dataId - *(uint32_t *)data; } APPSPAWN_STATIC AppSpawnNamespace *GetAppSpawnNamespace(const AppSpawnMgr *content) { APPSPAWN_CHECK_ONLY_EXPER(content != NULL, return NULL); uint32_t dataId = EXT_DATA_NAMESPACE; ListNode *node = OH_ListFind(&content->extData, (void *)&dataId, AppSpawnExtDataCompareDataId); if (node == NULL) { return NULL; } return (AppSpawnNamespace *)ListEntry(node, AppSpawnNamespace, extData); } static void DeleteAppSpawnNamespace(AppSpawnNamespace *namespace) { APPSPAWN_CHECK_ONLY_EXPER(namespace != NULL, return); APPSPAWN_LOGV("DeleteAppSpawnNamespace"); OH_ListRemove(&namespace->extData.node); OH_ListInit(&namespace->extData.node); if (namespace->nsInitPidFd > 0) { close(namespace->nsInitPidFd); namespace->nsInitPidFd = -1; } if (namespace->nsSelfPidFd > 0) { close(namespace->nsSelfPidFd); namespace->nsSelfPidFd = -1; } free(namespace); } static void FreeAppSpawnNamespace(struct TagAppSpawnExtData *data) { AppSpawnNamespace *namespace = ListEntry(data, AppSpawnNamespace, extData); APPSPAWN_CHECK_ONLY_EXPER(namespace != NULL, return); DeleteAppSpawnNamespace(namespace); } static AppSpawnNamespace *CreateAppSpawnNamespace(void) { APPSPAWN_LOGV("CreateAppSpawnNamespace"); AppSpawnNamespace *namespace = (AppSpawnNamespace *)calloc(1, sizeof(AppSpawnNamespace)); APPSPAWN_CHECK(namespace != NULL, return NULL, "Failed to create sandbox"); namespace->nsInitPidFd = -1; namespace->nsSelfPidFd = -1; // ext data init OH_ListInit(&namespace->extData.node); namespace->extData.dataId = EXT_DATA_NAMESPACE; namespace->extData.freeNode = FreeAppSpawnNamespace; namespace->extData.dumpNode = NULL; return namespace; } APPSPAWN_STATIC pid_t GetPidByName(const char *name) { int pid = -1; // initial pid set to -1 DIR *dir = opendir("/proc"); if (dir == NULL) { return -1; } struct dirent *entry; while ((entry = readdir(dir)) != NULL) { if (entry->d_type != DT_DIR) { continue; } long pidNum = strtol(entry->d_name, NULL, 10); // pid will not exceed a 10-digit decimal number if (pidNum <= 0) { continue; } char path[32]; // path that contains the process name if (snprintf_s(path, sizeof(path), sizeof(path) - 1, "/proc/%s/comm", entry->d_name) < 0) { continue; } FILE *file = fopen(path, "r"); if (file == NULL) { continue; } char buffer[32]; // read the process name if (fgets(buffer, sizeof(buffer), file) == NULL) { (void)fclose(file); continue; } buffer[strcspn(buffer, "\n")] = 0; if (strcmp(buffer, name) != 0) { (void)fclose(file); continue; } APPSPAWN_LOGI("get pid of %{public}s success", name); pid = (int)pidNum; (void)fclose(file); break; } closedir(dir); return pid; } APPSPAWN_STATIC int NsInitFunc() { setuid(PID_NS_INIT_UID); setgid(PID_NS_INIT_GID); #ifdef WITH_SELINUX setcon("u:r:pid_ns_init:s0"); #endif char *argv[] = {"/system/bin/pid_ns_init", NULL}; execve("/system/bin/pid_ns_init", argv, NULL); #ifndef APPSPAWN_TEST _exit(0); #endif return 0; } APPSPAWN_STATIC int GetNsPidFd(pid_t pid) { char nsPath[256]; // filepath of ns pid int ret = snprintf_s(nsPath, sizeof(nsPath), sizeof(nsPath) - 1, "/proc/%d/ns/pid", pid); if (ret < 0) { APPSPAWN_LOGE("SetPidNamespace failed, snprintf_s error:%{public}s", strerror(errno)); return -1; } int nsFd = open(nsPath, O_RDONLY); if (nsFd < 0) { APPSPAWN_LOGE("open ns pid:%{public}d failed, err:%{public}s", pid, strerror(errno)); return -1; } return nsFd; } APPSPAWN_STATIC int PreLoadEnablePidNs(AppSpawnMgr *content) { APPSPAWN_LOGI("Enable pid namespace flags: 0x%{public}x", content->content.sandboxNsFlags); if (IsColdRunMode(content)) { return 0; } if (IsNWebSpawnMode(content)) { // only for appspawn return 0; } if (!(content->content.sandboxNsFlags & CLONE_NEWPID)) { return 0; } AppSpawnNamespace *namespace = CreateAppSpawnNamespace(); APPSPAWN_CHECK(namespace != NULL, return -1, "Failed to create namespace"); int ret = -1; // check if process pid_ns_init exists, this is the init process for pid namespace pid_t pid = GetPidByName("pid_ns_init"); if (pid == -1) { APPSPAWN_LOGI("Start Create pid_ns_init %{public}d", pid); pid = clone(NsInitFunc, NULL, CLONE_NEWPID, NULL); if (pid < 0) { APPSPAWN_LOGE("clone pid ns init failed"); DeleteAppSpawnNamespace(namespace); return ret; } } else { APPSPAWN_LOGI("pid_ns_init exists, no need to create"); } namespace->nsSelfPidFd = GetNsPidFd(getpid()); if (namespace->nsSelfPidFd < 0) { APPSPAWN_LOGE("open ns pid of appspawn fail"); DeleteAppSpawnNamespace(namespace); return ret; } namespace->nsInitPidFd = GetNsPidFd(pid); if (namespace->nsInitPidFd < 0) { APPSPAWN_LOGE("open ns pid of pid_ns_init fail"); DeleteAppSpawnNamespace(namespace); return ret; } OH_ListAddTail(&content->extData, &namespace->extData.node); APPSPAWN_LOGI("Enable pid namespace success."); return 0; } // after calling setns, new process will be in the same pid namespace of the input pid static int SetPidNamespace(int nsPidFd, int nsType) { APPSPAWN_LOGI("SetPidNamespace 0x%{public}x", nsType); #ifndef APPSPAWN_TEST if (setns(nsPidFd, nsType) < 0) { APPSPAWN_LOGE("set pid namespace nsType:%{public}d failed", nsType); return -1; } #endif return 0; } static int PreForkSetPidNamespace(AppSpawnMgr *content, AppSpawningCtx *property) { AppSpawnNamespace *namespace = GetAppSpawnNamespace(content); if (namespace == NULL) { return 0; } if (content->content.sandboxNsFlags & CLONE_NEWPID) { SetPidNamespace(namespace->nsInitPidFd, CLONE_NEWPID); // pid_ns_init is the init process } return 0; } static int PostForkSetPidNamespace(AppSpawnMgr *content, AppSpawningCtx *property) { AppSpawnNamespace *namespace = GetAppSpawnNamespace(content); if (namespace == NULL) { return 0; } if (content->content.sandboxNsFlags & CLONE_NEWPID) { SetPidNamespace(namespace->nsSelfPidFd, 0); // go back to original pid namespace } return 0; } MODULE_CONSTRUCTOR(void) { AddPreloadHook(HOOK_PRIO_LOWEST, PreLoadEnablePidNs); AddAppSpawnHook(STAGE_PARENT_PRE_FORK, HOOK_PRIO_LOWEST, PreForkSetPidNamespace); AddAppSpawnHook(STAGE_PARENT_POST_FORK, HOOK_PRIO_HIGHEST, PostForkSetPidNamespace); }