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 cpuInfo = mDevice.pullFileContents("/proc/cpuinfo"); 184 185 for (String line : cpuInfo.split("\n")) { 186 /* Qualcomm SoCs */ 187 if (line.startsWith("Hardware")) { 188 String[] hardwareLine = line.split(" "); 189 hardware = hardwareLine[hardwareLine.length - 1]; 190 break; 191 } 192 } 193 /* TODO lookup other hardware as we get exemption requests. */ 194 return hardwareMitigations.containsKey(hardware) ? hardware : "DEFAULT"; 195 } 196 doesFileExist(String filePath)197 private boolean doesFileExist(String filePath) throws Exception { 198 String lsGrep = mDevice.executeShellCommand(String.format("ls \"%s\"", filePath)); 199 return lsGrep.trim().equals(filePath); 200 } 201 202 private Map<String, String[]> hardwareMitigations = new HashMap<String, String[]>() { 203 { 204 put("Kirin980", new String[]{"CONFIG_HARDEN_BRANCH_PREDICTOR=y"}); 205 put("Kirin970", new String[]{"CONFIG_HARDEN_BRANCH_PREDICTOR=y"}); 206 put("Kirin810", null); 207 put("Kirin710", new String[]{"CONFIG_HARDEN_BRANCH_PREDICTOR=y"}); 208 put("SM8150", new String[]{"CONFIG_HARDEN_BRANCH_PREDICTOR=y"}); 209 put("DEFAULT", new String[]{"CONFIG_HARDEN_BRANCH_PREDICTOR=y", 210 "CONFIG_UNMAP_KERNEL_AT_EL0=y"}); 211 }}; 212 lookupMitigations()213 private String[] lookupMitigations() throws Exception { 214 return hardwareMitigations.get(getHardware()); 215 } 216 217 /** 218 * Test that the kernel has Spectre/Meltdown mitigations for architectures and kernel versions 219 * that support it. Exempt platforms which are known to not be vulnerable. 220 * 221 * @throws Exception 222 */ 223 @CddTest(requirement="9.7") testConfigHardwareMitigations()224 public void testConfigHardwareMitigations() throws Exception { 225 if (PropertyUtil.getFirstApiLevel(mDevice) < 28) { 226 return; 227 } 228 229 if (CpuFeatures.isArm64(mDevice) && !CpuFeatures.kernelVersionLessThan(mDevice, 4, 4)) { 230 for (String mitigation : lookupMitigations()) { 231 assertTrue("Linux kernel must have " + mitigation + " enabled.", 232 configSet.contains(mitigation)); 233 } 234 } else if (CpuFeatures.isX86(mDevice)) { 235 assertTrue("Linux kernel must have KPTI enabled: CONFIG_PAGE_TABLE_ISOLATION=y", 236 configSet.contains("CONFIG_PAGE_TABLE_ISOLATION=y")); 237 } 238 } 239 } 240