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