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