-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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>
- Loading branch information
Matthew Garrett
authored and
Ingo Molnar
committed
Jan 10, 2020
1 parent
ea7d87f
commit 4444f85
Showing
7 changed files
with
168 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,114 @@ | ||
// SPDX-License-Identifier: GPL-2.0 | ||
/* | ||
* PCI-related functions used by the EFI stub on multiple | ||
* architectures. | ||
* | ||
* Copyright 2019 Google, LLC | ||
*/ | ||
|
||
#include <linux/efi.h> | ||
#include <linux/pci.h> | ||
|
||
#include <asm/efi.h> | ||
|
||
#include "efistub.h" | ||
|
||
void efi_pci_disable_bridge_busmaster(void) | ||
{ | ||
efi_guid_t pci_proto = EFI_PCI_IO_PROTOCOL_GUID; | ||
unsigned long pci_handle_size = 0; | ||
efi_handle_t *pci_handle = NULL; | ||
efi_handle_t handle; | ||
efi_status_t status; | ||
u16 class, command; | ||
int i; | ||
|
||
status = efi_bs_call(locate_handle, EFI_LOCATE_BY_PROTOCOL, &pci_proto, | ||
NULL, &pci_handle_size, NULL); | ||
|
||
if (status != EFI_BUFFER_TOO_SMALL) { | ||
if (status != EFI_SUCCESS && status != EFI_NOT_FOUND) | ||
pr_efi_err("Failed to locate PCI I/O handles'\n"); | ||
return; | ||
} | ||
|
||
status = efi_bs_call(allocate_pool, EFI_LOADER_DATA, pci_handle_size, | ||
(void **)&pci_handle); | ||
if (status != EFI_SUCCESS) { | ||
pr_efi_err("Failed to allocate memory for 'pci_handle'\n"); | ||
return; | ||
} | ||
|
||
status = efi_bs_call(locate_handle, EFI_LOCATE_BY_PROTOCOL, &pci_proto, | ||
NULL, &pci_handle_size, pci_handle); | ||
if (status != EFI_SUCCESS) { | ||
pr_efi_err("Failed to locate PCI I/O handles'\n"); | ||
goto free_handle; | ||
} | ||
|
||
for_each_efi_handle(handle, pci_handle, pci_handle_size, i) { | ||
efi_pci_io_protocol_t *pci; | ||
unsigned long segment_nr, bus_nr, device_nr, func_nr; | ||
|
||
status = efi_bs_call(handle_protocol, handle, &pci_proto, | ||
(void **)&pci); | ||
if (status != EFI_SUCCESS) | ||
continue; | ||
|
||
/* | ||
* Disregard devices living on bus 0 - these are not behind a | ||
* bridge so no point in disconnecting them from their drivers. | ||
*/ | ||
status = efi_call_proto(pci, get_location, &segment_nr, &bus_nr, | ||
&device_nr, &func_nr); | ||
if (status != EFI_SUCCESS || bus_nr == 0) | ||
continue; | ||
|
||
/* | ||
* Don't disconnect VGA controllers so we don't risk losing | ||
* access to the framebuffer. Drivers for true PCIe graphics | ||
* controllers that are behind a PCIe root port do not use | ||
* DMA to implement the GOP framebuffer anyway [although they | ||
* may use it in their implentation of Gop->Blt()], and so | ||
* disabling DMA in the PCI bridge should not interfere with | ||
* normal operation of the device. | ||
*/ | ||
status = efi_call_proto(pci, pci.read, EfiPciIoWidthUint16, | ||
PCI_CLASS_DEVICE, 1, &class); | ||
if (status != EFI_SUCCESS || class == PCI_CLASS_DISPLAY_VGA) | ||
continue; | ||
|
||
/* Disconnect this handle from all its drivers */ | ||
efi_bs_call(disconnect_controller, handle, NULL, NULL); | ||
} | ||
|
||
for_each_efi_handle(handle, pci_handle, pci_handle_size, i) { | ||
efi_pci_io_protocol_t *pci; | ||
|
||
status = efi_bs_call(handle_protocol, handle, &pci_proto, | ||
(void **)&pci); | ||
if (status != EFI_SUCCESS || !pci) | ||
continue; | ||
|
||
status = efi_call_proto(pci, pci.read, EfiPciIoWidthUint16, | ||
PCI_CLASS_DEVICE, 1, &class); | ||
|
||
if (status != EFI_SUCCESS || class != PCI_CLASS_BRIDGE_PCI) | ||
continue; | ||
|
||
/* Disable busmastering */ | ||
status = efi_call_proto(pci, pci.read, EfiPciIoWidthUint16, | ||
PCI_COMMAND, 1, &command); | ||
if (status != EFI_SUCCESS || !(command & PCI_COMMAND_MASTER)) | ||
continue; | ||
|
||
command &= ~PCI_COMMAND_MASTER; | ||
status = efi_call_proto(pci, pci.write, EfiPciIoWidthUint16, | ||
PCI_COMMAND, 1, &command); | ||
if (status != EFI_SUCCESS) | ||
pr_efi_err("Failed to disable PCI busmastering\n"); | ||
} | ||
|
||
free_handle: | ||
efi_bs_call(free_pool, pci_handle); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters