• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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