Skip to content

Commit 4444f85

Browse files
Matthew GarrettIngo Molnar
authored andcommitted
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 <[email protected]> Signed-off-by: Matthew Garrett <[email protected]> Signed-off-by: Ard Biesheuvel <[email protected]> Cc: Andy Lutomirski <[email protected]> Cc: Ard Biesheuvel <[email protected]> Cc: Arvind Sankar <[email protected]> Cc: Matthew Garrett <[email protected]> Cc: [email protected] Link: https://lkml.kernel.org/r/[email protected] Signed-off-by: Ingo Molnar <[email protected]>
1 parent ea7d87f commit 4444f85

File tree

7 files changed

+168
-3
lines changed

7 files changed

+168
-3
lines changed

Documentation/admin-guide/kernel-parameters.txt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1165,7 +1165,8 @@
11651165

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

11841189
efi_no_storage_paranoia [EFI; X86]
11851190
Using this parameter you can use more than 50% of

arch/x86/include/asm/efi.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,11 @@ static inline void *efi64_zero_upper(void *p)
283283
#define __efi64_argmap_locate_protocol(protocol, reg, interface) \
284284
((protocol), (reg), efi64_zero_upper(interface))
285285

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+
286291
/*
287292
* The macros below handle the plumbing for the argument mapping. To add a
288293
* mapping for a specific EFI method, simply define a macro

drivers/firmware/efi/Kconfig

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,28 @@ config EFI_RCI2_TABLE
215215

216216
Say Y here for Dell EMC PowerEdge systems.
217217

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+
218240
endmenu
219241

220242
config UEFI_CPER

drivers/firmware/efi/libstub/Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ OBJECT_FILES_NON_STANDARD := y
3939
KCOV_INSTRUMENT := n
4040

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

4444
# include the stub's generic dependencies from lib/ when building for ARM/arm64
4545
arm-deps-y := fdt_rw.c fdt_ro.c fdt_wip.c fdt.c fdt_empty_tree.c fdt_sw.c

drivers/firmware/efi/libstub/efi-stub-helper.c

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ static bool __efistub_global efi_nokaslr;
3333
static bool __efistub_global efi_quiet;
3434
static bool __efistub_global efi_novamap;
3535
static bool __efistub_global efi_nosoftreserve;
36+
static bool __efistub_global efi_disable_pci_dma =
37+
IS_ENABLED(CONFIG_EFI_DISABLE_PCI_DMA);
3638

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

495+
if (!strncmp(str, "disable_early_pci_dma", 21)) {
496+
str += strlen("disable_early_pci_dma");
497+
efi_disable_pci_dma = true;
498+
}
499+
500+
if (!strncmp(str, "no_disable_early_pci_dma", 24)) {
501+
str += strlen("no_disable_early_pci_dma");
502+
efi_disable_pci_dma = false;
503+
}
504+
493505
/* Group words together, delimited by "," */
494506
while (*str && *str != ' ' && *str != ',')
495507
str++;
@@ -876,6 +888,9 @@ efi_status_t efi_exit_boot_services(void *handle,
876888
if (status != EFI_SUCCESS)
877889
goto free_map;
878890

891+
if (efi_disable_pci_dma)
892+
efi_pci_disable_bridge_busmaster();
893+
879894
status = efi_bs_call(exit_boot_services, handle, *map->key_ptr);
880895

881896
if (status == EFI_INVALID_PARAMETER) {

drivers/firmware/efi/libstub/pci.c

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
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+
}

include/linux/efi.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -319,7 +319,9 @@ typedef union {
319319
void *stall;
320320
void *set_watchdog_timer;
321321
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);
323325
void *open_protocol;
324326
void *close_protocol;
325327
void *open_protocol_information;
@@ -1692,4 +1694,6 @@ struct linux_efi_memreserve {
16921694
#define EFI_MEMRESERVE_COUNT(size) (((size) - sizeof(struct linux_efi_memreserve)) \
16931695
/ sizeof(((struct linux_efi_memreserve *)0)->entry[0]))
16941696

1697+
void efi_pci_disable_bridge_busmaster(void);
1698+
16951699
#endif /* _LINUX_EFI_H */

0 commit comments

Comments
 (0)