Skip to content

feat(sev): add AMD SEV support #542

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 20 commits into from
May 4, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
19a03a9
feat(sev): add AMD SEV support
zyuiop Apr 11, 2025
1b26f6c
x86_64/sev: move SEV structures to codebase
zyuiop Apr 29, 2025
0a8a281
x86_64/memory_encryption: don't enable ME by default
zyuiop Apr 29, 2025
073c196
x86_64/memory_encryption: fix review and clippy issues
zyuiop Apr 29, 2025
6558dd8
memory_encryption: refactor for Intel TDX support
zyuiop Apr 30, 2025
684aaa7
memory_encryption: enable memory_encryption flag in CI
zyuiop Apr 30, 2025
8a3abd1
memory_encryption: reduce function duplication in page_table.rs
zyuiop Apr 30, 2025
96f7233
memory_encryption: remove feature `dynamic_flags`
zyuiop Apr 30, 2025
1560047
memory_encryption: prevent setting the encryption bit in PhysAddr
zyuiop Apr 30, 2025
8519ada
memory_encryption: missing documentation
zyuiop Apr 30, 2025
3e05fa7
ci: test memory_encryption separately
zyuiop May 1, 2025
43e3db9
memory_encryption: address PR feedback
zyuiop May 1, 2025
c13dd06
memory_encryption: keep `const` functions const if `memory_encryption…
zyuiop May 1, 2025
29e9466
memory_encryption: exclude memory_encryption feature when checking fo…
zyuiop May 1, 2025
65bdd52
memory_encryption: fix `const_fn` name already taken
zyuiop May 1, 2025
6fb583a
memory_encryption: fix formatting
zyuiop May 1, 2025
648ae66
memory_encryption: review fixes
zyuiop May 1, 2025
a872185
memory_encryption: review fixes + fix imports changes
zyuiop May 1, 2025
bd7a9ef
memory_encryption: fix import style
zyuiop May 1, 2025
5370c17
ci: only run semver checks on default features
zyuiop May 1, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,10 @@ volatile = "0.4.4"
rustversion = "1.0.5"

[features]
default = ["nightly", "instructions"]
default = ["nightly", "instructions", "amd_sev"]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure if we should enable this by default. Most users won't be running their code in an SEV VM, and there's likely a non-zero performance impact.

People have also been using this crate with AMD SEV-SNP and Intel TDX for a while, even without explicit support in this crate, and we don't want to accidentally break their code. In practice, on all currently available platforms, the C-bit/S-bit is always an address bit (I think this is even guaranteed on Intel TDX). People have been considering it as such and not a page table bit, so they're just setting the bit in PhysAddr/PhysFrame directly.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right - it should not be enabled by default, I will fix.

The reason I'm making this patch is that I tried running an unmodified Hermit OS in an QEMU SEV VM, and it did not work, because of the c-bit.
In practice, I could retract the patch and do what you suggest with physical address bits in Hermit directly.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hermit OS will need to be modified anyway; SEV/SEV-ES/SEV-SNP/TDX all require kernel modifications.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI: At Hermit we are also working on SEV support, but as it indeed requires numerous kernel modifications, this is not (yet) ready for publication. We also made our own quick-and-dirty changes to this crate - not too different from what is proposed here, but also not yet ready and generic. So a clean and upstream version of SEV support in this crate would be much appreciated 👍

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's great to hear. Out of curiosity, what generation are you targeting, SEV, SEV-ES, or SEV-SNP?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI: At Hermit we are also working on SEV support, but as it indeed requires numerous kernel modifications, this is not (yet) ready for publication. We also made our own quick-and-dirty changes to this crate - not too different from what is proposed here, but also not yet ready and generic. So a clean and upstream version of SEV support in this crate would be much appreciated 👍

Oh, is there any way we could get in touch? I should probably have started with that, but better late than never.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's great to hear. Out of curiosity, what generation are you targeting, SEV, SEV-ES, or SEV-SNP?

Well, SEV without SNP doesn't make much sense anymore, right? 😁

Oh, is there any way we could get in touch? I should probably have started with that, but better late than never.

Sure, just drop us a mail 😉

instructions = []
amd_sev = ["dynamic_flags"]
dynamic_flags = []
nightly = ["const_fn", "step_trait", "abi_x86_interrupt", "asm_const"]
abi_x86_interrupt = []
# deprecated, no longer needed
Expand Down
129 changes: 129 additions & 0 deletions src/structures/amd_sev.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
use core::sync::atomic::AtomicBool;
use core::sync::atomic::Ordering::SeqCst;

use bit_field::BitField;

use crate::registers::model_specific::Msr;
use crate::structures::paging;
use crate::structures::paging::PageTableFlags;

const MSR_AMD_SEV: Msr = Msr::new(0xc0010131);

const SEV_INITIALIZED: AtomicBool = AtomicBool::new(false);


/// Retrieves the AMD SEV status, after calling `init`
pub fn sev_state<'a>() -> Option<&'a SevState> {
AMD_SEV_STATUS.as_ref()
}

/// Initializes AMD Secure Encrypted Virtualization, if enabled.
///
/// It is required to call this function in an SEV enabled guest, as it sets-up a dynamic page table
/// flag for memory encryption. If this is not set, page table operations may cause panics due to
/// invalid canonical addresses.
pub fn init<'a>() -> Option<&'a SevState> {
if SEV_INITIALIZED.fetch_or(true, SeqCst) {
return sev_state();
}

// https://github.com/torvalds/linux/blob/900241a5cc15e6e0709a012051cc72d224cd6a6e/arch/x86/mm/mem_encrypt_identity.c#L566
// Check for SME support
let (cpuid_max, _) = unsafe { core::arch::x86_64::__get_cpuid_max(0x8000_0000) };
if cpuid_max < 0x8000_001F {
return None;
}

// Check if SME is available on the current CPU then read the C-Bit position and define the mask
let sme_features = unsafe { core::arch::x86_64::__cpuid(0x8000_001F) };
let sme_available = (sme_features.eax & 0x3) != 0; // either SME or SEV bit is set
if !sme_available {
return None;
}

let cbit_mask = (1u64) << sme_features.ebx.get_bits(0..=5) as u16;

// Check the MSR to see if SME is currently enabled
let sme_status = unsafe { MSR_AMD_SEV.read() };
let sev_enabled = (sme_status & 0x1) == 1;
let sev_es_enabled = (sme_status & 0x2) == 0x2;
let snp_enabled = (sme_status & 0x4) == 0x4;

if !sev_enabled && !snp_enabled {
return None;
}

let state = Some(SevState {
sev_enabled,
snp_enabled,
sev_es_enabled,
c_bit_flag: PageTableFlags::from_bits_retain(cbit_mask),
});

// Update the memory mask
unsafe {
assign_static_immutable(&paging::page_table::PHYSICAL_ADDRESS_MASK, |pa| {
*pa &= !cbit_mask
});
assign_static_immutable(&AMD_SEV_STATUS, |opt| *opt = state);
}

sev_state()
}

unsafe fn assign_static_immutable<T, F>(attr: &'static T, op: F)
where
F: FnOnce(&mut T),
{
let ptr = attr as *const T;
unsafe {
let ptr = ptr as u64;
let ptr = ptr as *mut T;
op(ptr.as_mut().unwrap());
}
}

static AMD_SEV_STATUS: Option<SevState> = None;


/// Represents the current enablement state of AMD Secure Encrypted Virtualization features
#[derive(Copy, Clone, Debug)]
pub struct SevState {
/// True if Secure Encrypted Virtualization is enabled
pub sev_enabled: bool,

/// True if Secure Nested Paging is enabled.
///
/// Implies `sev_es_enabled` = true and `sev_enabled` = true
pub snp_enabled: bool,

/// True if Encrypted State is enabled.
///
/// Implies `sev_enabled` = true
pub sev_es_enabled: bool,

/// Custom flag used to set the encryption bit in page table entries
pub c_bit_flag: PageTableFlags,
}

impl PageTableFlags {
#[inline]
fn c_bit_mask() -> PageTableFlags {
sev_state().map(|s| s.c_bit_flag)
.expect("fatal: memory encryption is not enabled")
}

/// Sets the encryption bit on the page table entry.
///
/// Requires memory encryption to be enabled, or this will panic.
pub fn set_encrypted(&mut self, encrypted: bool) {
self.set(Self::c_bit_mask(), encrypted);
}

/// Checks if the encryption bit is set on the page table entry.
///
/// Requires memory encryption to be enabled, or this will panic.
pub fn is_encrypted(&self) -> bool {
self.contains(Self::c_bit_mask())
}
}
2 changes: 2 additions & 0 deletions src/structures/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ pub mod idt;
pub mod paging;
pub mod port;
pub mod tss;
#[cfg(feature = "amd_sev")]
pub mod amd_sev;

/// A struct describing a pointer to a descriptor table (GDT / IDT).
/// This is in a format suitable for giving to 'lgdt' or 'lidt'.
Expand Down
71 changes: 58 additions & 13 deletions src/structures/paging/page_table.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ use core::fmt;
#[cfg(feature = "step_trait")]
use core::iter::Step;
use core::ops::{Index, IndexMut};

use super::{PageSize, PhysFrame, Size4KiB};
use crate::addr::PhysAddr;

Expand All @@ -20,11 +19,15 @@ pub enum FrameError {
HugeFrame,
}

/// The mask used to remove flags from a page table entry to obtain the physical address
#[cfg(feature = "dynamic_flags")]
pub(crate) static PHYSICAL_ADDRESS_MASK: u64 = 0x000f_ffff_ffff_f000u64;

/// A 64-bit page table entry.
#[derive(Clone)]
#[repr(transparent)]
pub struct PageTableEntry {
entry: u64,
pub(crate) entry: u64,
}

impl PageTableEntry {
Expand All @@ -49,13 +52,7 @@ impl PageTableEntry {
/// Returns the flags of this entry.
#[inline]
pub const fn flags(&self) -> PageTableFlags {
PageTableFlags::from_bits_truncate(self.entry)
}

/// Returns the physical address mapped by this entry, might be zero.
#[inline]
pub fn addr(&self) -> PhysAddr {
PhysAddr::new(self.entry & 0x000f_ffff_ffff_f000)
PageTableFlags::from_address_truncated(self.entry)
}

/// Returns the physical frame mapped by this entry.
Expand All @@ -76,11 +73,17 @@ impl PageTableEntry {
}
}

/// Sets the flags of this entry.
#[inline]
pub fn set_flags(&mut self, flags: PageTableFlags) {
self.entry = self.addr().as_u64() | flags.bits_dynamic();
}

/// Map the entry to the specified physical address with the specified flags.
#[inline]
pub fn set_addr(&mut self, addr: PhysAddr, flags: PageTableFlags) {
assert!(addr.is_aligned(Size4KiB::SIZE));
self.entry = (addr.as_u64()) | flags.bits();
self.entry = (addr.as_u64()) | flags.bits_dynamic();
}

/// Map the entry to the specified physical frame with the specified flags.
Expand All @@ -89,14 +92,27 @@ impl PageTableEntry {
assert!(!flags.contains(PageTableFlags::HUGE_PAGE));
self.set_addr(frame.start_address(), flags)
}
}

/// Sets the flags of this entry.
#[cfg(feature = "dynamic_flags")]
impl PageTableEntry {
/// Returns the physical address mapped by this entry, might be zero.
#[inline]
pub fn set_flags(&mut self, flags: PageTableFlags) {
self.entry = self.addr().as_u64() | flags.bits();
pub fn addr(&self) -> PhysAddr {
PhysAddr::new(self.entry & PHYSICAL_ADDRESS_MASK)
}
}

#[cfg(not(feature = "dynamic_flags"))]
impl PageTableEntry {
/// Returns the physical address mapped by this entry, might be zero.
#[inline]
pub fn addr(&self) -> PhysAddr {
PhysAddr::new(self.entry & 0x000f_ffff_ffff_f000u64)
}
}


impl Default for PageTableEntry {
#[inline]
fn default() -> Self {
Expand Down Expand Up @@ -175,9 +191,38 @@ bitflags! {
/// Can be only used when the no-execute page protection feature is enabled in the EFER
/// register.
const NO_EXECUTE = 1 << 63;

// Consider other as defined
#[cfg(feature = "dynamic_flags")]
const _ = !0;
}
}


#[cfg(feature = "dynamic_flags")]
impl PageTableFlags {
pub const fn from_address_truncated(address: u64) -> PageTableFlags {
PageTableFlags::from_bits_retain(address & !PHYSICAL_ADDRESS_MASK)
}

pub const fn bits_dynamic(&self) -> u64 {
self.bits() & !PHYSICAL_ADDRESS_MASK
}
}


#[cfg(not(feature = "dynamic_flags"))]
impl PageTableFlags {
pub const fn from_address_truncated(address: u64) -> PageTableFlags {
PageTableFlags::from_bits_truncate(address)
}

pub const fn bits_dynamic(&self) -> u64 {
self.bits()
}
}


/// The number of entries in a page table.
const ENTRY_COUNT: usize = 512;

Expand Down
Loading