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