1 /* 2 * Copyright (C) 2022 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.sdksandbox; 18 19 import static com.google.common.truth.Truth.assertThat; 20 21 import android.Manifest; 22 import android.app.sdksandbox.LoadSdkException; 23 import android.content.Context; 24 import android.content.pm.ApplicationInfo; 25 import android.content.pm.PackageManager; 26 import android.os.Bundle; 27 import android.os.Process; 28 import android.os.UserHandle; 29 30 import androidx.test.platform.app.InstrumentationRegistry; 31 32 import com.android.dx.mockito.inline.extended.ExtendedMockito; 33 import com.android.modules.utils.build.SdkLevel; 34 import com.android.sdksandbox.ISdkSandboxService; 35 import com.android.server.wm.ActivityInterceptorCallbackRegistry; 36 37 import org.junit.After; 38 import org.junit.Before; 39 import org.junit.Test; 40 import org.mockito.Mockito; 41 import org.mockito.MockitoSession; 42 import org.mockito.quality.Strictness; 43 44 import java.io.FileDescriptor; 45 46 public class SdkSandboxShellCommandUnitTest { 47 48 private static final String DEBUGGABLE_PACKAGE = "android.app.debuggable"; 49 private static final String NON_DEBUGGABLE_PACKAGE = "android.app.nondebuggable"; 50 private static final int UID = 10214; 51 private static final String INVALID_PACKAGE = "android.app.invalid"; 52 private Context mSpyContext; 53 private FakeSdkSandboxManagerService mService; 54 55 private final FileDescriptor mIn = FileDescriptor.in; 56 private final FileDescriptor mOut = FileDescriptor.out; 57 private final FileDescriptor mErr = FileDescriptor.err; 58 59 private PackageManager mPackageManager; 60 61 private MockitoSession mStaticMockSession; 62 63 @Before setup()64 public void setup() throws Exception { 65 if (SdkLevel.isAtLeastU()) { 66 mStaticMockSession = 67 ExtendedMockito.mockitoSession() 68 // TODO(b/267320397): Remove LENIENT to enable mock Exceptions. 69 .strictness(Strictness.LENIENT) 70 .mockStatic(ActivityInterceptorCallbackRegistry.class) 71 .startMocking(); 72 ActivityInterceptorCallbackRegistry registryMock = 73 Mockito.mock(ActivityInterceptorCallbackRegistry.class); 74 ExtendedMockito.doReturn(registryMock) 75 .when(ActivityInterceptorCallbackRegistry::getInstance); 76 } 77 78 mSpyContext = Mockito.spy(InstrumentationRegistry.getInstrumentation().getContext()); 79 80 InstrumentationRegistry.getInstrumentation() 81 .getUiAutomation() 82 .adoptShellPermissionIdentity( 83 Manifest.permission.READ_DEVICE_CONFIG, 84 // Required for Context#registerReceiverForAllUsers 85 Manifest.permission.INTERACT_ACROSS_USERS_FULL); 86 mService = Mockito.spy(new FakeSdkSandboxManagerService(mSpyContext)); 87 88 mPackageManager = Mockito.mock(PackageManager.class); 89 90 Mockito.when(mSpyContext.getPackageManager()).thenReturn(mPackageManager); 91 92 final ApplicationInfo debuggableInfo = Mockito.mock(ApplicationInfo.class); 93 debuggableInfo.flags |= ApplicationInfo.FLAG_DEBUGGABLE; 94 debuggableInfo.uid = UID; 95 96 Mockito.doReturn(debuggableInfo) 97 .when(mPackageManager) 98 .getApplicationInfoAsUser( 99 Mockito.eq(DEBUGGABLE_PACKAGE), 100 Mockito.anyInt(), 101 Mockito.any(UserHandle.class)); 102 103 final ApplicationInfo nonDebuggableInfo = Mockito.mock(ApplicationInfo.class); 104 nonDebuggableInfo.uid = UID; 105 106 Mockito.doReturn(nonDebuggableInfo) 107 .when(mPackageManager) 108 .getApplicationInfoAsUser( 109 Mockito.eq(NON_DEBUGGABLE_PACKAGE), 110 Mockito.anyInt(), 111 Mockito.any(UserHandle.class)); 112 113 Mockito.doThrow(new PackageManager.NameNotFoundException()) 114 .when(mPackageManager) 115 .getApplicationInfoAsUser( 116 Mockito.eq(INVALID_PACKAGE), 117 Mockito.anyInt(), 118 Mockito.any(UserHandle.class)); 119 } 120 121 @After tearDown()122 public void tearDown() { 123 if (mStaticMockSession != null) { 124 mStaticMockSession.finishMocking(); 125 } 126 } 127 128 @Test testCommandFailsIfCallerNotShellOrRoot()129 public void testCommandFailsIfCallerNotShellOrRoot() { 130 final SdkSandboxShellCommand.Injector injector = 131 new SdkSandboxShellCommand.Injector() { 132 @Override 133 int getCallingUid() { 134 return UserHandle.USER_ALL; 135 } 136 }; 137 final SdkSandboxShellCommand cmd = 138 new SdkSandboxShellCommand(mService, mSpyContext, injector); 139 140 assertThat(cmd.exec(mService, mIn, mOut, mErr, new String[] {"start", DEBUGGABLE_PACKAGE})) 141 .isEqualTo(-1); 142 } 143 144 @Test testStartFailsForInvalidPackage()145 public void testStartFailsForInvalidPackage() throws Exception { 146 final SdkSandboxShellCommand cmd = 147 new SdkSandboxShellCommand(mService, mSpyContext, new ShellInjector()); 148 149 assertThat(cmd.exec(mService, mIn, mOut, mErr, new String[] {"start", INVALID_PACKAGE})) 150 .isEqualTo(-1); 151 152 Mockito.verify(mPackageManager) 153 .getApplicationInfoAsUser( 154 Mockito.eq(INVALID_PACKAGE), 155 Mockito.anyInt(), 156 Mockito.any(UserHandle.class)); 157 } 158 159 @Test testStartFailsWhenSdkSandboxDisabled()160 public void testStartFailsWhenSdkSandboxDisabled() { 161 final CallingInfo callingInfo = new CallingInfo(UID, DEBUGGABLE_PACKAGE); 162 Mockito.doReturn(false).when(mService).isSdkSandboxServiceRunning(callingInfo); 163 final SdkSandboxShellCommand cmd = 164 new SdkSandboxShellCommand(mService, mSpyContext, new ShellInjector()); 165 mService.setIsSdkSandboxDisabledResponse(true); 166 167 assertThat(cmd.exec(mService, mIn, mOut, mErr, new String[] {"start", DEBUGGABLE_PACKAGE})) 168 .isEqualTo(-1); 169 170 Mockito.verify(mService) 171 .stopSdkSandboxService( 172 Mockito.any(CallingInfo.class), 173 Mockito.eq( 174 "Shell command `sdk_sandbox start` failed due to sandbox" 175 + " disabled.")); 176 mService.setIsSdkSandboxDisabledResponse(false); 177 } 178 179 @Test testStartFailsForNonDebuggablePackage()180 public void testStartFailsForNonDebuggablePackage() throws Exception { 181 final SdkSandboxShellCommand cmd = 182 new SdkSandboxShellCommand(mService, mSpyContext, new ShellInjector()); 183 184 assertThat( 185 cmd.exec( 186 mService, 187 mIn, 188 mOut, 189 mErr, 190 new String[] {"start", NON_DEBUGGABLE_PACKAGE})) 191 .isEqualTo(-1); 192 193 Mockito.verify(mPackageManager) 194 .getApplicationInfoAsUser( 195 Mockito.eq(NON_DEBUGGABLE_PACKAGE), 196 Mockito.anyInt(), 197 Mockito.any(UserHandle.class)); 198 199 Mockito.verify(mService, Mockito.never()) 200 .startSdkSandboxIfNeeded( 201 Mockito.any(CallingInfo.class), 202 Mockito.any(SdkSandboxManagerService.SandboxBindingCallback.class)); 203 } 204 205 @Test testStartFailsWhenSandboxAlreadyRunning()206 public void testStartFailsWhenSandboxAlreadyRunning() throws Exception { 207 final CallingInfo callingInfo = new CallingInfo(UID, DEBUGGABLE_PACKAGE); 208 Mockito.doReturn(true).when(mService).isSdkSandboxServiceRunning(callingInfo); 209 210 final SdkSandboxShellCommand cmd = 211 new SdkSandboxShellCommand(mService, mSpyContext, new ShellInjector()); 212 213 assertThat(cmd.exec(mService, mIn, mOut, mErr, new String[] {"start", DEBUGGABLE_PACKAGE})) 214 .isEqualTo(-1); 215 216 Mockito.verify(mPackageManager) 217 .getApplicationInfoAsUser( 218 Mockito.eq(DEBUGGABLE_PACKAGE), 219 Mockito.anyInt(), 220 Mockito.any(UserHandle.class)); 221 222 Mockito.verify(mService).isSdkSandboxServiceRunning(callingInfo); 223 224 Mockito.verify(mService, Mockito.never()) 225 .startSdkSandboxIfNeeded( 226 Mockito.any(CallingInfo.class), 227 Mockito.any(SdkSandboxManagerService.SandboxBindingCallback.class)); 228 } 229 230 @Test testStartSucceedsForDebuggablePackageWhenNotAlreadyRunning()231 public void testStartSucceedsForDebuggablePackageWhenNotAlreadyRunning() throws Exception { 232 final CallingInfo callingInfo = new CallingInfo(UID, DEBUGGABLE_PACKAGE); 233 Mockito.doReturn(false).when(mService).isSdkSandboxServiceRunning(callingInfo); 234 235 final SdkSandboxShellCommand cmd = 236 new SdkSandboxShellCommand(mService, mSpyContext, new ShellInjector()); 237 238 assertThat(cmd.exec(mService, mIn, mOut, mErr, new String[] {"start", DEBUGGABLE_PACKAGE})) 239 .isEqualTo(0); 240 241 Mockito.verify(mPackageManager) 242 .getApplicationInfoAsUser( 243 Mockito.eq(DEBUGGABLE_PACKAGE), 244 Mockito.anyInt(), 245 Mockito.any(UserHandle.class)); 246 247 Mockito.verify(mService).isSdkSandboxServiceRunning(callingInfo); 248 249 Mockito.verify(mService) 250 .startSdkSandboxIfNeeded( 251 Mockito.eq(callingInfo), 252 Mockito.any(SdkSandboxManagerService.SandboxBindingCallback.class)); 253 } 254 255 @Test testStartFailsWhenBindingSandboxFails()256 public void testStartFailsWhenBindingSandboxFails() throws Exception { 257 mService.setBindingSuccessful(false); 258 259 final CallingInfo callingInfo = new CallingInfo(UID, DEBUGGABLE_PACKAGE); 260 Mockito.doReturn(false).when(mService).isSdkSandboxServiceRunning(callingInfo); 261 262 final SdkSandboxShellCommand cmd = 263 new SdkSandboxShellCommand(mService, mSpyContext, new ShellInjector()); 264 265 assertThat(cmd.exec(mService, mIn, mOut, mErr, new String[] {"start", DEBUGGABLE_PACKAGE})) 266 .isEqualTo(-1); 267 268 Mockito.verify(mPackageManager) 269 .getApplicationInfoAsUser( 270 Mockito.eq(DEBUGGABLE_PACKAGE), 271 Mockito.anyInt(), 272 Mockito.any(UserHandle.class)); 273 274 Mockito.verify(mService).isSdkSandboxServiceRunning(callingInfo); 275 276 Mockito.verify(mService) 277 .startSdkSandboxIfNeeded( 278 Mockito.eq(callingInfo), 279 Mockito.any(SdkSandboxManagerService.SandboxBindingCallback.class)); 280 } 281 282 @Test testStopFailsForInvalidPackage()283 public void testStopFailsForInvalidPackage() throws Exception { 284 final SdkSandboxShellCommand cmd = 285 new SdkSandboxShellCommand(mService, mSpyContext, new ShellInjector()); 286 287 assertThat(cmd.exec(mService, mIn, mOut, mErr, new String[] {"stop", INVALID_PACKAGE})) 288 .isEqualTo(-1); 289 290 Mockito.verify(mPackageManager) 291 .getApplicationInfoAsUser( 292 Mockito.eq(INVALID_PACKAGE), 293 Mockito.anyInt(), 294 Mockito.any(UserHandle.class)); 295 296 Mockito.verify(mService, Mockito.never()) 297 .stopSdkSandboxService(Mockito.any(CallingInfo.class), Mockito.anyString()); 298 } 299 300 @Test testStopFailsForNonDebuggablePackage()301 public void testStopFailsForNonDebuggablePackage() throws Exception { 302 final SdkSandboxShellCommand cmd = 303 new SdkSandboxShellCommand(mService, mSpyContext, new ShellInjector()); 304 305 assertThat( 306 cmd.exec( 307 mService, 308 mIn, 309 mOut, 310 mErr, 311 new String[] {"stop", NON_DEBUGGABLE_PACKAGE})) 312 .isEqualTo(-1); 313 314 Mockito.verify(mPackageManager) 315 .getApplicationInfoAsUser( 316 Mockito.eq(NON_DEBUGGABLE_PACKAGE), 317 Mockito.anyInt(), 318 Mockito.any(UserHandle.class)); 319 320 Mockito.verify(mService, Mockito.never()) 321 .stopSdkSandboxService(Mockito.any(CallingInfo.class), Mockito.anyString()); 322 } 323 324 @Test testStopFailsWhenSandboxIsNotRunning()325 public void testStopFailsWhenSandboxIsNotRunning() throws Exception { 326 final CallingInfo callingInfo = new CallingInfo(UID, DEBUGGABLE_PACKAGE); 327 Mockito.doReturn(false).when(mService).isSdkSandboxServiceRunning(callingInfo); 328 329 final SdkSandboxShellCommand cmd = 330 new SdkSandboxShellCommand(mService, mSpyContext, new ShellInjector()); 331 332 assertThat(cmd.exec(mService, mIn, mOut, mErr, new String[] {"stop", DEBUGGABLE_PACKAGE})) 333 .isEqualTo(-1); 334 335 Mockito.verify(mPackageManager) 336 .getApplicationInfoAsUser( 337 Mockito.eq(DEBUGGABLE_PACKAGE), 338 Mockito.anyInt(), 339 Mockito.any(UserHandle.class)); 340 341 Mockito.verify(mService).isSdkSandboxServiceRunning(callingInfo); 342 343 Mockito.verify(mService, Mockito.never()) 344 .stopSdkSandboxService(Mockito.any(CallingInfo.class), Mockito.anyString()); 345 } 346 347 @Test testStopSucceedsForDebuggablePackageWhenAlreadyRunning()348 public void testStopSucceedsForDebuggablePackageWhenAlreadyRunning() throws Exception { 349 final CallingInfo callingInfo = new CallingInfo(UID, DEBUGGABLE_PACKAGE); 350 Mockito.doReturn(true).when(mService).isSdkSandboxServiceRunning(callingInfo); 351 352 final SdkSandboxShellCommand cmd = 353 new SdkSandboxShellCommand(mService, mSpyContext, new ShellInjector()); 354 355 assertThat(cmd.exec(mService, mIn, mOut, mErr, new String[] {"stop", DEBUGGABLE_PACKAGE})) 356 .isEqualTo(0); 357 358 Mockito.verify(mPackageManager) 359 .getApplicationInfoAsUser( 360 Mockito.eq(DEBUGGABLE_PACKAGE), 361 Mockito.anyInt(), 362 Mockito.any(UserHandle.class)); 363 364 Mockito.verify(mService).isSdkSandboxServiceRunning(callingInfo); 365 366 Mockito.verify(mService) 367 .stopSdkSandboxService(callingInfo, "Shell command 'sdk_sandbox stop' issued"); 368 } 369 370 private static class ShellInjector extends SdkSandboxShellCommand.Injector { 371 372 @Override getCallingUid()373 int getCallingUid() { 374 return Process.SHELL_UID; 375 } 376 } 377 378 private static class FakeSdkSandboxManagerService extends SdkSandboxManagerService { 379 380 private boolean mBindingSuccessful = true; 381 private boolean mIsDisabledResponse = false; 382 FakeSdkSandboxManagerService(Context context)383 FakeSdkSandboxManagerService(Context context) { 384 super(context); 385 } 386 387 @Override startSdkSandboxIfNeeded(CallingInfo callingInfo, SandboxBindingCallback callback)388 void startSdkSandboxIfNeeded(CallingInfo callingInfo, SandboxBindingCallback callback) { 389 if (mBindingSuccessful) { 390 callback.onBindingSuccessful(Mockito.mock(ISdkSandboxService.class), -1); 391 } else { 392 callback.onBindingFailed(new LoadSdkException(null, new Bundle()), -1); 393 } 394 } 395 396 @Override stopSdkSandboxService(CallingInfo callingInfo, String reason)397 void stopSdkSandboxService(CallingInfo callingInfo, String reason) {} 398 399 @Override isSdkSandboxServiceRunning(CallingInfo callingInfo)400 boolean isSdkSandboxServiceRunning(CallingInfo callingInfo) { 401 // Must be mocked in the tests accordingly 402 throw new RuntimeException(); 403 } 404 405 @Override isSdkSandboxDisabled(ISdkSandboxService boundService)406 boolean isSdkSandboxDisabled(ISdkSandboxService boundService) { 407 return mIsDisabledResponse; 408 } 409 setIsSdkSandboxDisabledResponse(boolean response)410 private void setIsSdkSandboxDisabledResponse(boolean response) { 411 mIsDisabledResponse = response; 412 } 413 setBindingSuccessful(boolean successful)414 private void setBindingSuccessful(boolean successful) { 415 mBindingSuccessful = successful; 416 } 417 } 418 } 419