1 /*
2  * Copyright (C) 2018 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.security.cts;
18 
19 import com.android.tradefed.build.IBuildInfo;
20 import com.android.tradefed.device.ITestDevice;
21 import com.android.tradefed.testtype.DeviceTestCase;
22 import com.android.tradefed.testtype.IBuildReceiver;
23 import com.android.tradefed.testtype.IDeviceTest;
24 import com.android.compatibility.common.util.CddTest;
25 import com.android.compatibility.common.util.CpuFeatures;
26 import com.android.compatibility.common.util.PropertyUtil;
27 
28 import java.io.BufferedReader;
29 import java.io.File;
30 import java.io.FileInputStream;
31 import java.io.InputStreamReader;
32 import java.lang.String;
33 import java.util.stream.Collectors;
34 import java.util.HashMap;
35 import java.util.HashSet;
36 import java.util.Map;
37 import java.util.zip.GZIPInputStream;
38 
39 /**
40  * Host-side kernel config tests.
41  *
42  * These tests analyze /proc/config.gz to verify that certain kernel config options are set.
43  */
44 public class KernelConfigTest extends DeviceTestCase implements IBuildReceiver, IDeviceTest {
45 
46     private static final Map<ITestDevice, HashSet<String>> cachedConfigGzSet = new HashMap<>(1);
47 
48     private HashSet<String> configSet;
49 
50     private ITestDevice mDevice;
51     private IBuildInfo mBuild;
52 
53     /**
54      * {@inheritDoc}
55      */
56     @Override
setBuild(IBuildInfo build)57     public void setBuild(IBuildInfo build) {
58         mBuild = build;
59     }
60 
61     /**
62      * {@inheritDoc}
63      */
64     @Override
setDevice(ITestDevice device)65     public void setDevice(ITestDevice device) {
66         super.setDevice(device);
67         mDevice = device;
68     }
69 
70     @Override
setUp()71     protected void setUp() throws Exception {
72         super.setUp();
73         configSet = getDeviceConfig(mDevice, cachedConfigGzSet);
74     }
75 
76     /*
77      * IMPLEMENTATION DETAILS: Cache the configurations from /proc/config.gz on per-device basis
78      * in case CTS is being run against multiple devices at the same time. This speeds up testing
79      * by avoiding pulling/parsing the config file for each individual test
80      */
getDeviceConfig(ITestDevice device, Map<ITestDevice, HashSet<String>> cache)81     private static HashSet<String> getDeviceConfig(ITestDevice device,
82             Map<ITestDevice, HashSet<String>> cache) throws Exception {
83         if (!device.doesFileExist("/proc/config.gz")){
84             throw new Exception();
85         }
86         HashSet<String> set;
87         synchronized (cache) {
88             set = cache.get(device);
89         }
90         if (set != null) {
91             return set;
92         }
93         File file = File.createTempFile("config.gz", ".tmp");
94         file.deleteOnExit();
95         device.pullFile("/proc/config.gz", file);
96 
97         BufferedReader reader = new BufferedReader(new InputStreamReader(new GZIPInputStream(new FileInputStream(file))));
98         set = new HashSet<String>(reader.lines().collect(Collectors.toList()));
99 
100         synchronized (cache) {
101             cache.put(device, set);
102         }
103         return set;
104     }
105 
106     /**
107      * Test that the kernel has Stack Protector Strong enabled.
108      *
109      * @throws Exception
110      */
111     @CddTest(requirement="9.7")
testConfigStackProtectorStrong()112     public void testConfigStackProtectorStrong() throws Exception {
113         assertTrue("Linux kernel must have Stack Protector enabled: " +
114                 "CONFIG_STACKPROTECTOR_STRONG=y or CONFIG_CC_STACKPROTECTOR_STRONG=y",
115                 configSet.contains("CONFIG_STACKPROTECTOR_STRONG=y") ||
116                 configSet.contains("CONFIG_CC_STACKPROTECTOR_STRONG=y"));
117     }
118 
119     /**
120      * Test that the kernel's executable code is read-only, read-only data is non-executable and
121      * non-writable, and writable data is non-executable.
122      *
123      * @throws Exception
124      */
125     @CddTest(requirement="9.7")
testConfigROData()126     public void testConfigROData() throws Exception {
127         if (configSet.contains("CONFIG_UH_RKP=y"))
128             return;
129 
130         assertTrue("Linux kernel must have RO data enabled: " +
131                 "CONFIG_DEBUG_RODATA=y or CONFIG_STRICT_KERNEL_RWX=y",
132                 configSet.contains("CONFIG_DEBUG_RODATA=y") ||
133                 configSet.contains("CONFIG_STRICT_KERNEL_RWX=y"));
134 
135         if (configSet.contains("CONFIG_MODULES=y")) {
136             assertTrue("Linux kernel modules must also have RO data enabled: " +
137                     "CONFIG_DEBUG_SET_MODULE_RONX=y or CONFIG_STRICT_MODULE_RWX=y",
138                     configSet.contains("CONFIG_DEBUG_SET_MODULE_RONX=y") ||
139                     configSet.contains("CONFIG_STRICT_MODULE_RWX=y"));
140         }
141     }
142 
143     /**
144      * Test that the kernel implements static and dynamic object size bounds checking of copies
145      * between user-space and kernel-space.
146      *
147      * @throws Exception
148      */
149     @CddTest(requirement="9.7")
testConfigHardenedUsercopy()150     public void testConfigHardenedUsercopy() throws Exception {
151         if (PropertyUtil.getFirstApiLevel(mDevice) < 28) {
152             return;
153         }
154 
155         assertTrue("Linux kernel must have Hardened Usercopy enabled: CONFIG_HARDENED_USERCOPY=y",
156                 configSet.contains("CONFIG_HARDENED_USERCOPY=y"));
157     }
158 
159     /**
160      * Test that the kernel has PAN emulation enabled from architectures that support it.
161      *
162      * @throws Exception
163      */
164     @CddTest(requirement="9.7")
testConfigPAN()165     public void testConfigPAN() throws Exception {
166         if (PropertyUtil.getFirstApiLevel(mDevice) < 28) {
167             return;
168         }
169 
170         if (CpuFeatures.isArm64(mDevice)) {
171             assertTrue("Linux kernel must have PAN emulation enabled: " +
172                     "CONFIG_ARM64_SW_TTBR0_PAN=y or CONFIG_ARM64_PAN=y",
173                     (configSet.contains("CONFIG_ARM64_SW_TTBR0_PAN=y") ||
174                     configSet.contains("CONFIG_ARM64_PAN=y")));
175         } else if (CpuFeatures.isArm32(mDevice)) {
176             assertTrue("Linux kernel must have PAN emulation enabled: CONFIG_CPU_SW_DOMAIN_PAN=y",
177                     configSet.contains("CONFIG_CPU_SW_DOMAIN_PAN=y"));
178         }
179     }
180 
getHardware()181     private String getHardware() throws Exception {
182         String hardware = "DEFAULT";
183         String[] pathList = new String[]{"/proc/cpuinfo", "/sys/devices/soc0/soc_id"};
184 
185         for (String nodeInfo : pathList) {
186             if (!mDevice.doesFileExist(nodeInfo))
187                 continue;
188 
189             String nodeContent = mDevice.pullFileContents(nodeInfo);
190             if (nodeContent == null)
191                 continue;
192 
193             for (String line : nodeContent.split("\n")) {
194                 /* Qualcomm SoCs */
195                 if (line.startsWith("Hardware")) {
196                     String[] hardwareLine = line.split(" ");
197                     hardware = hardwareLine[hardwareLine.length - 1];
198                     break;
199                 }
200                 /* Samsung Exynos SoCs */
201                 else if (line.startsWith("EXYNOS")) {
202                     hardware = line;
203                     break;
204                 }
205             }
206         }
207         /* TODO lookup other hardware as we get exemption requests. */
208         return hardwareMitigations.containsKey(hardware) ? hardware : "DEFAULT";
209     }
210 
doesFileExist(String filePath)211     private boolean doesFileExist(String filePath) throws Exception {
212         String lsGrep = mDevice.executeShellCommand(String.format("ls \"%s\"", filePath));
213         return lsGrep.trim().equals(filePath);
214     }
215 
216     private Map<String, String[]> hardwareMitigations = new HashMap<String, String[]>() {
217     {
218         put("EXYNOS990", null);
219         put("EXYNOS980", null);
220         put("EXYNOS850", null);
221         put("EXYNOS3830", null);
222         put("EXYNOS9630", null);
223         put("EXYNOS9830", null);
224         put("EXYNOS7870", null);
225         put("EXYNOS7880", null);
226         put("EXYNOS7570", null);
227         put("EXYNOS7872", new String[]{"CONFIG_HARDEN_BRANCH_PREDICTOR=y"});
228         put("EXYNOS7885", new String[]{"CONFIG_HARDEN_BRANCH_PREDICTOR=y"});
229         put("EXYNOS9610", new String[]{"CONFIG_HARDEN_BRANCH_PREDICTOR=y"});
230         put("Kirin980", new String[]{"CONFIG_HARDEN_BRANCH_PREDICTOR=y"});
231         put("Kirin970", new String[]{"CONFIG_HARDEN_BRANCH_PREDICTOR=y"});
232         put("Kirin810", null);
233         put("Kirin710", new String[]{"CONFIG_HARDEN_BRANCH_PREDICTOR=y"});
234         put("SM6150", new String[]{"CONFIG_HARDEN_BRANCH_PREDICTOR=y"});
235         put("SM7150", new String[]{"CONFIG_HARDEN_BRANCH_PREDICTOR=y"});
236         put("SM7250", null);
237         put("LITO", null);
238         put("LAGOON", null);
239         put("SM8150", new String[]{"CONFIG_HARDEN_BRANCH_PREDICTOR=y"});
240         put("SM8150P", new String[]{"CONFIG_HARDEN_BRANCH_PREDICTOR=y"});
241         put("SM8250", null);
242         put("KONA", null);
243         put("SDM429", null);
244         put("SDM439", null);
245         put("QM215", null);
246         put("BENGAL", new String[]{"CONFIG_HARDEN_BRANCH_PREDICTOR=y"});
247         put("DEFAULT", new String[]{"CONFIG_HARDEN_BRANCH_PREDICTOR=y",
248             "CONFIG_UNMAP_KERNEL_AT_EL0=y"});
249     }};
250 
lookupMitigations()251     private String[] lookupMitigations() throws Exception {
252         return hardwareMitigations.get(getHardware());
253     }
254 
255     /**
256      * Test that the kernel has Spectre/Meltdown mitigations for architectures and kernel versions
257      * that support it. Exempt platforms which are known to not be vulnerable.
258      *
259      * @throws Exception
260      */
261     @CddTest(requirement="9.7")
testConfigHardwareMitigations()262     public void testConfigHardwareMitigations() throws Exception {
263         String mitigations[];
264 
265         if (PropertyUtil.getFirstApiLevel(mDevice) < 28) {
266             return;
267         }
268 
269         if (CpuFeatures.isArm64(mDevice) && !CpuFeatures.kernelVersionLessThan(mDevice, 4, 4)) {
270             mitigations = lookupMitigations();
271             if (mitigations != null) {
272                 for (String mitigation : mitigations) {
273                     assertTrue("Linux kernel must have " + mitigation + " enabled.",
274                             configSet.contains(mitigation));
275                 }
276             }
277         } else if (CpuFeatures.isX86(mDevice)) {
278             assertTrue("Linux kernel must have KPTI enabled: CONFIG_PAGE_TABLE_ISOLATION=y",
279                     configSet.contains("CONFIG_PAGE_TABLE_ISOLATION=y"));
280         }
281     }
282 }
283