Skip to content

Commit 0fc14fe

Browse files
committed
greq: Add SnpGuestRequestDriver
Add a driver to send SNP_GUEST_REQUEST commands to the PSP. The command can be any of the request or response command types defined in the SEV-SNP spec, regardless if it's a regular or an extended command. The send_regular_guest_request() and send_extended_guest_request() functions can be used to send regular and extended commands, respectively. guest_request_driver_init() is used to initialize the static driver instance. Signed-off-by: Claudio Carvalho <[email protected]>
1 parent b36fd72 commit 0fc14fe

File tree

5 files changed

+404
-0
lines changed

5 files changed

+404
-0
lines changed

src/greq/driver.rs

+375
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,375 @@
1+
// SPDX-License-Identifier: MIT OR Apache-2.0
2+
//
3+
// Copyright (C) 2023 IBM
4+
//
5+
// Authors: Claudio Carvalho <[email protected]>
6+
7+
//! Driver to send `SNP_GUEST_REQUEST` commands to the PSP. It can be any of the
8+
//! request or response command types defined in the SEV-SNP spec, regardless if it's
9+
//! a regular or an extended command.
10+
11+
extern crate alloc;
12+
13+
use alloc::boxed::Box;
14+
use core::{cell::OnceCell, mem::size_of};
15+
16+
use crate::{
17+
address::VirtAddr,
18+
cpu::percpu::this_cpu_mut,
19+
error::SvsmError,
20+
greq::msg::{SnpGuestRequestExtData, SnpGuestRequestMsg, SnpGuestRequestMsgType},
21+
locking::SpinLock,
22+
protocols::errors::{SvsmReqError, SvsmResultCode},
23+
sev::{
24+
ghcb::GhcbError,
25+
secrets_page::{disable_vmpck0, get_vmpck0, is_vmpck0_clear, VMPCK_SIZE},
26+
},
27+
types::PAGE_SHIFT,
28+
BIT,
29+
};
30+
31+
/// Global `SNP_GUEST_REQUEST` driver instance
32+
static GREQ_DRIVER: SpinLock<OnceCell<SnpGuestRequestDriver>> = SpinLock::new(OnceCell::new());
33+
34+
// Hypervisor error codes
35+
36+
/// Buffer provided is too small
37+
const SNP_GUEST_REQ_INVALID_LEN: u64 = BIT!(32);
38+
/// Hypervisor busy, try again
39+
const SNP_GUEST_REQ_ERR_BUSY: u64 = BIT!(33);
40+
41+
/// Class of the `SNP_GUEST_REQUEST` command: Regular or Extended
42+
#[derive(Clone, Copy, Debug, PartialEq)]
43+
#[repr(u8)]
44+
enum SnpGuestRequestClass {
45+
Regular = 0,
46+
Extended = 1,
47+
}
48+
49+
/// `SNP_GUEST_REQUEST` driver
50+
#[derive(Debug)]
51+
struct SnpGuestRequestDriver {
52+
/// Shared page used for the `SNP_GUEST_REQUEST` request
53+
request: Box<SnpGuestRequestMsg>,
54+
/// Shared page used for the `SNP_GUEST_REQUEST` response
55+
response: Box<SnpGuestRequestMsg>,
56+
/// Encrypted page where we perform crypto operations
57+
staging: Box<SnpGuestRequestMsg>,
58+
/// Extended data buffer that will be provided to the hypervisor
59+
/// to store the SEV-SNP certificates
60+
ext_data: Box<SnpGuestRequestExtData>,
61+
/// Extended data size (`certs` size) provided by the user in [`get_extended_report()`].
62+
/// It will be provided to the hypervisor.
63+
user_extdata_size: usize,
64+
/// Each `SNP_GUEST_REQUEST` message contains a sequence number per VMPCK.
65+
/// The sequence number is incremented with each message sent. Messages
66+
/// sent by the guest to the PSP and by the PSP to the guest must be
67+
/// delivered in order. If not, the PSP will reject subsequent messages
68+
/// by the guest when it detects that the sequence numbers are out of sync.
69+
///
70+
/// NOTE: If the vmpl field of a `SNP_GUEST_REQUEST` message is set to VMPL0,
71+
/// then it must contain the VMPL0 sequence number and be protected (encrypted)
72+
/// with the VMPCK0 key; additionally, if this message fails, the VMPCK0 key
73+
/// must be disabled. The same idea applies to the other VMPL levels.
74+
///
75+
/// The SVSM needs to support only VMPL0 `SNP_GUEST_REQUEST` commands because
76+
/// other layers in the software stack (e.g. OVMF and guest kernel) can send
77+
/// non-VMPL0 commands directly to PSP. Therefore, the SVSM needs to maintain
78+
/// the sequence number and the VMPCK only for VMPL0.
79+
vmpck0_seqno: u64,
80+
}
81+
82+
impl SnpGuestRequestDriver {
83+
/// Create a new [`SnpGuestRequestDriver`]
84+
pub fn new() -> Result<Self, SvsmReqError> {
85+
let mut request = SnpGuestRequestMsg::boxed_new()?;
86+
let mut response = SnpGuestRequestMsg::boxed_new()?;
87+
let staging = SnpGuestRequestMsg::boxed_new()?;
88+
let mut ext_data = SnpGuestRequestExtData::boxed_new()?;
89+
90+
response.set_shared()?;
91+
request.set_shared()?;
92+
ext_data.set_shared()?;
93+
94+
Ok(Self {
95+
request,
96+
response,
97+
staging,
98+
ext_data,
99+
user_extdata_size: size_of::<SnpGuestRequestExtData>(),
100+
vmpck0_seqno: 0,
101+
})
102+
}
103+
104+
/// Get the last VMPCK0 sequence number accounted
105+
fn seqno_last_used(&self) -> u64 {
106+
self.vmpck0_seqno
107+
}
108+
109+
/// Increase the VMPCK0 sequence number by two. In order to keep the
110+
/// sequence number in-sync with the PSP, this is called only when the
111+
/// `SNP_GUEST_REQUEST` response is received.
112+
fn seqno_add_two(&mut self) {
113+
self.vmpck0_seqno += 2;
114+
}
115+
116+
/// Set the user_extdata_size to `n` and clear the first `n` bytes from `ext_data`
117+
pub fn set_user_extdata_size(&mut self, n: usize) -> Result<(), SvsmReqError> {
118+
// At least one page
119+
if (n >> PAGE_SHIFT) == 0 {
120+
return Err(SvsmReqError::invalid_parameter());
121+
}
122+
self.ext_data.nclear(n)?;
123+
self.user_extdata_size = n;
124+
125+
Ok(())
126+
}
127+
128+
/// Call the GHCB layer to send the encrypted SNP_GUEST_REQUEST message
129+
/// to the PSP.
130+
fn send(&mut self, req_class: SnpGuestRequestClass) -> Result<(), SvsmReqError> {
131+
self.response.clear();
132+
133+
let req_page = VirtAddr::from(&mut *self.request as *mut SnpGuestRequestMsg);
134+
let resp_page = VirtAddr::from(&mut *self.response as *mut SnpGuestRequestMsg);
135+
let data_pages = VirtAddr::from(&mut *self.ext_data as *mut SnpGuestRequestExtData);
136+
137+
if req_class == SnpGuestRequestClass::Extended {
138+
let num_user_pages = (self.user_extdata_size >> PAGE_SHIFT) as u64;
139+
this_cpu_mut().ghcb().guest_ext_request(
140+
req_page,
141+
resp_page,
142+
data_pages,
143+
num_user_pages,
144+
)?;
145+
} else {
146+
this_cpu_mut().ghcb().guest_request(req_page, resp_page)?;
147+
}
148+
149+
self.seqno_add_two();
150+
151+
Ok(())
152+
}
153+
154+
// Encrypt the request message from encrypted memory
155+
fn encrypt_request(
156+
&mut self,
157+
msg_type: SnpGuestRequestMsgType,
158+
msg_seqno: u64,
159+
buffer: &mut [u8],
160+
command_len: usize,
161+
) -> Result<(), SvsmReqError> {
162+
// VMPL0 `SNP_GUEST_REQUEST` commands are encrypted with the VMPCK0 key
163+
let vmpck0: [u8; VMPCK_SIZE] = get_vmpck0();
164+
165+
let inbuf = buffer
166+
.get(..command_len)
167+
.ok_or_else(SvsmReqError::invalid_parameter)?;
168+
169+
// For security reasons, encrypt the message in protected memory (staging)
170+
// and then copy the result to shared memory (request)
171+
self.staging
172+
.encrypt_set(msg_type, msg_seqno, &vmpck0, inbuf)?;
173+
self.request.copy_from(&self.staging);
174+
Ok(())
175+
}
176+
177+
// Decrypt the response message from encrypted memory
178+
fn decrypt_response(
179+
&mut self,
180+
msg_seqno: u64,
181+
msg_type: SnpGuestRequestMsgType,
182+
buffer: &mut [u8],
183+
) -> Result<usize, SvsmReqError> {
184+
let vmpck0: [u8; VMPCK_SIZE] = get_vmpck0();
185+
186+
// For security reasons, decrypt the message in protected memory (staging)
187+
self.staging.copy_from(&self.response);
188+
let result = self
189+
.staging
190+
.decrypt_get(msg_type, msg_seqno, &vmpck0, buffer);
191+
192+
if let Err(e) = result {
193+
match e {
194+
// The buffer provided is too small to store the unwrapped response.
195+
// There is no need to clear the VMPCK0, just report it as invalid parameter.
196+
SvsmReqError::RequestError(SvsmResultCode::INVALID_PARAMETER) => (),
197+
_ => disable_vmpck0(),
198+
}
199+
}
200+
201+
result
202+
}
203+
204+
/// Send the provided VMPL0 `SNP_GUEST_REQUEST` command to the PSP.
205+
///
206+
/// The command will be encrypted using AES-256 GCM.
207+
///
208+
/// # Arguments
209+
///
210+
/// * `req_class`: whether this is a regular or extended `SNP_GUEST_REQUEST` command
211+
/// * `msg_type`: type of the command stored in `buffer`, e.g. [`SNP_MSG_REPORT_REQ`]
212+
/// * `buffer`: buffer with the `SNP_GUEST_REQUEST` command to be sent.
213+
/// The same buffer will also be used to store the response.
214+
/// * `command_len`: Size (in bytes) of the command stored in `buffer`
215+
///
216+
/// # Returns
217+
///
218+
/// * Success:
219+
/// * `usize`: Size (in bytes) of the response stored in `buffer`
220+
/// * Error:
221+
/// * [`SvsmReqError`]
222+
fn send_request(
223+
&mut self,
224+
req_class: SnpGuestRequestClass,
225+
msg_type: SnpGuestRequestMsgType,
226+
buffer: &mut [u8],
227+
command_len: usize,
228+
) -> Result<usize, SvsmReqError> {
229+
if is_vmpck0_clear() {
230+
return Err(SvsmReqError::invalid_request());
231+
}
232+
233+
// Message sequence number overflow, the driver will not able
234+
// to send subsequent `SNP_GUEST_REQUEST` messages to the PSP.
235+
// The sequence number is restored only when the guest is rebooted.
236+
let Some(msg_seqno) = self.seqno_last_used().checked_add(1) else {
237+
log::error!("SNP_GUEST_REQUEST: sequence number overflow");
238+
disable_vmpck0();
239+
return Err(SvsmReqError::invalid_request());
240+
};
241+
242+
self.encrypt_request(msg_type, msg_seqno, buffer, command_len)?;
243+
244+
if let Err(e) = self.send(req_class) {
245+
if let SvsmReqError::FatalError(SvsmError::Ghcb(GhcbError::VmgexitError(_rbx, info2))) =
246+
e
247+
{
248+
// For some reason the hypervisor did not forward the request to the PSP.
249+
//
250+
// Because the message sequence number is used as part of the AES-GCM IV, it is important that the
251+
// guest retry the request before allowing another request to be performed so that the IV cannot be
252+
// reused on a new message payload.
253+
match info2 & 0xffff_ffff_0000_0000u64 {
254+
// The certificate buffer provided is too small.
255+
SNP_GUEST_REQ_INVALID_LEN => {
256+
if req_class == SnpGuestRequestClass::Extended {
257+
if let Err(e1) = self.send(SnpGuestRequestClass::Regular) {
258+
log::error!(
259+
"SNP_GUEST_REQ_INVALID_LEN. Aborting, request resend failed"
260+
);
261+
disable_vmpck0();
262+
return Err(e1);
263+
}
264+
return Err(e);
265+
} else {
266+
// We sent a regular SNP_GUEST_REQUEST, but the hypervisor returned
267+
// an error code that is exclusive for extended SNP_GUEST_REQUEST
268+
disable_vmpck0();
269+
return Err(SvsmReqError::invalid_request());
270+
}
271+
}
272+
// The hypervisor is busy.
273+
SNP_GUEST_REQ_ERR_BUSY => {
274+
if let Err(e2) = self.send(req_class) {
275+
log::error!("SNP_GUEST_REQ_ERR_BUSY. Aborting, request resend failed");
276+
disable_vmpck0();
277+
return Err(e2);
278+
}
279+
// ... request resend worked, continue normally.
280+
}
281+
// Failed for unknown reason. Status codes can be found in
282+
// the AMD SEV-SNP spec or in the linux kernel include/uapi/linux/psp-sev.h
283+
_ => {
284+
log::error!("SNP_GUEST_REQUEST failed, unknown error code={}\n", info2);
285+
disable_vmpck0();
286+
return Err(e);
287+
}
288+
}
289+
}
290+
}
291+
292+
let msg_seqno = self.seqno_last_used();
293+
let resp_msg_type = SnpGuestRequestMsgType::try_from(msg_type as u8 + 1)?;
294+
295+
self.decrypt_response(msg_seqno, resp_msg_type, buffer)
296+
}
297+
298+
/// Send the provided regular `SNP_GUEST_REQUEST` command to the PSP
299+
pub fn send_regular_guest_request(
300+
&mut self,
301+
msg_type: SnpGuestRequestMsgType,
302+
buffer: &mut [u8],
303+
command_len: usize,
304+
) -> Result<usize, SvsmReqError> {
305+
self.send_request(SnpGuestRequestClass::Regular, msg_type, buffer, command_len)
306+
}
307+
308+
/// Send the provided extended `SNP_GUEST_REQUEST` command to the PSP
309+
pub fn send_extended_guest_request(
310+
&mut self,
311+
msg_type: SnpGuestRequestMsgType,
312+
buffer: &mut [u8],
313+
command_len: usize,
314+
certs: &mut [u8],
315+
) -> Result<usize, SvsmReqError> {
316+
self.set_user_extdata_size(certs.len())?;
317+
318+
let outbuf_len: usize = self.send_request(
319+
SnpGuestRequestClass::Extended,
320+
msg_type,
321+
buffer,
322+
command_len,
323+
)?;
324+
325+
// The SEV-SNP certificates can be used to verify the attestation report. At this point, a zeroed
326+
// ext_data buffer indicates that the certificates were not imported.
327+
// The VM owner can import them from the host using the virtee/snphost project
328+
if self.ext_data.is_nclear(certs.len())? {
329+
log::warn!("SEV-SNP certificates not found. Make sure they were loaded from the host.");
330+
} else {
331+
self.ext_data.copy_to_slice(certs)?;
332+
}
333+
334+
Ok(outbuf_len)
335+
}
336+
}
337+
338+
/// Initialize the global `SnpGuestRequestDriver`
339+
///
340+
/// # Panics
341+
///
342+
/// This function panics if we fail to initialize any of the `SnpGuestRequestDriver` fields.
343+
pub fn guest_request_driver_init() {
344+
let cell = GREQ_DRIVER.lock();
345+
let _ = cell.get_or_init(|| {
346+
SnpGuestRequestDriver::new().expect("SnpGuestRequestDriver failed to initialize")
347+
});
348+
}
349+
350+
/// Send the provided regular `SNP_GUEST_REQUEST` command to the PSP.
351+
/// Further details can be found in the `SnpGuestRequestDriver.send_request()` documentation.
352+
pub fn send_regular_guest_request(
353+
msg_type: SnpGuestRequestMsgType,
354+
buffer: &mut [u8],
355+
request_len: usize,
356+
) -> Result<usize, SvsmReqError> {
357+
let mut cell = GREQ_DRIVER.lock();
358+
let driver: &mut SnpGuestRequestDriver =
359+
cell.get_mut().ok_or_else(SvsmReqError::invalid_request)?;
360+
driver.send_regular_guest_request(msg_type, buffer, request_len)
361+
}
362+
363+
/// Send the provided extended `SNP_GUEST_REQUEST` command to the PSP
364+
/// Further details can be found in the `SnpGuestRequestDriver.send_request()` documentation.
365+
pub fn send_extended_guest_request(
366+
msg_type: SnpGuestRequestMsgType,
367+
buffer: &mut [u8],
368+
request_len: usize,
369+
certs: &mut [u8],
370+
) -> Result<usize, SvsmReqError> {
371+
let mut cell = GREQ_DRIVER.lock();
372+
let driver: &mut SnpGuestRequestDriver =
373+
cell.get_mut().ok_or_else(SvsmReqError::invalid_request)?;
374+
driver.send_extended_guest_request(msg_type, buffer, request_len, certs)
375+
}

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)