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