lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [day] [month] [year] [list]
Message-ID: <20251012125218.45c5f972.michal.pecio@gmail.com>
Date: Sun, 12 Oct 2025 12:52:18 +0200
From: Michal Pecio <michal.pecio@...il.com>
To: iommu@...ts.linux.dev, linux-kernel@...r.kernel.org,
 linux-pci@...r.kernel.org
Subject: [PATCH RFC] Implement DMA Guard Pages

Hi all,

I wonder if there is any interest in a feature like this?

DMA Guard Pages are unmapped pages inserted between consecutive DMA
mappings for devices behind IOMMUs. A device accessing its mappings
out of bounds will hopefully fault instead of hitting other memory.

I wrote this hack yesterday to debug such a device, since getting
an IOMMU fault is easier to detect and more convenient than looking
at some weird malfunction and wondering where it came from.

(BTW, can a PCI driver "catch" IOMMU faults by its devices?)

It looks like a useful aid for PCI driver developers and testers,
or maybe somebody would want this in regular use for reliability?

Honestly, I was surprised that no such thing (apparently?) exists.
So I dug into dma-iommu.c and wrote my own. The implementation is
trivial, it hooks into iommu_dma_alloc_iova()/iommu_dma_free_iova()
which appear to be a bottleneck where all iova (de)allocations for
DMA mappings must pass. The allocations are increased a little, but
callers are unaware of that and only map what they wanted to map.

It even seems to work, but beware it's first time I touch this code.

Michal

---
 drivers/iommu/Kconfig     | 18 ++++++++++++++++++
 drivers/iommu/dma-iommu.c | 29 ++++++++++++++++++++++-------
 2 files changed, 40 insertions(+), 7 deletions(-)

diff --git a/drivers/iommu/Kconfig b/drivers/iommu/Kconfig
index 70d29b14d851..f607873bf39a 100644
--- a/drivers/iommu/Kconfig
+++ b/drivers/iommu/Kconfig
@@ -157,6 +157,24 @@ config IOMMU_DMA
 	select NEED_SG_DMA_LENGTH
 	select NEED_SG_DMA_FLAGS if SWIOTLB
 
+config IOMMU_DMA_GUARD_PAGES_KB
+	int "DMA Guard Pages size in KB"
+	default 0
+	depends on IOMMU_DMA && EXPERT
+	help
+	  Specify the minimum amount of Guard Pages to be inserted between
+	  consecutive DMA mappings to devices behind IOMMUs. DMA Guard Pages
+	  are not mapped to any memory and hardware attempts to access them
+	  will fault. This helps catch hardware accessing valid mappings out
+	  of bounds which could otherwise unintentionally consume or corrupt
+	  other memory mapped adjacently.
+
+	  Size will be automatically increased to one or more IOMMU pages,
+	  depending on applicable alignment constraints. Small power-of-two
+	  mappings may get as many Guard Pages as the mapping uses itself.
+
+	  If unsure, use the default size of zero to disable DMA Guard Pages.
+
 # Shared Virtual Addressing
 config IOMMU_SVA
 	select IOMMU_MM_DATA
diff --git a/drivers/iommu/dma-iommu.c b/drivers/iommu/dma-iommu.c
index 7944a3af4545..51edf148f6c4 100644
--- a/drivers/iommu/dma-iommu.c
+++ b/drivers/iommu/dma-iommu.c
@@ -746,6 +746,17 @@ static int dma_info_to_prot(enum dma_data_direction dir, bool coherent,
 	}
 }
 
+static unsigned long size_to_iova_len(struct iova_domain *iovad, size_t size)
+{
+	size_t guard_size = 0;
+
+	/* allocate optional guard pages after the requested mapping */
+	if (CONFIG_IOMMU_DMA_GUARD_PAGES_KB)
+		guard_size = iova_align(iovad, CONFIG_IOMMU_DMA_GUARD_PAGES_KB << 10);
+
+	return (size + guard_size) >> iova_shift(iovad);
+}
+
 static dma_addr_t iommu_dma_alloc_iova(struct iommu_domain *domain,
 		size_t size, u64 dma_limit, struct device *dev)
 {
@@ -759,7 +770,7 @@ static dma_addr_t iommu_dma_alloc_iova(struct iommu_domain *domain,
 	}
 
 	shift = iova_shift(iovad);
-	iova_len = size >> shift;
+	iova_len = size_to_iova_len(iovad, size);
 
 	dma_limit = min_not_zero(dma_limit, dev->bus_dma_limit);
 
@@ -796,17 +807,21 @@ static void iommu_dma_free_iova(struct iommu_domain *domain, dma_addr_t iova,
 				size_t size, struct iommu_iotlb_gather *gather)
 {
 	struct iova_domain *iovad = &domain->iova_cookie->iovad;
+	unsigned long iova_len;
 
 	/* The MSI case is only ever cleaning up its most recent allocation */
-	if (domain->cookie_type == IOMMU_COOKIE_DMA_MSI)
+	if (domain->cookie_type == IOMMU_COOKIE_DMA_MSI) {
 		domain->msi_cookie->msi_iova -= size;
-	else if (gather && gather->queued)
+		return;
+	}
+
+	iova_len = size_to_iova_len(iovad, size);
+
+	if (gather && gather->queued)
 		queue_iova(domain->iova_cookie, iova_pfn(iovad, iova),
-				size >> iova_shift(iovad),
-				&gather->freelist);
+				iova_len, &gather->freelist);
 	else
-		free_iova_fast(iovad, iova_pfn(iovad, iova),
-				size >> iova_shift(iovad));
+		free_iova_fast(iovad, iova_pfn(iovad, iova), iova_len);
 }
 
 static void __iommu_dma_unmap(struct device *dev, dma_addr_t dma_addr,
-- 
2.48.1

Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ