1 /*
2  * Copyright (C) 2017 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 package android.appsecurity.cts;
17 
18 import static org.junit.Assert.assertFalse;
19 import static org.junit.Assert.assertTrue;
20 
21 import android.platform.test.annotations.AppModeFull;
22 import android.platform.test.annotations.Presubmit;
23 
24 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
25 
26 import org.junit.After;
27 import org.junit.Before;
28 import org.junit.Test;
29 import org.junit.runner.RunWith;
30 
31 import java.util.HashMap;
32 
33 @Presubmit
34 @RunWith(DeviceJUnit4ClassRunner.class)
35 @AppModeFull(reason = "Overlays cannot be instant apps")
36 public class OverlayHostTest extends BaseAppSecurityTest {
37 
38     // Test applications
39     private static final String TARGET_OVERLAYABLE_APK = "CtsOverlayTarget.apk";
40     private static final String TARGET_NO_OVERLAYABLE_APK = "CtsOverlayTargetNoOverlayable.apk";
41 
42     private static final String OVERLAY_ANDROID_APK = "CtsOverlayAndroid.apk";
43     private static final String OVERLAY_ALL_APK = "CtsOverlayPolicyAll.apk";
44     private static final String OVERLAY_ALL_HAS_CODE_APK = "CtsOverlayPolicyAllHasCode.apk";
45     private static final String OVERLAY_ALL_NO_NAME_APK = "CtsOverlayPolicyAllNoName.apk";
46     private static final String OVERLAY_ALL_NO_NAME_DIFFERENT_CERT_APK =
47             "CtsOverlayPolicyAllNoNameDifferentCert.apk";
48     private static final String OVERLAY_ALL_PIE_APK = "CtsOverlayPolicyAllPie.apk";
49     private static final String OVERLAY_PRODUCT_APK = "CtsOverlayPolicyProduct.apk";
50     private static final String OVERLAY_SYSTEM_APK = "CtsOverlayPolicySystem.apk";
51     private static final String OVERLAY_VENDOR_APK = "CtsOverlayPolicyVendor.apk";
52     private static final String OVERLAY_DIFFERENT_SIGNATURE_APK = "CtsOverlayPolicySignatureDifferent.apk";
53 
54     // Test application package names
55     private static final String TARGET_PACKAGE = "com.android.cts.overlay.target";
56     private static final String OVERLAY_ANDROID_PACKAGE = "com.android.cts.overlay.android";
57     private static final String OVERLAY_ALL_PACKAGE = "com.android.cts.overlay.all";
58     private static final String OVERLAY_PRODUCT_PACKAGE = "com.android.cts.overlay.policy.product";
59     private static final String OVERLAY_SYSTEM_PACKAGE = "com.android.cts.overlay.policy.system";
60     private static final String OVERLAY_VENDOR_PACKAGE = "com.android.cts.overlay.policy.vendor";
61     private static final String OVERLAY_DIFFERENT_SIGNATURE_PACKAGE = "com.android.cts.overlay.policy.signature";
62 
63     // Test application test class
64     private static final String TEST_APP_APK = "CtsOverlayApp.apk";
65     private static final String TEST_APP_PACKAGE = "com.android.cts.overlay.app";
66     private static final String TEST_APP_CLASS = "com.android.cts.overlay.app.OverlayableTest";
67     private static final String OVERLAY_TARGET_TEST_APP_CLASS =
68             "com.android.cts.overlay.target.OverlayTargetTest";
69 
70     // Overlay states
71     private static final String STATE_DISABLED = "STATE_DISABLED";
72     private static final String STATE_ENABLED = "STATE_ENABLED";
73     private static final String STATE_NO_IDMAP = "STATE_NO_IDMAP";
74 
75     // test arguments
76     private static final String PARAM_START_SERVICE = "start_service";
77 
78     private static final long OVERLAY_WAIT_TIMEOUT = 10000; // 10 seconds
79 
80     @Before
setUp()81     public void setUp() throws Exception {
82         new InstallMultiple().addFile(TEST_APP_APK).run();
83     }
84 
85     @After
tearDown()86     public void tearDown() throws Exception {
87         getDevice().uninstallPackage(TEST_APP_PACKAGE);
88     }
89 
getStateForOverlay(String overlayPackage)90     private String getStateForOverlay(String overlayPackage) throws Exception {
91         String result = getDevice().executeShellCommand("cmd overlay dump");
92 
93         String overlayPackageForCurrentUser = overlayPackage + ":" + getDevice().getCurrentUser();
94 
95         int startIndex = result.indexOf(overlayPackageForCurrentUser);
96         if (startIndex < 0) {
97             return null;
98         }
99 
100         int endIndex = result.indexOf('}', startIndex);
101         assertTrue(endIndex > startIndex);
102 
103         int stateIndex = result.indexOf("mState", startIndex);
104         assertTrue(startIndex < stateIndex && stateIndex < endIndex);
105 
106         int colonIndex = result.indexOf(':', stateIndex);
107         assertTrue(stateIndex < colonIndex && colonIndex < endIndex);
108 
109         int endLineIndex = result.indexOf('\n', colonIndex);
110         assertTrue(colonIndex < endLineIndex && endLineIndex < endIndex);
111         return result.substring(colonIndex + 2, endLineIndex);
112     }
113 
114     private void waitForOverlayState(String overlayPackage, String state) throws Exception {
115         boolean overlayFound = false;
116         long startTime = System.currentTimeMillis();
117 
118         while (!overlayFound && (System.currentTimeMillis() - startTime < OVERLAY_WAIT_TIMEOUT)) {
119             String result = getStateForOverlay(overlayPackage);
120             overlayFound = state.equals(result);
121         }
122 
123         assertTrue(overlayFound);
124     }
125 
126     private void assertFailToGenerateIdmap(String overlayApk, String overlayPackage)
127             throws Exception {
128         try {
129             getDevice().uninstallPackage(TARGET_PACKAGE);
130             getDevice().uninstallPackage(overlayPackage);
131             assertFalse(getDevice().getInstalledPackageNames().contains(OVERLAY_ALL_PACKAGE));
132             assertFalse(getDevice().getInstalledPackageNames().contains(overlayPackage));
133 
134             new InstallMultiple().addFile(TARGET_OVERLAYABLE_APK).run();
135             new InstallMultiple().addFile(overlayApk).run();
136 
137             waitForOverlayState(overlayPackage, STATE_NO_IDMAP);
138             getDevice().executeShellCommand("cmd overlay enable  --user current " + overlayPackage);
139             waitForOverlayState(overlayPackage, STATE_NO_IDMAP);
140         } finally {
141             getDevice().uninstallPackage(TARGET_PACKAGE);
142             getDevice().uninstallPackage(overlayPackage);
143         }
144     }
145 
146     private void runOverlayDeviceTest(String targetApk, String overlayApk, String overlayPackage,
147             String testMethod)
148             throws Exception {
149         try {
150             getDevice().uninstallPackage(TARGET_PACKAGE);
151             getDevice().uninstallPackage(overlayPackage);
152             assertFalse(getDevice().getInstalledPackageNames().contains(TARGET_PACKAGE));
153             assertFalse(getDevice().getInstalledPackageNames().contains(overlayPackage));
154 
155             new InstallMultiple().addFile(overlayApk).run();
156             new InstallMultiple().addFile(targetApk).run();
157 
158             waitForOverlayState(overlayPackage, STATE_DISABLED);
159             getDevice().executeShellCommand("cmd overlay enable --user current " + overlayPackage);
160             waitForOverlayState(overlayPackage, STATE_ENABLED);
161 
162             runDeviceTests(TEST_APP_PACKAGE, TEST_APP_CLASS, testMethod, false /* instant */);
163         } finally {
164             getDevice().uninstallPackage(TARGET_PACKAGE);
165             getDevice().uninstallPackage(overlayPackage);
166         }
167     }
168 
169     private void runDeviceTests(String packageName, String testClassName, String testMethodName,
170             HashMap<String, String> testArgs) throws Exception {
171         Utils.runDeviceTestsAsCurrentUser(getDevice(), packageName, testClassName, testMethodName,
172                 testArgs);
173     }
174 
175     /**
176      * Overlays that target android and are not signed with the platform signature must not be
177      * installed successfully.
178      */
179     @Test
180     public void testCannotInstallTargetAndroidNotPlatformSigned() throws Exception {
181         try {
182             getDevice().uninstallPackage(OVERLAY_ANDROID_PACKAGE);
183             assertFalse(getDevice().getInstalledPackageNames().contains(OVERLAY_ANDROID_PACKAGE));
184 
185             // Try to install the overlay, but expect an error.
186             new InstallMultiple().addFile(OVERLAY_ANDROID_APK).runExpectingFailure();
187 
188             // The install should have failed.
189             assertFalse(getDevice().getInstalledPackageNames().contains(OVERLAY_ANDROID_PACKAGE));
190 
191             // The package of the installed overlay should not appear in the overlay manager list.
192             assertFalse(getDevice().executeShellCommand("cmd overlay list --user current ")
193                     .contains(" " + OVERLAY_ANDROID_PACKAGE + "\n"));
194         } finally {
195             getDevice().uninstallPackage(OVERLAY_ANDROID_PACKAGE);
196         }
197     }
198 
199     /**
200      * Overlays that target a pre-Q sdk and that are not signed with the platform signature must not
201      * be installed.
202      **/
203     @Test
204     public void testCannotInstallPieOverlayNotPlatformSigned() throws Exception {
205         try {
206             getDevice().uninstallPackage(OVERLAY_ALL_PACKAGE);
207             assertFalse(getDevice().getInstalledPackageNames().contains(OVERLAY_ALL_PACKAGE));
208 
209             // Try to install the overlay, but expect an error.
210             new InstallMultiple().addFile(OVERLAY_ALL_PIE_APK).runExpectingFailure();
211 
212             // The install should have failed.
213             assertFalse(getDevice().getInstalledPackageNames().contains(OVERLAY_ALL_PACKAGE));
214 
215             // The package of the installed overlay should not appear in the overlay manager list.
216             assertFalse(getDevice().executeShellCommand("cmd overlay list")
217                     .contains(" " + OVERLAY_ALL_PACKAGE + "\n"));
218         } finally {
219             getDevice().uninstallPackage(OVERLAY_ALL_PACKAGE);
220         }
221     }
222 
223     /**
224      * Overlays that target Q or higher, that do not specify an android:targetName, and that are
225      * not signed with the same signature as the target package must not be installed.
226      **/
227     @Test
228     public void testCannotInstallDifferentSignaturesNoName() throws Exception {
229         try {
230             getDevice().uninstallPackage(TARGET_PACKAGE);
231             getDevice().uninstallPackage(OVERLAY_ALL_PACKAGE);
232             assertFalse(getDevice().getInstalledPackageNames().contains(TARGET_PACKAGE));
233             assertFalse(getDevice().getInstalledPackageNames().contains(OVERLAY_ALL_PACKAGE));
234 
235             // Try to install the overlay, but expect an error.
236             new InstallMultiple().addFile(TARGET_NO_OVERLAYABLE_APK).run();
237             new InstallMultiple().addFile(
238                     OVERLAY_ALL_NO_NAME_DIFFERENT_CERT_APK).runExpectingFailure();
239 
240             // The install should have failed.
241             assertFalse(getDevice().getInstalledPackageNames().contains(OVERLAY_ALL_PACKAGE));
242 
243             // The package of the installed overlay should not appear in the overlay manager list.
244             assertFalse(getDevice().executeShellCommand("cmd overlay list --user current")
245                     .contains(" " + OVERLAY_ALL_PACKAGE + "\n"));
246         } finally {
247             getDevice().uninstallPackage(OVERLAY_ALL_PACKAGE);
248             getDevice().uninstallPackage(TARGET_PACKAGE);
249         }
250     }
251 
252     /**
253      * Overlays that target Q or higher, that do not specify an android:targetName, and are
254      * installed before the target must not be allowed to successfully generate an idmap if the
255      * overlay is not signed with the same signature as the target package.
256      **/
257     @Test
258     public void testFailIdmapDifferentSignaturesNoName() throws Exception {
259         assertFailToGenerateIdmap(OVERLAY_ALL_NO_NAME_APK, OVERLAY_ALL_PACKAGE);
260     }
261 
262     /**
263      * Overlays that target Q or higher, that do not specify an android:targetName, and are
264      * installed before the target must be allowed to successfully generate an idmap if the
265      * overlay is signed with the same signature as the target package.
266      **/
267     @Test
268     public void testSameSignatureNoOverlayableSucceeds() throws Exception {
269         String testMethod = "testSameSignatureNoOverlayableSucceeds";
270         runOverlayDeviceTest(TARGET_NO_OVERLAYABLE_APK, OVERLAY_ALL_NO_NAME_APK,
271                 OVERLAY_ALL_PACKAGE, testMethod);
272     }
273 
274     /**
275      * Overlays installed on the data partition may only overlay resources defined under the public
276      * and signature policies if the overlay is signed with the same signature as the target.
277      */
278     @Test
279     public void testOverlayPolicyAll() throws Exception {
280         String testMethod = "testOverlayPolicyAll";
281         runOverlayDeviceTest(TARGET_OVERLAYABLE_APK, OVERLAY_ALL_APK, OVERLAY_ALL_PACKAGE,
282                 testMethod);
283     }
284 
285     @Test
286     public void testOverlayCodeNotLoaded() throws Exception {
287         String testMethod = "testOverlayCodeNotLoaded";
288         runOverlayDeviceTest(TARGET_OVERLAYABLE_APK, OVERLAY_ALL_HAS_CODE_APK, OVERLAY_ALL_PACKAGE,
289                 testMethod);
290     }
291 
292     @Test
293     public void testOverlayPolicyAllNoNameFails() throws Exception {
294         assertFailToGenerateIdmap(OVERLAY_ALL_NO_NAME_APK, OVERLAY_ALL_PACKAGE);
295     }
296 
297     @Test
298     public void testOverlayPolicyProductFails() throws Exception {
299         assertFailToGenerateIdmap(OVERLAY_PRODUCT_APK, OVERLAY_PRODUCT_PACKAGE);
300     }
301 
302     @Test
303     public void testOverlayPolicySystemFails() throws Exception {
304         assertFailToGenerateIdmap(OVERLAY_SYSTEM_APK, OVERLAY_SYSTEM_PACKAGE);
305     }
306 
307     @Test
308     public void testOverlayPolicyVendorFails() throws Exception {
309         assertFailToGenerateIdmap(OVERLAY_VENDOR_APK, OVERLAY_VENDOR_PACKAGE);
310     }
311 
312     @Test
313     public void testOverlayPolicyDifferentSignatureFails() throws Exception {
314         assertFailToGenerateIdmap(OVERLAY_DIFFERENT_SIGNATURE_APK,
315                 OVERLAY_DIFFERENT_SIGNATURE_PACKAGE);
316     }
317 
318     @Test
319     public void testFrameworkDoesNotDefineOverlayable() throws Exception {
320         String testMethod = "testFrameworkDoesNotDefineOverlayable";
321         runDeviceTests(TEST_APP_PACKAGE, TEST_APP_CLASS, testMethod, false /* instant */);
322     }
323 
324     /** Overlays must not overlay assets. */
325     @Test
326     public void testCannotOverlayAssets() throws Exception {
327         String testMethod = "testCannotOverlayAssets";
328         runOverlayDeviceTest(TARGET_OVERLAYABLE_APK, OVERLAY_ALL_APK, OVERLAY_ALL_PACKAGE,
329                 testMethod);
330     }
331 
332     @Test
333     public void testOverlayEnabled_activityInForeground() throws Exception {
334         final HashMap<String, String> testArgs = new HashMap<>();
335         testArgs.put(PARAM_START_SERVICE, Boolean.FALSE.toString());
336         try {
337             new InstallMultiple().addFile(OVERLAY_ALL_APK).run();
338             new InstallMultiple().addFile(TARGET_OVERLAYABLE_APK).run();
339 
340             runDeviceTests(TARGET_PACKAGE, OVERLAY_TARGET_TEST_APP_CLASS,
341                     "overlayEnabled_activityInForeground", testArgs);
342         } finally {
343             getDevice().uninstallPackage(TARGET_PACKAGE);
344             getDevice().uninstallPackage(OVERLAY_ALL_PACKAGE);
345         }
346     }
347 
348     @Test
349     public void testOverlayEnabled_activityInBackground_toForeground() throws Exception {
350         final HashMap<String, String> testArgs = new HashMap<>();
351         testArgs.put(PARAM_START_SERVICE, Boolean.FALSE.toString());
352         try {
353             new InstallMultiple().addFile(OVERLAY_ALL_APK).run();
354             new InstallMultiple().addFile(TARGET_OVERLAYABLE_APK).run();
355 
356             runDeviceTests(TARGET_PACKAGE, OVERLAY_TARGET_TEST_APP_CLASS,
357                     "overlayEnabled_activityInBackground_toForeground", testArgs);
358         } finally {
359             getDevice().uninstallPackage(TARGET_PACKAGE);
360             getDevice().uninstallPackage(OVERLAY_ALL_PACKAGE);
361         }
362     }
363 
364     @Test
365     public void testOverlayEnabled_activityWithServiceInForeground() throws Exception {
366         final HashMap<String, String> testArgs = new HashMap<>();
367         testArgs.put(PARAM_START_SERVICE, Boolean.TRUE.toString());
368         try {
369             new InstallMultiple().addFile(OVERLAY_ALL_APK).run();
370             new InstallMultiple().addFile(TARGET_OVERLAYABLE_APK).run();
371 
372             runDeviceTests(TARGET_PACKAGE, OVERLAY_TARGET_TEST_APP_CLASS,
373                     "overlayEnabled_activityInForeground", testArgs);
374         } finally {
375             getDevice().uninstallPackage(TARGET_PACKAGE);
376             getDevice().uninstallPackage(OVERLAY_ALL_PACKAGE);
377         }
378     }
379 
380     @Test
381     public void testOverlayEnabled_activityWithServiceInBackground_toForeground() throws Exception {
382         final HashMap<String, String> testArgs = new HashMap<>();
383         testArgs.put(PARAM_START_SERVICE, Boolean.TRUE.toString());
384         try {
385             new InstallMultiple().addFile(OVERLAY_ALL_APK).run();
386             new InstallMultiple().addFile(TARGET_OVERLAYABLE_APK).run();
387 
388             runDeviceTests(TARGET_PACKAGE, OVERLAY_TARGET_TEST_APP_CLASS,
389                     "overlayEnabled_activityInBackground_toForeground", testArgs);
390         } finally {
391             getDevice().uninstallPackage(TARGET_PACKAGE);
392             getDevice().uninstallPackage(OVERLAY_ALL_PACKAGE);
393         }
394     }
395 }
396