• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
225     private Map<String, String[]> hardwareMitigations = new HashMap<String, String[]>() {
226     {
227         put("VULN_SAFE", null);
228         put("EXYNOS990", null);
229         put("EXYNOS980", null);
230         put("EXYNOS850", null);
231         put("EXYNOS3830", null);
232         put("EXYNOS9630", null);
233         put("EXYNOS9830", null);
234         put("EXYNOS7870", null);
235         put("EXYNOS7880", null);
236         put("EXYNOS7570", null);
237         put("EXYNOS7872", null);
238         put("EXYNOS7885", null);
239         put("EXYNOS9610", null);
240         put("Kirin980", null);
241         put("Kirin970", null);
242         put("Kirin810", null);
243         put("Kirin710", null);
244         put("MT6889Z/CZA", null);
245         put("MT6889Z/CIZA", null);
246         put("mt6873", null);
247         put("MT6853V/TZA", null);
248         put("MT6853V/TNZA", null);
249         put("MT6833V/ZA", null);
250         put("MT6833V/NZA", null);
251         put("MT6833V/TZA", null);
252         put("MT6833V/TNZA", null);
253         put("MT6833V/MZA", null);
254         put("MT6833V/MNZA", null);
255         put("MT6877V/ZA", null);
256         put("MT6877V/NZA", null);
257         put("MT6877V/TZA", null);
258         put("MT6877V/TNZA", null);
259         put("MT6768V/WA", null);
260         put("MT6768V/CA", null);
261         put("MT6768V/WB", null);
262         put("MT6768V/CB", null);
263         put("MT6767V/WA", null);
264         put("MT6767V/CA", null);
265         put("MT6767V/WB", null);
266         put("MT6767V/CB", null);
267         put("MT6769V/WA", null);
268         put("MT6769V/CA", null);
269         put("MT6769V/WB", null);
270         put("MT6769V/CB", null);
271         put("MT6769V/WT", null);
272         put("MT6769V/CT", null);
273         put("MT6769V/WU", null);
274         put("MT6769V/CU", null);
275         put("MT6769V/WZ", null);
276         put("MT6769V/CZ", null);
277         put("MT6769V/WY", null);
278         put("MT6769V/CY", null);
279         put("SDMMAGPIE", null);
280         put("SM6150", null);
281         put("SM7150", null);
282         put("SM7250", null);
283         put("LITO", null);
284         put("LAGOON", null);
285         put("SM8150", null);
286         put("SM8150P", null);
287         put("SM8250", null);
288         put("KONA", null);
289         put("SDM429", null);
290         put("SDM439", null);
291         put("QM215", null);
292         put("ATOLL", null);
293         put("ATOLL-AB", null);
294         put("SDM660", null);
295         put("BENGAL", null);
296         put("KHAJE", null);
297         put("BENGAL-IOT", null);
298         put("BENGALP-IOT", null);
299         put("DEFAULT", new String[]{"CONFIG_UNMAP_KERNEL_AT_EL0=y"});
300     }};
301 
lookupMitigations()302     private String[] lookupMitigations() throws Exception {
303         return hardwareMitigations.get(getHardware());
304     }
305 
306     /**
307      * Test that the kernel has Spectre/Meltdown mitigations for architectures and kernel versions
308      * that support it. Exempt platforms which are known to not be vulnerable.
309      *
310      * @throws Exception
311      */
312     @CddTest(requirement="9.7")
313     @Test
testConfigHardwareMitigations()314     public void testConfigHardwareMitigations() throws Exception {
315         String mitigations[];
316 
317         if (PropertyUtil.getFirstApiLevel(mDevice) < 28) {
318             return;
319         }
320 
321         if (CpuFeatures.isArm64(mDevice) && !CpuFeatures.kernelVersionLessThan(mDevice, 4, 4)) {
322             mitigations = lookupMitigations();
323             if (mitigations != null) {
324                 for (String mitigation : mitigations) {
325                     assertTrue("Linux kernel must have " + mitigation + " enabled.",
326                             configSet.contains(mitigation));
327                 }
328             }
329         } else if (CpuFeatures.isX86(mDevice)) {
330             assertTrue("Linux kernel must have KPTI enabled: CONFIG_PAGE_TABLE_ISOLATION=y",
331                     configSet.contains("CONFIG_PAGE_TABLE_ISOLATION=y"));
332         }
333     }
334 
335     /**
336      * Test that the kernel enables static usermodehelper and sets
337      * the path to a whitelisted path.
338      *
339      * @throws Exception
340      */
341     @CddTest(requirement="9.7")
342     @Test
testConfigDisableUsermodehelper()343     public void testConfigDisableUsermodehelper() throws Exception {
344         if (PropertyUtil.getFirstApiLevel(mDevice) < 30) {
345             return;
346         }
347 
348         final String ENABLE_CONFIG = "CONFIG_STATIC_USERMODEHELPER=y";
349         final String PATH_CONFIG = "CONFIG_STATIC_USERMODEHELPER_PATH=";
350 
351         final Set<String> ALLOWED_PATH_PREFIXES = new HashSet<String>();
352         ALLOWED_PATH_PREFIXES.add("/vendor/");
353         ALLOWED_PATH_PREFIXES.add("/system/");
354         ALLOWED_PATH_PREFIXES.add("/system_ext/");
355 
356         assertTrue("Linux kernel must enable static usermodehelper: " + ENABLE_CONFIG,
357             configSet.contains(ENABLE_CONFIG));
358 
359         String configPath = null;
360 
361         for (String option : configSet) {
362             if (option.startsWith(PATH_CONFIG)) {
363                 configPath = option;
364             }
365         }
366 
367         int index = configPath.indexOf('=');
368         String path = configPath.substring(index+1).replace("\"", "");
369 
370         assertTrue("Linux kernel must specify an absolute path for static usermodehelper path",
371             configPath.contains("..") == false);
372 
373         boolean pathIsWhitelisted = false;
374 
375         for (String allowedPath : ALLOWED_PATH_PREFIXES) {
376             if (path.startsWith(allowedPath)) {
377                 pathIsWhitelisted = true;
378                 break;
379             }
380         }
381 
382         // Specifying no path, which disables usermodehelper, is also
383         // valid.
384         pathIsWhitelisted |= path.isEmpty();
385 
386         String whitelistedPathPrefixExample = "'" +
387             String.join("', '", ALLOWED_PATH_PREFIXES) + "'";
388 
389         assertTrue("Linux kernel must specify a whitelisted static usermodehelper path, "
390                    + "and it must be empty or start with one of the following "
391                    + "prefixes: " + whitelistedPathPrefixExample, pathIsWhitelisted);
392     }
393 
394     /**
395      * Test that the kernel enables fs-verity and its built-in signature support.
396      */
397     @CddTest(requirement="9.10")
398     @Test
testConfigFsVerity()399     public void testConfigFsVerity() throws Exception {
400         if (PropertyUtil.getFirstApiLevel(mDevice) < 30 &&
401                 PropertyUtil.getPropertyInt(mDevice, "ro.apk_verity.mode") != 2) {
402             return;
403         }
404         assertTrue("Linux kernel must have fs-verity enabled: CONFIG_FS_VERITY=y",
405                 configSet.contains("CONFIG_FS_VERITY=y"));
406         assertTrue("Linux kernel must have fs-verity's builtin signature enabled: "
407                 + "CONFIG_FS_VERITY_BUILTIN_SIGNATURES=y",
408                 configSet.contains("CONFIG_FS_VERITY_BUILTIN_SIGNATURES=y"));
409     }
410 
assumeSecurityModelCompat()411     private void assumeSecurityModelCompat() throws Exception {
412         // This feature name check only applies to devices that first shipped with
413         // SC or later.
414         final int firstApiLevel = Math.min(PropertyUtil.getFirstApiLevel(mDevice),
415                 PropertyUtil.getVendorApiLevel(mDevice));
416         if (firstApiLevel >= 31) {
417             assumeTrue("Skipping test: FEATURE_SECURITY_MODEL_COMPATIBLE missing.",
418                     getDevice().hasFeature("feature:android.hardware.security.model.compatible"));
419         }
420     }
421 
422     /**
423      * Test that the kernel is using kASLR.
424      *
425      * @throws Exception
426      */
427     @CddTest(requirement="9.7")
428     @Test
testConfigRandomizeBase()429     public void testConfigRandomizeBase() throws Exception {
430         if (PropertyUtil.getFirstApiLevel(mDevice) < 33) {
431             return;
432         }
433 
434         if (CpuFeatures.isArm32(mDevice)) {
435             return;
436         }
437 
438         assertTrue("The kernel's base address must be randomized",
439                 configSet.contains("CONFIG_RANDOMIZE_BASE=y"));
440     }
441 
442     /**
443      * Test that CONFIG_VMAP_STACK is enabled on architectures that support it.
444      *
445      * @throws Exception
446      */
447     @CddTest(requirement="9.7")
448     @Test
testConfigVmapStack()449     public void testConfigVmapStack() throws Exception {
450         if (PropertyUtil.getFirstApiLevel(mDevice) < 33) {
451             return;
452         }
453 
454         if (!configSet.contains("CONFIG_HAVE_ARCH_VMAP_STACK=y")) {
455             return;
456         }
457 
458         assertTrue("CONFIG_VMAP_STACK must be enabled on architectures that support it.",
459                 configSet.contains("CONFIG_VMAP_STACK=y"));
460     }
461 }
462