Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

efi: Allow disabling PCI busmastering on bridges during boot

Add an option to disable the busmaster bit in the control register on
all PCI bridges before calling ExitBootServices() and passing control
to the runtime kernel. System firmware may configure the IOMMU to prevent
malicious PCI devices from being able to attack the OS via DMA. However,
since firmware can't guarantee that the OS is IOMMU-aware, it will tear
down IOMMU configuration when ExitBootServices() is called. This leaves
a window between where a hostile device could still cause damage before
Linux configures the IOMMU again.

If CONFIG_EFI_DISABLE_PCI_DMA is enabled or "efi=disable_early_pci_dma"
is passed on the command line, the EFI stub will clear the busmaster bit
on all PCI bridges before ExitBootServices() is called. This will
prevent any malicious PCI devices from being able to perform DMA until
the kernel reenables busmastering after configuring the IOMMU.

This option may cause failures with some poorly behaved hardware and
should not be enabled without testing. The kernel commandline options
"efi=disable_early_pci_dma" or "efi=no_disable_early_pci_dma" may be
used to override the default. Note that PCI devices downstream from PCI
bridges are disconnected from their drivers first, using the UEFI
driver model API, so that DMA can be disabled safely at the bridge
level.

[ardb: disconnect PCI I/O handles first, as suggested by Arvind]

Co-developed-by: Matthew Garrett <mjg59@google.com>
Signed-off-by: Matthew Garrett <mjg59@google.com>
Signed-off-by: Ard Biesheuvel <ardb@kernel.org>
Cc: Andy Lutomirski <luto@kernel.org>
Cc: Ard Biesheuvel <ard.biesheuvel@linaro.org>
Cc: Arvind Sankar <nivedita@alum.mit.edu>
Cc: Matthew Garrett <matthewgarrett@google.com>
Cc: linux-efi@vger.kernel.org
Link: https://lkml.kernel.org/r/20200103113953.9571-18-ardb@kernel.org
Signed-off-by: Ingo Molnar <mingo@kernel.org>

authored by

Matthew Garrett and committed by
Ingo Molnar
4444f854 ea7d87f9

+168 -3
+6 -1
Documentation/admin-guide/kernel-parameters.txt
··· 1165 1165 1166 1166 efi= [EFI] 1167 1167 Format: { "old_map", "nochunk", "noruntime", "debug", 1168 - "nosoftreserve" } 1168 + "nosoftreserve", "disable_early_pci_dma", 1169 + "no_disable_early_pci_dma" } 1169 1170 old_map [X86-64]: switch to the old ioremap-based EFI 1170 1171 runtime services mapping. 32-bit still uses this one by 1171 1172 default. ··· 1181 1180 claim. Specify efi=nosoftreserve to disable this 1182 1181 reservation and treat the memory by its base type 1183 1182 (i.e. EFI_CONVENTIONAL_MEMORY / "System RAM"). 1183 + disable_early_pci_dma: Disable the busmaster bit on all 1184 + PCI bridges while in the EFI boot stub 1185 + no_disable_early_pci_dma: Leave the busmaster bit set 1186 + on all PCI bridges while in the EFI boot stub 1184 1187 1185 1188 efi_no_storage_paranoia [EFI; X86] 1186 1189 Using this parameter you can use more than 50% of
+5
arch/x86/include/asm/efi.h
··· 283 283 #define __efi64_argmap_locate_protocol(protocol, reg, interface) \ 284 284 ((protocol), (reg), efi64_zero_upper(interface)) 285 285 286 + /* PCI I/O */ 287 + #define __efi64_argmap_get_location(protocol, seg, bus, dev, func) \ 288 + ((protocol), efi64_zero_upper(seg), efi64_zero_upper(bus), \ 289 + efi64_zero_upper(dev), efi64_zero_upper(func)) 290 + 286 291 /* 287 292 * The macros below handle the plumbing for the argument mapping. To add a 288 293 * mapping for a specific EFI method, simply define a macro
+22
drivers/firmware/efi/Kconfig
··· 215 215 216 216 Say Y here for Dell EMC PowerEdge systems. 217 217 218 + config EFI_DISABLE_PCI_DMA 219 + bool "Clear Busmaster bit on PCI bridges during ExitBootServices()" 220 + help 221 + Disable the busmaster bit in the control register on all PCI bridges 222 + while calling ExitBootServices() and passing control to the runtime 223 + kernel. System firmware may configure the IOMMU to prevent malicious 224 + PCI devices from being able to attack the OS via DMA. However, since 225 + firmware can't guarantee that the OS is IOMMU-aware, it will tear 226 + down IOMMU configuration when ExitBootServices() is called. This 227 + leaves a window between where a hostile device could still cause 228 + damage before Linux configures the IOMMU again. 229 + 230 + If you say Y here, the EFI stub will clear the busmaster bit on all 231 + PCI bridges before ExitBootServices() is called. This will prevent 232 + any malicious PCI devices from being able to perform DMA until the 233 + kernel reenables busmastering after configuring the IOMMU. 234 + 235 + This option will cause failures with some poorly behaved hardware 236 + and should not be enabled without testing. The kernel commandline 237 + options "efi=disable_early_pci_dma" or "efi=no_disable_early_pci_dma" 238 + may be used to override this option. 239 + 218 240 endmenu 219 241 220 242 config UEFI_CPER
+1 -1
drivers/firmware/efi/libstub/Makefile
··· 39 39 KCOV_INSTRUMENT := n 40 40 41 41 lib-y := efi-stub-helper.o gop.o secureboot.o tpm.o \ 42 - random.o 42 + random.o pci.o 43 43 44 44 # include the stub's generic dependencies from lib/ when building for ARM/arm64 45 45 arm-deps-y := fdt_rw.c fdt_ro.c fdt_wip.c fdt.c fdt_empty_tree.c fdt_sw.c
+15
drivers/firmware/efi/libstub/efi-stub-helper.c
··· 33 33 static bool __efistub_global efi_quiet; 34 34 static bool __efistub_global efi_novamap; 35 35 static bool __efistub_global efi_nosoftreserve; 36 + static bool __efistub_global efi_disable_pci_dma = 37 + IS_ENABLED(CONFIG_EFI_DISABLE_PCI_DMA); 36 38 37 39 bool __pure nokaslr(void) 38 40 { ··· 492 490 efi_nosoftreserve = true; 493 491 } 494 492 493 + if (!strncmp(str, "disable_early_pci_dma", 21)) { 494 + str += strlen("disable_early_pci_dma"); 495 + efi_disable_pci_dma = true; 496 + } 497 + 498 + if (!strncmp(str, "no_disable_early_pci_dma", 24)) { 499 + str += strlen("no_disable_early_pci_dma"); 500 + efi_disable_pci_dma = false; 501 + } 502 + 495 503 /* Group words together, delimited by "," */ 496 504 while (*str && *str != ' ' && *str != ',') 497 505 str++; ··· 887 875 status = priv_func(map, priv); 888 876 if (status != EFI_SUCCESS) 889 877 goto free_map; 878 + 879 + if (efi_disable_pci_dma) 880 + efi_pci_disable_bridge_busmaster(); 890 881 891 882 status = efi_bs_call(exit_boot_services, handle, *map->key_ptr); 892 883
+114
drivers/firmware/efi/libstub/pci.c
··· 1 + // SPDX-License-Identifier: GPL-2.0 2 + /* 3 + * PCI-related functions used by the EFI stub on multiple 4 + * architectures. 5 + * 6 + * Copyright 2019 Google, LLC 7 + */ 8 + 9 + #include <linux/efi.h> 10 + #include <linux/pci.h> 11 + 12 + #include <asm/efi.h> 13 + 14 + #include "efistub.h" 15 + 16 + void efi_pci_disable_bridge_busmaster(void) 17 + { 18 + efi_guid_t pci_proto = EFI_PCI_IO_PROTOCOL_GUID; 19 + unsigned long pci_handle_size = 0; 20 + efi_handle_t *pci_handle = NULL; 21 + efi_handle_t handle; 22 + efi_status_t status; 23 + u16 class, command; 24 + int i; 25 + 26 + status = efi_bs_call(locate_handle, EFI_LOCATE_BY_PROTOCOL, &pci_proto, 27 + NULL, &pci_handle_size, NULL); 28 + 29 + if (status != EFI_BUFFER_TOO_SMALL) { 30 + if (status != EFI_SUCCESS && status != EFI_NOT_FOUND) 31 + pr_efi_err("Failed to locate PCI I/O handles'\n"); 32 + return; 33 + } 34 + 35 + status = efi_bs_call(allocate_pool, EFI_LOADER_DATA, pci_handle_size, 36 + (void **)&pci_handle); 37 + if (status != EFI_SUCCESS) { 38 + pr_efi_err("Failed to allocate memory for 'pci_handle'\n"); 39 + return; 40 + } 41 + 42 + status = efi_bs_call(locate_handle, EFI_LOCATE_BY_PROTOCOL, &pci_proto, 43 + NULL, &pci_handle_size, pci_handle); 44 + if (status != EFI_SUCCESS) { 45 + pr_efi_err("Failed to locate PCI I/O handles'\n"); 46 + goto free_handle; 47 + } 48 + 49 + for_each_efi_handle(handle, pci_handle, pci_handle_size, i) { 50 + efi_pci_io_protocol_t *pci; 51 + unsigned long segment_nr, bus_nr, device_nr, func_nr; 52 + 53 + status = efi_bs_call(handle_protocol, handle, &pci_proto, 54 + (void **)&pci); 55 + if (status != EFI_SUCCESS) 56 + continue; 57 + 58 + /* 59 + * Disregard devices living on bus 0 - these are not behind a 60 + * bridge so no point in disconnecting them from their drivers. 61 + */ 62 + status = efi_call_proto(pci, get_location, &segment_nr, &bus_nr, 63 + &device_nr, &func_nr); 64 + if (status != EFI_SUCCESS || bus_nr == 0) 65 + continue; 66 + 67 + /* 68 + * Don't disconnect VGA controllers so we don't risk losing 69 + * access to the framebuffer. Drivers for true PCIe graphics 70 + * controllers that are behind a PCIe root port do not use 71 + * DMA to implement the GOP framebuffer anyway [although they 72 + * may use it in their implentation of Gop->Blt()], and so 73 + * disabling DMA in the PCI bridge should not interfere with 74 + * normal operation of the device. 75 + */ 76 + status = efi_call_proto(pci, pci.read, EfiPciIoWidthUint16, 77 + PCI_CLASS_DEVICE, 1, &class); 78 + if (status != EFI_SUCCESS || class == PCI_CLASS_DISPLAY_VGA) 79 + continue; 80 + 81 + /* Disconnect this handle from all its drivers */ 82 + efi_bs_call(disconnect_controller, handle, NULL, NULL); 83 + } 84 + 85 + for_each_efi_handle(handle, pci_handle, pci_handle_size, i) { 86 + efi_pci_io_protocol_t *pci; 87 + 88 + status = efi_bs_call(handle_protocol, handle, &pci_proto, 89 + (void **)&pci); 90 + if (status != EFI_SUCCESS || !pci) 91 + continue; 92 + 93 + status = efi_call_proto(pci, pci.read, EfiPciIoWidthUint16, 94 + PCI_CLASS_DEVICE, 1, &class); 95 + 96 + if (status != EFI_SUCCESS || class != PCI_CLASS_BRIDGE_PCI) 97 + continue; 98 + 99 + /* Disable busmastering */ 100 + status = efi_call_proto(pci, pci.read, EfiPciIoWidthUint16, 101 + PCI_COMMAND, 1, &command); 102 + if (status != EFI_SUCCESS || !(command & PCI_COMMAND_MASTER)) 103 + continue; 104 + 105 + command &= ~PCI_COMMAND_MASTER; 106 + status = efi_call_proto(pci, pci.write, EfiPciIoWidthUint16, 107 + PCI_COMMAND, 1, &command); 108 + if (status != EFI_SUCCESS) 109 + pr_efi_err("Failed to disable PCI busmastering\n"); 110 + } 111 + 112 + free_handle: 113 + efi_bs_call(free_pool, pci_handle); 114 + }
+5 -1
include/linux/efi.h
··· 319 319 void *stall; 320 320 void *set_watchdog_timer; 321 321 void *connect_controller; 322 - void *disconnect_controller; 322 + efi_status_t (__efiapi *disconnect_controller)(efi_handle_t, 323 + efi_handle_t, 324 + efi_handle_t); 323 325 void *open_protocol; 324 326 void *close_protocol; 325 327 void *open_protocol_information; ··· 1693 1691 1694 1692 #define EFI_MEMRESERVE_COUNT(size) (((size) - sizeof(struct linux_efi_memreserve)) \ 1695 1693 / sizeof(((struct linux_efi_memreserve *)0)->entry[0])) 1694 + 1695 + void efi_pci_disable_bridge_busmaster(void); 1696 1696 1697 1697 #endif /* _LINUX_EFI_H */