[PATCH] ACPI: block AML access to confidential VM private memory

bfoing posted 1 patch 2 months ago
drivers/acpi/Makefile          |   1 +
drivers/acpi/acpica/exregion.c |  12 ++++
drivers/acpi/cvm_guard.c       | 121 +++++++++++++++++++++++++++++++++
3 files changed, 134 insertions(+)
create mode 100644 drivers/acpi/cvm_guard.c
[PATCH] ACPI: block AML access to confidential VM private memory
Posted by bfoing 2 months ago
From: Bertrand Foing <40759640+bfoing@users.noreply.github.com>

Add a guard in the ACPICA SystemMemory space handler that prevents AML
bytecode from reading or writing pages belonging to the confidential VM
private address range.

On TDX and SEV-SNP guests the ACPI tables are under host/VMM control.
Malicious AML ("BadAML") can issue SystemMemory region reads and writes
to arbitrary guest physical addresses, extracting secrets or corrupting
guest state without triggering any existing kernel protection.

The guard walks the kernel page tables for the target virtual address
and checks whether the page-table entry carries the platform-specific
is private the access is denied with AE_AML_ILLEGAL_ADDRESS.

Signed-off-by: Bertrand Foing <40759640+bfoing@users.noreply.github.com>
---
 drivers/acpi/Makefile          |   1 +
 drivers/acpi/acpica/exregion.c |  12 ++++
 drivers/acpi/cvm_guard.c       | 121 +++++++++++++++++++++++++++++++++
 3 files changed, 134 insertions(+)
 create mode 100644 drivers/acpi/cvm_guard.c

diff --git a/drivers/acpi/Makefile b/drivers/acpi/Makefile
index d1b0affb8..6743ece85 100644
--- a/drivers/acpi/Makefile
+++ b/drivers/acpi/Makefile
@@ -45,6 +45,7 @@ acpi-y				+= resource.o
 acpi-y				+= acpi_processor.o
 acpi-y				+= processor_core.o
 acpi-$(CONFIG_ARCH_MIGHT_HAVE_ACPI_PDC) += processor_pdc.o
+acpi-$(CONFIG_ARCH_HAS_CC_PLATFORM)	+= cvm_guard.o
 acpi-$(CONFIG_ACPI_EC)		+= ec.o
 acpi-$(CONFIG_ACPI_DOCK)	+= dock.o
 acpi-$(CONFIG_PCI)		+= pci_root.o pci_link.o pci_irq.o
diff --git a/drivers/acpi/acpica/exregion.c b/drivers/acpi/acpica/exregion.c
index a390a1c2b..f12cacff3 100644
--- a/drivers/acpi/acpica/exregion.c
+++ b/drivers/acpi/acpica/exregion.c
@@ -14,6 +14,12 @@
 #define _COMPONENT          ACPI_EXECUTER
 ACPI_MODULE_NAME("exregion")
 
+#ifdef CONFIG_ARCH_HAS_CC_PLATFORM
+bool acpi_cvm_guard_deny_access(unsigned long virt_addr);
+#else
+static inline bool acpi_cvm_guard_deny_access(unsigned long v) { return false; }
+#endif
+
 /*******************************************************************************
  *
  * FUNCTION:    acpi_ex_system_memory_space_handler
@@ -176,6 +182,12 @@ acpi_ex_system_memory_space_handler(u32 function,
 	logical_addr_ptr = mm->logical_address +
 		((u64) address - (u64) mm->physical_address);
 
+#ifdef CONFIG_ARCH_HAS_CC_PLATFORM
+	if (acpi_cvm_guard_deny_access((unsigned long)logical_addr_ptr)) {
+		return_ACPI_STATUS(AE_AML_ILLEGAL_ADDRESS);
+	}
+#endif
+
 	ACPI_DEBUG_PRINT((ACPI_DB_INFO,
 			  "System-Memory (width %u) R/W %u Address=%8.8X%8.8X\n",
 			  bit_width, function, ACPI_FORMAT_UINT64(address)));
diff --git a/drivers/acpi/cvm_guard.c b/drivers/acpi/cvm_guard.c
new file mode 100644
index 000000000..0524bf902
--- /dev/null
+++ b/drivers/acpi/cvm_guard.c
@@ -0,0 +1,121 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * CVM Guard - Block AML access to confidential VM private memory
+ *
+ * Copyright (C) 2026 Privasys
+ *
+ * On TDX and SEV-SNP guests the host VMM controls ACPI tables, so
+ * AML bytecode executing SystemMemory reads and writes can target
+ * arbitrary guest physical addresses.  This file provides a guard
+ * function called from the ACPICA SystemMemory space handler that
+ * checks whether the target virtual address maps to a page marked
+ * as encrypted (private) in the page tables, and denies the access
+ * if so.
+ *
+ * Reference: "BadAML: Exploiting AML in Confidential Virtual Machines"
+ *            Takekoshi et al., ACM CCS 2025
+ */
+
+#include <linux/cc_platform.h>
+#include <linux/mm.h>
+#include <linux/printk.h>
+#include <asm/coco.h>
+
+/* Prototype to satisfy -Wmissing-prototypes; declared here rather than in
+ * internal.h because this file does not need the full ACPI driver headers.
+ */
+bool acpi_cvm_guard_deny_access(unsigned long virt_addr);
+
+/*
+ * Walk the four-level kernel page tables for @addr and return the raw
+ * PTE/PMD/PUD value.  Returns 0 if the walk fails at any level.
+ * Handles 1 GB (PUD) and 2 MB (PMD) large pages.
+ */
+static unsigned long cvm_guard_pte_val(unsigned long addr)
+{
+	pgd_t *pgd;
+	p4d_t *p4d;
+	pud_t *pud;
+	pmd_t *pmd;
+	pte_t *pte;
+
+	pgd = pgd_offset_k(addr);
+	if (pgd_none(*pgd))
+		return 0;
+
+	p4d = p4d_offset(pgd, addr);
+	if (p4d_none(*p4d))
+		return 0;
+
+	pud = pud_offset(p4d, addr);
+	if (pud_none(*pud))
+		return 0;
+	if (pud_leaf(*pud))
+		return pud_val(*pud);
+
+	pmd = pmd_offset(pud, addr);
+	if (pmd_none(*pmd))
+		return 0;
+	if (pmd_leaf(*pmd))
+		return pmd_val(*pmd);
+
+	pte = pte_offset_kernel(pmd, addr);
+	if (pte_none(*pte))
+		return 0;
+
+	return pte_val(*pte);
+}
+
+/*
+ * Check whether @addr maps to a private (encrypted) page.
+ *
+ * cc_mkenc() applies the platform-specific encryption mask:
+ *   AMD SEV/SEV-SNP: sets the C-bit
+ *   Intel TDX:       clears the shared bit
+ *
+ * If the PTE already matches its encrypted form, the page is private
+ * and must not be accessible to AML.  If the walk fails (returns 0)
+ * we deny access - fail-closed is the safe default.
+ */
+static bool cvm_guard_page_is_private(unsigned long addr)
+{
+	unsigned long val;
+
+	val = cvm_guard_pte_val(addr);
+	if (!val) {
+		pr_warn_ratelimited("CVM guard: page table walk failed for %lx\n",
+				    addr);
+		return true;
+	}
+
+	return val == cc_mkenc(val);
+}
+
+/**
+ * acpi_cvm_guard_deny_access - block AML access to CVM private pages
+ * @virt_addr: kernel virtual address resolved by the SystemMemory handler
+ *
+ * Called from acpi_ex_system_memory_space_handler() after the virtual
+ * address has been computed but before any read or write.
+ *
+ * On non-CVM systems (CC_ATTR_MEM_ENCRYPT not set) this returns false.
+ *
+ * Return: true if the access must be denied, false if allowed.
+ */
+bool acpi_cvm_guard_deny_access(unsigned long virt_addr)
+{
+	if (!cc_platform_has(CC_ATTR_MEM_ENCRYPT))
+		return false;
+
+	pr_info_once("CVM guard: active, AML access to private pages will be denied\n");
+
+	virt_addr &= PAGE_MASK;
+
+	if (cvm_guard_page_is_private(virt_addr)) {
+		pr_warn_ratelimited("CVM guard: denied AML access to private page at %lx\n",
+				    virt_addr);
+		return true;
+	}
+
+	return false;
+}
-- 
2.43.0