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