Commit 4444f854 authored by Matthew Garrett's avatar Matthew Garrett Committed by Ingo Molnar
Browse files

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: default avatarMatthew Garrett <mjg59@google.com>
Signed-off-by: default avatarMatthew Garrett <mjg59@google.com>
Signed-off-by: default avatarArd 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: default avatarIngo Molnar <mingo@kernel.org>
parent ea7d87f9
Loading
Loading
Loading
Loading
+6 −1
Original line number Diff line number Diff line
@@ -1165,7 +1165,8 @@

	efi=		[EFI]
			Format: { "old_map", "nochunk", "noruntime", "debug",
				  "nosoftreserve" }
				  "nosoftreserve", "disable_early_pci_dma",
				  "no_disable_early_pci_dma" }
			old_map [X86-64]: switch to the old ioremap-based EFI
			runtime services mapping. 32-bit still uses this one by
			default.
@@ -1180,6 +1181,10 @@
			claim. Specify efi=nosoftreserve to disable this
			reservation and treat the memory by its base type
			(i.e. EFI_CONVENTIONAL_MEMORY / "System RAM").
			disable_early_pci_dma: Disable the busmaster bit on all
			PCI bridges while in the EFI boot stub
			no_disable_early_pci_dma: Leave the busmaster bit set
			on all PCI bridges while in the EFI boot stub

	efi_no_storage_paranoia [EFI; X86]
			Using this parameter you can use more than 50% of
+5 −0
Original line number Diff line number Diff line
@@ -283,6 +283,11 @@ static inline void *efi64_zero_upper(void *p)
#define __efi64_argmap_locate_protocol(protocol, reg, interface)	\
	((protocol), (reg), efi64_zero_upper(interface))

/* PCI I/O */
#define __efi64_argmap_get_location(protocol, seg, bus, dev, func)	\
	((protocol), efi64_zero_upper(seg), efi64_zero_upper(bus),	\
	 efi64_zero_upper(dev), efi64_zero_upper(func))

/*
 * The macros below handle the plumbing for the argument mapping. To add a
 * mapping for a specific EFI method, simply define a macro
+22 −0
Original line number Diff line number Diff line
@@ -215,6 +215,28 @@ config EFI_RCI2_TABLE

	  Say Y here for Dell EMC PowerEdge systems.

config EFI_DISABLE_PCI_DMA
       bool "Clear Busmaster bit on PCI bridges during ExitBootServices()"
       help
	  Disable the busmaster bit in the control register on all PCI bridges
	  while 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 you say Y here, 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 will 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 this option.

endmenu

config UEFI_CPER
+1 −1
Original line number Diff line number Diff line
@@ -39,7 +39,7 @@ OBJECT_FILES_NON_STANDARD := y
KCOV_INSTRUMENT			:= n

lib-y				:= efi-stub-helper.o gop.o secureboot.o tpm.o \
				   random.o
				   random.o pci.o

# include the stub's generic dependencies from lib/ when building for ARM/arm64
arm-deps-y := fdt_rw.c fdt_ro.c fdt_wip.c fdt.c fdt_empty_tree.c fdt_sw.c
+15 −0
Original line number Diff line number Diff line
@@ -33,6 +33,8 @@ static bool __efistub_global efi_nokaslr;
static bool __efistub_global efi_quiet;
static bool __efistub_global efi_novamap;
static bool __efistub_global efi_nosoftreserve;
static bool __efistub_global efi_disable_pci_dma =
					IS_ENABLED(CONFIG_EFI_DISABLE_PCI_DMA);

bool __pure nokaslr(void)
{
@@ -490,6 +492,16 @@ efi_status_t efi_parse_options(char const *cmdline)
			efi_nosoftreserve = true;
		}

		if (!strncmp(str, "disable_early_pci_dma", 21)) {
			str += strlen("disable_early_pci_dma");
			efi_disable_pci_dma = true;
		}

		if (!strncmp(str, "no_disable_early_pci_dma", 24)) {
			str += strlen("no_disable_early_pci_dma");
			efi_disable_pci_dma = false;
		}

		/* Group words together, delimited by "," */
		while (*str && *str != ' ' && *str != ',')
			str++;
@@ -876,6 +888,9 @@ efi_status_t efi_exit_boot_services(void *handle,
	if (status != EFI_SUCCESS)
		goto free_map;

	if (efi_disable_pci_dma)
		efi_pci_disable_bridge_busmaster();

	status = efi_bs_call(exit_boot_services, handle, *map->key_ptr);

	if (status == EFI_INVALID_PARAMETER) {
Loading