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