x86: Added TXT support.
This commit is contained in:
parent
abbd800ab1
commit
c94a88d436
10
README.md
10
README.md
|
@ -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 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
|
||||
|
||||
See the NOVA interface specification in the `doc` directory for details
|
||||
|
|
|
@ -26,6 +26,7 @@
|
|||
#include "io.hpp"
|
||||
#include "lowlevel.hpp"
|
||||
#include "macros.hpp"
|
||||
#include "txt.hpp"
|
||||
|
||||
class Acpi_fixed final
|
||||
{
|
||||
|
@ -162,7 +163,7 @@ class Acpi_fixed final
|
|||
/*
|
||||
* Wait for all APs to be offline
|
||||
*/
|
||||
static void offline_wait() {}
|
||||
static void offline_wait() { Txt::fini(); }
|
||||
|
||||
/*
|
||||
* Perform platform reset
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
|
||||
#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 char entry_sys;
|
||||
|
|
|
@ -53,6 +53,8 @@
|
|||
|
||||
// Global Area
|
||||
#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_MAP0 VIRT_ADDR (511, 510, 496, 0) // 4M + gap
|
||||
#define MMAP_GLB_UART VIRT_ADDR (511, 510, 488, 0) // 16M
|
||||
|
|
|
@ -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();
|
||||
};
|
|
@ -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:
|
|
@ -28,6 +28,7 @@ ENTRY(__init_bsp)
|
|||
|
||||
PHDRS
|
||||
{
|
||||
ptab PT_LOAD FLAGS (6);
|
||||
init PT_LOAD FLAGS (6);
|
||||
kern PT_LOAD FLAGS (6);
|
||||
}
|
||||
|
@ -38,6 +39,18 @@ SECTIONS
|
|||
|
||||
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.*)
|
||||
|
@ -79,6 +92,8 @@ SECTIONS
|
|||
PROVIDE_HIDDEN (CTORS_E = .);
|
||||
} : kern
|
||||
|
||||
PROVIDE_HIDDEN (HASH_HPAE = . - OFFSET);
|
||||
|
||||
.data : AT (ADDR (.data) - OFFSET)
|
||||
{
|
||||
*(.data .data.* .gnu.linkonce.d.*)
|
||||
|
@ -111,6 +126,12 @@ SECTIONS
|
|||
|
||||
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 (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_HPAE & OFFS_MASK (1)) == 0, "NOVA_HPAE not 2MB aligned");
|
||||
. = ASSERT ((HASH_HPAS & OFFS_MASK (0)) == 0, "HASH_HPAS not 4KB aligned");
|
||||
}
|
||||
|
|
|
@ -31,6 +31,7 @@
|
|||
#include "space_hst.hpp"
|
||||
#include "stdio.hpp"
|
||||
#include "string.hpp"
|
||||
#include "txt.hpp"
|
||||
|
||||
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()
|
||||
{
|
||||
if (!Acpi::resume && !Txt::launched)
|
||||
Cmdline::init();
|
||||
|
||||
Patch::detect();
|
||||
|
||||
Txt::launch();
|
||||
}
|
||||
|
||||
extern "C" void init()
|
||||
|
@ -82,8 +88,6 @@ extern "C" void init()
|
|||
|
||||
for (auto func { CTORS_S }; func != CTORS_E; (*func++)()) ;
|
||||
|
||||
Cmdline::init();
|
||||
|
||||
for (auto func { CTORS_C }; func != CTORS_S; (*func++)()) ;
|
||||
|
||||
// Now we're ready to talk to the world
|
||||
|
@ -92,6 +96,8 @@ extern "C" void init()
|
|||
Interrupt::setup();
|
||||
}
|
||||
|
||||
Txt::init();
|
||||
|
||||
Acpi::init();
|
||||
|
||||
Pic::init();
|
||||
|
|
|
@ -29,6 +29,7 @@
|
|||
#include "stc.hpp"
|
||||
#include "stdio.hpp"
|
||||
#include "timeout.hpp"
|
||||
#include "txt.hpp"
|
||||
#include "vectors.hpp"
|
||||
|
||||
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))) {
|
||||
|
||||
send_exc (0, Delivery::DLV_INIT);
|
||||
if (!Txt::launched)
|
||||
send_exc (0, Delivery::DLV_INIT);
|
||||
|
||||
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);
|
||||
|
||||
send_exc (Acpi::sipi >> PAGE_BITS, Delivery::DLV_SIPI);
|
||||
Acpi_fixed::delay (1);
|
||||
send_exc (Acpi::sipi >> PAGE_BITS, Delivery::DLV_SIPI);
|
||||
if (!Txt::launched) {
|
||||
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);
|
||||
|
|
|
@ -34,7 +34,7 @@
|
|||
#define PTE_ATTR_S BIT (7) // Superpage
|
||||
#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
|
||||
|
@ -223,6 +223,19 @@ __init_bsp: // Check if we launched via Multiboot v2
|
|||
REL_SYM (__relo_pdb - OFFSET)
|
||||
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)
|
||||
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
|
||||
|
@ -247,10 +260,153 @@ __init_bsp: // Check if we launched via Multiboot v2
|
|||
lea __init_all(%ebp), %ebx
|
||||
mov %ebx, __relo_jip - OFFSET(%ebp)
|
||||
movw $SEL_KERN_CODE, __relo_jcs - OFFSET(%ebp)
|
||||
|
||||
.Lrlp:
|
||||
INIT_PAGING
|
||||
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)
|
||||
*/
|
||||
|
@ -278,6 +434,8 @@ __init_aps__:
|
|||
__boot_gdt: .quad 0 // SEL_NULL
|
||||
.quad 0x00a09b0000000000 // SEL_KERN_CODE (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:
|
||||
|
||||
/*
|
||||
|
@ -321,6 +479,9 @@ __init_all: mov $.Lhigh, %rax
|
|||
movzwl __boot_lock, %edx
|
||||
jmp .Llock_retry
|
||||
.Llock_end:
|
||||
// Restore TXT state
|
||||
call txt_restore
|
||||
|
||||
// Init (BSP only)
|
||||
test %ebx, %ebx
|
||||
jz .Lskip_init
|
||||
|
|
|
@ -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();
|
||||
}
|
Loading…
Reference in New Issue