x86: Added CET support.

This commit is contained in:
Udo Steinberg 2022-02-21 15:04:05 +01:00
parent 027d2b1065
commit ce88d30867
16 changed files with 197 additions and 5 deletions

View File

@ -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)

View File

@ -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)

View File

@ -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

52
inc/x86_64/cet.hpp Normal file
View File

@ -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
}
};

View File

@ -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,

View File

@ -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");

View File

@ -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;

View File

@ -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

View File

@ -21,3 +21,5 @@
#define NOP_OPC 0x90
#define PATCH_XSAVES 0
#define PATCH_CET_IBT 1
#define PATCH_CET_SSS 2

View File

@ -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;

View File

@ -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

View File

@ -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 :
{

View File

@ -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();
}

View File

@ -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]];

View File

@ -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

View File

@ -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);