[PATCH v4 10/23] x86/boot/slaunch-early: early Intel TXT sanity checks

Sergii Dmytruk <[email protected]> Sun, 2 Aug 2026 16:09:26 +0300
Newsgroups gmane.comp.emulators.xen.devel
Message-ID <474ab4540b1b78a818f873e77b29e586c37cedfd.1785668458.git.sergii.dmytruk@3mdeb.com>
From: Krystian Hebel <[email protected]>

The tests validate that important parts of memory are protected against
DMA attacks, including Xen and MBI. Modules can be tested later, when it
is possible to report issues to a user before invoking TXT reset.

The protection used here is Protected Memory Regions (PMRs), which is
not available on modern hardware like MeteorLake that uses TXT DMA
Protection Ranges (TPR) and is to be added separately.

TPM event log validation is temporarily disabled due to an issue with
its allocation by bootloader (GRUB) which will need to be modified to
address this. Ultimately event log will also have to be validated early
as it is used immediately after these tests to hold MBI measurements.
See larger comment in txt_verify_pmr_ranges().

Signed-off-by: Krystian Hebel <[email protected]>
Signed-off-by: Sergii Dmytruk <[email protected]>
---

Notes:
    v4: was "x86/boot/slaunch-early: early TXT checks and boot data retrieval"
    v4: updates to the handling of TXT heap sections due to different API
    v4: use `bool` instead of `int` in two places
    v4: don't use low range in is_in_pmr(), it must be zero
    v4: use `struct multiboot2_fixed_t` instead of casting and dereferencing `uint32_t *`
    v4: TPM event log check being covered by PMR can't be safely uncommented without constraining where TPM event log is allocated

 xen/arch/x86/boot/slaunch-early.c    |   6 ++
 xen/arch/x86/include/asm/intel-txt.h | 118 +++++++++++++++++++++++++++
 2 files changed, 124 insertions(+)

diff --git a/xen/arch/x86/boot/slaunch-early.c b/xen/arch/x86/boot/slaunch-early.c
index 35992cb9b3..00c772cfdf 100644
--- a/xen/arch/x86/boot/slaunch-early.c
+++ b/xen/arch/x86/boot/slaunch-early.c
@@ -21,11 +21,14 @@ void asmlinkage slaunch_early_init(uint32_t load_base_addr,
     void *txt_heap;
     const struct txt_os_mle_data *os_mle;
     const struct slr_table *slrt;
+    const struct txt_os_sinit_data *os_sinit;
     const struct slr_entry_hdr *entry;
     const struct slr_entry_intel_info *intel_info;
+    uint32_t size = tgt_end_addr - tgt_base_addr;
 
     txt_heap = txt_init();
     os_mle = txt_start(txt_heap, TXT_OS2MLE);
+    os_sinit = txt_start(txt_heap, TXT_OS2SINIT);
 
     if ( os_mle->slrt & ~0xffffffffULL )
         txt_reset(SLAUNCH_ERROR_BAD_SLRT_ADDRESS);
@@ -43,4 +46,7 @@ void asmlinkage slaunch_early_init(uint32_t load_base_addr,
         txt_reset(SLAUNCH_ERROR_BAD_VENDOR_INFO);
 
     result->mbi_pa = intel_info->boot_params_base;
+
+    txt_verify_pmr_ranges(os_mle, os_sinit, intel_info,
+                          load_base_addr, tgt_base_addr, size);
 }
diff --git a/xen/arch/x86/include/asm/intel-txt.h b/xen/arch/x86/include/asm/intel-txt.h
index dc6c689f1a..66039dbeee 100644
--- a/xen/arch/x86/include/asm/intel-txt.h
+++ b/xen/arch/x86/include/asm/intel-txt.h
@@ -68,6 +68,9 @@
 
 #ifndef __ASSEMBLER__
 
+#include <xen/multiboot2.h>
+#include <xen/slr-table.h>
+
 /* Need to differentiate between pre- and post paging enabled. */
 #ifdef __EARLY_SLAUNCH__
 #include <xen/macros.h>
@@ -265,6 +268,121 @@ static inline void *txt_init(void)
     return txt_heap;
 }
 
+static inline bool is_in_pmr(const struct txt_os_sinit_data *os_sinit,
+                             uint64_t base, uint32_t size, bool check_high)
+{
+    /* Check for size overflow. */
+    if ( base + size < base )
+        txt_reset(SLAUNCH_ERROR_INTEGER_OVERFLOW);
+
+    /*
+     * txt_verify_pmr_ranges() makes sure the low range always starts at 0, so
+     * its size is also end address.
+     */
+    if ( base + size <= os_sinit->vtd_pmr_lo_size )
+        return true;
+
+    if ( check_high && os_sinit->vtd_pmr_hi_size != 0 )
+    {
+        if ( base >= os_sinit->vtd_pmr_hi_base &&
+             base + size <= os_sinit->vtd_pmr_hi_base +
+                            os_sinit->vtd_pmr_hi_size )
+            return true;
+    }
+
+    return false;
+}
+
+static inline void txt_verify_pmr_ranges(
+    const struct txt_os_mle_data *os_mle,
+    const struct txt_os_sinit_data *os_sinit,
+    const struct slr_entry_intel_info *info,
+    uint32_t load_base_addr,
+    uint32_t tgt_base_addr,
+    uint32_t xen_size)
+{
+    bool check_high_pmr = false;
+
+    /* Verify the value of the low PMR base. It should always be 0. */
+    if ( os_sinit->vtd_pmr_lo_base != 0 )
+        txt_reset(SLAUNCH_ERROR_LO_PMR_BASE);
+
+    /*
+     * Low PMR size should not be 0 on current platforms. There is an ongoing
+     * transition to TPR-based DMA protection instead of PMR-based; this is not
+     * yet supported by the code.
+     */
+    if ( os_sinit->vtd_pmr_lo_size == 0 )
+        txt_reset(SLAUNCH_ERROR_LO_PMR_SIZE);
+
+    /* Check if regions overlap. Treat regions with no hole between as error. */
+    if ( os_sinit->vtd_pmr_hi_size != 0 &&
+         os_sinit->vtd_pmr_hi_base <= os_sinit->vtd_pmr_lo_size )
+        txt_reset(SLAUNCH_ERROR_HI_PMR_BASE);
+
+    /* Check for size overflow. */
+    if ( os_sinit->vtd_pmr_hi_base + os_sinit->vtd_pmr_hi_size <
+         os_sinit->vtd_pmr_hi_size )
+        txt_reset(SLAUNCH_ERROR_INTEGER_OVERFLOW);
+
+    /* All regions accessed by 32b code must be below 4G. */
+    if ( os_sinit->vtd_pmr_hi_base + os_sinit->vtd_pmr_hi_size <=
+         0x100000000ULL )
+        check_high_pmr = true;
+
+    /*
+     * ACM checks that TXT heap and MLE memory is protected against DMA. We have
+     * to check if MBI and whole Xen memory is protected. The latter is done in
+     * case bootloader failed to set whole image as MLE and to make sure that
+     * both pre- and post-relocation code is protected.
+     */
+
+    /* Check if all of Xen before relocation is protected. */
+    if ( !is_in_pmr(os_sinit, load_base_addr, xen_size, check_high_pmr) )
+        txt_reset(SLAUNCH_ERROR_LO_PMR_MLE);
+
+    /* Check if all of Xen after relocation is protected. */
+    if ( load_base_addr != tgt_base_addr &&
+         !is_in_pmr(os_sinit, tgt_base_addr, xen_size, check_high_pmr) )
+        txt_reset(SLAUNCH_ERROR_LO_PMR_MLE);
+
+    /* If present, check that MBI is protected. */
+    if ( info->boot_params_base != 0 )
+    {
+        const multiboot2_fixed_t *mbi =
+            (const multiboot2_fixed_t *)(uintptr_t)info->boot_params_base;
+
+        if ( !is_in_pmr(os_sinit, info->boot_params_base, mbi->total_size,
+                        check_high_pmr) )
+            txt_reset(SLAUNCH_ERROR_BUFFER_BEYOND_PMR);
+    }
+
+    /* Check if TPM event log (if present) is protected. */
+    /*
+     * FIXME: currently commented out as GRUB allocates it in a hole between
+     * PMR and reserved RAM, due to 2MB resolution of PMR. There are no other
+     * easy-to-use DMA protection mechanisms that would allow to protect that
+     * part of memory. TPR (TXT DMA Protection Range) gives 1MB resolution, but
+     * it still wouldn't be enough.
+     *
+     * One possible solution would be for GRUB to allocate log at lower address,
+     * but this would further increase memory space fragmentation. Another
+     * option is to align PMR up instead of down, making PMR cover part of
+     * reserved region, but it is unclear what the consequences may be.
+     *
+     * In tboot this issue was resolved by reserving leftover chunks of memory
+     * in e820 and/or UEFI memory map. This is also a valid solution, but would
+     * require more changes to GRUB than the ones listed above, as event log is
+     * allocated much earlier than PMRs.
+     */
+    /*
+    if ( os_mle->evtlog_addr != 0 && os_mle->evtlog_size != 0 &&
+         !is_in_pmr(os_sinit, os_mle->evtlog_addr, os_mle->evtlog_size,
+                    check_high_pmr) )
+        txt_reset(SLAUNCH_ERROR_BUFFER_BEYOND_PMR);
+    */
+}
+
 #endif /* !__ASSEMBLER__ */
 
 #endif /* X86_INTEL_TXT_H */
-- 
2.55.0