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