1/*
2 * Copyright (c) 2024 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 *     http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16#include <errno.h>
17#include <inttypes.h>
18#include <limits.h>
19#include <fcntl.h>
20#include <signal.h>
21#include <stdio.h>
22#include <stdlib.h>
23#include <string.h>
24#include <unistd.h>
25#include <sys/stat.h>
26#include <sys/types.h>
27
28#include "appspawn_adapter.h"
29#include "appspawn_hook.h"
30#include "appspawn_manager.h"
31#include "appspawn_utils.h"
32#include "securec.h"
33#include "cJSON.h"
34#include <sys/ioctl.h>
35
36APPSPAWN_STATIC int GetCgroupPath(const AppSpawnedProcessInfo *appInfo, char *buffer, uint32_t buffLen)
37{
38    const int userId = appInfo->uid / UID_BASE;
39#ifdef APPSPAWN_TEST
40    int ret = snprintf_s(buffer, buffLen, buffLen - 1, APPSPAWN_BASE_DIR "/dev/pids/testpids/%d/%s/%d/",
41        userId, appInfo->name, appInfo->pid);
42#else
43    int ret = snprintf_s(buffer, buffLen, buffLen - 1, "/dev/pids/%d/%s/app_%d/", userId, appInfo->name, appInfo->pid);
44#endif
45    APPSPAWN_CHECK(ret > 0, return ret, "Failed to snprintf_s errno: %{public}d", errno);
46    APPSPAWN_LOGV("Cgroup path %{public}s ", buffer);
47    return 0;
48}
49
50
51APPSPAWN_STATIC int WriteToFile(const char *path, int truncated, pid_t pids[], uint32_t count)
52{
53    char pidName[32] = {0}; // 32 max len
54    int fd = open(path, O_RDWR | (truncated ? O_TRUNC : O_APPEND));
55    APPSPAWN_CHECK(fd >= 0, return APPSPAWN_SYSTEM_ERROR,
56        "Failed to open file errno: %{public}d path: %{public}s", errno, path);
57    int ret = 0;
58    for (uint32_t i = 0; i < count; i++) {
59        APPSPAWN_LOGV(" WriteToFile pid %{public}d ", pids[i]);
60        ret = snprintf_s(pidName, sizeof(pidName), sizeof(pidName) - 1, "%d\n", pids[i]);
61        APPSPAWN_CHECK(ret > 0, break, "Failed to snprintf_s errno: %{public}d", errno);
62        ret = write(fd, pidName, strlen(pidName));
63        APPSPAWN_CHECK(ret > 0, break,
64            "Failed to write file errno: %{public}d path: %{public}s %{public}s", errno, path, pidName);
65        ret = 0;
66    }
67    close(fd);
68    return ret;
69}
70
71#define APP_PIDS_MAX_ENCAPS "encaps"
72#define APP_PIDS_MAX_OHOS_ENCAPS_COUNT_KEY "ohos.encaps.count"
73#define APP_PIDS_MAX_OHOS_ENCAPS_FORK_KEV "ohos.encaps.fork"
74#define APP_PIDS_MAX_OHOS_ENCAPS_FORK_DFX_KEY "ohos.encaps.fork.dfx"
75#define APP_PIDS_MAX_OHOS_ENCAPS_FORK_WEBDFX_KEY "ohos.encaps.fork.webdfx"
76#define APP_PIDS_MAX_OHOS_ENCAPS_COUNT_VALUE 3
77#define APP_PIDS_MAX_OHOS_ENCAPS_FORK_DFX_AND_WEBDFX_VALUE 5
78#define APP_ENABLE_ENCAPS 1
79#define ASSICN_ENCAPS_CMD _lOW('E', 0x1A, char *)
80static int WritePidMax(const char *path, uint32_t max)
81{
82#if APP_ENABLE_ENCAPS == 0
83    cJSON *encaps = cJSON_CreateObject();
84    cJSON_AddNumberToObject(encaps, APP_PIDS_MAX_OHOS_ENCAPS_COUNT_KEY,
85        APP_PIDS_MAX_OHOS_ENCAPS_COUNT_VALUE);
86    cJSON_AddNumberToObject(encaps, APP_PIDS_MAX_OHOS_ENCAPS_FORK_KEV, max);
87    cJSON_AddNumberToObject(encaps, APP_PIDS_MAX_OHOS_ENCAPS_FORK_DFX_KEY,
88        APP_PIDS_MAX_OHOS_ENCAPS_FORK_DFX_AND_WEBDFX_VALUE);
89    cJSON_AddNumberToObject(encaps, APP_PIDS_MAX_OHOS_ENCAPS_FORK_WEBDFX_KEY,
90        APP_PIDS_MAX_OHOS_ENCAPS_FORK_DFX_AND_WEBDFX_VALUE);
91    cJSON *addGinseng = cJSON_CreateObject();
92    cJSON_AddItemToObject(addGinseng, APP_PIDS_MAX_ENCAPS, encaps);
93    char *maxPid = cJSON_PrintUnformatted(addGinseng);
94    int ret = 0;
95    int fd = 0;
96    fd = open("dev/encaps", O_RDWR);
97    ret = ioctl(fd, ASSICN_ENCAPS_CMD, maxPid);
98    close(fd);
99    free(maxPid);
100    cJSON_Delete(addGinseng);
101    cJSON_Delete(encaps);
102    return ret;
103#else
104    char value[32] = {0}; // 32 max len
105    int fd = open(path, O_RDWR | O_TRUNC);
106    APPSPAWN_CHECK(fd >= 0, return -1,
107        "Failed to open file errno: %{public}d path: %{public}s", errno, path);
108    int ret = 0;
109    do {
110        ret = snprintf_s(value, sizeof(value), sizeof(value) - 1, "%u\n", max);
111        APPSPAWN_CHECK(ret > 0, break, "Failed to snprintf_s errno: %{public}d", errno);
112        ret = write(fd, value, strlen(value));
113        APPSPAWN_CHECK(ret > 0, break,
114            "Failed to write file errno: %{public}d path: %{public}s %{public}s %{public}d", errno, path, value, ret);
115        ret = 0;
116    } while (0);
117    close(fd);
118    return ret;
119#endif
120}
121
122static void SetForkDenied(const AppSpawnedProcessInfo *appInfo)
123{
124    char pathForkDenied[PATH_MAX] = {};
125    int ret = GetCgroupPath(appInfo, pathForkDenied, sizeof(pathForkDenied));
126    APPSPAWN_CHECK(ret == 0, return, "Failed to get cgroup path errno: %{public}d", errno);
127    ret = strcat_s(pathForkDenied, sizeof(pathForkDenied), "pids.fork_denied");
128    APPSPAWN_CHECK(ret == 0, return, "Failed to strcat_s fork_denied path errno: %{public}d", errno);
129    int fd = open(pathForkDenied, O_RDWR);
130    if (fd < 0) {
131        APPSPAWN_LOGW("SetForkDenied %{public}d open failed ", appInfo->pid);
132        return;
133    }
134    do {
135        ret = write(fd, "1", 1);
136        APPSPAWN_CHECK(ret >= 0, break,
137        "Failed to write file errno: %{public}d path: %{public}s %{public}d", errno, pathForkDenied, ret);
138        fsync(fd);
139        APPSPAWN_LOGI("SetForkDenied success, cgroup's owner:%{public}d", appInfo->pid);
140    } while (0);
141    close(fd);
142}
143
144static void KillProcessesByCGroup(const char *path, AppSpawnMgr *content, const AppSpawnedProcessInfo *appInfo)
145{
146    SetForkDenied(appInfo);
147    FILE *file = fopen(path, "r");
148    APPSPAWN_CHECK(file != NULL, return, "Open file fail %{public}s errno: %{public}d", path, errno);
149    pid_t pid = 0;
150    while (fscanf_s(file, "%d\n", &pid) == 1 && pid > 0) {
151        APPSPAWN_LOGV(" KillProcessesByCGroup pid %{public}d ", pid);
152        if (pid == appInfo->pid) {
153            continue;
154        }
155        AppSpawnedProcessInfo *tmp = GetSpawnedProcess(pid);
156        if (tmp != NULL) {
157            APPSPAWN_LOGI("Got app %{public}s in same group for pid %{public}d.", tmp->name, pid);
158            continue;
159        }
160        APPSPAWN_LOGI("Kill app pid %{public}d now ...", pid);
161#ifndef APPSPAWN_TEST
162        if (kill(pid, SIGKILL) != 0) {
163            APPSPAWN_LOGE("unable to kill process, pid: %{public}d ret %{public}d", pid, errno);
164        }
165#endif
166    }
167    (void)fclose(file);
168}
169
170APPSPAWN_STATIC int ProcessMgrRemoveApp(const AppSpawnMgr *content, const AppSpawnedProcessInfo *appInfo)
171{
172    APPSPAWN_CHECK_ONLY_EXPER(content != NULL, return -1);
173    APPSPAWN_CHECK_ONLY_EXPER(appInfo != NULL, return -1);
174    if (IsNWebSpawnMode(content) || strcmp(appInfo->name, NWEBSPAWN_SERVER_NAME) == 0) {
175        return 0;
176    }
177    char cgroupPath[PATH_MAX] = {};
178    APPSPAWN_LOGV("ProcessMgrRemoveApp %{public}d %{public}d to cgroup ", appInfo->pid, appInfo->uid);
179    int ret = GetCgroupPath(appInfo, cgroupPath, sizeof(cgroupPath));
180    APPSPAWN_CHECK(ret == 0, return -1, "Failed to get real path errno: %{public}d", errno);
181    char procPath[PATH_MAX] = {};
182    ret = memcpy_s(procPath, sizeof(procPath), cgroupPath, sizeof(cgroupPath));
183    if (ret != 0) {
184        return APPSPAWN_ERROR_UTILS_MEM_FAIL;
185    }
186    ret = strcat_s(procPath, sizeof(procPath), "cgroup.procs");
187    APPSPAWN_CHECK(ret == 0, return ret, "Failed to strcat_s errno: %{public}d", errno);
188    KillProcessesByCGroup(procPath, (AppSpawnMgr *)content, appInfo);
189    ret = rmdir(cgroupPath);
190    if (ret != 0) {
191        return APPSPAWN_ERROR_FILE_RMDIR_FAIL;
192    }
193    return ret;
194}
195
196APPSPAWN_STATIC int ProcessMgrAddApp(const AppSpawnMgr *content, const AppSpawnedProcessInfo *appInfo)
197{
198    APPSPAWN_CHECK_ONLY_EXPER(content != NULL, return -1);
199    APPSPAWN_CHECK_ONLY_EXPER(appInfo != NULL, return -1);
200    if (IsNWebSpawnMode(content)) {
201        return 0;
202    }
203    char path[PATH_MAX] = {};
204    APPSPAWN_LOGV("ProcessMgrAddApp %{public}d %{public}d to cgroup ", appInfo->pid, appInfo->uid);
205    int ret = GetCgroupPath(appInfo, path, sizeof(path));
206    APPSPAWN_CHECK(ret == 0, return -1, "Failed to get real path errno: %{public}d", errno);
207    (void)CreateSandboxDir(path, 0750);  // 0750 default mode
208    uint32_t pathLen = strlen(path);
209    ret = strcat_s(path, sizeof(path), "cgroup.procs");
210    APPSPAWN_CHECK(ret == 0, return ret, "Failed to strcat_s errno: %{public}d", errno);
211    ret = WriteToFile(path, 0, (pid_t *)&appInfo->pid, 1);
212    APPSPAWN_CHECK(ret == 0, return ret, "write pid to cgroup.procs fail %{public}s", path);
213    if (appInfo->max != 0) {
214        path[pathLen] = '\0';
215        ret = strcat_s(path, sizeof(path), "pids.max");
216        APPSPAWN_CHECK(ret == 0, return ret, "Failed to strcat_s errno: %{public}d", errno);
217        ret = WritePidMax(path, appInfo->max);
218        APPSPAWN_CHECK(ret == 0, return ret, "write max to pids.max fail %{public}s", path);
219    }
220    APPSPAWN_LOGV("Add app %{public}d to cgroup %{public}s success", appInfo->pid, path);
221    return 0;
222}
223
224MODULE_CONSTRUCTOR(void)
225{
226    AddProcessMgrHook(STAGE_SERVER_APP_ADD, 0, ProcessMgrAddApp);
227    AddProcessMgrHook(STAGE_SERVER_APP_DIED, 0, ProcessMgrRemoveApp);
228}
229