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 <dirent.h>
17#include <fcntl.h>
18#include <sched.h>
19#include <signal.h>
20#include <stdio.h>
21#include <stdlib.h>
22#include <unistd.h>
23
24#include "appspawn_hook.h"
25#include "appspawn_manager.h"
26#include "appspawn_utils.h"
27#include "securec.h"
28#ifdef WITH_SELINUX
29#include "selinux/selinux.h"
30#endif
31
32#define PID_NS_INIT_UID 100000  // reserved for pid_ns_init process, avoid app, render proc, etc.
33#define PID_NS_INIT_GID 100000
34
35typedef struct TagAppSpawnNamespace {
36    AppSpawnExtData extData;
37    int nsSelfPidFd;  // ns pid fd of appspawn
38    int nsInitPidFd;  // ns pid fd of pid_ns_init
39} AppSpawnNamespace;
40
41APPSPAWN_STATIC pid_t GetPidByName(const char *name);
42static int AppSpawnExtDataCompareDataId(ListNode *node, void *data)
43{
44    AppSpawnExtData *extData = (AppSpawnExtData *)ListEntry(node, AppSpawnExtData, node);
45    return extData->dataId - *(uint32_t *)data;
46}
47
48APPSPAWN_STATIC AppSpawnNamespace *GetAppSpawnNamespace(const AppSpawnMgr *content)
49{
50    APPSPAWN_CHECK_ONLY_EXPER(content != NULL, return NULL);
51    uint32_t dataId = EXT_DATA_NAMESPACE;
52    ListNode *node = OH_ListFind(&content->extData, (void *)&dataId, AppSpawnExtDataCompareDataId);
53    if (node == NULL) {
54        return NULL;
55    }
56    return (AppSpawnNamespace *)ListEntry(node, AppSpawnNamespace, extData);
57}
58
59APPSPAWN_STATIC void DeleteAppSpawnNamespace(AppSpawnNamespace *namespace)
60{
61    APPSPAWN_CHECK_ONLY_EXPER(namespace != NULL, return);
62    APPSPAWN_LOGV("DeleteAppSpawnNamespace");
63    OH_ListRemove(&namespace->extData.node);
64    OH_ListInit(&namespace->extData.node);
65
66    if (namespace->nsInitPidFd > 0) {
67        close(namespace->nsInitPidFd);
68        namespace->nsInitPidFd = -1;
69    }
70    if (namespace->nsSelfPidFd > 0) {
71        close(namespace->nsSelfPidFd);
72        namespace->nsSelfPidFd = -1;
73    }
74    free(namespace);
75}
76
77APPSPAWN_STATIC void FreeAppSpawnNamespace(struct TagAppSpawnExtData *data)
78{
79    AppSpawnNamespace *namespace = ListEntry(data, AppSpawnNamespace, extData);
80    APPSPAWN_CHECK_ONLY_EXPER(namespace != NULL, return);
81    DeleteAppSpawnNamespace(namespace);
82}
83
84APPSPAWN_STATIC AppSpawnNamespace *CreateAppSpawnNamespace(void)
85{
86    APPSPAWN_LOGV("CreateAppSpawnNamespace");
87    AppSpawnNamespace *namespace = (AppSpawnNamespace *)calloc(1, sizeof(AppSpawnNamespace));
88    APPSPAWN_CHECK(namespace != NULL, return NULL, "Failed to create sandbox");
89    namespace->nsInitPidFd = -1;
90    namespace->nsSelfPidFd = -1;
91    // ext data init
92    OH_ListInit(&namespace->extData.node);
93    namespace->extData.dataId = EXT_DATA_NAMESPACE;
94    namespace->extData.freeNode = FreeAppSpawnNamespace;
95    namespace->extData.dumpNode = NULL;
96    return namespace;
97}
98
99APPSPAWN_STATIC pid_t GetPidByName(const char *name)
100{
101    int pid = -1;  // initial pid set to -1
102    DIR *dir = opendir("/proc");
103    if (dir == NULL) {
104        return -1;
105    }
106
107    struct dirent *entry;
108    while ((entry = readdir(dir)) != NULL) {
109        if (entry->d_type != DT_DIR) {
110            continue;
111        }
112        long pidNum = strtol(entry->d_name, NULL, 10);  // pid will not exceed a 10-digit decimal number
113        if (pidNum <= 0) {
114            continue;
115        }
116
117        char path[32];  // path that contains the process name
118        if (snprintf_s(path, sizeof(path), sizeof(path) - 1, "/proc/%s/comm", entry->d_name) < 0) {
119            continue;
120        }
121        FILE *file = fopen(path, "r");
122        if (file == NULL) {
123            continue;
124        }
125        char buffer[32];  // read the process name
126        if (fgets(buffer, sizeof(buffer), file) == NULL) {
127            (void)fclose(file);
128            continue;
129        }
130        buffer[strcspn(buffer, "\n")] = 0;
131        if (strcmp(buffer, name) != 0) {
132            (void)fclose(file);
133            continue;
134        }
135
136        APPSPAWN_LOGI("get pid of %{public}s success", name);
137        pid = (int)pidNum;
138        (void)fclose(file);
139        break;
140    }
141
142    closedir(dir);
143    return pid;
144}
145
146APPSPAWN_STATIC int NsInitFunc()
147{
148    setuid(PID_NS_INIT_UID);
149    setgid(PID_NS_INIT_GID);
150#ifdef WITH_SELINUX
151    setcon("u:r:pid_ns_init:s0");
152#endif
153    char *argv[] = {"/system/bin/pid_ns_init", NULL};
154    execve("/system/bin/pid_ns_init", argv, NULL);
155#ifndef APPSPAWN_TEST
156    _exit(0);
157#endif
158    return 0;
159}
160
161APPSPAWN_STATIC int GetNsPidFd(pid_t pid)
162{
163    char nsPath[256];  // filepath of ns pid
164    int ret = snprintf_s(nsPath, sizeof(nsPath), sizeof(nsPath) - 1, "/proc/%d/ns/pid", pid);
165    if (ret < 0) {
166        APPSPAWN_LOGE("SetPidNamespace failed, snprintf_s error:%{public}s", strerror(errno));
167        return -1;
168    }
169    int nsFd = open(nsPath, O_RDONLY);
170    if (nsFd < 0) {
171        APPSPAWN_LOGE("open ns pid:%{public}d failed, err:%{public}s", pid, strerror(errno));
172        return -1;
173    }
174    return nsFd;
175}
176
177APPSPAWN_STATIC int PreLoadEnablePidNs(AppSpawnMgr *content)
178{
179    APPSPAWN_LOGI("Enable pid namespace flags: 0x%{public}x", content->content.sandboxNsFlags);
180    if (IsColdRunMode(content)) {
181        return 0;
182    }
183    if (IsNWebSpawnMode(content)) {  // only for appspawn
184        return 0;
185    }
186    if (!(content->content.sandboxNsFlags & CLONE_NEWPID)) {
187        return 0;
188    }
189    AppSpawnNamespace *namespace = CreateAppSpawnNamespace();
190    APPSPAWN_CHECK(namespace != NULL, return -1, "Failed to create namespace");
191
192    int ret = -1;
193    // check if process pid_ns_init exists, this is the init process for pid namespace
194    pid_t pid = GetPidByName("pid_ns_init");
195    if (pid == -1) {
196        APPSPAWN_LOGI("Start Create pid_ns_init %{public}d", pid);
197        pid = clone(NsInitFunc, NULL, CLONE_NEWPID, NULL);
198        if (pid < 0) {
199            APPSPAWN_LOGE("clone pid ns init failed");
200            DeleteAppSpawnNamespace(namespace);
201            return ret;
202        }
203    } else {
204        APPSPAWN_LOGI("pid_ns_init exists, no need to create");
205    }
206
207    namespace->nsSelfPidFd = GetNsPidFd(getpid());
208    if (namespace->nsSelfPidFd < 0) {
209        APPSPAWN_LOGE("open ns pid of appspawn fail");
210        DeleteAppSpawnNamespace(namespace);
211        return ret;
212    }
213
214    namespace->nsInitPidFd = GetNsPidFd(pid);
215    if (namespace->nsInitPidFd < 0) {
216        APPSPAWN_LOGE("open ns pid of pid_ns_init fail");
217        DeleteAppSpawnNamespace(namespace);
218        return ret;
219    }
220    OH_ListAddTail(&content->extData, &namespace->extData.node);
221    APPSPAWN_LOGI("Enable pid namespace success.");
222    return 0;
223}
224
225// after calling setns, new process will be in the same pid namespace of the input pid
226static int SetPidNamespace(int nsPidFd, int nsType)
227{
228    APPSPAWN_LOGI("SetPidNamespace 0x%{public}x", nsType);
229#ifndef APPSPAWN_TEST
230    if (setns(nsPidFd, nsType) < 0) {
231        APPSPAWN_LOGE("set pid namespace nsType:%{public}d failed", nsType);
232        return -1;
233    }
234#endif
235    return 0;
236}
237
238APPSPAWN_STATIC int PreForkSetPidNamespace(AppSpawnMgr *content, AppSpawningCtx *property)
239{
240    AppSpawnNamespace *namespace = GetAppSpawnNamespace(content);
241    if (namespace == NULL) {
242        return 0;
243    }
244    if (content->content.sandboxNsFlags & CLONE_NEWPID) {
245        SetPidNamespace(namespace->nsInitPidFd, CLONE_NEWPID);  // pid_ns_init is the init process
246    }
247    return 0;
248}
249
250APPSPAWN_STATIC int PostForkSetPidNamespace(AppSpawnMgr *content, AppSpawningCtx *property)
251{
252    AppSpawnNamespace *namespace = GetAppSpawnNamespace(content);
253    if (namespace == NULL) {
254        return 0;
255    }
256    if (content->content.sandboxNsFlags & CLONE_NEWPID) {
257        SetPidNamespace(namespace->nsSelfPidFd, 0);  // go back to original pid namespace
258    }
259
260    return 0;
261}
262
263MODULE_CONSTRUCTOR(void)
264{
265    AddPreloadHook(HOOK_PRIO_LOWEST, PreLoadEnablePidNs);
266    AddAppSpawnHook(STAGE_PARENT_PRE_FORK, HOOK_PRIO_LOWEST, PreForkSetPidNamespace);
267    AddAppSpawnHook(STAGE_PARENT_POST_FORK, HOOK_PRIO_HIGHEST, PostForkSetPidNamespace);
268}
269