blob: 2a1469fa296172a49f9f2cc6c3eaa3412d0692c2 [file] [log] [blame]
// Copyright 2019 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package kernel
import (
"compress/gzip"
"context"
"io"
"io/ioutil"
"os"
"regexp"
"strings"
"chromiumos/tast/common/testexec"
"chromiumos/tast/errors"
"chromiumos/tast/local/sysutil"
"chromiumos/tast/testing"
)
func init() {
testing.AddTest(&testing.Test{
Func: ConfigVerify,
Desc: "Examines a kernel build CONFIG list to make sure various things are present, missing, built as modules, etc",
Contacts: []string{
"vapier@chromium.org",
"chromeos-kernel-test@google.com",
"oka@chromium.org", // Tast port author
},
Attr: []string{"group:mainline"},
})
}
// ConfigVerify reads the Linux kernel version and arch to verify validity of
// the information returned depending on version.
func ConfigVerify(ctx context.Context, s *testing.State) {
ver, arch, err := sysutil.KernelVersionAndArch()
if err != nil {
s.Fatal("Failed to get kernel version and arch: ", err)
}
conf, err := readKernelConfig(ctx)
if err != nil {
s.Fatal("Failed to read kernel config: ", err)
}
newKernelConfigCheck(ver, arch).test(conf, s)
}
// readKernelConfig reads the kernel config key value pairs trimming CONFIG_ prefix from the keys.
func readKernelConfig(ctx context.Context) (map[string]string, error) {
configs, err := readKernelConfigBytes(ctx)
if err != nil {
return nil, err
}
res := make(map[string]string)
for _, line := range strings.Split(string(configs), "\n") {
line := strings.TrimSpace(line)
if line == "" || strings.HasPrefix(line, "#") {
continue
}
kv := strings.SplitN(line, "=", 2)
if len(kv) < 2 || kv[1] == "" {
return nil, errors.Errorf("unexpected config line %q", line)
}
const configPrefix = "CONFIG_"
if !strings.HasPrefix(kv[0], configPrefix) {
return nil, errors.Errorf("config %q doesn't start with %s unexpectedly", kv[0], configPrefix)
}
res[strings.TrimPrefix(kv[0], configPrefix)] = kv[1]
}
return res, nil
}
func readKernelConfigBytes(ctx context.Context) ([]byte, error) {
const filename = "/proc/config.gz"
// Load configs module to generate /proc/config.gz.
if err := testexec.CommandContext(ctx, "modprobe", "configs").Run(); err != nil {
return nil, errors.Wrap(err, "failed to generate kernel config file")
}
var r io.ReadCloser
f, err := os.Open(filename)
if err != nil {
testing.ContextLogf(ctx, "Falling back: failed to open %s: %v", filename, err)
u, err := sysutil.Uname()
if err != nil {
return nil, errors.Wrap(err, "failed to get uname")
}
fallbackFile := "/boot/config-" + u.Release
r, err = os.Open(fallbackFile)
if err != nil {
return nil, errors.Wrapf(err, "failed to open %s", fallbackFile)
}
} else { // Normal path.
defer f.Close()
r, err = gzip.NewReader(f)
if err != nil {
return nil, errors.Wrapf(err, "failed to create gzip reader for %s", filename)
}
}
defer r.Close()
configs, err := ioutil.ReadAll(r)
if err != nil {
return nil, errors.Wrap(err, "failed to read config")
}
return configs, nil
}
// kernelConfigCheck contains configs to check.
type kernelConfigCheck struct {
// exclusive contains regexes. The kernel config keys matching a regex should be listed in one of the following fields except missing. The keys are compared with removing the CONFIG_ prefix.
exclusive []*regexp.Regexp
// builtin contains keys that should be set to y.
builtin []string
// module contains keys that should be set to m.
module []string
// enabled contains keys that should be set to y or m.
enabled []string
// value contains key and value pairs that should be set.
value map[string]string
// same contains pairs of keys that should be set to the same value.
same [][2]string
// optional contains keys that may or may not exist.
optional []string
// missing contains keys that shouldn't exist.
missing []string
}
func newKernelConfigCheck(ver *sysutil.KernelVersion, arch string) *kernelConfigCheck {
exclusive := []*regexp.Regexp{
// Security; no surprise binary formats.
regexp.MustCompile(`^BINFMT_`),
// Security; no surprise filesystem formats.
regexp.MustCompile(`^.*_FS$`),
// Security; no surprise partition formats.
regexp.MustCompile(`^.*_PARTITION$`),
}
builtin := []string{
// Validity checks; should be present in builds as builtins.
"INET",
"MMU",
"MODULES",
"PRINTK",
"SECURITY",
// Security; enables the SECCOMP application API.
"SECCOMP",
// Security; blocks direct physical memory access.
"STRICT_DEVMEM",
// Security; provides some protections against SYN flooding.
"SYN_COOKIES",
// Security; make sure PID_NS, NET_NS, and USER_NS are enabled for
// Chrome's layer 1 sandbox.
"PID_NS",
"NET_NS",
"USER_NS",
// Security; perform additional validation of credentials.
"DEBUG_CREDENTIALS",
// Security; make sure the Chrome OS LSM is in use.
"SECURITY_CHROMIUMOS",
// Binary formats.
"BINFMT_ELF",
// Filesystem formats.
"DEBUG_FS",
"ECRYPT_FS",
"EXT4_FS",
"PROC_FS",
"SCSI_PROC_FS",
// Partition formats.
"EFI_PARTITION",
// MAC is for external drive formatted on Macintosh.
"MAC_PARTITION",
"MSDOS_PARTITION",
// Kernel hardening.
// Settings that are commented out need to be enabled in the kernel first.
// TODO(crbug.com/1061514): Start enabling these.
// CONFIG_SHUFFLE_PAGE_ALLOCATOR=y (since v5.2)
// CONFIG_INIT_ON_ALLOC_DEFAULT_ON=y (since v5.3)
}
module := []string{
// Validity checks; should be present in builds as modules.
"BLK_DEV_SR",
"BT",
"TUN",
// Useful modules for users that should not be removed.
"USB_SERIAL_OTI6858",
"FAT_FS",
"FUSE_FS",
"HFSPLUS_FS",
"ISO9660_FS",
"UDF_FS",
"VFAT_FS",
}
enabled := []string{
// Either module or enabled, depending on platform.
"VIDEO_V4L2",
}
value := map[string]string{
// Security; NULL-address hole should be as large as possible.
// Upstream kernel recommends 64k, which should be large enough
// to catch nearly all dereferenced structures. For
// compatibility with ARM binaries (even on x86) this needs to
// be 32k.
"DEFAULT_MMAP_MIN_ADDR": "32768",
// NaCl; allow mprotect+PROT_EXEC on noexec mapped files.
"MMAP_NOEXEC_TAINT": "0",
}
var same [][2]string
optional := []string{
// OVERLAY_FS is needed in moblab images, and allowed to exist in general. https://crbug.com/990741#c9
"OVERLAY_FS",
}
missing := []string{
// Never going to optimize to this CPU.
"M386",
// Config not in real kernel config var list.
"CHARLIE_THE_UNICORN",
// Dangerous; allows direct physical memory writing.
"ACPI_CUSTOM_METHOD",
// Dangerous; disables brk(2) ASLR.
"COMPAT_BRK",
// Dangerous; allows direct kernel memory writing.
"DEVKMEM",
// Dangerous; allows replacement of running kernel.
"KEXEC",
// Dangerous; allows replacement of running kernel.
"HIBERNATION",
// We don't need to provide access to *all* symbols in /proc/kallsyms.
"KALLSYMS_ALL",
// This callback can be subverted to point to arbitrary programs. We
// require firmware to be in the rootfs at normal locations which lets
// the kernel locate things itself.
"FW_LOADER_USER_HELPER",
"FW_LOADER_USER_HELPER_FALLBACK",
// Validity checks (binfmt); one disabled, one does not exist.
"BINFMT_AOUT",
"BINFMT_IMPOSSIBLE",
// Validity checks (fs); ones disabled, one does not exist.
"EXT2_FS",
"EXT3_FS",
"XFS_FS",
"IMPOSSIBLE_FS",
// Validity checks (partition); one disabled, one does not exist.
"LDM_PARTITION",
"IMPOSSIBLE_PARTITION",
}
if ver.IsOrLater(3, 10) {
builtin = append(builtin, "BINFMT_SCRIPT")
}
if ver.IsOrLater(3, 14) {
builtin = append(builtin, "BINFMT_SCRIPT", "BINFMT_MISC")
builtin = append(builtin, "HARDENED_USERCOPY")
module = append(module, "TEST_ASYNC_DRIVER_PROBE", "NFS_FS")
} else {
// Assists heap memory attacks; best to keep interface disabled.
missing = append(missing, "INET_DIAG")
}
if ver.IsOrLater(3, 18) {
builtin = append(builtin, "SND_PROC_FS", "USB_CONFIGFS_F_FS", "ESD_FS")
module = append(module, "USB_F_FS")
enabled = append(enabled, "CONFIGFS_FS")
// Like FW_LOADER_USER_HELPER, these may be exploited by userspace.
// We run udev everywhere which uses netlink sockets for event
// propagation rather than executing programs, so don't need this.
missing = append(missing, "UEVENT_HELPER", "UEVENT_HELPER_PATH")
}
if ver.IsOrLater(4, 4) {
// Security; make sure usermode helper is our tool for linux-4.4+.
builtin = append(builtin, "STATIC_USERMODEHELPER")
value["STATIC_USERMODEHELPER_PATH"] = `"/sbin/usermode-helper"`
// Security; prevent overflows that can be checked at compile-time.
builtin = append(builtin, "FORTIFY_SOURCE")
} else {
// For kernels older than linux-4.4.
builtin = append(builtin, "EXT4_USE_FOR_EXT23")
}
if arch == "aarch64" && ver.IsOrLater(4, 10) {
// Security; use software emulated Privileged Access Never (PAN).
builtin = append(builtin, "ARM64_SW_TTBR0_PAN")
}
// Security; marks data segments as RO/NX, text as RO.
if ver.IsOrLater(4, 11) {
builtin = append(builtin, "STRICT_KERNEL_RWX", "STRICT_MODULE_RWX")
} else {
builtin = append(builtin, "DEBUG_RODATA", "DEBUG_SET_MODULE_RONX")
}
if arch == "aarch64" && ver.IsOrLess(5, 6) {
builtin = append(builtin, "DEBUG_ALIGN_RODATA")
}
if ver.IsOrLater(4, 14) {
// Security; harden the SLAB/SLUB allocators against common freelist exploit methods.
builtin = append(builtin, "SLAB_FREELIST_RANDOM")
builtin = append(builtin, "SLAB_FREELIST_HARDENED")
// Security; initialize uninitialized local variables, variable fields, and padding.
// (Clang only).
if ver.IsOrLater(5, 4) {
builtin = append(builtin, "INIT_STACK_ALL_ZERO")
} else {
builtin = append(builtin, "INIT_STACK_ALL")
}
if arch != "armv7l" {
// Security; randomizes the virtual address at which the kernel image is loaded.
builtin = append(builtin, "RANDOMIZE_BASE")
// Security; virtually map the kernel stack to better defend against overflows.
builtin = append(builtin, "VMAP_STACK")
}
}
if arch == "aarch64" && ver.IsOrLater(4, 16) {
// Security: unmaps kernel from page tables at EL0 (KPTI)
builtin = append(builtin, "UNMAP_KERNEL_AT_EL0")
}
if ver.IsOrLater(4, 19) {
builtin = append(builtin, "HAVE_EBPF_JIT", "BPF_JIT_ALWAYS_ON", "STACKPROTECTOR")
} else {
// Security; adds stack buffer overflow protections.
builtin = append(builtin, "CC_STACKPROTECTOR")
// bpf(2) syscall can be used to generate code patterns in kernel memory.
missing = append(missing, "BPF_SYSCALL")
}
if arch == "aarch64" && ver.IsOrLater(5, 9) && ver.IsOrLess(5, 10) {
// SET_FS is used to mark architectures that have set_fs(). arm64 has this on 5.9 and 5.10 only.
builtin = append(builtin, "SET_FS")
}
isX86Family := regexp.MustCompile(`^i\d86$`).MatchString(arch) || arch == "x86_64"
if isX86Family {
// Kernel: make sure port 0xED is the one used for I/O delay.
builtin = append(builtin, "IO_DELAY_0XED")
if ver.IsOrLess(4, 19) {
same = append(same, [2]string{"IO_DELAY_TYPE_0XED", "DEFAULT_IO_DELAY_TYPE"})
}
// Security; make sure NX page table bits are usable.
if arch == "x86_64" {
builtin = append(builtin, "X86_64")
} else {
builtin = append(builtin, "X86_PAE")
}
// Kernel hardening.
builtin = append(builtin, "PAGE_TABLE_ISOLATION")
// builtin = append(builtin, "RANDOMIZE_MEMORY")
// Retpoline is a Spectre v2 mitigation.
if ver.IsOrLater(3, 18) {
builtin = append(builtin, "RETPOLINE")
}
// Dangerous; disables VDSO ASLR.
missing = append(missing, "COMPAT_VDSO")
}
return &kernelConfigCheck{
exclusive: exclusive,
builtin: builtin,
module: module,
enabled: enabled,
value: value,
same: same,
optional: optional,
missing: missing,
}
}
func (c *kernelConfigCheck) test(conf map[string]string, s *testing.State) {
for _, k := range c.builtin {
if got := conf[k]; got != "y" {
s.Errorf("%s: got %s, want y", k, got)
}
}
for _, k := range c.module {
if got := conf[k]; got != "m" {
s.Errorf("%s: got %s, want m", k, got)
}
}
for _, k := range c.enabled {
if got := conf[k]; got != "y" && got != "m" {
s.Errorf("%s: got %s, want y or m", k, got)
}
}
for k, want := range c.value {
if got := conf[k]; got != want {
s.Errorf("%s: got %s, want %v", k, got, want)
}
}
for _, k := range c.same {
if x, y := conf[k[0]], conf[k[1]]; x != y {
s.Errorf("Values of %s and %s should be the same but were %s and %s", k[0], k[1], x, y)
} else if x == "" {
s.Errorf("%s and %s should exist but didn't", k[0], k[1])
}
}
for _, k := range c.missing {
if got, ok := conf[k]; ok {
s.Errorf("%s should not exist but was %s", k, got)
}
}
// Test exclusive.
declared := make(map[string]bool)
for _, l := range [][]string{c.builtin, c.module, c.enabled, c.optional} {
for _, k := range l {
declared[k] = true
}
}
for k := range c.value {
declared[k] = true
}
for _, k := range c.same {
declared[k[0]] = true
declared[k[1]] = true
}
for _, r := range c.exclusive {
for k := range conf {
if r.MatchString(k) && !declared[k] {
// Construct error message.
var allowed []string
for d := range declared {
if r.MatchString(d) {
allowed = append(allowed, d)
}
}
s.Errorf("Setting %q found in config via %q when only %q are allowed", k, r, allowed)
}
}
}
}