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

Open
wants to merge 20 commits into
base: master
Choose a base branch
from

Conversation

zyuiop
Copy link

@zyuiop zyuiop commented Apr 24, 2025

Currently, this library's page table features does not work on an AMD encrypted memory system.

This is because AMD-SME and AMD-SEV use one physical address bit to map the encryption status in the page table entry.
The position of this bit is not known at compile time and must be determined at runtime.
We therefore need to know before using any page table feature where this bit is so that we can remove it from physical addresses when using page table entries.

My approach is as follows:

  • Modify the PageTableFlags structure to allow any unknown bits ;
  • Define a static mask for the physical address bits, that can be (unsafely) modified, and use that to determine the physical address in a page table entry ;
  • Add a amd_sev module with an init function that detects the presence of an memory encryption features, and if so, (unsafely) modifies the physical address mask.

Someone implementing an operating system designed to work under AMD memory encryption features would need to call amd_sev::init before doing any page table operations.

I am not 100% confident my approach, in particular unsafely modifying static variables, so I gladly take feedback on this PR.

Copy link
Member

@Freax13 Freax13 left a comment

Choose a reason for hiding this comment

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

Thank you for your contribution!

For my own curiosity: What's your intended use case for this? Are you using SEV or SEV-SNP?

Cargo.toml Outdated
@@ -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
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
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 😉

@zyuiop
Copy link
Author

zyuiop commented Apr 28, 2025

Thanks a lot for your review, I will submit a modified patch as soon as possible.

I was trying to run Hermit-OS in an AMD SEV environment, and was quickly blocked by an error as addresses were not canonical because of the C bit. I found this way to solve this, although you're right that it may be simpler to leave that to the OS.

I am indeed targeting SEV-ES and SEV-SNP, and I have written a VC handler as well as structures for the GHCB protocol, but I am not sure it would be a good idea to put them in this library - I feel like a separate library would be better as most x86_64 users are not interested by this feature.

@Freax13
Copy link
Member

Freax13 commented Apr 29, 2025

[...] as well as structures for the GHCB protocol, but I am not sure it would be a good idea to put them in this library - I feel like a separate library would be better as most x86_64 users are not interested by this feature.

Yeah, I agree with that, the GHCB spec isn't really an architectural thing by rather a standard for compliant guests and hypervisors to communicate (like a hypercall interface). There's nothing in the CPU that enforces (or even directly interacts) with any of it.

That said, if you wanted to contribute some of the more architectural stuff (e.g. the sev status MSR, the GHCB MSR, pvalidate, rmpadjust wrappers), we'd be very welcome to that. No pressure, though.

Copy link
Member

@Freax13 Freax13 left a comment

Choose a reason for hiding this comment

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

I like the direction in which this is heading.

Comment on lines 23 to 25
/// 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: AtomicU64 = AtomicU64::new(0x000f_ffff_ffff_f000u64);
Copy link
Member

Choose a reason for hiding this comment

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

Given that this is currently only used for an encryption bit, let's not over-engineer things for now and just have the memory_encryption feature and remove the dynamic_flags feature.

Choose a reason for hiding this comment

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

I'm also not sure if the dynamic_flags feature is the way to go. It has the risk of feature-explosion, but without too much knowledge about the x86_64 codebase, I feel like defining the bit via a cargo feature would be the way to go. I mean, the flag does not change during runtime, and the global state is a little error-prone.

Copy link
Member

Choose a reason for hiding this comment

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

The bit doesn't change at runtime, but it also can't be known ahead of time. Different CPU generations use different positions for the C-bit (e.g. bit 47 for EPYC Milan and bit 51 for EPYC Genoa and Turin). With Intel TDX, the S-bit position is configurable, it's either bit 47 or 51.

Hard-coding the bit position would certainly make things easier and possibly improve performance, but it comes at the cost of compatibility.

Copy link

@jounathaen jounathaen Apr 29, 2025

Choose a reason for hiding this comment

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

I completely see that point, and I'm not 100% sure what is the correct approach here. My argument would be, that if you use one of these memory encryption technologies, you'll have to customize the code to that specific architecture anyway. Ok, not necessarily for the encryption of the page, but basically for everything else.

Maybe a combination of both approaches could be a solution: Have a dynamic_encryption_bit feature, an encryption_bit_51 feature and an encryption_bit_47 feature (mutually exclusive)?

Copy link
Author

Choose a reason for hiding this comment

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

The AMD programmers manual recommends determining the position of the bit at runtime, I suspect that's the same for Intel TDX?

Copy link
Member

Choose a reason for hiding this comment

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

I completely see that point, and I'm not 100% sure what is the correct approach here. My argument would be, that if you use one of these memory encryption technologies, you'll have to customize the code to that specific architecture anyway. Ok, not necessarily for the encryption of the page, but basically for everything else.

I wouldn't call Milan vs Genoa/Turin different architectures. If one wants to run some code on Milan, I'd reasonably expect the same code to run on Genoa/Turin. I don't think one usually has to change a lot of code (if any) when porting stuff from Milan to Genoa/Turin. Most kernels (including Linux) I'm aware of support more than one bit position.

Maybe a combination of both approaches could be a solution: Have a dynamic_encryption_bit feature, an encryption_bit_51 feature and an encryption_bit_47 feature (mutually exclusive)?

Yeah, that's an interesting approach. We'd need separate flags for SEV and TDX though, so we'd need four different flags to cover everything, and that seems a bit excessive.

If there's demand for hard-coding the bit position, I'd certainly consider this, but I'm not sure that there is a lot of demand.

Copy link
Member

Choose a reason for hiding this comment

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

The AMD programmers manual recommends determining the position of the bit at runtime, I suspect that's the same for Intel TDX?

I don't think they make any value judgments on what users should do, but the architecture is setup so that that information is available to the user.

Copy link
Author

Choose a reason for hiding this comment

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

I was mostly referring to Vol.2, §7.10.1, (and similarly in §15.34.1) in AMD's programmer manual:

the physical address size of the processor may be reduced when memory encryption features are enabled, for example from 48 to 43 bits. [...]
When memory encryption is supported in an implementation, CPUID Fn8000_001F[EBX]
reports any physical address size reduction present.

There is indeed no judgement but it read as the recommended approach to me.

Cargo.toml Outdated
@@ -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.

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

Copy link
Member

@Freax13 Freax13 left a comment

Choose a reason for hiding this comment

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

We should also update the PhysAddr/PhysFrame types to reject the encryption bit being set.

@zyuiop zyuiop force-pushed the feat/sev-upstream branch 3 times, most recently from 5cb8438 to 99dad6d Compare April 30, 2025 08:29
@zyuiop
Copy link
Author

zyuiop commented Apr 30, 2025

We should also update the PhysAddr/PhysFrame types to reject the encryption bit being set.

I tried something in 1560047, not sure it's the best approach

@zyuiop zyuiop force-pushed the feat/sev-upstream branch 2 times, most recently from ac566d2 to 2063040 Compare April 30, 2025 09:05
@zyuiop zyuiop force-pushed the feat/sev-upstream branch from 2063040 to 8519ada Compare April 30, 2025 09:15
@zyuiop
Copy link
Author

zyuiop commented May 1, 2025

I've applied your suggestions, except for the const which I'm not sure how I should do.
Should I also bump the version number in Cargo.toml, considering the semver test failure?

@Freax13
Copy link
Member

Freax13 commented May 1, 2025

Should I also bump the version number in Cargo.toml, considering the semver test failure?

No, please don't do that. There shouldn't be any breaking changes in this PR; the semver checks CI job should succeed.

@zyuiop
Copy link
Author

zyuiop commented May 1, 2025

Should I also bump the version number in Cargo.toml, considering the semver test failure?

No, please don't do that. There shouldn't be any breaking changes in this PR; the semver checks CI job should succeed.

Okay.
I have tested locally with the const_fn crate, and it seems I'm still getting the semver errors - even though I should not

Edit: I have found a solution. It requires changing the CI configuration though, and I'd strongly suggest reverting that change when it's time for the next release... Happy to take any other suggestion.

Edit2: Due to the existing const_fn feature, I had to make the dependency optional and remove the legacy feature. Issue is, any package that disables default features will possibly break. Open to suggestions to fix that...

Edit3: Had forgotten about dependencies renaming. A bit ugly, but at least it really does not break semver compatibility this time

@zyuiop zyuiop force-pushed the feat/sev-upstream branch from 77a7ee7 to 023cd44 Compare May 1, 2025 14:46
@zyuiop zyuiop force-pushed the feat/sev-upstream branch from 595d6b3 to 65bdd52 Compare May 1, 2025 15:06
@Freax13
Copy link
Member

Freax13 commented May 1, 2025

@zyuiop Please let me know when this is ready for review.

@zyuiop
Copy link
Author

zyuiop commented May 1, 2025

@zyuiop Please let me know when this is ready for review.

I think it now is. Sorry for the multiple attempts and formatting fails.

Copy link
Member

@Freax13 Freax13 left a comment

Choose a reason for hiding this comment

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

This is looking great so far, I think this will be ready soon.

@jounathaen Given that you also expressed interest in SME/SEV support, do you have time for a review of this PR to make sure it satisfies your needs?

Copy link

@jounathaen jounathaen left a comment

Choose a reason for hiding this comment

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

I think this looks pretty good! 👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants