1 /*
2  * Copyright (C) 2018 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5  * use this file except in compliance with the License. You may obtain a copy
6  * 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, WITHOUT
12  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13  * License for the specific language governing permissions and limitations
14  * under the License.
15  */
16 package android.appsecurity.cts;
17 
18 import static org.junit.Assert.assertFalse;
19 import static org.junit.Assert.assertNotEquals;
20 import static org.junit.Assert.assertNotNull;
21 import static org.junit.Assert.fail;
22 
23 import android.platform.test.annotations.AppModeFull;
24 import com.android.ddmlib.Log.LogLevel;
25 import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
26 import com.android.tradefed.build.IBuildInfo;
27 import com.android.tradefed.device.DeviceNotAvailableException;
28 import com.android.tradefed.device.ITestDevice;
29 import com.android.tradefed.log.LogUtil.CLog;
30 import com.android.tradefed.testtype.DeviceTestCase;
31 import com.android.tradefed.testtype.IBuildReceiver;
32 import com.android.tradefed.util.FileUtil;
33 
34 import org.junit.After;
35 import org.junit.Assert;
36 import org.junit.Before;
37 
38 import java.io.File;
39 import java.io.FileNotFoundException;
40 
41 /**
42  * Set of tests that verify that corrupt APKs are properly rejected by PackageManager and
43  * do not cause the system to crash.
44  */
45 @AppModeFull(reason = "the corrupt APKs were provided as-is and we cannot modify them to comply with instant mode")
46 public class CorruptApkTests extends DeviceTestCase implements IBuildReceiver {
47 
48     private static final String CORRUPT_APK_PACKAGE_NAME = "android.content.cts.corruptapk";
49     private static final String FAIL_COMPRESSED_ARSC_PLATFORM_CONFIG_ID = "132742131";
50     private static final String COMPRESSED_ARSC_Q = "CtsCorruptApkTests_Compressed_Q.apk";
51     private static final String COMPRESSED_ARSC_R = "CtsCorruptApkTests_Compressed_R.apk";
52     private static final String UNALIGNED_ARSC_Q = "CtsCorruptApkTests_Unaligned_Q.apk";
53     private static final String UNALIGNED_ARSC_R = "CtsCorruptApkTests_Unaligned_R.apk";
54 
55     private IBuildInfo mBuildInfo;
56 
57     /** A container for information about the system_server process. */
58     private class SystemServerInformation {
59         final long mPid;
60         final long mStartTime;
61 
SystemServerInformation(long pid, long startTime)62         SystemServerInformation(long pid, long startTime) {
63             this.mPid = pid;
64             this.mStartTime = startTime;
65         }
66 
67         @Override
equals(Object actual)68         public boolean equals(Object actual) {
69             return (actual instanceof SystemServerInformation)
70                 && mPid == ((SystemServerInformation) actual).mPid
71                 && mStartTime == ((SystemServerInformation) actual).mStartTime;
72         }
73     }
74 
75     /** Retrieves the process id and elapsed run time of system_server. */
retrieveInfo()76     private SystemServerInformation retrieveInfo() throws DeviceNotAvailableException {
77         final ITestDevice device = getDevice();
78 
79         // Retrieve the process id of system_server
80         String pidResult = device.executeShellCommand("pidof system_server").trim();
81         assertNotNull("Failed to retrieve pid of system_server", pidResult);
82         long pid = 0;
83         try {
84             pid = Long.parseLong(pidResult);
85         } catch (NumberFormatException | IndexOutOfBoundsException e) {
86             fail("Unable to parse pid of system_server '" + pidResult + "'");
87         }
88 
89         // Retrieve the start time of system_server
90         long startTime = 0;
91         String pidStats = device.executeShellCommand("cat /proc/" + pid + "/stat");
92         assertNotNull("Failed to retrieve stat of system_server with pid '" + pid + "'", pidStats);
93         try {
94             String startTimeJiffies = pidStats.split("\\s+")[21];
95             startTime = Long.parseLong(startTimeJiffies);
96         } catch (NumberFormatException | IndexOutOfBoundsException e) {
97             fail("Unable to parse system_server stat file '" + pidStats + "'");
98         }
99 
100         return new SystemServerInformation(pid, startTime);
101     }
102 
103     @Override
setBuild(IBuildInfo buildInfo)104     public void setBuild(IBuildInfo buildInfo) {
105         mBuildInfo = buildInfo;
106     }
107 
108    /** Uninstall any test APKs already present on device. */
uninstallApks()109     private void uninstallApks() throws DeviceNotAvailableException {
110         final ITestDevice device = getDevice();
111         device.uninstallPackage("com.android.appsecurity.b71360999");
112         device.uninstallPackage("com.android.appsecurity.b71361168");
113         device.uninstallPackage("com.android.appsecurity.b79488511");
114         // WARNING: PlatformCompat overrides for package parsing changes must be reset before the
115         // package is uninstalled because overrides cannot be reset if the package is not present.
116         device.executeShellCommand("am compat reset-all " + CORRUPT_APK_PACKAGE_NAME);
117         device.uninstallPackage(CORRUPT_APK_PACKAGE_NAME);
118     }
119 
120     @Before
121     @Override
setUp()122     public void setUp() throws Exception {
123         super.setUp();
124         uninstallApks();
125     }
126 
127     @After
128     @Override
tearDown()129     public void tearDown() throws Exception {
130         super.tearDown();
131         uninstallApks();
132     }
133 
install(String apk)134     private String install(String apk) throws DeviceNotAvailableException, FileNotFoundException  {
135         return getDevice().installPackage(
136                 new CompatibilityBuildHelper(mBuildInfo).getTestFile(apk),
137                 true /* reinstall */);
138     }
139 
140     /**
141      * Asserts that installing the application does not cause a native error causing system_server
142      * to crash (typically the result of a buffer overflow or an out-of-bounds read).
143      */
assertInstallDoesNotCrashSystem(String apk)144     private void assertInstallDoesNotCrashSystem(String apk) throws Exception {
145         SystemServerInformation beforeInfo = retrieveInfo();
146 
147         final String result = install(apk);
148         CLog.logAndDisplay(LogLevel.INFO, "Result: '" + result + "'");
149         if (result != null) {
150             assertFalse("Install package segmentation faulted",
151                 result.toLowerCase().contains("segmentation fault"));
152         }
153 
154         assertEquals("system_server restarted", beforeInfo, retrieveInfo());
155     }
156 
157     /** Installing the APK described in b/71360999 must not crash the device. */
testSafeInstallOfCorruptAPK_b71360999()158     public void testSafeInstallOfCorruptAPK_b71360999() throws Exception {
159         assertInstallDoesNotCrashSystem("CtsCorruptApkTests_b71360999.apk");
160     }
161 
162     /** Installing the APK described in b/71361168 must not crash the device. */
testSafeInstallOfCorruptAPK_b71361168()163     public void testSafeInstallOfCorruptAPK_b71361168() throws Exception {
164         assertInstallDoesNotCrashSystem("CtsCorruptApkTests_b71361168.apk");
165     }
166 
167     /** Installing the APK described in b/79488511 must not crash the device. */
testSafeInstallOfCorruptAPK_b79488511()168     public void testSafeInstallOfCorruptAPK_b79488511() throws Exception {
169         assertInstallDoesNotCrashSystem("CtsCorruptApkTests_b79488511.apk");
170     }
171 
172     /** APKs that target pre-R and have a compressed resources.arsc can be installed. */
testFailInstallCompressedARSC_Q()173     public void testFailInstallCompressedARSC_Q() throws Exception {
174         assertNull(install(COMPRESSED_ARSC_Q));
175     }
176 
testFailInstallCompressedARSC_Q_PlatformConfig_enabled()177     public void testFailInstallCompressedARSC_Q_PlatformConfig_enabled() throws Exception {
178         assertNull(install(COMPRESSED_ARSC_Q));
179         getDevice().executeShellCommand(String.format("am compat enable %s %s",
180                 FAIL_COMPRESSED_ARSC_PLATFORM_CONFIG_ID, CORRUPT_APK_PACKAGE_NAME));
181         assertNotNull(install(COMPRESSED_ARSC_Q));
182     }
183 
184     /** APKs that target R+ and have a compressed resources.arsc must not be installed. */
testFailInstallCompressedARSC_R()185     public void testFailInstallCompressedARSC_R() throws Exception {
186         assertNotNull(install(COMPRESSED_ARSC_R));
187     }
188 
189     /** APKs that target pre-R and have a unaligned resources.arsc can be installed. */
testFailInstallUnalignedARSC_Q()190     public void testFailInstallUnalignedARSC_Q() throws Exception {
191         assertNull(install(UNALIGNED_ARSC_Q));
192     }
193 
testFailInstallUnalignedARSC_Q_PlatformConfig_enabled()194     public void testFailInstallUnalignedARSC_Q_PlatformConfig_enabled() throws Exception {
195         assertNull(install(UNALIGNED_ARSC_Q));
196         getDevice().executeShellCommand(String.format("am compat enable %s %s",
197                 FAIL_COMPRESSED_ARSC_PLATFORM_CONFIG_ID, CORRUPT_APK_PACKAGE_NAME));
198         assertNotNull(install(UNALIGNED_ARSC_Q));
199     }
200 
201     /** APKs that target R+ and have a unaligned resources.arsc must not be installed. */
testFailInstallUnalignedARSC_R()202     public void testFailInstallUnalignedARSC_R() throws Exception {
203         assertNotNull(install(UNALIGNED_ARSC_R));
204     }
205 }