1 /*
2  * Copyright (C) 2020 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.server.am;
18 
19 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
20 
21 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
22 import static com.android.dx.mockito.inline.extended.ExtendedMockito.eq;
23 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
24 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy;
25 
26 import static org.junit.Assert.assertEquals;
27 import static org.junit.Assert.assertTrue;
28 
29 import android.content.ComponentName;
30 import android.content.Context;
31 import android.content.pm.ApplicationInfo;
32 import android.content.pm.PackageManagerInternal;
33 import android.os.Handler;
34 import android.os.HandlerThread;
35 import android.os.Process;
36 import android.platform.test.annotations.Presubmit;
37 import android.util.ArrayMap;
38 import android.util.ArraySet;
39 
40 import com.android.dx.mockito.inline.extended.StaticMockitoSession;
41 import com.android.server.LocalServices;
42 import com.android.server.ServiceThread;
43 import com.android.server.appop.AppOpsService;
44 import com.android.server.wm.ActivityTaskManagerService;
45 
46 import org.junit.After;
47 import org.junit.Before;
48 import org.junit.BeforeClass;
49 import org.junit.Rule;
50 import org.junit.Test;
51 import org.junit.rules.TestRule;
52 import org.junit.runner.Description;
53 import org.junit.runners.model.Statement;
54 import org.mockito.Mock;
55 import org.mockito.MockitoAnnotations;
56 import org.mockito.quality.Strictness;
57 
58 import java.io.ByteArrayInputStream;
59 import java.io.File;
60 import java.io.FileNotFoundException;
61 import java.io.IOException;
62 import java.io.InputStream;
63 
64 @Presubmit
65 public class AppChildProcessTest {
66     private static final String TAG = AppChildProcessTest.class.getSimpleName();
67 
68     @Rule public ServiceThreadRule mServiceThreadRule = new ServiceThreadRule();
69     @Mock private AppOpsService mAppOpsService;
70     @Mock private PackageManagerInternal mPackageManagerInt;
71     private StaticMockitoSession mMockitoSession;
72 
73     private Context mContext = getInstrumentation().getTargetContext();
74     private TestInjector mInjector;
75     private PhantomTestInjector mPhantomInjector;
76     private ActivityManagerService mAms;
77     private ProcessList mProcessList;
78     private PhantomProcessList mPhantomProcessList;
79     private Handler mHandler;
80     private HandlerThread mHandlerThread;
81 
82     @BeforeClass
setUpOnce()83     public static void setUpOnce() {
84         System.setProperty("dexmaker.share_classloader", "true");
85     }
86 
87     @Before
setUp()88     public void setUp() {
89         MockitoAnnotations.initMocks(this);
90         mMockitoSession = mockitoSession()
91             .spyStatic(Process.class)
92             .strictness(Strictness.LENIENT)
93             .startMocking();
94 
95         mHandlerThread = new HandlerThread(TAG);
96         mHandlerThread.start();
97         mHandler = new Handler(mHandlerThread.getLooper());
98         final ProcessList pList = new ProcessList();
99         mProcessList = spy(pList);
100 
101         mInjector = new TestInjector(mContext);
102         mPhantomInjector = new PhantomTestInjector();
103         mAms = new ActivityManagerService(mInjector, mServiceThreadRule.getThread());
104         mAms.mActivityTaskManager = new ActivityTaskManagerService(mContext);
105         mAms.mActivityTaskManager.initialize(null, null, mContext.getMainLooper());
106         mAms.mAtmInternal = spy(mAms.mActivityTaskManager.getAtmInternal());
107         mAms.mPackageManagerInt = mPackageManagerInt;
108         pList.mService = mAms;
109         mPhantomProcessList = mAms.mPhantomProcessList;
110         mPhantomProcessList.mInjector = mPhantomInjector;
111         doReturn(new ComponentName("", "")).when(mPackageManagerInt).getSystemUiServiceComponent();
112         doReturn(false).when(() -> Process.supportsPidFd());
113         // Remove stale instance of PackageManagerInternal if there is any
114         LocalServices.removeServiceForTest(PackageManagerInternal.class);
115         LocalServices.addService(PackageManagerInternal.class, mPackageManagerInt);
116     }
117 
118     @After
tearDown()119     public void tearDown() {
120         LocalServices.removeServiceForTest(PackageManagerInternal.class);
121         if (mMockitoSession != null) {
122             mMockitoSession.finishMocking();
123         }
124         if (mHandlerThread != null) {
125             mHandlerThread.quit();
126         }
127     }
128 
129     @Test
testManageAppChildProcesses()130     public void testManageAppChildProcesses() throws Exception {
131         final int initPid = 1;
132         final int rootUid = 0;
133         final int zygote64Pid = 100;
134         final int zygote32Pid = 101;
135         final int app1Pid = 200;
136         final int app2Pid = 201;
137         final int app1Uid = 10000;
138         final int app2Uid = 10001;
139         final int child1Pid = 300;
140         final int child2Pid = 301;
141         final int nativePid = 400;
142         final String zygote64ProcessName = "zygote64";
143         final String zygote32ProcessName = "zygote32";
144         final String app1ProcessName = "test1";
145         final String app2ProcessName = "test2";
146         final String child1ProcessName = "test1_child1";
147         final String child2ProcessName = "test1_child1_child2";
148         final String nativeProcessName = "test_native";
149 
150         makeProcess(rootUid, zygote64Pid, zygote64ProcessName);
151         makeParent(rootUid, zygote64Pid, initPid);
152         makeProcess(rootUid, zygote32Pid, zygote32ProcessName);
153         makeParent(rootUid, zygote32Pid, initPid);
154 
155         makeAppProcess(app1Pid, app1Uid, app1ProcessName, app1ProcessName);
156         makeAppProcess(app2Pid, app2Uid, app2ProcessName, app2ProcessName);
157 
158         mPhantomProcessList.lookForPhantomProcessesLocked();
159 
160         assertEquals(0, mPhantomProcessList.mPhantomProcesses.size());
161 
162         // Verify zygote itself isn't a phantom process
163         assertEquals(null, mPhantomProcessList.getOrCreatePhantomProcessIfNeededLocked(
164                 zygote64ProcessName, rootUid, zygote64Pid, false));
165         assertEquals(null, mPhantomProcessList.getOrCreatePhantomProcessIfNeededLocked(
166                 zygote32ProcessName, rootUid, zygote32Pid, false));
167         // Verify none of the app isn't a phantom process
168         assertEquals(null, mPhantomProcessList.getOrCreatePhantomProcessIfNeededLocked(
169                 app1ProcessName, app1Uid, app1Pid, false));
170         assertEquals(null, mPhantomProcessList.getOrCreatePhantomProcessIfNeededLocked(
171                 app2ProcessName, app2Uid, app2Pid, false));
172 
173         // "Fork" an app child process
174         makeProcess(app1Uid, child1Pid, child1ProcessName);
175         makeParent(app1Uid, child1Pid, app1Pid);
176         mPhantomProcessList.lookForPhantomProcessesLocked();
177 
178         PhantomProcessRecord pr = mPhantomProcessList
179                 .getOrCreatePhantomProcessIfNeededLocked(
180                         child1ProcessName, app1Uid, child1Pid, true);
181         assertTrue(pr != null);
182         assertEquals(1, mPhantomProcessList.mPhantomProcesses.size());
183         assertEquals(pr, mPhantomProcessList.mPhantomProcesses.valueAt(0));
184         verifyPhantomProcessRecord(pr, child1ProcessName, app1Uid, child1Pid);
185 
186         // Create another native process from init
187         makeProcess(rootUid, nativePid, nativeProcessName);
188         makeParent(rootUid, nativePid, initPid);
189         mPhantomProcessList.lookForPhantomProcessesLocked();
190 
191         assertEquals(null, mPhantomProcessList.getOrCreatePhantomProcessIfNeededLocked(
192                 nativeProcessName, rootUid, nativePid, false));
193         assertEquals(1, mPhantomProcessList.mPhantomProcesses.size());
194         assertEquals(pr, mPhantomProcessList.mPhantomProcesses.valueAt(0));
195 
196         // "Fork" another app child process
197         makeProcess(app1Uid, child2Pid, child2ProcessName);
198         makeParent(app1Uid, child2Pid, app1Pid);
199         mPhantomProcessList.lookForPhantomProcessesLocked();
200 
201         PhantomProcessRecord pr2 = mPhantomProcessList
202                 .getOrCreatePhantomProcessIfNeededLocked(
203                         child2ProcessName, app1Uid, child2Pid, false);
204         assertTrue(pr2 != null);
205         assertEquals(2, mPhantomProcessList.mPhantomProcesses.size());
206         verifyPhantomProcessRecord(pr2, child2ProcessName, app1Uid, child2Pid);
207 
208         ArraySet<PhantomProcessRecord> set = new ArraySet<>();
209         set.add(pr);
210         set.add(pr2);
211         for (int i = mPhantomProcessList.mPhantomProcesses.size() - 1; i >= 0; i--) {
212             set.remove(mPhantomProcessList.mPhantomProcesses.valueAt(i));
213         }
214         assertEquals(0, set.size());
215     }
216 
verifyPhantomProcessRecord(PhantomProcessRecord pr, String processName, int uid, int pid)217     private void verifyPhantomProcessRecord(PhantomProcessRecord pr,
218             String processName, int uid, int pid) {
219         assertEquals(processName, pr.mProcessName);
220         assertEquals(uid, pr.mUid);
221         assertEquals(pid, pr.mPid);
222     }
223 
makeProcess(int uid, int pid, String processName)224     private void makeProcess(int uid, int pid, String processName) {
225         doReturn(uid).when(() -> Process.getUidForPid(eq(pid)));
226         mPhantomInjector.mPidToName.put(pid, processName);
227     }
228 
makeAppProcess(int pid, int uid, String packageName, String processName)229     private void makeAppProcess(int pid, int uid, String packageName, String processName) {
230         makeProcess(uid, pid, processName);
231         ApplicationInfo ai = new ApplicationInfo();
232         ai.packageName = packageName;
233         ai.uid = uid;
234         ProcessRecord app = new ProcessRecord(mAms, ai, processName, uid);
235         app.setPid(pid);
236         mAms.mPidsSelfLocked.doAddInternal(app.getPid(), app);
237         mPhantomInjector.addToProcess(uid, pid, pid);
238     }
239 
makeParent(int uid, int pid, int ppid)240     private void makeParent(int uid, int pid, int ppid) {
241         mPhantomInjector.addToProcess(uid, ppid, pid);
242     }
243 
244     private class TestInjector extends ActivityManagerService.Injector {
TestInjector(Context context)245         TestInjector(Context context) {
246             super(context);
247         }
248 
249         @Override
getAppOpsService(File recentAccessesFile, File storageFile, Handler handler)250         public AppOpsService getAppOpsService(File recentAccessesFile, File storageFile,
251                 Handler handler) {
252             return mAppOpsService;
253         }
254 
255         @Override
getUiHandler(ActivityManagerService service)256         public Handler getUiHandler(ActivityManagerService service) {
257             return mHandler;
258         }
259 
260         @Override
getProcessList(ActivityManagerService service)261         public ProcessList getProcessList(ActivityManagerService service) {
262             return mProcessList;
263         }
264     }
265 
266     private class PhantomTestInjector extends PhantomProcessList.Injector {
267         ArrayMap<String, InputStream> mPathToInput = new ArrayMap<>();
268         ArrayMap<String, StringBuffer> mPathToData = new ArrayMap<>();
269         ArrayMap<InputStream, StringBuffer> mInputToData = new ArrayMap<>();
270         ArrayMap<Integer, String> mPidToName = new ArrayMap<>();
271 
272         @Override
openCgroupProcs(String path)273         InputStream openCgroupProcs(String path) throws FileNotFoundException, SecurityException {
274             InputStream input = mPathToInput.get(path);
275             if (input != null) {
276                 return input;
277             }
278             input = new ByteArrayInputStream(new byte[8]); // buf size doesn't matter here
279             mPathToInput.put(path, input);
280             StringBuffer sb = mPathToData.get(path);
281             if (sb == null) {
282                 sb = new StringBuffer();
283                 mPathToData.put(path, sb);
284             }
285             mInputToData.put(input, sb);
286             return input;
287         }
288 
289         @Override
readCgroupProcs(InputStream input, byte[] buf, int offset, int len)290         int readCgroupProcs(InputStream input, byte[] buf, int offset, int len) throws IOException {
291             StringBuffer sb = mInputToData.get(input);
292             if (sb == null) {
293                 return -1;
294             }
295             byte[] avail = sb.toString().getBytes();
296             System.arraycopy(avail, 0, buf, offset, Math.min(len, avail.length));
297             return Math.min(len, avail.length);
298         }
299 
300         @Override
getProcessName(final int pid)301         String getProcessName(final int pid) {
302             return mPidToName.get(pid);
303         }
304 
addToProcess(int uid, int pid, int newPid)305         void addToProcess(int uid, int pid, int newPid) {
306             final String path = mPhantomProcessList.getCgroupFilePath(uid, pid);
307             StringBuffer sb = mPathToData.get(path);
308             if (sb == null) {
309                 sb = new StringBuffer();
310                 mPathToData.put(path, sb);
311             }
312             sb.append(newPid).append('\n');
313         }
314     }
315 
316     static class ServiceThreadRule implements TestRule {
317         private ServiceThread mThread;
318 
getThread()319         ServiceThread getThread() {
320             return mThread;
321         }
322 
323         @Override
apply(Statement base, Description description)324         public Statement apply(Statement base, Description description) {
325             return new Statement() {
326                 @Override
327                 public void evaluate() throws Throwable {
328                     mThread = new ServiceThread("TestServiceThread",
329                             Process.THREAD_PRIORITY_DEFAULT, true /* allowIo */);
330                     mThread.start();
331                     try {
332                         base.evaluate();
333                     } finally {
334                         mThread.getThreadHandler().runWithScissors(mThread::quit, 0 /* timeout */);
335                     }
336                 }
337             };
338         }
339     }
340 
341     // TODO: [b/302724778] Remove manual JNI load
342     static {
343         System.loadLibrary("mockingservicestestjni");
344     }
345 }
346