blob: 113ec76ddd27a72c84300be0fedb7f6b6bfa0773 [file] [log] [blame]
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.security.cts;
import com.android.tradefed.build.IBuildInfo;
import com.android.tradefed.device.ITestDevice;
import com.android.tradefed.testtype.DeviceTestCase;
import com.android.tradefed.testtype.IBuildReceiver;
import com.android.tradefed.testtype.IDeviceTest;
import com.android.compatibility.common.util.CddTest;
import com.android.compatibility.common.util.CpuFeatures;
import com.android.compatibility.common.util.PropertyUtil;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.lang.String;
import java.util.stream.Collectors;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.zip.GZIPInputStream;
/**
* Host-side kernel config tests.
*
* These tests analyze /proc/config.gz to verify that certain kernel config options are set.
*/
public class KernelConfigTest extends DeviceTestCase implements IBuildReceiver, IDeviceTest {
private static final Map<ITestDevice, HashSet<String>> cachedConfigGzSet = new HashMap<>(1);
private HashSet<String> configSet;
private ITestDevice mDevice;
private IBuildInfo mBuild;
/**
* {@inheritDoc}
*/
@Override
public void setBuild(IBuildInfo build) {
mBuild = build;
}
/**
* {@inheritDoc}
*/
@Override
public void setDevice(ITestDevice device) {
super.setDevice(device);
mDevice = device;
}
@Override
protected void setUp() throws Exception {
super.setUp();
configSet = getDeviceConfig(mDevice, cachedConfigGzSet);
}
/*
* IMPLEMENTATION DETAILS: Cache the configurations from /proc/config.gz on per-device basis
* in case CTS is being run against multiple devices at the same time. This speeds up testing
* by avoiding pulling/parsing the config file for each individual test
*/
private static HashSet<String> getDeviceConfig(ITestDevice device,
Map<ITestDevice, HashSet<String>> cache) throws Exception {
if (!device.doesFileExist("/proc/config.gz")){
throw new Exception();
}
HashSet<String> set;
synchronized (cache) {
set = cache.get(device);
}
if (set != null) {
return set;
}
File file = File.createTempFile("config.gz", ".tmp");
file.deleteOnExit();
device.pullFile("/proc/config.gz", file);
BufferedReader reader = new BufferedReader(new InputStreamReader(new GZIPInputStream(new FileInputStream(file))));
set = new HashSet<String>(reader.lines().collect(Collectors.toList()));
synchronized (cache) {
cache.put(device, set);
}
return set;
}
/**
* Test that the kernel has Stack Protector Strong enabled.
*
* @throws Exception
*/
@CddTest(requirement="9.7")
public void testConfigStackProtectorStrong() throws Exception {
assertTrue("Linux kernel must have Stack Protector enabled: " +
"CONFIG_STACKPROTECTOR_STRONG=y or CONFIG_CC_STACKPROTECTOR_STRONG=y",
configSet.contains("CONFIG_STACKPROTECTOR_STRONG=y") ||
configSet.contains("CONFIG_CC_STACKPROTECTOR_STRONG=y"));
}
/**
* Test that the kernel's executable code is read-only, read-only data is non-executable and
* non-writable, and writable data is non-executable.
*
* @throws Exception
*/
@CddTest(requirement="9.7")
public void testConfigROData() throws Exception {
if (configSet.contains("CONFIG_UH_RKP=y"))
return;
assertTrue("Linux kernel must have RO data enabled: " +
"CONFIG_DEBUG_RODATA=y or CONFIG_STRICT_KERNEL_RWX=y",
configSet.contains("CONFIG_DEBUG_RODATA=y") ||
configSet.contains("CONFIG_STRICT_KERNEL_RWX=y"));
if (configSet.contains("CONFIG_MODULES=y")) {
assertTrue("Linux kernel modules must also have RO data enabled: " +
"CONFIG_DEBUG_SET_MODULE_RONX=y or CONFIG_STRICT_MODULE_RWX=y",
configSet.contains("CONFIG_DEBUG_SET_MODULE_RONX=y") ||
configSet.contains("CONFIG_STRICT_MODULE_RWX=y"));
}
}
/**
* Test that the kernel implements static and dynamic object size bounds checking of copies
* between user-space and kernel-space.
*
* @throws Exception
*/
@CddTest(requirement="9.7")
public void testConfigHardenedUsercopy() throws Exception {
if (PropertyUtil.getFirstApiLevel(mDevice) < 28) {
return;
}
assertTrue("Linux kernel must have Hardened Usercopy enabled: CONFIG_HARDENED_USERCOPY=y",
configSet.contains("CONFIG_HARDENED_USERCOPY=y"));
}
/**
* Test that the kernel has PAN emulation enabled from architectures that support it.
*
* @throws Exception
*/
@CddTest(requirement="9.7")
public void testConfigPAN() throws Exception {
if (PropertyUtil.getFirstApiLevel(mDevice) < 28) {
return;
}
if (CpuFeatures.isArm64(mDevice)) {
assertTrue("Linux kernel must have PAN emulation enabled: " +
"CONFIG_ARM64_SW_TTBR0_PAN=y or CONFIG_ARM64_PAN=y",
(configSet.contains("CONFIG_ARM64_SW_TTBR0_PAN=y") ||
configSet.contains("CONFIG_ARM64_PAN=y")));
} else if (CpuFeatures.isArm32(mDevice)) {
assertTrue("Linux kernel must have PAN emulation enabled: CONFIG_CPU_SW_DOMAIN_PAN=y",
configSet.contains("CONFIG_CPU_SW_DOMAIN_PAN=y"));
}
}
private String getHardware() throws Exception {
String hardware = "DEFAULT";
String cpuInfo = mDevice.pullFileContents("/proc/cpuinfo");
for (String line : cpuInfo.split("\n")) {
/* Qualcomm SoCs */
if (line.startsWith("Hardware")) {
String[] hardwareLine = line.split(" ");
hardware = hardwareLine[hardwareLine.length - 1];
break;
}
}
/* TODO lookup other hardware as we get exemption requests. */
return hardwareMitigations.containsKey(hardware) ? hardware : "DEFAULT";
}
private boolean doesFileExist(String filePath) throws Exception {
String lsGrep = mDevice.executeShellCommand(String.format("ls \"%s\"", filePath));
return lsGrep.trim().equals(filePath);
}
private Map<String, String[]> hardwareMitigations = new HashMap<String, String[]>() {
{
put("Kirin980", new String[]{"CONFIG_HARDEN_BRANCH_PREDICTOR=y"});
put("Kirin970", new String[]{"CONFIG_HARDEN_BRANCH_PREDICTOR=y"});
put("Kirin810", null);
put("Kirin710", new String[]{"CONFIG_HARDEN_BRANCH_PREDICTOR=y"});
put("SM8150", new String[]{"CONFIG_HARDEN_BRANCH_PREDICTOR=y"});
put("DEFAULT", new String[]{"CONFIG_HARDEN_BRANCH_PREDICTOR=y",
"CONFIG_UNMAP_KERNEL_AT_EL0=y"});
}};
private String[] lookupMitigations() throws Exception {
return hardwareMitigations.get(getHardware());
}
/**
* Test that the kernel has Spectre/Meltdown mitigations for architectures and kernel versions
* that support it. Exempt platforms which are known to not be vulnerable.
*
* @throws Exception
*/
@CddTest(requirement="9.7")
public void testConfigHardwareMitigations() throws Exception {
if (PropertyUtil.getFirstApiLevel(mDevice) < 28) {
return;
}
if (CpuFeatures.isArm64(mDevice) && !CpuFeatures.kernelVersionLessThan(mDevice, 4, 4)) {
for (String mitigation : lookupMitigations()) {
assertTrue("Linux kernel must have " + mitigation + " enabled.",
configSet.contains(mitigation));
}
} else if (CpuFeatures.isX86(mDevice)) {
assertTrue("Linux kernel must have KPTI enabled: CONFIG_PAGE_TABLE_ISOLATION=y",
configSet.contains("CONFIG_PAGE_TABLE_ISOLATION=y"));
}
}
}