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 static org.junit.Assert.assertTrue;
20 import static org.junit.Assume.assumeTrue;
21 
22 import com.android.compatibility.common.util.CddTest;
23 import com.android.compatibility.common.util.CpuFeatures;
24 import com.android.compatibility.common.util.PropertyUtil;
25 import com.android.tradefed.build.IBuildInfo;
26 import com.android.tradefed.device.ITestDevice;
27 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
28 import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
29 
30 import org.junit.Before;
31 import org.junit.Test;
32 import org.junit.runner.RunWith;
33 
34 import java.io.BufferedReader;
35 import java.io.File;
36 import java.io.FileInputStream;
37 import java.io.InputStreamReader;
38 import java.util.HashMap;
39 import java.util.HashSet;
40 import java.util.Map;
41 import java.util.Set;
42 import java.util.stream.Collectors;
43 import java.util.zip.GZIPInputStream;
44 
45 /**
46  * Host-side kernel config tests.
47  *
48  * These tests analyze /proc/config.gz to verify that certain kernel config options are set.
49  */
50 @RunWith(DeviceJUnit4ClassRunner.class)
51 public class KernelConfigTest extends BaseHostJUnit4Test {
52 
53     private static final Map<ITestDevice, HashSet<String>> cachedConfigGzSet = new HashMap<>(1);
54 
55     private HashSet<String> configSet;
56 
57     private ITestDevice mDevice;
58     private IBuildInfo mBuild;
59 
60     @Before
setUp()61     public void setUp() throws Exception {
62         mDevice = getDevice();
63         mBuild = getBuild();
64         configSet = getDeviceConfig(mDevice, cachedConfigGzSet);
65         // Assumes every test in this file asserts a requirement of CDD section 9.
66         assumeSecurityModelCompat();
67     }
68 
69     /*
70      * IMPLEMENTATION DETAILS: Cache the configurations from /proc/config.gz on per-device basis
71      * in case CTS is being run against multiple devices at the same time. This speeds up testing
72      * by avoiding pulling/parsing the config file for each individual test
73      */
getDeviceConfig(ITestDevice device, Map<ITestDevice, HashSet<String>> cache)74     private static HashSet<String> getDeviceConfig(ITestDevice device,
75             Map<ITestDevice, HashSet<String>> cache) throws Exception {
76         if (!device.doesFileExist("/proc/config.gz")){
77             throw new Exception();
78         }
79         HashSet<String> set;
80         synchronized (cache) {
81             set = cache.get(device);
82         }
83         if (set != null) {
84             return set;
85         }
86         File file = File.createTempFile("config.gz", ".tmp");
87         file.deleteOnExit();
88         device.pullFile("/proc/config.gz", file);
89 
90         BufferedReader reader = new BufferedReader(new InputStreamReader(new GZIPInputStream(new FileInputStream(file))));
91         set = new HashSet<String>(reader.lines().collect(Collectors.toList()));
92 
93         synchronized (cache) {
94             cache.put(device, set);
95         }
96         return set;
97     }
98 
99     /**
100      * Test that the kernel has Stack Protector Strong enabled.
101      *
102      * @throws Exception
103      */
104     @CddTest(requirement="9.7")
105     @Test
testConfigStackProtectorStrong()106     public void testConfigStackProtectorStrong() throws Exception {
107         assertTrue("Linux kernel must have Stack Protector enabled: " +
108                 "CONFIG_STACKPROTECTOR_STRONG=y or CONFIG_CC_STACKPROTECTOR_STRONG=y",
109                 configSet.contains("CONFIG_STACKPROTECTOR_STRONG=y") ||
110                 configSet.contains("CONFIG_CC_STACKPROTECTOR_STRONG=y"));
111     }
112 
113     /**
114      * Test that the kernel's executable code is read-only, read-only data is non-executable and
115      * non-writable, and writable data is non-executable.
116      *
117      * @throws Exception
118      */
119     @CddTest(requirement="9.7")
120     @Test
testConfigROData()121     public void testConfigROData() throws Exception {
122         if (configSet.contains("CONFIG_UH_RKP=y"))
123             return;
124 
125         assertTrue("Linux kernel must have RO data enabled: " +
126                 "CONFIG_DEBUG_RODATA=y or CONFIG_STRICT_KERNEL_RWX=y",
127                 configSet.contains("CONFIG_DEBUG_RODATA=y") ||
128                 configSet.contains("CONFIG_STRICT_KERNEL_RWX=y"));
129 
130         if (configSet.contains("CONFIG_MODULES=y")) {
131             assertTrue("Linux kernel modules must also have RO data enabled: " +
132                     "CONFIG_DEBUG_SET_MODULE_RONX=y or CONFIG_STRICT_MODULE_RWX=y",
133                     configSet.contains("CONFIG_DEBUG_SET_MODULE_RONX=y") ||
134                     configSet.contains("CONFIG_STRICT_MODULE_RWX=y"));
135         }
136     }
137 
138     /**
139      * Test that the kernel implements static and dynamic object size bounds checking of copies
140      * between user-space and kernel-space.
141      *
142      * @throws Exception
143      */
144     @CddTest(requirement="9.7")
145     @Test
testConfigHardenedUsercopy()146     public void testConfigHardenedUsercopy() throws Exception {
147         if (PropertyUtil.getFirstApiLevel(mDevice) < 28) {
148             return;
149         }
150 
151         assertTrue("Linux kernel must have Hardened Usercopy enabled: CONFIG_HARDENED_USERCOPY=y",
152                 configSet.contains("CONFIG_HARDENED_USERCOPY=y"));
153     }
154 
155     /**
156      * Test that the kernel has PAN emulation enabled from architectures that support it.
157      *
158      * @throws Exception
159      */
160     @CddTest(requirement="9.7")
161     @Test
testConfigPAN()162     public void testConfigPAN() throws Exception {
163         if (PropertyUtil.getFirstApiLevel(mDevice) < 28) {
164             return;
165         }
166 
167         if (CpuFeatures.isArm64(mDevice)) {
168             assertTrue("Linux kernel must have PAN emulation enabled: " +
169                     "CONFIG_ARM64_SW_TTBR0_PAN=y or CONFIG_ARM64_PAN=y",
170                     (configSet.contains("CONFIG_ARM64_SW_TTBR0_PAN=y") ||
171                     configSet.contains("CONFIG_ARM64_PAN=y")));
172         } else if (CpuFeatures.isArm32(mDevice)) {
173             assertTrue("Linux kernel must have PAN emulation enabled: " +
174                     "CONFIG_CPU_SW_DOMAIN_PAN=y or CONFIG_CPU_TTBR0_PAN=y",
175                     (configSet.contains("CONFIG_CPU_SW_DOMAIN_PAN=y") ||
176                     configSet.contains("CONFIG_CPU_TTBR0_PAN=y")));
177         }
178     }
179 
getHardware()180     private String getHardware() throws Exception {
181         String hardware = "DEFAULT";
182         String[] pathList = new String[]{"/proc/cpuinfo", "/sys/devices/soc0/soc_id"};
183         String mitigationInfoMeltdown =
184                 mDevice.pullFileContents("/sys/devices/system/cpu/vulnerabilities/meltdown");
185         String mitigationInfoSpectreV2 =
186                 mDevice.pullFileContents("/sys/devices/system/cpu/vulnerabilities/spectre_v2");
187 
188         if (mitigationInfoMeltdown != null && mitigationInfoSpectreV2 != null &&
189             !mitigationInfoMeltdown.contains("Vulnerable") &&
190             (!mitigationInfoSpectreV2.contains("Vulnerable") ||
191               mitigationInfoSpectreV2.equals("Vulnerable: Unprivileged eBPF enabled\n")))
192                 return "VULN_SAFE";
193 
194         for (String nodeInfo : pathList) {
195             if (!mDevice.doesFileExist(nodeInfo))
196                 continue;
197 
198             String nodeContent = mDevice.pullFileContents(nodeInfo);
199             if (nodeContent == null)
200                 continue;
201 
202             for (String line : nodeContent.split("\n")) {
203                 /* Qualcomm SoCs */
204                 if (line.startsWith("Hardware")) {
205                     String[] hardwareLine = line.split(" ");
206                     hardware = hardwareLine[hardwareLine.length - 1];
207                     break;
208                 }
209                 /* Samsung Exynos SoCs */
210                 else if (line.startsWith("EXYNOS")) {
211                     hardware = line;
212                     break;
213                 }
214             }
215         }
216         /* TODO lookup other hardware as we get exemption requests. */
217         return hardwareMitigations.containsKey(hardware) ? hardware : "DEFAULT";
218     }
219 
doesFileExist(String filePath)220     private boolean doesFileExist(String filePath) throws Exception {
221         String lsGrep = mDevice.executeShellCommand(String.format("ls \"%s\"", filePath));
222         return lsGrep.trim().equals(filePath);
223     }
224 
createHardwareMitigations()225     private static Map<String, String[]> createHardwareMitigations() {
226         Map<String, String[]> result = new HashMap<>();
227         result.put("VULN_SAFE", null);
228         result.put("EXYNOS990", null);
229         result.put("EXYNOS980", null);
230         result.put("EXYNOS850", null);
231         result.put("EXYNOS3830", null);
232         result.put("EXYNOS9630", null);
233         result.put("EXYNOS9830", null);
234         result.put("EXYNOS7870", null);
235         result.put("EXYNOS7880", null);
236         result.put("EXYNOS7570", null);
237         result.put("EXYNOS7872", null);
238         result.put("EXYNOS7885", null);
239         result.put("EXYNOS9610", null);
240         result.put("Kirin980", null);
241         result.put("Kirin970", null);
242         result.put("Kirin810", null);
243         result.put("Kirin710", null);
244         result.put("MT6889Z/CZA", null);
245         result.put("MT6889Z/CIZA", null);
246         result.put("mt6873", null);
247         result.put("MT6853V/TZA", null);
248         result.put("MT6853V/TNZA", null);
249         result.put("MT6833V/ZA", null);
250         result.put("MT6833V/NZA", null);
251         result.put("MT6833V/TZA", null);
252         result.put("MT6833V/TNZA", null);
253         result.put("MT6833V/MZA", null);
254         result.put("MT6833V/MNZA", null);
255         result.put("MT6877V/ZA", null);
256         result.put("MT6877V/NZA", null);
257         result.put("MT6877V/TZA", null);
258         result.put("MT6877V/TNZA", null);
259         result.put("MT6768V/WA", null);
260         result.put("MT6768V/CA", null);
261         result.put("MT6768V/WB", null);
262         result.put("MT6768V/CB", null);
263         result.put("MT6767V/WA", null);
264         result.put("MT6767V/CA", null);
265         result.put("MT6767V/WB", null);
266         result.put("MT6767V/CB", null);
267         result.put("MT6769V/WA", null);
268         result.put("MT6769V/CA", null);
269         result.put("MT6769V/WB", null);
270         result.put("MT6769V/CB", null);
271         result.put("MT6769V/WT", null);
272         result.put("MT6769V/CT", null);
273         result.put("MT6769V/WU", null);
274         result.put("MT6769V/CU", null);
275         result.put("MT6769V/WZ", null);
276         result.put("MT6769V/CZ", null);
277         result.put("MT6769V/WY", null);
278         result.put("MT6769V/CY", null);
279         result.put("SDMMAGPIE", null);
280         result.put("SM6150", null);
281         result.put("SM7150", null);
282         result.put("SM7250", null);
283         result.put("LITO", null);
284         result.put("LAGOON", null);
285         result.put("SM8150", null);
286         result.put("SM8150P", null);
287         result.put("SM8250", null);
288         result.put("KONA", null);
289         result.put("SDM429", null);
290         result.put("SDM439", null);
291         result.put("QM215", null);
292         result.put("ATOLL", null);
293         result.put("ATOLL-AB", null);
294         result.put("SDM660", null);
295         result.put("BENGAL", null);
296         result.put("KHAJE", null);
297         result.put("BENGAL-IOT", null);
298         result.put("BENGALP-IOT", null);
299         result.put("SCUBAIIOT", null);
300         result.put("SCUBAPIIOT", null);
301         result.put("DEFAULT", new String[]{"CONFIG_UNMAP_KERNEL_AT_EL0=y"});
302         return result;
303     }
304 
305     private Map<String, String[]> hardwareMitigations = createHardwareMitigations();
306 
lookupMitigations()307     private String[] lookupMitigations() throws Exception {
308         return hardwareMitigations.get(getHardware());
309     }
310 
311     /**
312      * Test that the kernel has Spectre/Meltdown mitigations for architectures and kernel versions
313      * that support it. Exempt platforms which are known to not be vulnerable.
314      *
315      * @throws Exception
316      */
317     @CddTest(requirement="9.7")
318     @Test
testConfigHardwareMitigations()319     public void testConfigHardwareMitigations() throws Exception {
320         String mitigations[];
321 
322         if (PropertyUtil.getFirstApiLevel(mDevice) < 28) {
323             return;
324         }
325 
326         if (CpuFeatures.isArm64(mDevice) && !CpuFeatures.kernelVersionLessThan(mDevice, 4, 4)) {
327             mitigations = lookupMitigations();
328             if (mitigations != null) {
329                 for (String mitigation : mitigations) {
330                     assertTrue("Linux kernel must have " + mitigation + " enabled.",
331                             configSet.contains(mitigation));
332                 }
333             }
334         } else if (CpuFeatures.isX86(mDevice)) {
335             assertTrue("Linux kernel must have KPTI enabled: CONFIG_PAGE_TABLE_ISOLATION=y",
336                     configSet.contains("CONFIG_PAGE_TABLE_ISOLATION=y"));
337         }
338     }
339 
340     /**
341      * Test that the kernel enables static usermodehelper and sets
342      * the path to an allowlisted path.
343      *
344      * @throws Exception
345      */
346     @CddTest(requirement="9.7")
347     @Test
testConfigDisableUsermodehelper()348     public void testConfigDisableUsermodehelper() throws Exception {
349         if (PropertyUtil.getFirstApiLevel(mDevice) < 30) {
350             return;
351         }
352 
353         final String ENABLE_CONFIG = "CONFIG_STATIC_USERMODEHELPER=y";
354         final String PATH_CONFIG = "CONFIG_STATIC_USERMODEHELPER_PATH=";
355 
356         final Set<String> ALLOWED_PATH_PREFIXES = new HashSet<String>();
357         ALLOWED_PATH_PREFIXES.add("/vendor/");
358         ALLOWED_PATH_PREFIXES.add("/system/");
359         ALLOWED_PATH_PREFIXES.add("/system_ext/");
360 
361         assertTrue("Linux kernel must enable static usermodehelper: " + ENABLE_CONFIG,
362             configSet.contains(ENABLE_CONFIG));
363 
364         String configPath = null;
365 
366         for (String option : configSet) {
367             if (option.startsWith(PATH_CONFIG)) {
368                 configPath = option;
369             }
370         }
371 
372         int index = configPath.indexOf('=');
373         String path = configPath.substring(index+1).replace("\"", "");
374 
375         assertTrue("Linux kernel must specify an absolute path for static usermodehelper path",
376             configPath.contains("..") == false);
377 
378         boolean pathIsWhitelisted = false;
379 
380         for (String allowedPath : ALLOWED_PATH_PREFIXES) {
381             if (path.startsWith(allowedPath)) {
382                 pathIsWhitelisted = true;
383                 break;
384             }
385         }
386 
387         // Specifying no path, which disables usermodehelper, is also
388         // valid.
389         pathIsWhitelisted |= path.isEmpty();
390 
391         String whitelistedPathPrefixExample = "'" +
392             String.join("', '", ALLOWED_PATH_PREFIXES) + "'";
393 
394         assertTrue("Linux kernel must specify a whitelisted static usermodehelper path, "
395                    + "and it must be empty or start with one of the following "
396                    + "prefixes: " + whitelistedPathPrefixExample, pathIsWhitelisted);
397     }
398 
399     /**
400      * Test that the kernel enables fs-verity.
401      */
402     @CddTest(requirement="9.10")
403     @Test
testConfigFsVerity()404     public void testConfigFsVerity() throws Exception {
405         if (PropertyUtil.getFirstApiLevel(mDevice) < 30 &&
406                 PropertyUtil.getPropertyInt(mDevice, "ro.apk_verity.mode") != 2) {
407             return;
408         }
409         assertTrue("Linux kernel must have fs-verity enabled: CONFIG_FS_VERITY=y",
410                 configSet.contains("CONFIG_FS_VERITY=y"));
411     }
412 
assumeSecurityModelCompat()413     private void assumeSecurityModelCompat() throws Exception {
414         // This feature name check only applies to devices that first shipped with
415         // SC or later.
416         final int firstApiLevel = Math.min(PropertyUtil.getFirstApiLevel(mDevice),
417                 PropertyUtil.getVendorApiLevel(mDevice));
418         if (firstApiLevel >= 31) {
419             assumeTrue("Skipping test: FEATURE_SECURITY_MODEL_COMPATIBLE missing.",
420                     getDevice().hasFeature("feature:android.hardware.security.model.compatible"));
421         }
422     }
423 
424     /**
425      * Test that the kernel is using kASLR.
426      *
427      * @throws Exception
428      */
429     @CddTest(requirement="9.7")
430     @Test
testConfigRandomizeBase()431     public void testConfigRandomizeBase() throws Exception {
432         if (PropertyUtil.getFirstApiLevel(mDevice) < 33) {
433             return;
434         }
435 
436         if (CpuFeatures.isArm32(mDevice)) {
437             return;
438         }
439 
440         assertTrue("The kernel's base address must be randomized",
441                 configSet.contains("CONFIG_RANDOMIZE_BASE=y"));
442     }
443 
444     /**
445      * Test that CONFIG_VMAP_STACK is enabled on architectures that support it.
446      *
447      * @throws Exception
448      */
449     @CddTest(requirement="9.7")
450     @Test
testConfigVmapStack()451     public void testConfigVmapStack() throws Exception {
452         if (PropertyUtil.getFirstApiLevel(mDevice) < 33) {
453             return;
454         }
455 
456         if (!configSet.contains("CONFIG_HAVE_ARCH_VMAP_STACK=y")) {
457             return;
458         }
459 
460         assertTrue("CONFIG_VMAP_STACK must be enabled on architectures that support it.",
461                 configSet.contains("CONFIG_VMAP_STACK=y"));
462     }
463 }
464