1 /*
2  * Copyright (C) 2020 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 android.appsecurity.cts;
18 
19 import static org.junit.Assert.fail;
20 import static org.junit.Assume.assumeTrue;
21 
22 import android.platform.test.annotations.AppModeFull;
23 
24 import com.android.compatibility.common.util.CddTest;
25 import com.android.tradefed.device.DeviceNotAvailableException;
26 import com.android.tradefed.device.ITestDevice;
27 import com.android.tradefed.testtype.junit4.DeviceParameterizedRunner;
28 import com.android.tradefed.testtype.junit4.DeviceTestRunOptions;
29 
30 import org.junit.After;
31 import org.junit.Before;
32 import org.junit.Test;
33 import org.junit.runner.RunWith;
34 
35 import java.util.HashMap;
36 
37 import junitparams.Parameters;
38 
39 @RunWith(DeviceParameterizedRunner.class)
40 @AppModeFull
41 public final class ApkVerityInstallTest extends BaseAppSecurityTest {
42 
43     private static final String PACKAGE_NAME = "android.appsecurity.cts.apkveritytestapp";
44 
45     private static final String BASE_APK = "CtsApkVerityTestAppPrebuilt.apk";
46     private static final String BASE_APK_DM = "CtsApkVerityTestAppPrebuilt.dm";
47     private static final String SPLIT_APK = "CtsApkVerityTestAppSplitPrebuilt.apk";
48     private static final String SPLIT_APK_DM = "CtsApkVerityTestAppSplitPrebuilt.dm";
49     private static final String BAD_BASE_APK = "CtsApkVerityTestApp2Prebuilt.apk";
50     private static final String BAD_BASE_APK_DM = "CtsApkVerityTestApp2Prebuilt.dm";
51     private static final String FSV_SIG_SUFFIX = ".fsv_sig";
52     private static final String ID_SIG_SUFFIX = ".idsig";
53     private static final String APK_VERITY_STANDARD_MODE = "2";
54 
55     private static final boolean INCREMENTAL = true;
56     private static final boolean NON_INCREMENTAL = false;
57 
58     private static final boolean SUPPORTED = true;
59     private static final boolean UNSUPPORTED = false;
60 
61     private static final HashMap<String, String> ORIGINAL_TO_INSTALL_NAME = new HashMap<>() {{
62         put(BASE_APK, "base.apk");
63         put(BASE_APK_DM, "base.dm");
64         put(SPLIT_APK, "split_feature_x.apk");
65         put(SPLIT_APK_DM, "split_feature_x.dm");
66     }};
67 
68     private boolean mDmRequireFsVerity;
69 
installSingle()70     private static final Object[] installSingle() {
71         // Non-Incremental and Incremental.
72         return new Boolean[][]{{NON_INCREMENTAL}, {INCREMENTAL}};
73     }
74 
installAndUpdate()75     private static final Object[] installAndUpdate() {
76         // Non-Incremental -> Non-Incremental: supported
77         // Incremental -> Non-Incremental: supported
78         // Incremental -> Incremental: supported
79         // Non-Incremental -> Incremental: unsupported
80         return new Boolean[][]{
81                 {NON_INCREMENTAL, NON_INCREMENTAL, SUPPORTED},
82                 {INCREMENTAL, NON_INCREMENTAL, SUPPORTED},
83                 {INCREMENTAL, INCREMENTAL, SUPPORTED},
84                 {NON_INCREMENTAL, INCREMENTAL, UNSUPPORTED}
85         };
86     }
87 
88     private int mLaunchApiLevel;
89     @Before
setUp()90     public void setUp() throws DeviceNotAvailableException {
91         ITestDevice device = getDevice();
92         String apkVerityMode = device.getProperty("ro.apk_verity.mode");
93         mLaunchApiLevel = device.getLaunchApiLevel();
94         assumeTrue(mLaunchApiLevel >= 30 || APK_VERITY_STANDARD_MODE.equals(apkVerityMode));
95         mDmRequireFsVerity = "true".equals(device.getProperty("pm.dexopt.dm.require_fsverity"));
96         assumeSecurityModelCompat();
97     }
98 
99     @After
tearDown()100     public void tearDown() throws DeviceNotAvailableException {
101         getDevice().uninstallPackage(PACKAGE_NAME);
102     }
103 
104     @CddTest(requirement = "9.10/C-0-3")
105     @Test
106     @Parameters(method = "installSingle")
testInstallBase(boolean incremental)107     public void testInstallBase(boolean incremental) throws Exception {
108         assumePreconditions(incremental);
109         new InstallMultiple(incremental)
110                 .addFile(BASE_APK)
111                 .addFile(BASE_APK + FSV_SIG_SUFFIX)
112                 .run();
113         verifyFsverityInstall(incremental, BASE_APK);
114     }
115 
116     @CddTest(requirement = "9.10/C-0-3")
117     @Test
118     @Parameters(method = "installSingle")
testInstallBaseWithWrongSignature(boolean incremental)119     public void testInstallBaseWithWrongSignature(boolean incremental) throws Exception {
120         assumePreconditions(incremental);
121         InstallMultiple install = new InstallMultiple(incremental)
122                 .addFile(BAD_BASE_APK)
123                 .addFile(BAD_BASE_APK + FSV_SIG_SUFFIX);
124 
125         // S with IncFsV1 silently skips fs-verity signatures.
126         boolean expectingSuccess = incremental && !isIncrementalDeliveryV2Feature();
127         install.run(expectingSuccess);
128     }
129 
130     @CddTest(requirement = "9.10/C-0-3,C-0-5")
131     @Test
132     @Parameters(method = "installSingle")
testInstallBaseWithSplit(boolean incremental)133     public void testInstallBaseWithSplit(boolean incremental) throws Exception {
134         assumePreconditions(incremental);
135         new InstallMultiple(incremental)
136                 .addFile(BASE_APK)
137                 .addFile(BASE_APK + FSV_SIG_SUFFIX)
138                 .addFile(SPLIT_APK)
139                 .addFile(SPLIT_APK + FSV_SIG_SUFFIX)
140                 .run();
141         verifyFsverityInstall(incremental, BASE_APK, SPLIT_APK);
142     }
143 
144     @CddTest(requirement = "9.10/C-0-3,C-0-5")
145     @Test
146     @Parameters(method = "installSingle")
testInstallBaseWithDm(boolean incremental)147     public void testInstallBaseWithDm(boolean incremental) throws Exception {
148         assumePreconditions(incremental);
149         new InstallMultiple(incremental)
150                 .addFile(BASE_APK)
151                 .addFile(BASE_APK + FSV_SIG_SUFFIX)
152                 .addFile(BASE_APK_DM)
153                 .addFile(BASE_APK_DM + FSV_SIG_SUFFIX)
154                 .run();
155         verifyFsverityInstall(incremental, BASE_APK, BASE_APK_DM);
156     }
157 
158     @CddTest(requirement = "9.10/C-0-3,C-0-5")
159     @Test
160     @Parameters(method = "installSingle")
testInstallEverything(boolean incremental)161     public void testInstallEverything(boolean incremental) throws Exception {
162         assumePreconditions(incremental);
163         new InstallMultiple(incremental)
164                 .addFile(BASE_APK)
165                 .addFile(BASE_APK + FSV_SIG_SUFFIX)
166                 .addFile(BASE_APK_DM)
167                 .addFile(BASE_APK_DM + FSV_SIG_SUFFIX)
168                 .addFile(SPLIT_APK)
169                 .addFile(SPLIT_APK + FSV_SIG_SUFFIX)
170                 .addFile(SPLIT_APK_DM)
171                 .addFile(SPLIT_APK_DM + FSV_SIG_SUFFIX)
172                 .run();
173         verifyFsverityInstall(incremental, BASE_APK, BASE_APK_DM, SPLIT_APK, SPLIT_APK_DM);
174     }
175 
176     @CddTest(requirement = "9.10/C-0-3,C-0-5")
177     @Test
178     @Parameters(method = "installAndUpdate")
testInstallSplitOnly(boolean installIncremental, boolean updateIncremental, boolean isSupported)179     public void testInstallSplitOnly(boolean installIncremental, boolean updateIncremental,
180             boolean isSupported) throws Exception {
181         assumePreconditions(installIncremental || updateIncremental);
182         new InstallMultiple(installIncremental)
183                 .addFile(BASE_APK)
184                 .addFile(BASE_APK + FSV_SIG_SUFFIX)
185                 .run();
186         verifyFsverityInstall(installIncremental, BASE_APK);
187 
188         new InstallMultiple(updateIncremental)
189                 .inheritFrom(PACKAGE_NAME)
190                 .addFile(SPLIT_APK)
191                 .addFile(SPLIT_APK + FSV_SIG_SUFFIX)
192                 .run(isSupported);
193         if (isSupported) {
194             verifyFsverityInstall(updateIncremental, BASE_APK, SPLIT_APK);
195         }
196     }
197 
198     @CddTest(requirement = "9.10/C-0-3,C-0-5")
199     @Test
200     @Parameters(method = "installAndUpdate")
testInstallSplitOnlyMissingSignature(boolean installIncremental, boolean updateIncremental, boolean isSupported)201     public void testInstallSplitOnlyMissingSignature(boolean installIncremental,
202             boolean updateIncremental, boolean isSupported) throws Exception {
203         assumePreconditions(installIncremental || updateIncremental);
204         new InstallMultiple(installIncremental)
205                 .addFile(BASE_APK)
206                 .addFile(BASE_APK + FSV_SIG_SUFFIX)
207                 .run();
208         verifyFsverityInstall(installIncremental, BASE_APK);
209 
210         InstallMultiple install = new InstallMultiple(updateIncremental)
211                 .inheritFrom(PACKAGE_NAME)
212                 .addFile(SPLIT_APK);
213 
214         // S with IncFsV1 silently skips fs-verity signatures.
215         boolean expectingSuccess =
216                 isSupported && installIncremental && !isIncrementalDeliveryV2Feature();
217         install.run(expectingSuccess);
218     }
219 
220     @CddTest(requirement = "9.10/C-0-3,C-0-5")
221     @Test
222     @Parameters(method = "installAndUpdate")
testInstallSplitOnlyWithoutBaseSignature(boolean installIncremental, boolean updateIncremental, boolean isSupported)223     public void testInstallSplitOnlyWithoutBaseSignature(boolean installIncremental,
224             boolean updateIncremental, boolean isSupported) throws Exception {
225         assumePreconditions(installIncremental || updateIncremental);
226         new InstallMultiple(installIncremental)
227                 .addFile(BASE_APK)
228                 .run();
229 
230         new InstallMultiple(updateIncremental)
231                 .inheritFrom(PACKAGE_NAME)
232                 .addFile(SPLIT_APK)
233                 .addFile(SPLIT_APK + FSV_SIG_SUFFIX)
234                 .run(isSupported);
235         if (isSupported) {
236             verifyFsverityInstall(updateIncremental, SPLIT_APK);
237         }
238     }
239 
240     @CddTest(requirement = "9.10/C-0-3,C-0-5")
241     @Test
242     @Parameters(method = "installAndUpdate")
testInstallSplitAndSignatureForBase(boolean installIncremental, boolean updateIncremental, boolean isSupported)243     public void testInstallSplitAndSignatureForBase(boolean installIncremental,
244             boolean updateIncremental, boolean isSupported) throws Exception {
245         assumePreconditions(installIncremental || updateIncremental);
246         new InstallMultiple(installIncremental)
247                 .addFile(BASE_APK)
248                 .run();
249 
250         new InstallMultiple(updateIncremental)
251                 .inheritFrom(PACKAGE_NAME)
252                 .addFile(BASE_APK)
253                 .addFile(BASE_APK + FSV_SIG_SUFFIX)
254                 .addFile(SPLIT_APK)
255                 .addFile(SPLIT_APK + FSV_SIG_SUFFIX)
256                 .run(isSupported);
257         if (isSupported) {
258             verifyFsverityInstall(updateIncremental, BASE_APK);
259         }
260     }
261 
262     @CddTest(requirement = "9.10/C-0-3,C-0-5")
263     @Test
264     @Parameters(method = "installAndUpdate")
testUpdateBaseWithSignature(boolean installIncremental, boolean updateIncremental, boolean isSupported)265     public void testUpdateBaseWithSignature(boolean installIncremental, boolean updateIncremental,
266             boolean isSupported) throws Exception {
267         assumePreconditions(installIncremental || updateIncremental);
268         new InstallMultiple(installIncremental)
269                 .addFile(BASE_APK)
270                 .addFile(BASE_APK + FSV_SIG_SUFFIX)
271                 .run();
272         verifyFsverityInstall(installIncremental, BASE_APK);
273 
274         new InstallMultiple(updateIncremental)
275                 .inheritFrom(PACKAGE_NAME)
276                 .addFile(BASE_APK)
277                 .addFile(BASE_APK + FSV_SIG_SUFFIX)
278                 .run(isSupported);
279         verifyFsverityInstall(updateIncremental, BASE_APK);
280     }
281 
282     @CddTest(requirement = "9.10/C-0-3,C-0-5")
283     @Test
284     @Parameters(method = "installSingle")
testInstallBaseWithFsvSigAndSplitWithout(boolean incremental)285     public void testInstallBaseWithFsvSigAndSplitWithout(boolean incremental) throws Exception {
286         assumePreconditions(incremental);
287         new InstallMultiple(incremental)
288                 .addFile(BASE_APK)
289                 .addFile(BASE_APK + FSV_SIG_SUFFIX)
290                 .addFile(BASE_APK_DM)
291                 .addFile(BASE_APK_DM + FSV_SIG_SUFFIX)
292                 .addFile(SPLIT_APK)
293                 .addFile(SPLIT_APK_DM)
294                 .addFile(SPLIT_APK_DM + FSV_SIG_SUFFIX)
295                 .runExpectingFailure();
296     }
297 
298     @CddTest(requirement = "9.10/C-0-3,C-0-5")
299     @Test
300     @Parameters(method = "installSingle")
testInstallDmWithFsvSig(boolean incremental)301     public void testInstallDmWithFsvSig(boolean incremental) throws Exception {
302         assumePreconditions(incremental);
303         new InstallMultiple(incremental)
304                 .addFile(BASE_APK)
305                 .addFile(BASE_APK_DM)
306                 .addFile(BASE_APK_DM + FSV_SIG_SUFFIX)
307                 .addFile(SPLIT_APK)
308                 .addFile(SPLIT_APK_DM)
309                 .addFile(SPLIT_APK_DM + FSV_SIG_SUFFIX)
310                 .run();
311         verifyFsverityInstall(incremental, BASE_APK_DM, SPLIT_APK_DM);
312     }
313 
314     @CddTest(requirement = "9.10/C-0-3,C-0-5")
315     @Test
316     @Parameters(method = "installSingle")
testInstallDmWithMissingFsvSig(boolean incremental)317     public void testInstallDmWithMissingFsvSig(boolean incremental) throws Exception {
318         assumePreconditions(incremental);
319         InstallMultiple installer = new InstallMultiple(incremental)
320                 .addFile(BASE_APK)
321                 .addFile(BASE_APK_DM)
322                 .addFile(BASE_APK_DM + FSV_SIG_SUFFIX)
323                 .addFile(SPLIT_APK)
324                 .addFile(SPLIT_APK_DM);
325         if (mDmRequireFsVerity) {
326             installer.runExpectingFailure();
327         } else {
328             installer.run();
329             verifyFsverityInstall(incremental, BASE_APK_DM);
330         }
331     }
332 
333     @CddTest(requirement = "9.10/C-0-3,C-0-5")
334     @Test
335     @Parameters(method = "installSingle")
testInstallSplitWithFsvSigAndBaseWithout(boolean incremental)336     public void testInstallSplitWithFsvSigAndBaseWithout(boolean incremental) throws Exception {
337         assumePreconditions(incremental);
338         InstallMultiple installer = new InstallMultiple(incremental)
339                 .addFile(BASE_APK)
340                 .addFile(BASE_APK_DM)
341                 .addFile(BASE_APK_DM + FSV_SIG_SUFFIX)
342                 .addFile(SPLIT_APK)
343                 .addFile(SPLIT_APK_DM)
344                 .addFile(SPLIT_APK_DM + FSV_SIG_SUFFIX);
345         if (mDmRequireFsVerity) {
346             installer.runExpectingFailure();
347         } else {
348             installer.run();
349             verifyFsverityInstall(incremental, BASE_APK_DM, SPLIT_APK_DM);
350         }
351     }
352 
353     @CddTest(requirement = "9.10/C-0-3,C-0-5")
354     @Test
355     @Parameters(method = "installAndUpdate")
testInstallBaseWithFsvSigThenSplitWithout(boolean installIncremental, boolean updateIncremental, boolean isSupported)356     public void testInstallBaseWithFsvSigThenSplitWithout(boolean installIncremental,
357             boolean updateIncremental, boolean isSupported) throws Exception {
358         assumePreconditions(installIncremental || updateIncremental);
359         new InstallMultiple(installIncremental)
360                 .addFile(BASE_APK)
361                 .addFile(BASE_APK + FSV_SIG_SUFFIX)
362                 .run();
363         verifyFsverityInstall(installIncremental, BASE_APK);
364 
365         new InstallMultiple(updateIncremental)
366                 .addFile(SPLIT_APK)
367                 .runExpectingFailure();
368     }
369 
370     @Test
testInstallBaseIncrementally()371     public void testInstallBaseIncrementally() throws Exception {
372         assumeTrue(hasIncrementalDeliveryFeature());
373         new InstallMultiple(/*incremental=*/true)
374                 .addFile(BASE_APK)
375                 .run();
376     }
377 
378     @Test
testInstallBaseIncrementallyWithFsvSig()379     public void testInstallBaseIncrementallyWithFsvSig() throws Exception {
380         assumeTrue(isIncrementalDeliveryV2Feature());
381         new InstallMultiple(/*incremental=*/true)
382                 .addFile(BASE_APK)
383                 .addFile(BASE_APK + FSV_SIG_SUFFIX)
384                 .run();
385         verifyFsverityInstall(true, BASE_APK);
386     }
387 
388     @Test
testInstallBaseIncrementallyWithFsvSigAndIdSig()389     public void testInstallBaseIncrementallyWithFsvSigAndIdSig() throws Exception {
390         assumeTrue(isIncrementalDeliveryV2Feature());
391         new InstallMultiple(/*incremental=*/true)
392                 .addFile(BASE_APK)
393                 .pushFile(BASE_APK + ID_SIG_SUFFIX)
394                 .addFile(BASE_APK + FSV_SIG_SUFFIX)
395                 .run();
396         verifyFsverityInstall(true, BASE_APK);
397     }
398 
399     @Test
testInstallBaseIncrementallyWithIdSigAndWrongFsvSig()400     public void testInstallBaseIncrementallyWithIdSigAndWrongFsvSig() throws Exception {
401         assumeTrue(isIncrementalDeliveryV2Feature());
402         new InstallMultiple(/*incremental=*/true)
403                 .addFile(BASE_APK)
404                 .pushFile(BASE_APK + ID_SIG_SUFFIX)
405                 .renameAndAddFile(BAD_BASE_APK + FSV_SIG_SUFFIX, BASE_APK + FSV_SIG_SUFFIX)
406                 .runExpectingFailure();
407     }
408 
409     @Test
testInstallBaseIncrementallyWithWrongIdSigAndFsvSig()410     public void testInstallBaseIncrementallyWithWrongIdSigAndFsvSig() throws Exception {
411         assumeTrue(isIncrementalDeliveryV2Feature());
412         new InstallMultiple(/*incremental=*/true)
413                 .addFile(BASE_APK)
414                 .renameAndPushFile(BAD_BASE_APK + ID_SIG_SUFFIX, BASE_APK + ID_SIG_SUFFIX)
415                 .addFile(BASE_APK + FSV_SIG_SUFFIX)
416                 .runExpectingFailure();
417     }
418 
assumePreconditions(boolean requiresIncremental)419     private void assumePreconditions(boolean requiresIncremental) throws Exception {
420         if (requiresIncremental) {
421             assumeTrue(hasIncrementalDeliveryFeature());
422         }
423     }
424 
hasIncrementalDeliveryFeature()425     private boolean hasIncrementalDeliveryFeature() throws Exception {
426         return "true\n".equals(getDevice().executeShellCommand(
427                 "pm has-feature android.software.incremental_delivery"));
428     }
429 
isIncrementalDeliveryV2Feature()430     private boolean isIncrementalDeliveryV2Feature() throws Exception {
431         return "true\n".equals(getDevice().executeShellCommand(
432                 "pm has-feature android.software.incremental_delivery 2"));
433     }
434 
assumeSecurityModelCompat()435     private void assumeSecurityModelCompat() throws DeviceNotAvailableException {
436         // This feature name check only applies to devices that first shipped with
437         // SC or later.
438         if (mLaunchApiLevel >= 31) {
439             assumeTrue("Skipping test: FEATURE_SECURITY_MODEL_COMPATIBLE missing.",
440                     getDevice().hasFeature("feature:android.hardware.security.model.compatible"));
441         }
442     }
443 
verifyFsverityInstall(boolean incremental, String... files)444     void verifyFsverityInstall(boolean incremental, String... files) throws Exception {
445         if (incremental && !isIncrementalDeliveryV2Feature()) {
446             return;
447         }
448 
449         DeviceTestRunOptions options = new DeviceTestRunOptions(PACKAGE_NAME);
450         options.setTestClassName(PACKAGE_NAME + ".InstalledFilesCheck");
451         options.setTestMethodName("testFilesHaveFsverity");
452         options.addInstrumentationArg("Number",
453                 Integer.toString(files.length));
454         for (int i = 0; i < files.length; ++i) {
455             String installName = ORIGINAL_TO_INSTALL_NAME.get(files[i]);
456             if (installName == null) {
457                 fail("Install name is not defined for " + files[i]);
458             }
459             options.addInstrumentationArg("File" + i, installName);
460         }
461         runDeviceTests(options);
462     }
463 
464     private class InstallMultiple extends BaseInstallMultiple<InstallMultiple> {
InstallMultiple(boolean incremental)465         InstallMultiple(boolean incremental) throws Exception {
466             super(getDevice(), getBuild(), getAbi());
467             if (incremental) {
468                 useIncremental();
469             }
470         }
471 
472         @Override
deriveRemoteName(String originalName, int index)473         protected String deriveRemoteName(String originalName, int index) {
474             return originalName;
475         }
476     }
477 }
478