x86: Added CET support.
This commit is contained in:
parent
027d2b1065
commit
ce88d30867
2
Makefile
2
Makefile
|
@ -25,6 +25,7 @@
|
|||
ARCH ?= x86_64
|
||||
BOARD ?= acpi
|
||||
COMP ?= gcc
|
||||
CFP ?= none
|
||||
|
||||
# Tools
|
||||
INSTALL ?= install -m 644
|
||||
|
@ -97,6 +98,7 @@ PFLAGS := $(addprefix -D, $(DEFINES)) $(addprefix -I, $(INC_DIR))
|
|||
# Language options
|
||||
FFLAGS := $(or $(call check,-std=gnu++26), $(call check,-std=gnu++23), $(call check,-std=gnu++20))
|
||||
FFLAGS += -ffreestanding -fdata-sections -ffunction-sections -fno-asynchronous-unwind-tables -fno-exceptions -fno-rtti -fno-use-cxa-atexit -fomit-frame-pointer
|
||||
FFLAGS += $(call check,-fcf-protection=$(CFP))
|
||||
FFLAGS += $(call check,-fdiagnostics-color=auto)
|
||||
FFLAGS += $(call check,-fno-pic)
|
||||
FFLAGS += $(call check,-fno-stack-protector)
|
||||
|
|
|
@ -32,6 +32,10 @@ BOARD ?= acpi
|
|||
# Permitted values are: gcc
|
||||
COMP ?= gcc
|
||||
|
||||
# Configure control-flow protection
|
||||
# Permitted values are: none, branch, return, full
|
||||
CFP ?= none
|
||||
|
||||
# Configure build directory
|
||||
BLD_DIR ?= build-$(ARCH)
|
||||
|
||||
|
|
14
README.md
14
README.md
|
@ -68,6 +68,20 @@ and boards with Advanced Configuration and Power Interface (ACPI).
|
|||
| :------------------------------------ | :----------------- |
|
||||
| Generic x86 ACPI Platform | `make ARCH=x86_64` |
|
||||
|
||||
##### Control-Flow Enforcement Technology (CET)
|
||||
|
||||
NOVA can be built with support for control-flow protection. Because
|
||||
control-flow protected binaries require a CPU with CET support and because
|
||||
of the resulting performance overhead, CFP is disabled by default.
|
||||
Protection features can be enabled at build time as follows:
|
||||
|
||||
| **Feature Level** | **Build Command** |
|
||||
| :------------------------------------ | :---------------------------- |
|
||||
| No Control-Flow Protection (Default) | `make ARCH=x86_64 CFP=none` |
|
||||
| CET Indirect Branch Tracking (IBT) | `make ARCH=x86_64 CFP=branch` |
|
||||
| CET Supervisor Shadow Stacks (SSS) | `make ARCH=x86_64 CFP=return` |
|
||||
| CET IBT and CET SSS | `make ARCH=x86_64 CFP=full` |
|
||||
|
||||
## Booting
|
||||
|
||||
See the NOVA interface specification in the `doc` directory for details
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* Control-Flow Enforcement Technology (CET)
|
||||
*
|
||||
* 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 "extern.hpp"
|
||||
#include "patch.hpp"
|
||||
#include "types.hpp"
|
||||
|
||||
class Cet final
|
||||
{
|
||||
public:
|
||||
/*
|
||||
* Deactivate supervisor shadow stack by marking the token as not busy
|
||||
*/
|
||||
ALWAYS_INLINE
|
||||
static inline void sss_deactivate()
|
||||
{
|
||||
#if defined(__CET__) && (__CET__ & 2)
|
||||
#define ASM_CET_1 clrssbsy %0;
|
||||
asm volatile (EXPAND (PATCH (ASM_CET_1,,PATCH_CET_SSS)) : : "m" (DSHD_TOP));
|
||||
#endif
|
||||
}
|
||||
|
||||
/*
|
||||
* Unwind supervisor shadow stack up to the token
|
||||
*/
|
||||
ALWAYS_INLINE
|
||||
static inline void sss_unwind()
|
||||
{
|
||||
#if defined(__CET__) && (__CET__ & 2)
|
||||
uintptr_t ssp;
|
||||
#define ASM_CET_2 rdsspq %0; sub %0, %1; shr $3, %1; incsspq %1;
|
||||
asm volatile (EXPAND (PATCH (ASM_CET_2,,PATCH_CET_SSS)) : "=&r" (ssp) : "r" (&DSHD_TOP));
|
||||
#endif
|
||||
}
|
||||
};
|
|
@ -119,11 +119,13 @@ class Cpu final
|
|||
SMAP = 3 * 32 + 20, // Supervisor Mode Access Prevention
|
||||
// EAX=0x7 ECX=0x0 (ECX)
|
||||
UMIP = 4 * 32 + 2, // User Mode Instruction Prevention
|
||||
CET_SS = 4 * 32 + 7, // CET Shadow Stack
|
||||
SGX_LC = 4 * 32 + 30, // SGX Launch Configuration
|
||||
// EAX=0x7 ECX=0x0 (EDX)
|
||||
SRBDS_CTRL = 5 * 32 + 9, // Special Register Buffer Data Sampling
|
||||
MD_CLEAR = 5 * 32 + 10, // Microarchitectural Data Clear
|
||||
HYBRID = 5 * 32 + 15, // Hybrid Processor
|
||||
CET_IBT = 5 * 32 + 20, // CET Indirect Branch Tracking
|
||||
IBRS = 5 * 32 + 26, // Indirect Branch Restricted Speculation
|
||||
STIBP = 5 * 32 + 27, // Single Thread Indirect Branch Predictors
|
||||
L1D_FLUSH = 5 * 32 + 28, // L1 Data Cache Flushing
|
||||
|
@ -134,6 +136,7 @@ class Cpu final
|
|||
// EAX=0x7 ECX=0x1 (EBX)
|
||||
// EAX=0x7 ECX=0x1 (ECX)
|
||||
// EAX=0x7 ECX=0x1 (EDX)
|
||||
CET_SSS = 9 * 32 + 18, // CET Supervisor Shadow Stack
|
||||
APX_F = 9 * 32 + 21, // APX Foundation
|
||||
// EAX=0x7 ECX=0x2 (EDX)
|
||||
PSFD = 10 * 32 + 0,
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include "cet.hpp"
|
||||
#include "ec.hpp"
|
||||
#include "extern.hpp"
|
||||
#include "space_hst.hpp"
|
||||
|
@ -126,6 +127,8 @@ class Ec_arch final : private Ec
|
|||
|
||||
hst->make_current();
|
||||
|
||||
Cet::sss_unwind();
|
||||
|
||||
// Reset stack
|
||||
asm volatile ("lea %0, %%rsp" : : "m" (DSTK_TOP) : "memory");
|
||||
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
|
||||
#include "types.hpp"
|
||||
|
||||
extern char GIT_VER, NOVA_HPAS, KMEM_HVAS, KMEM_HVAF, PTAB_HVAS, DSTK_TOP;
|
||||
extern char GIT_VER, NOVA_HPAS, KMEM_HVAS, KMEM_HVAF, PTAB_HVAS, DSTK_TOP, DSHD_TOP;
|
||||
extern void (*CTORS_S[])(), (*CTORS_E[])(), (*CTORS_C[])(), (*CTORS_L[])();
|
||||
|
||||
extern char entry_sys;
|
||||
|
|
|
@ -97,6 +97,13 @@ class Msr final
|
|||
IA32_VMX_CTRL_EXI_SEC = 0x493, // VMX
|
||||
IA32_MCG_EXT_CTL = 0x4d0, // MCA and IA32_MCG_CAP[MCG_LMCE_P]
|
||||
IA32_SGX_SVN_STATUS = 0x500, // SGX
|
||||
IA32_U_CET = 0x6a0, // CET_SS or CET_IBT
|
||||
IA32_S_CET = 0x6a2, // CET_SS or CET_IBT
|
||||
IA32_PL0_SSP = 0x6a4, // CET_SS
|
||||
IA32_PL1_SSP = 0x6a5, // CET_SS
|
||||
IA32_PL2_SSP = 0x6a6, // CET_SS
|
||||
IA32_PL3_SSP = 0x6a7, // CET_SS
|
||||
IA32_INTERRUPT_SSP_TABLE_ADDR = 0x6a8, // CET_SS
|
||||
IA32_TSC_DEADLINE = 0x6e0, // TSC_DEADLINE
|
||||
IA32_PM_ENABLE = 0x770, // HWP
|
||||
IA32_HWP_CAPABILITIES = 0x771, // HWP
|
||||
|
|
|
@ -21,3 +21,5 @@
|
|||
#define NOP_OPC 0x90
|
||||
|
||||
#define PATCH_XSAVES 0
|
||||
#define PATCH_CET_IBT 1
|
||||
#define PATCH_CET_SSS 2
|
||||
|
|
|
@ -262,6 +262,8 @@ void Ec_arch::ret_user_hypercall (Ec *const self)
|
|||
|
||||
trace (TRACE_CONT, "EC:%p %s to CS:%#x IP:%#lx", static_cast<void *>(self), __func__, SEL_USER_CODE, r.exc.ip());
|
||||
|
||||
Cet::sss_deactivate();
|
||||
|
||||
asm volatile ("lea %0, %%rsp;" EXPAND (LOAD_GPR) "mov %%r11, %%rsp; mov %1, %%r11; sysretq" : : "m" (r.exc), "i" (RFL_IF | RFL_1) : "memory");
|
||||
|
||||
UNREACHED;
|
||||
|
@ -277,6 +279,8 @@ void Ec_arch::ret_user_exception (Ec *const self)
|
|||
|
||||
trace (TRACE_CONT, "EC:%p %s to CS:%#lx IP:%#lx", static_cast<void *>(self), __func__, r.exc.cs, r.exc.rip);
|
||||
|
||||
Cet::sss_unwind();
|
||||
|
||||
asm volatile ("lea %0, %%rsp;" EXPAND (LOAD_GPR IRET) : : "m" (r.exc) : "memory");
|
||||
|
||||
UNREACHED;
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
|
||||
#include "arch.hpp"
|
||||
#include "entry.hpp"
|
||||
#include "patch.hpp"
|
||||
#include "vectors.hpp"
|
||||
|
||||
.data
|
||||
|
@ -39,6 +40,9 @@ handlers:
|
|||
.macro INTRGATE IDT
|
||||
.balign 4, 0x90
|
||||
1:
|
||||
#if defined(__CET__) && (__CET__ & 1)
|
||||
endbr64
|
||||
#endif
|
||||
.data
|
||||
.quad 1b + \IDT
|
||||
.previous
|
||||
|
@ -161,7 +165,14 @@ INTERRUPT VEC
|
|||
*/
|
||||
.balign 4, 0x90
|
||||
.global entry_sys
|
||||
entry_sys: mov %rsp, %r11
|
||||
entry_sys:
|
||||
#if defined(__CET__) && (__CET__ & 1)
|
||||
endbr64
|
||||
#endif
|
||||
#if defined(__CET__) && (__CET__ & 2)
|
||||
PATCH (setssbsy,,PATCH_CET_SSS)
|
||||
#endif
|
||||
mov %rsp, %r11
|
||||
mov tss_run + 4, %rsp
|
||||
lea -7 * __SIZEOF_POINTER__(%rsp), %rsp
|
||||
SAVE_GPR
|
||||
|
|
|
@ -112,6 +112,7 @@ SECTIONS
|
|||
PROVIDE_HIDDEN (NOVA_HPAE = . - OFFSET);
|
||||
|
||||
PROVIDE_HIDDEN (DSTK_TOP = MMAP_CPU_DSTT);
|
||||
PROVIDE_HIDDEN (DSHD_TOP = MMAP_CPU_DTKN);
|
||||
|
||||
.cpulocal MMAP_CPU_DATA :
|
||||
{
|
||||
|
|
|
@ -51,6 +51,20 @@ extern "C" uintptr_t kern_ptab_setup (apic_t t)
|
|||
// Allocate and map kernel intr stack
|
||||
hptp.update (MMAP_CPU_ISTB, Kmem::ptr_to_phys (Buddy::alloc (0, Buddy::Fill::BITS0)), 0, Paging::Permissions (Paging::G | Paging::W | Paging::R), Memattr::ram());
|
||||
|
||||
#if defined(__CET__) && (__CET__ & 2)
|
||||
// Allocate and map data supervisor shadow stack
|
||||
auto const dsss { static_cast<uintptr_t *>(Buddy::alloc (0, Buddy::Fill::BITS0)) };
|
||||
hptp.update (MMAP_CPU_DSSS, Kmem::ptr_to_phys (dsss), 0, Paging::Permissions (Paging::SS | Paging::G | Paging::R), Memattr::ram());
|
||||
|
||||
// Allocate and map intr supervisor shadow stack
|
||||
auto const isss { static_cast<uintptr_t *>(Buddy::alloc (0, Buddy::Fill::BITS0)) };
|
||||
hptp.update (MMAP_CPU_ISSS, Kmem::ptr_to_phys (isss), 0, Paging::Permissions (Paging::SS | Paging::G | Paging::R), Memattr::ram());
|
||||
|
||||
// Install supervisor shadow stack tokens
|
||||
dsss[PAGE_SIZE (0) / sizeof (uintptr_t) - 1] = MMAP_CPU_DTKN;
|
||||
isss[PAGE_SIZE (0) / sizeof (uintptr_t) - 1] = MMAP_CPU_ITKN;
|
||||
#endif
|
||||
|
||||
return hptp.root_addr();
|
||||
}
|
||||
|
||||
|
|
|
@ -34,7 +34,13 @@ void Patch::detect()
|
|||
Fpu::compact = !!(eax & BIT (3));
|
||||
applied |= BIT (PATCH_XSAVES) * !Fpu::compact;
|
||||
[[fallthrough]];
|
||||
case 0x1 ... 0xc:
|
||||
case 0x7 ... 0xc:
|
||||
Cpu::cpuid (0x7, 0x0, eax, ebx, ecx, edx);
|
||||
applied |= BIT (PATCH_CET_IBT) * !(edx & BIT (20));
|
||||
Cpu::cpuid (0x7, 0x1, eax, ebx, ecx, edx);
|
||||
applied |= BIT (PATCH_CET_SSS) * !(edx & BIT (18));
|
||||
[[fallthrough]];
|
||||
case 0x1 ... 0x6:
|
||||
Cpu::cpuid (0x1, 0x0, eax, ebx, ecx, edx);
|
||||
Lapic::x2apic = ecx & BIT (21);
|
||||
[[fallthrough]];
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
#include "memattr.hpp"
|
||||
#include "memory.hpp"
|
||||
#include "multiboot.hpp"
|
||||
#include "patch.hpp"
|
||||
#include "selectors.hpp"
|
||||
|
||||
#define PTE_ATTR_P BIT (0) // Present
|
||||
|
@ -352,6 +353,66 @@ __init_all: mov $.Lhigh, %rax
|
|||
.Ltopo_end:
|
||||
call kern_ptab_setup
|
||||
mov %rax, %cr3
|
||||
#if defined(__CET__)
|
||||
// Track CET features in EAX
|
||||
xor %eax, %eax
|
||||
#if (__CET__ & 1)
|
||||
#define ASM_ENABLE_CET_1 or $(BIT_RANGE (5, 4) | BIT (2)), %al
|
||||
PATCH (ASM_ENABLE_CET_1,, PATCH_CET_IBT);
|
||||
#endif
|
||||
#if (__CET__ & 2)
|
||||
#define ASM_ENABLE_CET_2 or $BIT (0), %al
|
||||
PATCH (ASM_ENABLE_CET_2,, PATCH_CET_SSS);
|
||||
#endif
|
||||
// Check if any CET features are being enabled
|
||||
test %eax, %eax
|
||||
jz .Lcet_end
|
||||
|
||||
// Enable CET
|
||||
mov %cr4, %rdx
|
||||
or $CR4_CET, %edx
|
||||
mov %rdx, %cr4
|
||||
|
||||
// Enable CET features in EAX
|
||||
mov $0x6a2, %ecx
|
||||
xor %edx, %edx
|
||||
wrmsr
|
||||
#if (__CET__ & 2)
|
||||
// Check if CET_SSS is enabled
|
||||
test $BIT (0), %al
|
||||
jz .Lcet_end
|
||||
|
||||
.pushsection .rodata
|
||||
|
||||
.balign 64
|
||||
__interrupt_ssp_table: .quad 0
|
||||
.quad MMAP_CPU_ITKN
|
||||
.quad 0
|
||||
.quad 0
|
||||
.quad 0
|
||||
.quad 0
|
||||
.quad 0
|
||||
.quad 0
|
||||
.popsection
|
||||
// Initialize IA32_INTERRUPT_SSP_TABLE_ADDR
|
||||
mov $0x6a8, %ecx
|
||||
lea __interrupt_ssp_table, %rdx
|
||||
mov %rdx, %rax
|
||||
shr $0x20, %rdx
|
||||
wrmsr
|
||||
|
||||
// Initialize IA32_PL0_SSP
|
||||
mov $0x6a4, %ecx
|
||||
lea DSHD_TOP, %rdx
|
||||
mov %rdx, %rax
|
||||
shr $0x20, %rdx
|
||||
wrmsr
|
||||
|
||||
// Activate supervisor shadow stack
|
||||
setssbsy
|
||||
#endif
|
||||
.Lcet_end:
|
||||
#endif
|
||||
lea DSTK_TOP - __SIZEOF_POINTER__, %rsp
|
||||
jmp bootstrap
|
||||
|
||||
|
|
|
@ -99,6 +99,14 @@ void Vmcs::init (uintptr_t gsp, uintptr_t hsp, uintptr_t cr3, uint64_t apic, uin
|
|||
if (Cpu::feature (Cpu::Feature::LM))
|
||||
write (Encoding::HOST_EFER, Msr::read (Msr::Reg64::IA32_EFER));
|
||||
|
||||
if (Cpu::feature (Cpu::Feature::CET_SS) || Cpu::feature (Cpu::Feature::CET_IBT))
|
||||
write (Encoding::HOST_S_CET, Msr::read (Msr::Reg64::IA32_S_CET));
|
||||
|
||||
if (Cpu::feature (Cpu::Feature::CET_SS)) {
|
||||
write (Encoding::HOST_SSP, MMAP_CPU_DTKN);
|
||||
write (Encoding::HOST_SSP_TABLE_ADDR, Msr::read (Msr::Reg64::IA32_INTERRUPT_SSP_TABLE_ADDR));
|
||||
}
|
||||
|
||||
write (Encoding::PIN_CONTROLS, pin);
|
||||
write (Encoding::ENT_CONTROLS, ent);
|
||||
write (Encoding::EXI_CONTROLS_PRI, exi_pri);
|
||||
|
@ -125,12 +133,12 @@ void Vmcs::init()
|
|||
pin = (hyp_pin | static_cast<uint32_t>(vmx_pin)) & static_cast<uint32_t>(vmx_pin >> 32);
|
||||
|
||||
// VM-Entry Controls
|
||||
constexpr auto hyp_ent { Ent::ENT_LOAD_EFER | Ent::ENT_LOAD_PAT };
|
||||
constexpr auto hyp_ent { Ent::ENT_LOAD_CET | Ent::ENT_LOAD_EFER | Ent::ENT_LOAD_PAT };
|
||||
auto const vmx_ent { Msr::read (ctrl ? Msr::Reg64::IA32_VMX_TRUE_ENT : Msr::Reg64::IA32_VMX_CTRL_ENT) };
|
||||
ent = (hyp_ent | static_cast<uint32_t>(vmx_ent)) & static_cast<uint32_t>(vmx_ent >> 32);
|
||||
|
||||
// Primary VM-Exit Controls
|
||||
constexpr auto hyp_exi_pri { Exi_pri::EXI_SECONDARY | Exi_pri::EXI_LOAD_EFER | Exi_pri::EXI_SAVE_EFER | Exi_pri::EXI_LOAD_PAT | Exi_pri::EXI_SAVE_PAT | Exi_pri::EXI_INTA | Exi_pri::EXI_HOST_64 };
|
||||
constexpr auto hyp_exi_pri { Exi_pri::EXI_SECONDARY | Exi_pri::EXI_LOAD_CET | Exi_pri::EXI_LOAD_EFER | Exi_pri::EXI_SAVE_EFER | Exi_pri::EXI_LOAD_PAT | Exi_pri::EXI_SAVE_PAT | Exi_pri::EXI_INTA | Exi_pri::EXI_HOST_64 };
|
||||
auto const vmx_exi_pri { Msr::read (ctrl ? Msr::Reg64::IA32_VMX_TRUE_EXI : Msr::Reg64::IA32_VMX_CTRL_EXI_PRI) };
|
||||
exi_pri = (hyp_exi_pri | static_cast<uint32_t>(vmx_exi_pri)) & static_cast<uint32_t>(vmx_exi_pri >> 32);
|
||||
|
||||
|
|
Loading…
Reference in New Issue