x86: Added TXT support.

This commit is contained in:
Udo Steinberg 2023-02-22 13:36:30 +01:00
parent abbd800ab1
commit c94a88d436
11 changed files with 1054 additions and 10 deletions

View File

@ -82,6 +82,16 @@ Protection features can be enabled at build time as follows:
| CET Supervisor Shadow Stacks (SSS) | `make ARCH=x86_64 CFP=return` | | CET Supervisor Shadow Stacks (SSS) | `make ARCH=x86_64 CFP=return` |
| CET IBT and CET SSS | `make ARCH=x86_64 CFP=full` | | CET IBT and CET SSS | `make ARCH=x86_64 CFP=full` |
##### Trusted Execution Technology (TXT)
On TXT-enabled platforms, NOVA performs a measured launch to establish a
Dynamic Root of Trust for Measurement (DRTM) if an SINIT Authenticated Code
Module (ACM) matching the platform is present in TXT memory.
The SINIT ACM is typically loaded into TXT memory
- on server platforms: by the firmware
- on client platforms: by the bootloader
## Booting ## Booting
See the NOVA interface specification in the `doc` directory for details See the NOVA interface specification in the `doc` directory for details

View File

@ -26,6 +26,7 @@
#include "io.hpp" #include "io.hpp"
#include "lowlevel.hpp" #include "lowlevel.hpp"
#include "macros.hpp" #include "macros.hpp"
#include "txt.hpp"
class Acpi_fixed final class Acpi_fixed final
{ {
@ -162,7 +163,7 @@ class Acpi_fixed final
/* /*
* Wait for all APs to be offline * Wait for all APs to be offline
*/ */
static void offline_wait() {} static void offline_wait() { Txt::fini(); }
/* /*
* Perform platform reset * Perform platform reset

View File

@ -23,7 +23,7 @@
#include "types.hpp" #include "types.hpp"
extern char GIT_VER, NOVA_HPAS, KMEM_HVAS, KMEM_HVAF, PTAB_HVAS, DSTK_TOP, DSHD_TOP; extern char GIT_VER, NOVA_HPAS, KMEM_HVAS, KMEM_HVAF, PTAB_HVAS, DSTK_TOP, DSHD_TOP, HASH_HEAD, MLE_TL, MLE_L2, __head_mle;
extern void (*CTORS_S[])(), (*CTORS_E[])(), (*CTORS_C[])(), (*CTORS_L[])(); extern void (*CTORS_S[])(), (*CTORS_E[])(), (*CTORS_C[])(), (*CTORS_L[])();
extern char entry_sys; extern char entry_sys;

View File

@ -53,6 +53,8 @@
// Global Area // Global Area
#define MMAP_GLB_TPM2 VIRT_ADDR (511, 510, 508, 320) // 2M (0xfed40000 offset in 2M window) #define MMAP_GLB_TPM2 VIRT_ADDR (511, 510, 508, 320) // 2M (0xfed40000 offset in 2M window)
#define MMAP_GLB_TXTC VIRT_ADDR (511, 510, 508, 288) // 2M (0xfed20000 offset in 2M window)
#define MMAP_GLB_TXTH VIRT_ADDR (511, 510, 504, 0) // 4M + gap
#define MMAP_GLB_MAP1 VIRT_ADDR (511, 510, 500, 0) // 4M + gap #define MMAP_GLB_MAP1 VIRT_ADDR (511, 510, 500, 0) // 4M + gap
#define MMAP_GLB_MAP0 VIRT_ADDR (511, 510, 496, 0) // 4M + gap #define MMAP_GLB_MAP0 VIRT_ADDR (511, 510, 496, 0) // 4M + gap
#define MMAP_GLB_UART VIRT_ADDR (511, 510, 488, 0) // 16M #define MMAP_GLB_UART VIRT_ADDR (511, 510, 488, 0) // 16M

379
inc/x86_64/txt.hpp Normal file
View File

@ -0,0 +1,379 @@
/*
* Trusted Execution Technology (TXT)
*
* Copyright (C) 2019-2025 Udo Steinberg, BlueRock Security, Inc.
*
* This file is part of the NOVA microhypervisor.
*
* NOVA is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* NOVA is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License version 2 for more details.
*/
#pragma once
#include "compiler.hpp"
#include "endian.hpp"
#include "memory.hpp"
#include "std.hpp"
#include "uuid.hpp"
class Txt final
{
private:
static constexpr auto ver_pre_acm { 7 };
/*
* 0xfed20000-0xfed2ffff: 64K TXT private
* 0xfed30000-0xfed3ffff: 64K TXT public
* 0xfed40000-0xfed44fff: 20K TPM localities
* 0xfed45000-0xfed7ffff: 236K TXT reserved
*/
static constexpr auto txt_base { 0xfed20000 };
static constexpr auto txt_size { 0x60000 };
/*
* Section C.1: Extended Heap Element
*/
struct Element
{
// Section C.5.5: Registry of Extended Heap Elements
enum class Type : uint32_t
{
END = 0, // Terminator
VER = 1, // TXT BIOS Specification Version
ACM = 2, // ACM Information
STM = 3, // STM Information
CUSTOM = 4, // Customizable
LOG12 = 5, // TPM 1.2 Event Log
MADT = 6, // Validated ACPI MADT
LOG20_TXT = 7, // TPM 2.0 Event Log (TXT)
LOG20_TCG = 8, // TPM 2.0 Event Log (TCG)
MCFG = 9, // Validated ACPI MCFG
TPR = 13, // TPR Request
DPTR = 14, // Validated ACPI DPTR
CEDT = 15, // Validated ACPI CEDT
};
Unaligned_le<uint32_t> const type;
Unaligned_le<uint32_t> const size;
auto get_type() const { return Type { uint32_t { type } }; }
auto get_data() const { return reinterpret_cast<uintptr_t>(this + 1); }
auto get_next() const { return reinterpret_cast<Element const *>(reinterpret_cast<uintptr_t>(this) + size); }
explicit Element (Type t, uint32_t s) : type { std::to_underlying (t) }, size { s } {}
};
static_assert (__is_standard_layout (Element) && alignof (Element) == 1 && sizeof (Element) == 8);
/*
* Extended Heap Element: Type 0
*/
struct Element_end
{
Element const elem { Element::Type::END, sizeof (Element_end) };
};
static_assert (__is_standard_layout (Element_end) && alignof (Element_end) == 1 && sizeof (Element_end) == sizeof (Element));
/*
* Extended Heap Element: Type 7
*/
struct Element_log20_txt
{
Element const elem { Element::Type::LOG20_TXT, sizeof (Element_log20_txt) };
Unaligned_le<uint32_t> const count { 1 };
Unaligned_le<uint16_t> const alg { 0xb }; // SHA256
Unaligned_le<uint16_t> const reserved { 0 };
Unaligned_le<uint64_t> const phys;
Unaligned_le<uint32_t> const size;
Unaligned_le<uint32_t> const off_first { 0 };
Unaligned_le<uint32_t> const off_next { 0 };
explicit Element_log20_txt (uint64_t p, uint32_t s) : phys { p }, size { s } {}
};
static_assert (__is_standard_layout (Element_log20_txt) && alignof (Element_log20_txt) == 1 && sizeof (Element_log20_txt) == sizeof (Element) + 28);
/*
* Extended Heap Element: Type 8
*/
struct Element_log20_tcg
{
Element const elem { Element::Type::LOG20_TCG, sizeof (Element_log20_tcg) };
Unaligned_le<uint64_t> const phys;
Unaligned_le<uint32_t> const size;
Unaligned_le<uint32_t> const off_first { 0 };
Unaligned_le<uint32_t> const off_next { 0 };
explicit Element_log20_tcg (uint64_t p, uint32_t s) : phys { p }, size { s } {}
};
static_assert (__is_standard_layout (Element_log20_tcg) && alignof (Element_log20_tcg) == 1 && sizeof (Element_log20_tcg) == sizeof (Element) + 20);
/*
* Heap Data Size
*/
struct Data
{
Unaligned_le<uint64_t> size; // 0 v1+
auto next() const { return reinterpret_cast<void *>(reinterpret_cast<uintptr_t>(this) + size); }
};
static_assert (__is_standard_layout (Data) && alignof (Data) == 1 && sizeof (Data) == 8);
/*
* Section C.2: EFI to PRE Data
*/
struct Data_efi_pre
{
Data const data;
Unaligned_le<uint32_t> const version; // 0 v1+
Unaligned_le<uint32_t> const sinit_size; // 4 v1+
Unaligned_le<uint64_t> const lcp_pd_base; // 8 v2+
Unaligned_le<uint64_t> const lcp_pd_size; // 16 v2+
Unaligned_le<uint32_t> const num_cpu; // 24 v2+
Unaligned_le<uint32_t> const sinit_flags; // 28 v3+
Unaligned_le<uint32_t> const mle_flags; // 32 v5+
auto plat() const { return version < 6 ? 0 : mle_flags >> 1 & BIT_RANGE (1, 0); }
auto elem() const { return version < 4 ? nullptr : reinterpret_cast<Element const *>(this + 1); }
};
static_assert (__is_standard_layout (Data_efi_pre) && alignof (Data_efi_pre) == 1 && sizeof (Data_efi_pre) == sizeof (Data) + 36);
/*
* Section C.3: PRE to MLE Data
*/
struct Data_pre_mle
{
Data const data;
Unaligned_le<uint64_t> const ia32_mtrr_def_type; // 0
Unaligned_le<uint64_t> const ia32_misc_enable; // 8
Unaligned_le<uint64_t> const ia32_debugctl; // 16
struct Mtrr_backup
{
Unaligned_le<uint64_t> const base;
Unaligned_le<uint64_t> const mask;
};
auto mtrr() { return reinterpret_cast<Mtrr_backup *>(this + 1); }
auto mtrr() const { return reinterpret_cast<Mtrr_backup const *>(this + 1); }
};
static_assert (__is_standard_layout (Data_pre_mle) && alignof (Data_pre_mle) == 1 && sizeof (Data_pre_mle) == sizeof (Data) + 24);
/*
* Section C.4: PRE to ACM Data
*/
struct Data_pre_acm
{
Data data { sizeof (Data_pre_acm) };
Unaligned_le<uint32_t> const version { ver_pre_acm }; // 0 v1+
Unaligned_le<uint32_t> const flags; // 4 v7+
Unaligned_le<uint64_t> const mle_ptab; // 8 v1+
Unaligned_le<uint64_t> const mle_size; // 16 v1+
Unaligned_le<uint64_t> const mle_header; // 24 v1+
Unaligned_le<uint64_t> const pmr_lo_base; // 32 v3+
Unaligned_le<uint64_t> const pmr_lo_size; // 40 v3+
Unaligned_le<uint64_t> const pmr_hi_base { 0 }; // 48 v3+
Unaligned_le<uint64_t> const pmr_hi_size { 0 }; // 56 v3+
Unaligned_le<uint64_t> const lcp_po_base { 0 }; // 64 v3+
Unaligned_le<uint64_t> const lcp_po_size { 0 }; // 72 v3+
Unaligned_le<uint32_t> const caps; // 80 v3+
Unaligned_le<uint64_t> const rsdp; // 84 v6+
void append (Element const &e) { data.size = data.size + e.size; }
auto elem() const { return version < 6 ? nullptr : reinterpret_cast<Element const *>(this + 1); }
};
static_assert (__is_standard_layout (Data_pre_acm) && alignof (Data_pre_acm) == 1 && sizeof (Data_pre_acm) == sizeof (Data) + 92);
/*
* Section C.5: ACM to MLE Data
*/
struct Data_acm_mle
{
Data const data;
Unaligned_le<uint32_t> const version; // 0 v1+
Unaligned_le<uint32_t> const reserved1[29]; // 4
Unaligned_le<uint32_t> const rlp_wakeup; // 120 v5+
Unaligned_le<uint32_t> const reserved2; // 124
Unaligned_le<uint32_t> const mdrs_count; // 128 v5+
Unaligned_le<uint32_t> const mdrs_offset; // 132 v5+
Unaligned_le<uint32_t> const dmar_size; // 136 v5+
Unaligned_le<uint32_t> const dmar_offset; // 140 v5+
Unaligned_le<uint32_t> const scrtm_status; // 144 v8
auto elem() const { return version < 8 ? nullptr : reinterpret_cast<Element const *>(this + 1); }
};
static_assert (__is_standard_layout (Data_acm_mle) && alignof (Data_acm_mle) == 1 && sizeof (Data_acm_mle) == sizeof (Data) + 148);
/*
* Section 2.1: MLE Header
*/
struct Mle_header
{
Unaligned_le<Uuid> const uuid; // 0
Unaligned_le<uint32_t> const size; // 16
Unaligned_le<uint32_t> const version; // 20
Unaligned_le<uint32_t> const entry; // 24
Unaligned_le<uint32_t> const first; // 28
Unaligned_le<uint32_t> const mle_start; // 32
Unaligned_le<uint32_t> const mle_end; // 36
Unaligned_le<uint32_t> mle_caps; // 40
Unaligned_le<uint32_t> const cmd_start; // 44
Unaligned_le<uint32_t> const cmd_end; // 48
};
static_assert (__is_standard_layout (Mle_header) && alignof (Mle_header) == 1 && sizeof (Mle_header) == 52);
// Section B.1: TXT Registers
enum class Space : unsigned
{
PRIVATE = 0x00000,
PUBLIC = 0x10000,
};
enum class Reg8 : unsigned
{
RESET = 0x038, // -- -w System Reset
PRIVATE_OPEN = 0x040, // -- -w Private Space Open
PRIVATE_CLOSE = 0x048, // -- -w Private Space Close
MEMCFG_UNLOCK = 0x218, // -- -w Memory Configuration Unlock
BASE_LOCK = 0x230, // -- -w Base Registers Lock
BASE_UNLOCK = 0x238, // -- -w Base Registers Unlock
WB_FLUSH = 0x258, // -- -w Write Buffer Flush
LOCALITY1_OPEN = 0x380, // -- -w Locality 1 Open
LOCALITY1_CLOSE = 0x388, // -- -w Locality 1 Close
LOCALITY2_OPEN = 0x390, // -- -w Locality 2 Open
LOCALITY2_CLOSE = 0x398, // -- -w Locality 2 Close
SECRETS_SET = 0x8e0, // -- -w Set Secrets
SECRETS_CLR = 0x8e8, // -- -w Clr Secrets
};
enum class Reg32 : unsigned
{
ERRORCODE = 0x030, // r- rw Error Code
VER_FSBIF = 0x100, // r- r- Version: Frontside Bus Interface
VER_QPIIF = 0x200, // r- r- Version: Quickpath Interconnect Interface
NODMA_BASE = 0x260, // rw rw NODMA Base
NODMA_SIZE = 0x268, // r- r- NODMA Size
SINIT_BASE = 0x270, // rw rw SINIT Base
SINIT_SIZE = 0x278, // rw rw SINIT Size
MLE_JOIN = 0x290, // rw rw MLE Join Address
HEAP_BASE = 0x300, // rw rw TXT Heap Base
HEAP_SIZE = 0x308, // rw rw TXT Heap Size
MSEG_BASE = 0x310, // rw rw TXT MSEG Base
MSEG_SIZE = 0x318, // rw rw TXT MSEG Size
DPR = 0x330, // rw rw DMA Protected Range
};
enum class Reg64 : unsigned
{
STS = 0x000, // r- r- Status
ESTS = 0x008, // r- rw Error Status
THREADS_EXIST = 0x010, // r- r- Threads Exist
THREADS_JOIN = 0x020, // r- r- Threads Joined
ACM_STATUS = 0x0a0, // r- rw ACM Status
DIDVID = 0x110, // r- r- Device ID
ACM_ERRORCODE = 0x328, // rw rw ACM Errorcode
ACM_POLICY_STATUS = 0x378, // r- rw ACM Policy Status
PUBLIC_KEY = 0x400, // r- r- ACM Public Key Hash
DIDVID2 = 0x810, // r- r- Device ID 2
E2STS = 0x8f0, // r- rw Extended Error Status
};
enum STS
{
SEQ_IN_PROGRESS = BIT (17), // Set between TXT.SEQUENCE.START and TXT.SEQUENCE.DONE
LOCALITY2 = BIT (16), // Set between TXT.CMD.OPEN.LOCALITY2 and TXT.CMD.CLOSE.LOCALITY2 or between TXT.CMD.OPEN.PRIVATE and TXT.CMD.CLOSE.PRIVATE
LOCALITY1 = BIT (15), // Set between TXT.CMD.OPEN.LOCALITY1 and TXT.CMD.CLOSE.LOCALITY1
LOCALITY3 = BIT (14), // Set between TXT.CMD.OPEN.LOCALITY3 and TXT.CMD.CLOSE.LOCALITY3
OPENED_SMM = BIT (13),
LOCKED_PMRC = BIT (12),
MEMCFG_OK = BIT (11), // Set between TXT.CMD.MEM-CONFIG-CHECKED and TXT.CMD.UNLOCK-MEMCONFIG
NODMA_TABLE = BIT (10), // Set between TXT.CMD.NODMA-TABLE.EN and TXT.CMD.NODMA-TABLE.DIS
NODMA_CACHE = BIT (9), // Set between TXT.CMD.NODMA-CACHE.EN and TXT.CMD.NODMA-CACHE.DIS
OPENED_PRIVATE = BIT (7), // Set between TXT.CMD.OPEN-PRIVATE and TXT.CMD.CLOSE-PRIVATE
LOCKED_MEMCFG = BIT (6), // Clr by TXT.CMD.UNLOCK.MEMCONFIG (VTBAR/VTCTRL locked)
LOCKED_BASE = BIT (5), // Set between TXT.CMD.LOCK-BASE and TXT.CMD.UNLOCK-BASE (HEAP_BASE/HEAP_SIZE, MSEG_BASE/MSEG_SIZE, Scratchpad locked)
UNLOCKED_MEM = BIT (4), // Set by TXT.CMD.UNLOCK-MEMORY
DONE_SEXIT = BIT (1), // THREADS_JOIN == 0
DONE_SENTER = BIT (0), // THREADS_JOIN != 0 && THREADS_JOIN == THREADS_EXIST
};
enum ESTS
{
WAKE_ERROR = BIT (6), // Reset or power failure with secrets in memory
ALIAS_FAULT = BIT (5), // Alias Error Violation
MEMORY_ATTACK = BIT (2), // Illegal read of DRAM
ROGUE = BIT (1), // CPU has left the secure environment improperly
POISON = BIT (0), // TXT.POISON cycle received
};
enum E2STS
{
SECRETS = BIT (1), // Secrets in memory
};
enum VER_QPIIF
{
PRD = BIT (31), // Production Fused
TXT = BIT (27), // TXT Capable
DPR = BIT (26), // DPR Capable
PMRC = BIT (19), // PMRC Capable
};
static auto read (Space s, Reg8 r) { return *reinterpret_cast<uint8_t volatile *>(MMAP_GLB_TXTC + std::to_underlying (s) + std::to_underlying (r)); }
static auto read (Space s, Reg32 r) { return *reinterpret_cast<uint32_t volatile *>(MMAP_GLB_TXTC + std::to_underlying (s) + std::to_underlying (r)); }
static auto read (Space s, Reg64 r) { return *reinterpret_cast<uint64_t volatile *>(MMAP_GLB_TXTC + std::to_underlying (s) + std::to_underlying (r)); }
static void write (Space s, Reg8 r, uint8_t v) { *reinterpret_cast<uint8_t volatile *>(MMAP_GLB_TXTC + std::to_underlying (s) + std::to_underlying (r)) = v; }
static void write (Space s, Reg32 r, uint32_t v) { *reinterpret_cast<uint32_t volatile *>(MMAP_GLB_TXTC + std::to_underlying (s) + std::to_underlying (r)) = v; }
static void write (Space s, Reg64 r, uint64_t v) { *reinterpret_cast<uint64_t volatile *>(MMAP_GLB_TXTC + std::to_underlying (s) + std::to_underlying (r)) = v; }
[[nodiscard]] static bool command (Reg8 c, Reg64 s, uint64_t f)
{
/*
* Command registers are write-only in the TXT private configuration space.
* Accesses to command registers are done with 1-byte writes.
* The data bits associated with a command are undefined and have no specific meaning.
*/
write (Space::PRIVATE, c, 0);
/*
* After writing to a command register, software should read the
* corresponding status flag for that command to ensure that the
* command has completed successfully.
*/
return read (Space::PUBLIC, s) & f;
}
ALWAYS_INLINE static inline bool check_acm (Mle_header *, uint32_t, uint32_t, uint32_t, uint32_t &, uint32_t &, uint32_t &);
ALWAYS_INLINE static inline bool init_heap (Mle_header *, uint32_t, uint32_t, uint32_t, uint32_t, unsigned);
ALWAYS_INLINE static inline bool init_mtrr (uint64_t, uint64_t, unsigned, unsigned);
static void parse_elem (Element const *, void const *, uintptr_t);
static void restore() asm ("txt_restore");
public:
static inline constinit bool launched asm ("launched") { false };
static void launch();
static void init();
static void fini();
};

35
src/x86_64/head_mle.S Normal file
View File

@ -0,0 +1,35 @@
/*
* MLE Header
*
* Copyright (C) 2019-2025 Udo Steinberg, BlueRock Security, Inc.
*
* This file is part of the NOVA microhypervisor.
*
* NOVA is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* NOVA is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License version 2 for more details.
*/
#include "macros.hpp"
.global __head_mle
.section .head
.balign 8
__head_mle: .octa 0x42b651cba2555c0f74a7476f9082ac5a // 0 UUID
.long .Lhead_mle - __head_mle // 16 Length
.long 0x20003 // 20 Version
.long HASH_INIT // 24 ILP Entry
.long 0 // 28 First Page
.long 0 // 32 MLE Start
.long HASH_SIZE // 36 MLE End
.long BIT_RANGE (10, 0) // 40 MLE Caps
.long 0 // 44 Cmd Start
.long 0 // 48 Cmd End
.Lhead_mle:

View File

@ -28,6 +28,7 @@ ENTRY(__init_bsp)
PHDRS PHDRS
{ {
ptab PT_LOAD FLAGS (6);
init PT_LOAD FLAGS (6); init PT_LOAD FLAGS (6);
kern PT_LOAD FLAGS (6); kern PT_LOAD FLAGS (6);
} }
@ -38,6 +39,18 @@ SECTIONS
PROVIDE_HIDDEN (NOVA_HPAS = .); PROVIDE_HIDDEN (NOVA_HPAS = .);
#if defined(__x86_64__)
.ptab :
{
PROVIDE_HIDDEN (MLE_TL = .); . += 4K;
PROVIDE_HIDDEN (MLE_L2 = .); . += 4K;
PROVIDE_HIDDEN (MLE_L1 = .); . += 4K;
PROVIDE_HIDDEN (MLE_L0 = .); . += 4K;
} : ptab
#endif
PROVIDE_HIDDEN (HASH_HPAS = .);
.init : .init :
{ {
*(.init .init.*) *(.init .init.*)
@ -79,6 +92,8 @@ SECTIONS
PROVIDE_HIDDEN (CTORS_E = .); PROVIDE_HIDDEN (CTORS_E = .);
} : kern } : kern
PROVIDE_HIDDEN (HASH_HPAE = . - OFFSET);
.data : AT (ADDR (.data) - OFFSET) .data : AT (ADDR (.data) - OFFSET)
{ {
*(.data .data.* .gnu.linkonce.d.*) *(.data .data.* .gnu.linkonce.d.*)
@ -111,6 +126,12 @@ SECTIONS
PROVIDE_HIDDEN (NOVA_HPAE = . - OFFSET); PROVIDE_HIDDEN (NOVA_HPAE = . - OFFSET);
PROVIDE_HIDDEN (HASH_SIZE = HASH_HPAE - HASH_HPAS);
PROVIDE_HIDDEN (HASH_HEAD = __head_mle - HASH_HPAS);
PROVIDE_HIDDEN (HASH_INIT = __init_ilp - HASH_HPAS);
PROVIDE_HIDDEN (HASH_HPAL = (HASH_HPAE - 1) & ~OFFS_MASK (0));
PROVIDE_HIDDEN (MLE_L0_LS = MLE_L0 + __SIZEOF_POINTER__ * ((HASH_SIZE - 1) >> PAGE_BITS));
PROVIDE_HIDDEN (DSTK_TOP = MMAP_CPU_DSTT); PROVIDE_HIDDEN (DSTK_TOP = MMAP_CPU_DSTT);
PROVIDE_HIDDEN (DSHD_TOP = MMAP_CPU_DTKN); PROVIDE_HIDDEN (DSHD_TOP = MMAP_CPU_DTKN);
@ -130,4 +151,5 @@ SECTIONS
. = ASSERT ((NOVA_HPAS & ALIGNMENT_OFFS (ALIGNMENT_NOVA)) == 0, "NOVA_HPAS not ORD aligned"); . = ASSERT ((NOVA_HPAS & ALIGNMENT_OFFS (ALIGNMENT_NOVA)) == 0, "NOVA_HPAS not ORD aligned");
. = ASSERT ((NOVA_HPAE & OFFS_MASK (1)) == 0, "NOVA_HPAE not 2MB aligned"); . = ASSERT ((NOVA_HPAE & OFFS_MASK (1)) == 0, "NOVA_HPAE not 2MB aligned");
. = ASSERT ((HASH_HPAS & OFFS_MASK (0)) == 0, "HASH_HPAS not 4KB aligned");
} }

View File

@ -31,6 +31,7 @@
#include "space_hst.hpp" #include "space_hst.hpp"
#include "stdio.hpp" #include "stdio.hpp"
#include "string.hpp" #include "string.hpp"
#include "txt.hpp"
extern "C" uintptr_t kern_ptab_setup (apic_t t) extern "C" uintptr_t kern_ptab_setup (apic_t t)
{ {
@ -70,7 +71,12 @@ extern "C" uintptr_t kern_ptab_setup (apic_t t)
extern "C" void preinit() extern "C" void preinit()
{ {
if (!Acpi::resume && !Txt::launched)
Cmdline::init();
Patch::detect(); Patch::detect();
Txt::launch();
} }
extern "C" void init() extern "C" void init()
@ -82,8 +88,6 @@ extern "C" void init()
for (auto func { CTORS_S }; func != CTORS_E; (*func++)()) ; for (auto func { CTORS_S }; func != CTORS_E; (*func++)()) ;
Cmdline::init();
for (auto func { CTORS_C }; func != CTORS_S; (*func++)()) ; for (auto func { CTORS_C }; func != CTORS_S; (*func++)()) ;
// Now we're ready to talk to the world // Now we're ready to talk to the world
@ -92,6 +96,8 @@ extern "C" void init()
Interrupt::setup(); Interrupt::setup();
} }
Txt::init();
Acpi::init(); Acpi::init();
Pic::init(); Pic::init();

View File

@ -29,6 +29,7 @@
#include "stc.hpp" #include "stc.hpp"
#include "stdio.hpp" #include "stdio.hpp"
#include "timeout.hpp" #include "timeout.hpp"
#include "txt.hpp"
#include "vectors.hpp" #include "vectors.hpp"
void Lapic::init (uint32_t clk, uint32_t rat) void Lapic::init (uint32_t clk, uint32_t rat)
@ -92,7 +93,8 @@ void Lapic::init (uint32_t clk, uint32_t rat)
if ((Cpu::bsp = apic_base & BIT (8))) { if ((Cpu::bsp = apic_base & BIT (8))) {
send_exc (0, Delivery::DLV_INIT); if (!Txt::launched)
send_exc (0, Delivery::DLV_INIT);
write (Reg32::TMR_ICR, ~0U); write (Reg32::TMR_ICR, ~0U);
@ -112,9 +114,11 @@ void Lapic::init (uint32_t clk, uint32_t rat)
trace (TRACE_INTR, "FREQ: %lu Hz (%s) Ratio:%u", Stc::freq, f ? "enumerated" : "measured", ratio); trace (TRACE_INTR, "FREQ: %lu Hz (%s) Ratio:%u", Stc::freq, f ? "enumerated" : "measured", ratio);
send_exc (Acpi::sipi >> PAGE_BITS, Delivery::DLV_SIPI); if (!Txt::launched) {
Acpi_fixed::delay (1); send_exc (Acpi::sipi >> PAGE_BITS, Delivery::DLV_SIPI);
send_exc (Acpi::sipi >> PAGE_BITS, Delivery::DLV_SIPI); Acpi_fixed::delay (1);
send_exc (Acpi::sipi >> PAGE_BITS, Delivery::DLV_SIPI);
}
} }
write (Reg32::TMR_ICR, 0); write (Reg32::TMR_ICR, 0);

View File

@ -34,7 +34,7 @@
#define PTE_ATTR_S BIT (7) // Superpage #define PTE_ATTR_S BIT (7) // Superpage
#define PTE_ATTR_G BIT (8) // Global #define PTE_ATTR_G BIT (8) // Global
.global __init_bsp, __init_aps, __init_aps__, __wake_vec .global __init_bsp, __init_aps, __init_aps__, __wake_vec, __init_ilp
/* /*
* Macros * Macros
@ -223,6 +223,19 @@ __init_bsp: // Check if we launched via Multiboot v2
REL_SYM (__relo_pdb - OFFSET) REL_SYM (__relo_pdb - OFFSET)
lgdt (__relo_pdl - OFFSET)(%ebp) lgdt (__relo_pdl - OFFSET)(%ebp)
// Configure MLE Page Tables (4K pages)
PTE_FIX 2, MLE_L2, 0, MLE_L1, PTE_ATTR_P
PTE_FIX 1, MLE_L1, 0, MLE_L0, PTE_ATTR_P
lea MLE_L0(%ebp), %edx
lea HASH_HPAS + PTE_ATTR_P(%ebp), %ecx
lea HASH_HPAE(%ebp), %eax
.Lmle_loop: mov %ecx, (%edx)
add $__SIZEOF_POINTER__, %edx
add $PAGE_SIZE (0), %ecx
cmp %eax, %ecx
jb .Lmle_loop
.Lilp:
// Configure Page Tables: Load Section (2M page) // Configure Page Tables: Load Section (2M page)
PTE_REL 3, PT3S_HPAS, NOVA_HPAS, PT2L_HPAS, PTE_ATTR_A | PTE_ATTR_U | PTE_ATTR_W | PTE_ATTR_P PTE_REL 3, PT3S_HPAS, NOVA_HPAS, PT2L_HPAS, PTE_ATTR_A | PTE_ATTR_U | PTE_ATTR_W | PTE_ATTR_P
PTE_REL 2, PT2L_HPAS, NOVA_HPAS, PT1L_HPAS, PTE_ATTR_A | PTE_ATTR_U | PTE_ATTR_W | PTE_ATTR_P PTE_REL 2, PT2L_HPAS, NOVA_HPAS, PT1L_HPAS, PTE_ATTR_A | PTE_ATTR_U | PTE_ATTR_W | PTE_ATTR_P
@ -247,10 +260,153 @@ __init_bsp: // Check if we launched via Multiboot v2
lea __init_all(%ebp), %ebx lea __init_all(%ebp), %ebx
mov %ebx, __relo_jip - OFFSET(%ebp) mov %ebx, __relo_jip - OFFSET(%ebp)
movw $SEL_KERN_CODE, __relo_jcs - OFFSET(%ebp) movw $SEL_KERN_CODE, __relo_jcs - OFFSET(%ebp)
.Lrlp:
INIT_PAGING INIT_PAGING
ljmp *__relo_jip - OFFSET(%ebp) ljmp *__relo_jip - OFFSET(%ebp)
/*
* ILP Re-Entry
*/
__init_ilp: // Determine load skew
mov %ebx, %ebp
sub $__init_ilp, %ebp
// Crash if load skew is misaligned
test $ALIGNMENT_OFFS (ALIGNMENT_NOVA), %ebp
jnz .Lcrash
// Crash if pseudo descriptor limit is wrong
mov $.Lboot_gdt - __boot_gdt - 1, %eax
cmp %ax, %cs:__relo_pdl - OFFSET(%ebp)
jne .Lcrash
// Crash if pseudo descriptor base is wrong
lea __boot_gdt(%ebp), %ebx
cmp %ebx, %cs:__relo_pdb - OFFSET(%ebp)
jne .Lcrash
// Load measured boot GDT
lgdt %cs:__relo_pdl - OFFSET(%ebp)
// Establish valid 32-bit segments for SS/DS/ES
mov $SEL_KERN_DATA + 16, %ecx
mov %cx, %ss
mov %cx, %ds
mov %cx, %es
// Initialize MLE join structure
sub $8, %ecx
lea __init_rlp(%ebp), %edx
lea mle_join - OFFSET(%ebp), %edi
mov %eax, 0(%edi) // GDT Limit
mov %ebx, 4(%edi) // GDT Base
mov %ecx, 8(%edi) // SEL
mov %edx, 12(%edi) // EIP
// Traverse TXT heap
mov 0xfed30300, %esi // Data_efi_pre
add (%esi), %esi // Data_pre_mle
add (%esi), %esi // Data_pre_acm
// Crash if Data_pre_acm does not point to MLE_L2
lea MLE_L2(%ebp), %eax
cmp %eax, 16(%esi)
jne .Lcrash
// Crash if MLE_L2 does not point to MLE_L1 + attributes
lea MLE_L1 + PTE_ATTR_P(%ebp), %eax
cmp %eax, MLE_L2(%ebp)
jne .Lcrash
// Crash if MLE_L1 does not point to MLE_L0 + attributes
lea MLE_L0 + PTE_ATTR_P(%ebp), %eax
cmp %eax, MLE_L1(%ebp)
jne .Lcrash
// Crash if MLE first page is not virt/phys contiguous with re-entry point
lea HASH_HPAS + PTE_ATTR_P(%ebp), %eax
cmp %eax, MLE_L0(%ebp)
jne .Lcrash
// Crash if MLE last page is not virt/phys contiguous with re-entry point
lea HASH_HPAL + PTE_ATTR_P(%ebp), %eax
cmp %eax, MLE_L0_LS(%ebp)
jne .Lcrash
// Determine image range
lea NOVA_HPAS(%ebp), %eax // NOVA.S
lea NOVA_HPAE(%ebp), %ecx // NOVA.E (original)
mov multiboot_ea - OFFSET(%ebp), %ebx // NOVA.E (extended)
// Crash if NOVA.E is wrong (extended < original)
cmp %ecx, %ebx
jb .Lcrash
// Crash if NOVA.E is misaligned
test $OFFS_MASK (1), %ebx
jnz .Lcrash
// Crash if PMR does not cover NOVA.S...NOVA.E
mov %ebx, %ecx
sub %eax, %ecx
cmp %eax, 40(%esi) // PMR Lo Base
jne .Lcrash
cmp %ecx, 48(%esi) // PMR Lo Size
jne .Lcrash
// Traverse TXT heap
add (%esi), %esi // Data_acm_mle
// Crash if Data_acm_mle version < 5
cmpl $5, 8(%esi)
jb .Lcrash
// Traverse MDRs
mov %esi, %edi
mov 136(%esi), %ecx // MDR count
add 140(%esi), %edi // MDR pointer
.Lmdr_loop:
// Check if NOVA.S (ESP:EAX) >= MDR.S (ESI:EDX)
mov 0(%edi), %edx
mov 4(%edi), %esi
xor %esp, %esp
cmp %edx, %eax
sbb %esi, %esp
jc .Lmdr_next // NOVA.S < MDR.S
// Check if NOVA.E (ESP:EBX) <= MDR.E (ESI:EDX)
add 8(%edi), %edx
adc 12(%edi), %esi
xor %esp, %esp
cmp %ebx, %edx
sbb %esp, %esi
jc .Lmdr_next // NOVA.E > MDR.E
// Crash if MDR covering NOVA is != type 0
cmpb $0, 16(%edi)
jne .Lcrash
// All post-launch checks succeeded
orb $1, launched - OFFSET(%ebp)
jmp .Lilp
.Lmdr_next:
lea 24(%edi), %edi
loop .Lmdr_loop
.Lcrash:
ud2
/*
* RLP Re-Entry
*/
__init_rlp: // Determine load skew
mov 0xfed30290, %ebp
sub $mle_join - OFFSET, %ebp
// Mark as RLP
xor %ebx, %ebx
jmp .Lrlp
/* /*
* 16-bit Startup Code: Application Processors (APs) * 16-bit Startup Code: Application Processors (APs)
*/ */
@ -278,6 +434,8 @@ __init_aps__:
__boot_gdt: .quad 0 // SEL_NULL __boot_gdt: .quad 0 // SEL_NULL
.quad 0x00a09b0000000000 // SEL_KERN_CODE (64-bit) .quad 0x00a09b0000000000 // SEL_KERN_CODE (64-bit)
.quad 0x00a0930000000000 // SEL_KERN_DATA (64-bit) .quad 0x00a0930000000000 // SEL_KERN_DATA (64-bit)
.quad 0x00cf9b000000ffff // SEL_KERN_CODE (32-bit)
.quad 0x00cf93000000ffff // SEL_KERN_DATA (32-bit)
.Lboot_gdt: .Lboot_gdt:
/* /*
@ -321,6 +479,9 @@ __init_all: mov $.Lhigh, %rax
movzwl __boot_lock, %edx movzwl __boot_lock, %edx
jmp .Llock_retry jmp .Llock_retry
.Llock_end: .Llock_end:
// Restore TXT state
call txt_restore
// Init (BSP only) // Init (BSP only)
test %ebx, %ebx test %ebx, %ebx
jz .Lskip_init jz .Lskip_init

424
src/x86_64/txt.cpp Normal file
View File

@ -0,0 +1,424 @@
/*
* Trusted Execution Technology (TXT)
*
* Copyright (C) 2019-2025 Udo Steinberg, BlueRock Security, Inc.
*
* This file is part of the NOVA microhypervisor.
*
* NOVA is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* NOVA is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License version 2 for more details.
*/
#include "acm.hpp"
#include "acpi_table.hpp"
#include "cache_guard.hpp"
#include "mtrr.hpp"
#include "multiboot.hpp"
#include "smx.hpp"
#include "space_hst.hpp"
#include "stdio.hpp"
#include "tpm.hpp"
#include "tpm_log.hpp"
#include "txt.hpp"
#include "uefi.hpp"
bool Txt::check_acm (Mle_header *hdr, uint32_t sinit_base, uint32_t sinit_size, uint32_t fms, uint32_t &acm_size, uint32_t &acm_caps, uint32_t &tpm_caps)
{
auto const acm { static_cast<Acm::Header const *>(Hptp::map (MMAP_GLB_MAP0, sinit_base)) };
// FIXME: Check that ACM total size fits into remap window
// ACM must have correct type and fit into the range
if (acm->type != 2 || acm->subtype != 0 || sinit_size < 4 * acm->total_size) [[unlikely]]
return false;
auto const info { reinterpret_cast<Acm::Info const *>(reinterpret_cast<uintptr_t>(acm) + 4 * (acm->header_size + acm->scratch_size)) };
// ACM UUID must be valid
if (info->uuid != Uuid { 0x7fc03aaa, 0x46a7, 0x18db, { 0x2e, 0xac, 0x69, 0x8f, 0x8d, 0x41, 0x7f, 0x5a }}) [[unlikely]]
return false;
// FIXME: Check that "pch" and "cpu" list start/end are within remap window
// ACM must support PRE-ACM version
if (info->max_ver_pre_acm < ver_pre_acm) [[unlikely]]
return false;
// ACM must support MLE header version
if (info->min_ver_mle_hdr > hdr->version) [[unlikely]]
return false;
// ACM must support PCH
auto const pch { acm->pch (info) };
if (pch && !reinterpret_cast<Acm::Id_pch const *>(pch->list())->match (pch->count, read (Space::PUBLIC, Reg64::DIDVID))) [[unlikely]]
return false;
// ACM must support CPU
auto const cpu { acm->cpu (info) };
if (cpu && !reinterpret_cast<Acm::Id_cpu const *>(cpu->list())->match (cpu->count, fms, Msr::read (Msr::Reg64::IA32_PLATFORM_ID))) [[unlikely]]
return false;
// Patch out MLE caps as needed to work around ACM bugs (this will change the hash)
for (unsigned i { 0 }; i < sizeof Acm::quirks / sizeof *Acm::quirks; i++)
if (Acm::quirks[i].chipset == (acm->vendor << 16 | acm->chipset) && Acm::quirks[i].date == acm->date) [[unlikely]]
hdr->mle_caps = hdr->mle_caps & ~Acm::quirks[i].caps;
acm_size = 4 * acm->total_size;
acm_caps = info->capabilities;
tpm_caps = acm->tpm (info);
return true;
}
bool Txt::init_heap (Mle_header *hdr, uint32_t heap_base, uint32_t /*heap_size*/, uint32_t acm_caps, uint32_t tpm_caps, unsigned vcnt)
{
// EFI to PRE
auto const efi_pre { reinterpret_cast<Data_efi_pre const *>(MMAP_GLB_TXTH + (heap_base & Hpt::offs_mask (Hpt::bpl))) };
// FIXME: Check that EFI_PRE fits entirely into heap_size
// Number of CPUs must be within range
if (efi_pre->num_cpu < 1 || efi_pre->num_cpu > NUM_CPU) [[unlikely]]
return false;
// Platform type must be supported by SINIT
auto const plat { efi_pre->plat() };
if ((plat == 1 && !(acm_caps & Acm::Cap::PLAT_CLIENT)) || (plat == 2 && !(acm_caps & Acm::Cap::PLAT_SERVER))) [[unlikely]]
return false;
// PRE to MLE
auto const pre_mle { new (efi_pre->data.next()) Data_pre_mle
{
.data { Data { sizeof (Data_pre_mle) + vcnt * sizeof (Data_pre_mle::Mtrr_backup) } },
.ia32_mtrr_def_type { Msr::read (Msr::Reg64::IA32_MTRR_DEF_TYPE) },
.ia32_misc_enable { Msr::read (Msr::Reg64::IA32_MISC_ENABLE) },
.ia32_debugctl { Msr::read (Msr::Reg64::IA32_DEBUGCTL) },
} };
// FIXME: Check that EFI_PRE + PRE_MLE fits entirely into heap_size
// Save MTRRs
auto mtrr { pre_mle->mtrr() };
for (unsigned i { 0 }; i < vcnt; i++, mtrr++)
new (mtrr) Data_pre_mle::Mtrr_backup { Mtrr::get_base (i), Mtrr::get_mask (i) };
// PRE to ACM
auto const pre_acm { new (pre_mle->data.next()) Data_pre_acm
{
.flags { !!(tpm_caps & Acm::Tpm::MAX_PERFORMANCE) },
.mle_ptab { Kmem::sym_to_phys (&MLE_L2) },
.mle_size { hdr->mle_end - hdr->mle_start },
.mle_header { reinterpret_cast<uintptr_t>(&HASH_HEAD) },
.pmr_lo_base { Kmem::sym_to_phys (&NOVA_HPAS) },
.pmr_lo_size { Multiboot::ea - Kmem::sym_to_phys (&NOVA_HPAS) },
.caps { hdr->mle_caps & acm_caps },
.rsdp { Uefi::info.rsdp },
} };
// FIXME: Check that EFI_PRE + PRE_MLE + PRE_ACM fits entirely into heap_size
// Add LOG element
auto const tpm_log { Kmem::sym_to_phys (&MLE_TL) };
pre_acm->append (acm_caps & Acm::Cap::TPM_20_TCG_LOG ?
(new (pre_acm->data.next()) Element_log20_tcg { tpm_log, PAGE_SIZE (0) })->elem :
(new (pre_acm->data.next()) Element_log20_txt { tpm_log, PAGE_SIZE (0) })->elem);
// Add END element
pre_acm->append ((new (pre_acm->data.next()) Element_end)->elem);
return true;
}
bool Txt::init_mtrr (uint64_t phys, uint64_t size, unsigned vcnt, unsigned bits)
{
// Ensure size is a multiple of PAGE_SIZE
size = aligned_up (PAGE_SIZE (0), size);
auto const mask { BIT64 (bits) - 1 };
{ Cache_guard guard;
// Disable all MTRRs and set default memory type as UC
Msr::write (Msr::Reg64::IA32_MTRR_DEF_TYPE, CA_TYPE_MEM_UC);
// Update variable MTRRs to map SINIT ACM as WB
for (unsigned i { 0 }; i < vcnt; i++) {
uint64_t b { 0 }, m { 0 };
if (size) {
auto const s { BIT64 (aligned_order (size, phys)) };
b = phys | CA_TYPE_MEM_WB;
m = (mask & ~(s - 1)) | BIT (11);
size -= s;
phys += s;
}
Mtrr::set_base (i, b);
Mtrr::set_mask (i, m);
}
// Enable variable MTRRs and set default memory type as UC
Msr::write (Msr::Reg64::IA32_MTRR_DEF_TYPE, CA_TYPE_MEM_UC | BIT (11));
}
return true;
}
void Txt::launch()
{
uint32_t fms, ebx, ecx, edx, acm_size, acm_caps, tpm_caps;
Cpu::cpuid (0x1, fms, ebx, ecx, edx);
// Abort if SMX is not supported
if (!(ecx & BIT (6))) [[unlikely]]
return;
// Enable SMX
Cr::set_cr4 (Cr::get_cr4() | CR4_SMXE);
// Abort if SMX capabilities are missing
if ((Smx::capabilities() & Smx::required) != Smx::required) [[unlikely]]
return;
// Map TXT registers
Hptp::map (MMAP_GLB_TXTC, txt_base, Paging::Permissions (Paging::G | Paging::W | Paging::R), Memattr::dev(), 1);
auto const heap_base { read (Space::PUBLIC, Reg32::HEAP_BASE) };
auto const heap_size { read (Space::PUBLIC, Reg32::HEAP_SIZE) };
// Abort if HEAP region is invalid
if (!heap_base || !heap_size) [[unlikely]]
return;
// Map TXT heap
Hptp::map (MMAP_GLB_TXTH, heap_base, Paging::Permissions (Paging::G | Paging::W | Paging::R), Memattr::ram(), 2);
// The rest of the function is pre-launch only
if (launched)
return;
auto const sinit_base { read (Space::PUBLIC, Reg32::SINIT_BASE) };
auto const sinit_size { read (Space::PUBLIC, Reg32::SINIT_SIZE) };
// Abort if SINIT region is invalid
if (!sinit_base || !sinit_size) [[unlikely]]
return;
// Abort if the last launch failed
auto const error { read (Space::PUBLIC, Reg32::ERRORCODE) };
if (error & BIT (31)) [[unlikely]]
if (!(error & BIT (30)) || error >> 4 & BIT_RANGE (5, 0))
return;
// Abort if any MCE banks report errors
auto const banks { static_cast<uint8_t>(Msr::read (Msr::Reg64::IA32_MCG_CAP)) };
for (unsigned i { 0 }; i < banks; i++)
if (Msr::read (Msr::Arr64::IA32_MC_STATUS, 4, i) & BIT64 (63)) [[unlikely]]
return;
// Abort if MCE is in progress
if (Msr::read (Msr::Reg64::IA32_MCG_STATUS) & BIT (2)) [[unlikely]]
return;
// Abort if TPM initialization fails
if (!Tpm::init (false)) [[unlikely]]
return;
auto const mle { reinterpret_cast<Mle_header *>(Kmem::sym_to_virt (&__head_mle)) };
// Abort if SINIT ACM check fails
if (!check_acm (mle, sinit_base, sinit_size, fms, acm_size, acm_caps, tpm_caps)) [[unlikely]]
return;
// Determine number of variable MTRRs
auto const vcnt { Mtrr::get_vcnt() };
// Abort if TXT heap initialization fails
if (!init_heap (mle, heap_base, heap_size, acm_caps, tpm_caps, vcnt)) [[unlikely]]
return;
// Abort if MTRR initialization fails
if (!init_mtrr (sinit_base, acm_size, vcnt, acm_caps & Acm::Cap::MAXPHYADDR ? Memattr::kbits + Memattr::obits : 36)) [[unlikely]]
return;
// Enter measured launch environment
Smx::senter (sinit_base, acm_size);
}
void Txt::restore()
{
// Variable MTRR masks must not set any key bits
auto const mask { BIT64 (Memattr::obits) - 1 };
// Determine number of variable MTRRs
auto const vcnt { Mtrr::get_vcnt() };
if (launched) [[likely]] {
// TXT registers and heap have already been mapped by launch()
auto const efi_pre { reinterpret_cast<Data_efi_pre const *>(MMAP_GLB_TXTH + (read (Space::PUBLIC, Reg32::HEAP_BASE) & Hpt::offs_mask (Hpt::bpl))) };
auto const pre_mle { reinterpret_cast<Data_pre_mle const *>(efi_pre->data.next()) };
auto mtrr { pre_mle->mtrr() };
{ Cache_guard guard;
// Restore variable MTRRs and zap key bits
for (unsigned i { 0 }; i < vcnt; i++, mtrr++) {
Mtrr::set_base (i, mtrr->base);
Mtrr::set_mask (i, mtrr->mask & mask);
}
// Restore MSRs
Msr::write (Msr::Reg64::IA32_MTRR_DEF_TYPE, pre_mle->ia32_mtrr_def_type);
Msr::write (Msr::Reg64::IA32_MISC_ENABLE, pre_mle->ia32_misc_enable);
Msr::write (Msr::Reg64::IA32_DEBUGCTL, pre_mle->ia32_debugctl);
}
// Enable SMX
Cr::set_cr4 (Cr::get_cr4() | CR4_SMXE);
// Reenable SMI
Smx::smctrl();
} else {
// Check for buggy firmware that misprogrammed the MTRRs
if (!Mtrr::validate (vcnt, ~mask)) [[unlikely]] {
Cache_guard guard;
// Disable MTRRs
Msr::write (Msr::Reg64::IA32_MTRR_DEF_TYPE, Msr::read (Msr::Reg64::IA32_MTRR_DEF_TYPE) & ~BIT64 (11));
// Fix up variable MTRRs and zap key bits
for (unsigned i { 0 }; i < vcnt; i++)
Mtrr::set_mask (i, Mtrr::get_mask (i) & mask);
// Enable MTRRs
Msr::write (Msr::Reg64::IA32_MTRR_DEF_TYPE, Msr::read (Msr::Reg64::IA32_MTRR_DEF_TYPE) | BIT64 (11));
}
}
}
void Txt::parse_elem (Element const *e, void const *l, uintptr_t o)
{
if (!e) [[unlikely]]
return;
// Iterate Extended Heap Elements
for (; static_cast<void const *>(e) < l; e = e->get_next()) {
switch (e->get_type()) {
default:
break;
case Element::Type::END:
return;
// Override these ACPI tables with their ACM-validated copies on the TXT heap
case Element::Type::MADT:
case Element::Type::MCFG:
case Element::Type::DPTR:
case Element::Type::CEDT:
reinterpret_cast<Acpi_table const *>(e->get_data())->validate (e->get_data() - o);
break;
case Element::Type::LOG20_TCG:
auto const log { reinterpret_cast<Element_log20_tcg const *>(e) };
Tpm_log::init (log->phys, log->size, log->off_next);
break;
}
}
}
void Txt::init()
{
// Abort if there was no measured launch
if (!launched) [[unlikely]]
return;
Tpm::init (true);
auto const dv1 { read (Space::PUBLIC, Reg64::DIDVID) };
auto const dv2 { read (Space::PUBLIC, Reg64::DIDVID2) };
auto const ver { read (Space::PUBLIC, Reg32::VER_QPIIF) };
trace (TRACE_DRTM, "DRTM: %04x:%04x (%#x) %04x:%04x (%s)",
static_cast<uint16_t>(dv1), static_cast<uint16_t>(dv1 >> 16), static_cast<uint16_t>(dv1 >> 32),
static_cast<uint16_t>(dv2), static_cast<uint16_t>(dv2 >> 16), ver & VER_QPIIF::PRD ? "PRD" : "DBG");
auto const dpr { read (Space::PUBLIC, Reg32::DPR) };
auto const dma_size { (dpr >> 4 & BIT_RANGE (7, 0)) * BIT (20) };
auto const dma_base { (dpr & ~BIT_RANGE (11, 0)) - dma_size };
// Reserve DPR/TXT region
Space_hst::access_ctrl (dma_base, dma_size, Paging::NONE);
Space_hst::access_ctrl (txt_base, txt_size, Paging::NONE);
// Grant user read/write access to TPM localities 0/1 and read access to event log
Space_hst::access_ctrl (0xfed40000, 2 * PAGE_SIZE (0), Paging::Permissions (Paging::U | Paging::W | Paging::R));
Space_hst::access_ctrl (Kmem::sym_to_phys (&MLE_TL), PAGE_SIZE (0), Paging::Permissions (Paging::U | Paging::R));
auto const heap_base { read (Space::PUBLIC, Reg32::HEAP_BASE) };
auto const heap_size { read (Space::PUBLIC, Reg32::HEAP_SIZE) };
auto const heap_offs { MMAP_GLB_TXTH - (heap_base & ~Hpt::offs_mask (Hpt::bpl)) };
trace (TRACE_DRTM, "DRTM: %#x/%#x SINIT %#x/%#x HEAP", read (Space::PUBLIC, Reg32::SINIT_BASE), read (Space::PUBLIC, Reg32::SINIT_SIZE), heap_base, heap_size);
// TXT registers and heap have already been mapped by launch()
auto const efi_pre { reinterpret_cast<Data_efi_pre const *>(heap_base + heap_offs) };
auto const pre_mle { reinterpret_cast<Data_pre_mle const *>(efi_pre->data.next()) };
auto const pre_acm { reinterpret_cast<Data_pre_acm const *>(pre_mle->data.next()) };
auto const acm_mle { reinterpret_cast<Data_acm_mle const *>(pre_acm->data.next()) };
trace (TRACE_DRTM, "DRTM: EFI-PRE v%u: %4lu", uint32_t { efi_pre->version }, uint64_t { efi_pre->data.size });
trace (TRACE_DRTM, "DRTM: PRE-MLE v%u: %4lu", uint32_t { 0 }, uint64_t { pre_mle->data.size });
trace (TRACE_DRTM, "DRTM: PRE-ACM v%u: %4lu", uint32_t { pre_acm->version }, uint64_t { pre_acm->data.size });
trace (TRACE_DRTM, "DRTM: ACM-MLE v%u: %4lu", uint32_t { acm_mle->version }, uint64_t { acm_mle->data.size });
// Consume extended heap elements
parse_elem (pre_acm->elem(), pre_acm->data.next(), heap_offs);
parse_elem (acm_mle->elem(), acm_mle->data.next(), heap_offs);
// Override ACPI DMAR table with ACM-validated copy on the TXT heap (v5+)
auto const dmar { reinterpret_cast<uintptr_t>(acm_mle) + acm_mle->dmar_offset };
reinterpret_cast<Acpi_table const *>(dmar)->validate (dmar - heap_offs);
// Clear "success" from errorcode (because a soft reset won't)
write (Space::PRIVATE, Reg32::ERRORCODE, 0);
// Open locality 1 and enable secrets protection
if (!command (Reg8::LOCALITY1_OPEN, Reg64::STS, STS::LOCALITY1) || !command (Reg8::SECRETS_SET, Reg64::E2STS, E2STS::SECRETS)) [[unlikely]]
trace (TRACE_ERROR, "%s: TXT command failed", __func__);
// Wake RLPs
write (Space::PUBLIC, Reg32::MLE_JOIN, static_cast<uint32_t>(Kmem::ptr_to_phys (&Smx::mle_join)));
if (pre_acm->caps & Acm::Cap::WAKEUP_GETSEC)
Smx::wakeup();
else if (pre_acm->caps & Acm::Cap::WAKEUP_MONITOR)
*static_cast<Atomic<uint32_t> *>(Hptp::map (MMAP_GLB_MAP0, acm_mle->rlp_wakeup, Paging::W, Memattr::ram(), 1)) = 1;
}
void Txt::fini()
{
// Abort if there was no measured launch
if (!launched) [[unlikely]]
return;
// Close locality 1 and disable secrets protection
if (command (Reg8::LOCALITY1_CLOSE, Reg64::STS, STS::LOCALITY1) || command (Reg8::SECRETS_CLR, Reg64::E2STS, E2STS::SECRETS)) [[unlikely]]
trace (TRACE_ERROR, "%s: TXT command failed", __func__);
// Exit measured launch environment
Smx::sexit();
}