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.vendoroverlay;
18 
19 import com.android.tradefed.device.DeviceNotAvailableException;
20 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
21 import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
22 import com.android.tradefed.util.CommandResult;
23 import com.android.tradefed.util.CommandStatus;
24 import java.util.regex.Matcher;
25 import java.util.regex.Pattern;
26 import org.junit.After;
27 import org.junit.Assert;
28 import org.junit.Assume;
29 import org.junit.Before;
30 import org.junit.Test;
31 import org.junit.runner.RunWith;
32 
33 /**
34  * Test the vendor overlay feature. Requires adb remount with OverlayFS.
35  */
36 @RunWith(DeviceJUnit4ClassRunner.class)
37 public class VendorOverlayHostTest extends BaseHostJUnit4Test {
38   boolean wasRoot = false;
39   String vndkVersion = null;
40 
41   @Before
setup()42   public void setup() throws DeviceNotAvailableException {
43     vndkVersion = getDevice().executeShellV2Command("getprop ro.vndk.version").getStdout();
44     Assume.assumeTrue(
45         "Vendor Overlay is disabled for VNDK deprecated devices",
46         vndkVersion != null && !vndkVersion.trim().isEmpty());
47 
48     wasRoot = getDevice().isAdbRoot();
49     if (!wasRoot) {
50       Assume.assumeTrue("Test requires root", getDevice().enableAdbRoot());
51     }
52 
53     Assume.assumeTrue("Skipping vendor overlay test due to lack of necessary OverlayFS support",
54         testConditionsMet());
55 
56     getDevice().remountSystemWritable();
57     // Was OverlayFS used by adb remount? Without it we can't safely re-enable dm-verity.
58     Pattern vendorPattern = Pattern.compile("^overlay .+ /vendor$", Pattern.MULTILINE);
59     Pattern productPattern = Pattern.compile("^overlay .+ /product$", Pattern.MULTILINE);
60     CommandResult result = getDevice().executeShellV2Command("df");
61     Assume.assumeTrue("OverlayFS not used for adb remount on /vendor",
62         vendorPattern.matcher(result.getStdout()).find());
63     Assume.assumeTrue("OverlayFS not used for adb remount on /product",
64         productPattern.matcher(result.getStdout()).find());
65   }
66 
cmdSucceeded(CommandResult result)67   private boolean cmdSucceeded(CommandResult result) {
68     return result.getStatus() == CommandStatus.SUCCESS;
69   }
70 
assumeMkdirSuccess(String dir)71   private void assumeMkdirSuccess(String dir) throws DeviceNotAvailableException {
72     CommandResult result = getDevice().executeShellV2Command("mkdir -p " + dir);
73     Assume.assumeTrue("Couldn't create " + dir, cmdSucceeded(result));
74   }
75 
76   /**
77    * Tests that files in the appropriate /product/vendor_overlay dir are overlaid onto /vendor.
78    */
79   @Test
testVendorOverlay()80   public void testVendorOverlay() throws DeviceNotAvailableException {
81     // Create files and modify policy
82     CommandResult result = getDevice().executeShellV2Command(
83         "echo '/(product|system/product)/vendor_overlay/" + vndkVersion +
84         "/.* u:object_r:vendor_file:s0'" + " >> /system/etc/selinux/plat_file_contexts");
85     Assume.assumeTrue("Couldn't modify plat_file_contexts", cmdSucceeded(result));
86     assumeMkdirSuccess("/vendor/testdir");
87     assumeMkdirSuccess("/vendor/diffcontext");
88     assumeMkdirSuccess("/product/vendor_overlay/'" + vndkVersion + "'/testdir");
89     result = getDevice().executeShellV2Command(
90         "echo overlay > /product/vendor_overlay/'" + vndkVersion + "'/testdir/test");
91     Assume.assumeTrue("Couldn't create text file in testdir", cmdSucceeded(result));
92     assumeMkdirSuccess("/product/vendor_overlay/'" + vndkVersion + "'/noexist/test");
93     assumeMkdirSuccess("/product/vendor_overlay/'" + vndkVersion + "'/diffcontext/test");
94     result = getDevice().executeShellV2Command(
95         "restorecon -r /product/vendor_overlay/'" + vndkVersion + "'/testdir");
96     Assume.assumeTrue("Couldn't write testdir context", cmdSucceeded(result));
97 
98     getDevice().reboot();
99 
100     // Test that the file was overlaid properly
101     result = getDevice().executeShellV2Command("[ $(cat /vendor/testdir/test) = overlay ]");
102     Assert.assertTrue("test file was not overlaid onto /vendor/", cmdSucceeded(result));
103     result = getDevice().executeShellV2Command("[ ! -d /vendor/noexist/test ]");
104     Assert.assertTrue("noexist dir shouldn't exist on /vendor", cmdSucceeded(result));
105     result = getDevice().executeShellV2Command("[ ! -d /vendor/diffcontext/test ]");
106     Assert.assertTrue("diffcontext dir shouldn't exist on /vendor", cmdSucceeded(result));
107   }
108 
109   // Duplicate of fs_mgr_overlayfs_valid() logic
110   // Requires root
testConditionsMet()111   public boolean testConditionsMet() throws DeviceNotAvailableException {
112     if (cmdSucceeded(getDevice().executeShellV2Command(
113         "[ -e /sys/module/overlay/parameters/override_creds ]"))) {
114       return true;
115     }
116     if (cmdSucceeded(getDevice().executeShellV2Command("[ ! -e /sys/module/overlay ]"))) {
117       return false;
118     }
119     CommandResult result = getDevice().executeShellV2Command("awk '{ print $3 }' /proc/version");
120     Pattern kernelVersionPattern = Pattern.compile("([1-9])[.]([0-9]+).*");
121     Matcher kernelVersionMatcher = kernelVersionPattern.matcher(result.getStdout());
122     kernelVersionMatcher.find();
123     int majorKernelVersion;
124     int minorKernelVersion;
125     try {
126       majorKernelVersion = Integer.parseInt(kernelVersionMatcher.group(1));
127       minorKernelVersion = Integer.parseInt(kernelVersionMatcher.group(2));
128     } catch (Exception e) {
129       return false;
130     }
131     if (majorKernelVersion < 4) {
132       return true;
133     }
134     if (majorKernelVersion > 4) {
135       return false;
136     }
137     if (minorKernelVersion > 6) {
138       return false;
139     }
140     return true;
141   }
142 
143   @After
tearDown()144   public void tearDown() throws DeviceNotAvailableException {
145     if (getDevice().executeAdbCommand("enable-verity").contains("Now reboot your device")) {
146       getDevice().reboot();
147     }
148     if (!wasRoot) {
149       getDevice().disableAdbRoot();
150     }
151   }
152 }
153 
154