Skip to content

Commit 55e9106

Browse files
committed
greq: Add SnpGuestRequestDriver
SnpGuestRequestDriver is a driver for the SNP_GUEST_REQUEST mechanism. Its implementation follows the AMD SEV-SNP specification, chapter 7. The send_request() method of the driver is used to send a SNP_GUEST_REQUEST command to the PSP. The test case aes_gcm_encrypt_and_decrypt() is used to validate both the load and unwrap methods of the driver. Signed-off-by: Claudio Carvalho <[email protected]>
1 parent cd1f8e8 commit 55e9106

File tree

4 files changed

+396
-0
lines changed

4 files changed

+396
-0
lines changed

src/greq/driver.rs

+370
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,370 @@
1+
// SPDX-License-Identifier: MIT OR Apache-2.0
2+
//
3+
// Copyright (C) 2023 IBM
4+
//
5+
// Authors: Claudio Carvalho <[email protected]>
6+
7+
//! `SNP_GUEST_REQUEST` driver
8+
9+
extern crate alloc;
10+
11+
use crate::address::VirtAddr;
12+
use crate::cpu::percpu::this_cpu_mut;
13+
use crate::error::SvsmError;
14+
use crate::greq::msg::{MemoryPage, SnpGuestRequestExtData, SnpGuestRequestMsg};
15+
use crate::protocols::errors::{SvsmReqError, SvsmResultCode};
16+
use crate::sev::ghcb::GhcbError;
17+
use crate::sev::secrets_page::{disable_vmpck0, get_vmpck0, is_vmpck0_clear, VMPCK_SIZE};
18+
use crate::BIT;
19+
20+
// Hypervisor error codes
21+
22+
/// Buffer provided is too small
23+
pub const SNP_GUEST_REQ_INVALID_LEN: u64 = BIT!(32);
24+
/// Hypervisor busy, try again
25+
pub const SNP_GUEST_REQ_ERR_BUSY: u64 = BIT!(33);
26+
27+
#[derive(Debug, PartialEq)]
28+
enum DriverState {
29+
Initial = 0,
30+
Ready = 1,
31+
Failed = 2,
32+
}
33+
34+
#[derive(PartialEq, Copy, Clone)]
35+
pub enum SnpGuestRequestClass {
36+
Regular,
37+
Extended,
38+
}
39+
40+
/// `SNP_GUEST_REQUEST` driver
41+
#[derive(Debug)]
42+
pub struct SnpGuestRequestDriver {
43+
/// `SNP_GUEST_REQUEST` message that carries the request. Shared page.
44+
request: SnpGuestRequestMsg,
45+
/// `SNP_GUEST_REQUEST` message that carries the response. Shared page.
46+
response: SnpGuestRequestMsg,
47+
/// Protected page where we encrypt/decrypt the `SNP_GUEST_REQUEST` message.
48+
staging: SnpGuestRequestMsg,
49+
/// `SNP_GUEST_REQUEST` extended data, usually used to carry
50+
/// hypervisor-provided data (e.g. attestation report certificates)
51+
ext_data: SnpGuestRequestExtData,
52+
/// Each `SNP_GUEST_REQUEST` message contains a sequence number per VMPCK.
53+
/// It is incremented with each message sent. Messages sent by the guest to
54+
/// the PSP and by the PSP to the guest must be delivered in order. If not,
55+
/// the PSP will reject subsequent messages by the guest when it detects that
56+
/// the sequence numbers are out of sync.
57+
///
58+
/// NOTE: If the vmpl field of a `SNP_GUEST_REQUEST` message is set to VMPL0,
59+
/// then it must contain the VMPL0 sequence number and be protected (encrypted)
60+
/// with the VMPCK0 key; additionally, if this message fails, the VMPCK0 key
61+
/// must be disabled. The same idea applies to the other VMPL levels.
62+
///
63+
/// The guest kernel runs in VMPL1 and it should be able to protect and send
64+
/// VMPL1 `SNP_GUEST_REQUEST` messages directly to the PSP; hence,
65+
/// the SVSM needs to support only VMPL0 `SNP_GUEST_REQUEST` messages.
66+
/// In other words, it needs to maintain the sequence number only for VMPL0.
67+
vmpck0_seqno: u64,
68+
/// Driver current state. `SNP_GUEST_REQUEST` messages can be sent to the PSP
69+
/// only if the driver is in the ready state.
70+
state: DriverState,
71+
}
72+
73+
impl SnpGuestRequestDriver {
74+
/// Prepare for a new extended guest request
75+
pub fn extended_request_prepare(&mut self, len: usize) -> Result<(), SvsmReqError> {
76+
self.ext_data.set_len(len)?;
77+
self.ext_data.clear();
78+
79+
Ok(())
80+
}
81+
82+
/// Copy the first n bytes from the extended data to the provided buffer
83+
pub fn extended_data_ncopy_to(
84+
&self,
85+
n: usize,
86+
outbuf: VirtAddr,
87+
outbuf_size: usize,
88+
) -> Result<(), SvsmReqError> {
89+
self.ext_data.ncopy_to(n, outbuf, outbuf_size)
90+
}
91+
92+
/// Check if the extended data is zeroed
93+
pub fn is_extended_data_clear(&self) -> bool {
94+
self.ext_data.is_clear()
95+
}
96+
97+
/// The global driver instance GREQ_DRIVER requires "pub const fn default()",
98+
/// however, const Default trait is not supported (yet?)
99+
/// <https://github.com/rust-lang/rust/issues/67792>
100+
///
101+
/// Once it is supported we should be able to just annotate the structure
102+
/// with something like `#[derive(const_Default)]`
103+
pub const fn default() -> Self {
104+
Self {
105+
request: SnpGuestRequestMsg::default(),
106+
response: SnpGuestRequestMsg::default(),
107+
staging: SnpGuestRequestMsg::default(),
108+
ext_data: SnpGuestRequestExtData::default(),
109+
vmpck0_seqno: 0,
110+
state: DriverState::Initial,
111+
}
112+
}
113+
114+
fn do_init(&mut self) -> Result<(), SvsmReqError> {
115+
self.request.init(MemoryPage::Unencrypted)?;
116+
self.response.init(MemoryPage::Unencrypted)?;
117+
self.ext_data.init(MemoryPage::Unencrypted)?;
118+
119+
self.staging.init(MemoryPage::Encrypted)?;
120+
121+
Ok(())
122+
}
123+
124+
fn is_ready(&self) -> bool {
125+
self.state == DriverState::Ready
126+
}
127+
128+
/// Initialize the `SNP_GUEST_REQUEST` driver
129+
pub fn init(&mut self) -> Result<(), SvsmReqError> {
130+
if self.state != DriverState::Initial {
131+
return Err(SvsmReqError::invalid_request());
132+
}
133+
134+
let result = self.do_init();
135+
if result.is_ok() {
136+
self.state = DriverState::Ready;
137+
} else {
138+
self.free();
139+
self.state = DriverState::Failed;
140+
// Without the driver we can't send `SNP_GUEST_REQUEST` messages to the PSP.
141+
// Clear the VMPCK0 to prevent it from being exploited.
142+
disable_vmpck0();
143+
}
144+
145+
result
146+
}
147+
148+
/// Free the memory allocated for the `SNP_GUEST_REQUEST` driver
149+
pub fn free(&mut self) {
150+
self.request.free();
151+
self.response.free();
152+
self.staging.free();
153+
self.ext_data.free();
154+
}
155+
156+
/// Get the last VMPCK0 sequence number accounted
157+
fn seqno_last_used(&self) -> u64 {
158+
self.vmpck0_seqno
159+
}
160+
161+
/// Increase the VMPCK0 sequence number by two. In order to keep the
162+
/// sequence number in-sync with the PSP, this is called only when the
163+
/// `SNP_GUEST_REQUEST` response is received.
164+
fn seqno_add_two(&mut self) {
165+
self.vmpck0_seqno += 2;
166+
}
167+
168+
/// Call the GHCB layer to send the encrypted SNP_GUEST_REQUEST message
169+
/// to the PSP.
170+
fn send(&mut self, req_class: SnpGuestRequestClass) -> Result<(), SvsmReqError> {
171+
self.response.clear();
172+
173+
if req_class == SnpGuestRequestClass::Extended {
174+
this_cpu_mut().ghcb().guest_ext_request(
175+
self.request.as_va(),
176+
self.response.as_va(),
177+
self.ext_data.as_va(),
178+
self.ext_data.npages(),
179+
)?;
180+
} else {
181+
this_cpu_mut()
182+
.ghcb()
183+
.guest_request(self.request.as_va(), self.response.as_va())?;
184+
}
185+
186+
self.seqno_add_two();
187+
188+
Ok(())
189+
}
190+
191+
/// Send a VMPL0 `SNP_GUEST_REQUEST` command to the PSP.
192+
/// The command will be encrypted using AES-256 GCM.
193+
///
194+
/// # Parameters:
195+
///
196+
/// * `msg_type`: type of the command stored in `buffer`
197+
/// * `req_class`: whether or not this is an extended `SNP_GUEST_REQUEST` command
198+
/// * `buffer`: [VirtAddr] of the buffer that contains the `SNP_GUEST_REQUEST` command
199+
/// The same buffer will also be used to store the response.
200+
/// * `buffer_size`: Total size of `buffer` in bytes
201+
/// * `buffer_len`: Number of bytes from `buffer` being used for the command
202+
///
203+
/// # Return codes:
204+
///
205+
/// * Success:
206+
/// * `usize`: Number of bytes from `buffer` being used for the response
207+
/// * Error:
208+
/// * `SvsmReqError`
209+
pub fn send_request(
210+
&mut self,
211+
msg_type: u8,
212+
req_class: SnpGuestRequestClass,
213+
buffer: VirtAddr,
214+
buffer_size: usize,
215+
buffer_len: u16,
216+
) -> Result<usize, SvsmReqError> {
217+
if !self.is_ready() || is_vmpck0_clear() {
218+
return Err(SvsmReqError::invalid_request());
219+
}
220+
// Message sequence number overflow, the driver will not able
221+
// to send `SNP_GUEST_REQUEST` messages to the PSP. The sequence number is
222+
// restored only when the guest is rebooted.
223+
// Let's clear the VMPCK0 to prevent it from being exploited.
224+
let Some(msg_seqno) = self.seqno_last_used().checked_add(1) else {
225+
log::error!("SNP_GUEST_REQUEST: sequence number overflow");
226+
disable_vmpck0();
227+
return Err(SvsmReqError::invalid_request());
228+
};
229+
230+
// VMPL0 `SNP_GUEST_REQUEST` commands are encrypted with the VMPCK0
231+
let vmpck0: [u8; VMPCK_SIZE] = get_vmpck0();
232+
233+
// For security reasons, perform the message load in encrypted memory (staging)
234+
// and then copy it to shared memory (request)
235+
self.staging
236+
.load(msg_type, msg_seqno, &vmpck0, buffer, buffer_len)?;
237+
self.request.copy_from(&self.staging)?;
238+
239+
if let Err(e) = self.send(req_class) {
240+
if let SvsmReqError::FatalError(SvsmError::Ghcb(GhcbError::VmgexitError(_rbx, info2))) =
241+
e
242+
{
243+
// For some reason the hypervisor did not forward the request to the PSP.
244+
// Let's resend it to prevent the IV from being exploited.
245+
match info2 & 0xffff_ffff_0000_0000u64 {
246+
// The certificate buffer provided is too small.
247+
SNP_GUEST_REQ_INVALID_LEN => {
248+
if req_class == SnpGuestRequestClass::Extended {
249+
if let Err(e1) = self.send(SnpGuestRequestClass::Regular) {
250+
log::error!(
251+
"SNP_GUEST_REQ_INVALID_LEN. Aborting, request resend failed"
252+
);
253+
disable_vmpck0();
254+
return Err(e1);
255+
}
256+
return Err(e);
257+
} else {
258+
// We sent a regular SNP_GUEST_REQUEST, but the hypervisor returned
259+
// an error code that is exclusive for extended SNP_GUEST_REQUEST
260+
disable_vmpck0();
261+
return Err(SvsmReqError::invalid_request());
262+
}
263+
}
264+
// The hypervisor is busy.
265+
SNP_GUEST_REQ_ERR_BUSY => {
266+
if let Err(e2) = self.send(SnpGuestRequestClass::Regular) {
267+
log::error!("SNP_GUEST_REQ_ERR_BUSY. Aborting, request resend failed");
268+
disable_vmpck0();
269+
return Err(e2);
270+
}
271+
// ... request resend worked, continue normally.
272+
}
273+
// Failed for unknown reason. Status codes can be found in
274+
// the AMD SEV-SNP spec or in the linux kernel include/uapi/linux/psp-sev.h
275+
_ => {
276+
log::error!("SNP_GUEST_REQUEST failed, unknown error code={}\n", info2);
277+
disable_vmpck0();
278+
return Err(e);
279+
}
280+
}
281+
}
282+
}
283+
284+
let msg_seqno = self.seqno_last_used();
285+
286+
// For security reasons, perform the message unwrap in encrypted memory (staging)
287+
self.staging.copy_from(&self.response)?;
288+
let result = self
289+
.staging
290+
.unwrap(msg_type + 1, msg_seqno, &vmpck0, buffer, buffer_size);
291+
292+
if let Err(e) = result {
293+
match e {
294+
// The buffer provided is too small to store the unwrapped response.
295+
// There is no need to clear the VMPCK0, just report it as invalid parameter.
296+
SvsmReqError::RequestError(SvsmResultCode::INVALID_PARAMETER) => (),
297+
_ => disable_vmpck0(),
298+
}
299+
}
300+
301+
result
302+
}
303+
}
304+
305+
#[cfg(test)]
306+
mod tests {
307+
extern crate alloc;
308+
309+
use alloc::vec::Vec;
310+
311+
use crate::{
312+
address::VirtAddr,
313+
greq::driver::{DriverState, SnpGuestRequestDriver},
314+
greq::msg::{SnpGuestRequestMsg, SNP_MSG_REPORT_REQ},
315+
sev::secrets_page::VMPCK_SIZE,
316+
types::PAGE_SIZE,
317+
};
318+
319+
static mut DRIVER: SnpGuestRequestDriver = SnpGuestRequestDriver::default();
320+
static mut REQUEST: [u8; PAGE_SIZE] = [0u8; PAGE_SIZE];
321+
322+
fn guest_request_driver_init() {
323+
let req_va = unsafe { VirtAddr::from(REQUEST.as_mut_ptr()) };
324+
325+
unsafe {
326+
DRIVER.vmpck0_seqno = 1;
327+
DRIVER.request = SnpGuestRequestMsg { buffer: req_va };
328+
DRIVER.state = DriverState::Ready;
329+
}
330+
}
331+
332+
#[test]
333+
fn aes_gcm_encrypt_and_decrypt() {
334+
guest_request_driver_init();
335+
336+
let payload = b"request-to-be-encrypted";
337+
let vmpck0 = [5u8; VMPCK_SIZE];
338+
339+
unsafe {
340+
let result = DRIVER.request.load(
341+
SNP_MSG_REPORT_REQ,
342+
DRIVER.vmpck0_seqno,
343+
&vmpck0,
344+
VirtAddr::from(payload as *const u8),
345+
payload.len() as u16,
346+
);
347+
348+
assert!(result.is_ok());
349+
350+
let mut decrypted_payload: Vec<u8> = Vec::with_capacity(PAGE_SIZE);
351+
let decrypted_payload_va = VirtAddr::from(decrypted_payload.as_ptr());
352+
353+
let result = DRIVER.request.unwrap(
354+
SNP_MSG_REPORT_REQ,
355+
DRIVER.vmpck0_seqno,
356+
&vmpck0,
357+
decrypted_payload_va,
358+
PAGE_SIZE,
359+
);
360+
361+
assert!(result.is_ok());
362+
363+
let decrypted_payload_len = result.unwrap();
364+
assert_eq!(decrypted_payload_len, payload.len());
365+
366+
decrypted_payload.set_len(decrypted_payload_len);
367+
assert_eq!(decrypted_payload.as_slice(), payload);
368+
}
369+
}
370+
}

src/greq/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,5 @@
44
//
55
// Authors: Claudio Carvalho <[email protected]>
66

7+
pub mod driver;
78
pub mod msg;

0 commit comments

Comments
 (0)