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