• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2019 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.tests.stagedinstall.host;
18 
19 import static com.android.cts.shim.lib.ShimPackage.PRIVILEGED_SHIM_PACKAGE_NAME;
20 import static com.android.cts.shim.lib.ShimPackage.SHIM_APEX_PACKAGE_NAME;
21 import static com.android.cts.shim.lib.ShimPackage.SHIM_PACKAGE_NAME;
22 
23 import static com.google.common.truth.Truth.assertThat;
24 import static com.google.common.truth.Truth.assertWithMessage;
25 
26 import static org.junit.Assume.assumeTrue;
27 
28 import android.cts.install.lib.host.InstallUtilsHost;
29 import android.platform.test.annotations.LargeTest;
30 
31 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
32 import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
33 import com.android.tradefed.util.AaptParser;
34 import com.android.tradefed.util.CommandResult;
35 import com.android.tradefed.util.CommandStatus;
36 import com.android.tradefed.util.FileUtil;
37 import com.android.tradefed.util.RunUtil;
38 import com.android.tradefed.util.ZipUtil;
39 
40 import org.junit.After;
41 import org.junit.Before;
42 import org.junit.Test;
43 import org.junit.runner.RunWith;
44 
45 import java.io.File;
46 import java.io.IOException;
47 import java.util.ArrayList;
48 import java.util.Arrays;
49 import java.util.Enumeration;
50 import java.util.List;
51 import java.util.stream.Collectors;
52 import java.util.zip.ZipEntry;
53 import java.util.zip.ZipFile;
54 
55 /**
56  * Tests to validate that only what is considered a correct shim apex can be installed.
57  *
58  * <p>Shim apex is considered correct iff:
59  * <ul>
60  *     <li>It doesn't have any pre or post install hooks.</li>
61  *     <li>It's {@code apex_payload.img} contains only a regular text file called
62  *         {@code hash.txt}.</li>
63  *     <li>It's {@code sha512} hash is whitelisted in the {@code hash.txt} of pre-installed on the
64  *         {@code /system} partition shim apex.</li>
65  * </ul>
66  */
67 @RunWith(DeviceJUnit4ClassRunner.class)
68 public class ApexShimValidationTest extends BaseHostJUnit4Test {
69 
70     private final InstallUtilsHost mHostUtils = new InstallUtilsHost(this);
71 
72     private static final String SHIM_APK_CODE_PATH_PREFIX = "/apex/" + SHIM_APEX_PACKAGE_NAME + "/";
73     private static final String STAGED_INSTALL_TEST_FILE_NAME = "StagedInstallTest.apk";
74     private static final String APEX_FILE_SUFFIX = ".apex";
75     private static final String DEAPEXER_ZIP_FILE_NAME = "deapexer.zip";
76     private static final String DEAPEXING_FOLDER_NAME = "deapexing_";
77     private static final String DEAPEXER_FILE_NAME = "deapexer";
78     private static final String DEBUGFS_STATIC_FILE_NAME = "debugfs_static";
79     private static final String BLKID_FILE_NAME = "blkid";
80     private static final String FSCKEROFS_FILE_NAME = "fsck.erofs";
81 
82     private static final long DEFAULT_RUN_TIMEOUT_MS = 30 * 1000L;
83 
84     private static final List<String> ALLOWED_SHIM_PACKAGE_NAMES = Arrays.asList(
85             SHIM_PACKAGE_NAME, PRIVILEGED_SHIM_PACKAGE_NAME);
86 
87     private File mDeapexingDir;
88     private File mDeapexerZip;
89     private File mAllApexesZip;
90 
91     /**
92      * Runs the given phase of a test by calling into the device.
93      * Throws an exception if the test phase fails.
94      * <p>
95      * For example, <code>runPhase("testInstallStagedApkCommit");</code>
96      */
runPhase(String phase)97     private void runPhase(String phase) throws Exception {
98         assertThat(runDeviceTests("com.android.tests.stagedinstall",
99                 "com.android.tests.stagedinstall.ApexShimValidationTest",
100                 phase)).isTrue();
101     }
102 
cleanUp()103     private void cleanUp() throws Exception {
104         assertThat(runDeviceTests("com.android.tests.stagedinstall",
105                 "com.android.tests.stagedinstall.StagedInstallTest",
106                 "cleanUp")).isTrue();
107         if (mDeapexingDir != null) {
108             FileUtil.recursiveDelete(mDeapexingDir);
109         }
110     }
111 
112     @Before
setUp()113     public void setUp() throws Exception {
114         assumeTrue("Device doesn't support updating APEX", mHostUtils.isApexUpdateSupported());
115         cleanUp();
116         mDeapexerZip = getTestInformation().getDependencyFile(DEAPEXER_ZIP_FILE_NAME, false);
117         mAllApexesZip = getTestInformation().getDependencyFile(STAGED_INSTALL_TEST_FILE_NAME,
118                 false);
119     }
120 
121     @After
tearDown()122     public void tearDown() throws Exception {
123         cleanUp();
124     }
125 
126     @Test
testShimApexIsPreInstalled()127     public void testShimApexIsPreInstalled() throws Exception {
128         boolean isShimApexPreInstalled =
129                 getDevice().getActiveApexes().stream().anyMatch(
130                         apex -> apex.name.equals(SHIM_APEX_PACKAGE_NAME));
131         assertWithMessage("Shim APEX is not pre-installed").that(
132                 isShimApexPreInstalled).isTrue();
133     }
134 
135     @Test
testPackageNameOfShimApkIsAllowed()136     public void testPackageNameOfShimApkIsAllowed() throws Exception {
137         final List<String> shimPackages = getDevice().getAppPackageInfos().stream()
138                 .filter(pkg -> pkg.getCodePath().startsWith(SHIM_APK_CODE_PATH_PREFIX))
139                 .map(pkg -> pkg.getPackageName()).collect(Collectors.toList());
140         assertWithMessage("Packages in the shim apex are not allowed")
141                 .that(shimPackages).containsExactlyElementsIn(ALLOWED_SHIM_PACKAGE_NAMES);
142     }
143 
144     /**
145      * Deapexing all the apexes bundled in the staged install test. Verifies the package name of
146      * shim apk in the apex.
147      */
148     @Test
testPackageNameOfShimApkInAllBundledApexesIsAllowed()149     public void testPackageNameOfShimApkInAllBundledApexesIsAllowed() throws Exception {
150         mDeapexingDir = FileUtil.createTempDir(DEAPEXING_FOLDER_NAME);
151         final File deapexer = extractDeapexer(mDeapexingDir);
152         final File debugfs = new File(mDeapexingDir, DEBUGFS_STATIC_FILE_NAME);
153         final File blkid = new File(mDeapexingDir, BLKID_FILE_NAME);
154         final File fsckerofs = new File(mDeapexingDir, FSCKEROFS_FILE_NAME);
155         final List<File> apexes = extractApexes(mDeapexingDir);
156         for (File apex : apexes) {
157             final File outDir = new File(apex.getParent(), apex.getName().substring(
158                     0, apex.getName().length() - APEX_FILE_SUFFIX.length()));
159             try {
160                 runDeapexerExtract(deapexer, debugfs, blkid, fsckerofs, apex, outDir);
161                 final List<File> apkFiles = FileUtil.findFiles(outDir, ".+\\.apk").stream()
162                         .map(str -> new File(str)).collect(Collectors.toList());
163                 for (File apkFile : apkFiles) {
164                     final AaptParser parser = AaptParser.parse(apkFile);
165                     assertWithMessage("Apk " + apkFile + " in apex " + apex + " is not valid")
166                             .that(parser).isNotNull();
167                     assertWithMessage("Apk " + apkFile + " in apex " + apex
168                             + " has incorrect package name " + parser.getPackageName())
169                             .that(ALLOWED_SHIM_PACKAGE_NAMES).contains(parser.getPackageName());
170                 }
171             } finally {
172                 FileUtil.recursiveDelete(outDir);
173             }
174         }
175     }
176 
177     @Test
178     @LargeTest
testRejectsApexWithAdditionalFile()179     public void testRejectsApexWithAdditionalFile() throws Exception {
180         runPhase("testRejectsApexWithAdditionalFile_Commit");
181         getDevice().reboot();
182         runPhase("testInstallRejected_VerifyPostReboot");
183     }
184 
185     @Test
186     @LargeTest
testRejectsApexWithAdditionalFolder()187     public void testRejectsApexWithAdditionalFolder() throws Exception {
188         runPhase("testRejectsApexWithAdditionalFolder_Commit");
189         getDevice().reboot();
190         runPhase("testInstallRejected_VerifyPostReboot");
191     }
192 
193     @Test
194     @LargeTest
testRejectsApexWithPostInstallHook()195     public void testRejectsApexWithPostInstallHook() throws Exception {
196         runPhase("testRejectsApexWithPostInstallHook_Commit");
197         getDevice().reboot();
198         runPhase("testInstallRejected_VerifyPostReboot");
199     }
200 
201     @Test
202     @LargeTest
testRejectsApexWithPreInstallHook()203     public void testRejectsApexWithPreInstallHook() throws Exception {
204         runPhase("testRejectsApexWithPreInstallHook_Commit");
205         getDevice().reboot();
206         runPhase("testInstallRejected_VerifyPostReboot");
207     }
208 
209     @Test
210     @LargeTest
testRejectsApexWrongSHA()211     public void testRejectsApexWrongSHA() throws Exception {
212         runPhase("testRejectsApexWrongSHA_Commit");
213         getDevice().reboot();
214         runPhase("testInstallRejected_VerifyPostReboot");
215     }
216 
217     @Test
testRejectsApexWithAdditionalFile_rebootless()218     public void testRejectsApexWithAdditionalFile_rebootless() throws Exception {
219         runPhase("testRejectsApexWithAdditionalFile_rebootless");
220     }
221 
222     @Test
testRejectsApexWithAdditionalFolder_rebootless()223     public void testRejectsApexWithAdditionalFolder_rebootless() throws Exception {
224         runPhase("testRejectsApexWithAdditionalFolder_rebootless");
225     }
226 
227     @Test
testRejectsApexWithPostInstallHook_rebootless()228     public void testRejectsApexWithPostInstallHook_rebootless() throws Exception {
229         runPhase("testRejectsApexWithPostInstallHook_rebootless");
230     }
231 
232     @Test
testRejectsApexWithPreInstallHook_rebootless()233     public void testRejectsApexWithPreInstallHook_rebootless() throws Exception {
234         runPhase("testRejectsApexWithPreInstallHook_rebootless");
235     }
236 
237     @Test
testRejectsApexWrongSHA_rebootless()238     public void testRejectsApexWrongSHA_rebootless() throws Exception {
239         runPhase("testRejectsApexWrongSHA_rebootless");
240     }
241 
242     /**
243      * Extracts {@link #DEAPEXER_ZIP_FILE_NAME} into the destination folder. Updates executable
244      * attribute for the binaries of deapexer and debugfs_static.
245      *
246      * @param destDir A tmp folder for the deapexing.
247      * @return the deapexer file.
248      */
extractDeapexer(File destDir)249     private File extractDeapexer(File destDir) throws IOException {
250         ZipUtil.extractZip(new ZipFile(mDeapexerZip), destDir);
251         final File deapexer = FileUtil.findFile(destDir, DEAPEXER_FILE_NAME);
252         assertWithMessage("Can't find " + DEAPEXER_FILE_NAME + " binary file")
253                 .that(deapexer).isNotNull();
254         deapexer.setExecutable(true);
255         final File debugfs = FileUtil.findFile(destDir, DEBUGFS_STATIC_FILE_NAME);
256         assertWithMessage("Can't find " + DEBUGFS_STATIC_FILE_NAME + " binary file")
257                 .that(debugfs).isNotNull();
258         debugfs.setExecutable(true);
259         final File blkid = FileUtil.findFile(destDir, BLKID_FILE_NAME);
260         assertWithMessage("Can't find " + BLKID_FILE_NAME + " binary file")
261                 .that(debugfs).isNotNull();
262         blkid.setExecutable(true);
263         final File fsckerofs = FileUtil.findFile(destDir, FSCKEROFS_FILE_NAME);
264         assertWithMessage("Can't find " + FSCKEROFS_FILE_NAME + " binary file")
265                 .that(debugfs).isNotNull();
266         fsckerofs.setExecutable(true);
267         return deapexer;
268     }
269 
270     /**
271      * Extracts all bundled apex files from {@link #STAGED_INSTALL_TEST_FILE_NAME} into the
272      * destination folder.
273      *
274      * @param destDir A tmp folder for the deapexing.
275      * @return A list of apex files.
276      */
extractApexes(File destDir)277     private List<File> extractApexes(File destDir) throws IOException {
278         final List<File> apexes = new ArrayList<>();
279         final ZipFile apexZip = new ZipFile(mAllApexesZip);
280         final Enumeration<? extends ZipEntry> entries = apexZip.entries();
281         while (entries.hasMoreElements()) {
282             final ZipEntry entry = entries.nextElement();
283             if (entry.isDirectory() || !entry.getName().matches(
284                     SHIM_APEX_PACKAGE_NAME + ".*\\" + APEX_FILE_SUFFIX)) {
285                 continue;
286             }
287             final File apex = new File(destDir, entry.getName());
288             apex.getParentFile().mkdirs();
289             FileUtil.writeToFile(apexZip.getInputStream(entry), apex);
290             apexes.add(apex);
291         }
292         assertWithMessage("No apex file in the " + mAllApexesZip)
293                 .that(apexes).isNotEmpty();
294         return apexes;
295     }
296 
297     /**
298      * Extracts all contents of the apex file into the {@code outDir} using the deapexer.
299      *
300      * @param deapexer The deapexer file.
301      * @param debugfs The debugfs file.
302      * @param apex The apex file to be extracted.
303      * @param outDir The out folder.
304      */
runDeapexerExtract(File deapexer, File debugfs, File blkid, File fsckerofs, File apex, File outDir)305     private void runDeapexerExtract(File deapexer, File debugfs, File blkid, File fsckerofs,
306         File apex, File outDir) {
307         final RunUtil runUtil = new RunUtil();
308         final String os = System.getProperty("os.name").toLowerCase();
309         final boolean isMacOs = (os.startsWith("mac") || os.startsWith("darwin"));
310         if (isMacOs) {
311             runUtil.setEnvVariable("DYLD_LIBRARY_PATH", mDeapexingDir.getAbsolutePath());
312         } else {
313             runUtil.setEnvVariable("LD_LIBRARY_PATH", mDeapexingDir.getAbsolutePath());
314         }
315         final CommandResult result = runUtil.runTimedCmd(DEFAULT_RUN_TIMEOUT_MS,
316                 deapexer.getAbsolutePath(),
317                 "--debugfs_path",
318                 debugfs.getAbsolutePath(),
319                 "--blkid_path",
320                 blkid.getAbsolutePath(),
321                 "--fsckerofs_path",
322                 fsckerofs.getAbsolutePath(),
323                 "extract",
324                 apex.getAbsolutePath(),
325                 outDir.getAbsolutePath());
326         assertWithMessage("deapexer(" + apex + ") failed: " + result)
327                 .that(result.getStatus()).isEqualTo(CommandStatus.SUCCESS);
328         assertWithMessage("deapexer(" + apex + ") failed: no outDir created")
329                 .that(outDir.exists()).isTrue();
330     }
331 }
332