Add TSM module
This commit is contained in:
parent
31e352dc6b
commit
eb4edd25e8
|
|
@ -10,7 +10,7 @@ mod urandom;
|
|||
mod zero;
|
||||
|
||||
#[cfg(all(target_arch = "x86_64", feature = "cvm_guest"))]
|
||||
mod tdxguest;
|
||||
pub mod tdxguest;
|
||||
|
||||
use alloc::format;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,18 @@
|
|||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
use ostd::mm::{DmaCoherent, FrameAllocOptions, HasPaddr, VmIo};
|
||||
use tdx_guest::tdcall::{get_report, TdCallError};
|
||||
use core::{mem::size_of, time::Duration};
|
||||
|
||||
use align_ext::AlignExt;
|
||||
use aster_util::{field_ptr, safe_ptr::SafePtr};
|
||||
use ostd::{
|
||||
mm::{DmaCoherent, FrameAllocOptions, HasPaddr, HasSize, VmIo, PAGE_SIZE},
|
||||
sync::WaitQueue,
|
||||
};
|
||||
use tdx_guest::{
|
||||
tdcall::{get_report, TdCallError},
|
||||
tdvmcall::{get_quote, TdVmcallError},
|
||||
SHARED_MASK,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
events::IoEvents,
|
||||
|
|
@ -60,6 +71,26 @@ impl From<TdCallError> for Error {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<TdVmcallError> for Error {
|
||||
fn from(err: TdVmcallError) -> Self {
|
||||
match err {
|
||||
TdVmcallError::TdxRetry => {
|
||||
Error::with_message(Errno::EINVAL, "TdVmcallError::TdxRetry")
|
||||
}
|
||||
TdVmcallError::TdxOperandInvalid => {
|
||||
Error::with_message(Errno::EINVAL, "TdVmcallError::TdxOperandInvalid")
|
||||
}
|
||||
TdVmcallError::TdxGpaInuse => {
|
||||
Error::with_message(Errno::EINVAL, "TdVmcallError::TdxGpaInuse")
|
||||
}
|
||||
TdVmcallError::TdxAlignError => {
|
||||
Error::with_message(Errno::EINVAL, "TdVmcallError::TdxAlignError")
|
||||
}
|
||||
TdVmcallError::Other => Error::with_message(Errno::EAGAIN, "TdVmcallError::Other"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Pollable for TdxGuest {
|
||||
fn poll(&self, mask: IoEvents, _poller: Option<&mut PollHandle>) -> IoEvents {
|
||||
let events = IoEvents::IN | IoEvents::OUT;
|
||||
|
|
@ -84,37 +115,102 @@ impl FileIo for TdxGuest {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn tdx_get_quote(inblob: &[u8]) -> Result<Box<[u8]>> {
|
||||
const GET_QUOTE_IN_FLIGHT: u64 = 0xFFFF_FFFF_FFFF_FFFF;
|
||||
const GET_QUOTE_BUF_SIZE: usize = 8 * 1024;
|
||||
|
||||
let report = tdx_get_report(inblob)?;
|
||||
let buf = alloc_dma_buf(GET_QUOTE_BUF_SIZE)?;
|
||||
let report_ptr: SafePtr<TdxQuoteHdr, _, _> = SafePtr::new(&buf, 0);
|
||||
|
||||
field_ptr!(&report_ptr, TdxQuoteHdr, version).write(&1u64)?;
|
||||
field_ptr!(&report_ptr, TdxQuoteHdr, status).write(&0u64)?;
|
||||
field_ptr!(&report_ptr, TdxQuoteHdr, in_len).write(&(TDX_REPORT_LEN as u32))?;
|
||||
field_ptr!(&report_ptr, TdxQuoteHdr, out_len).write(&0u32)?;
|
||||
buf.write_bytes(size_of::<TdxQuoteHdr>(), &report)?;
|
||||
|
||||
// FIXME: The `get_quote` API from the `tdx_guest` crate should have been marked `unsafe`
|
||||
// because it has no way to determine if the input physical address is safe or not.
|
||||
get_quote((buf.paddr() as u64) | SHARED_MASK, buf.size() as u64)?;
|
||||
|
||||
// Poll for the quote to be ready.
|
||||
let status_ptr = field_ptr!(&report_ptr, TdxQuoteHdr, status);
|
||||
let sleep_queue = WaitQueue::new();
|
||||
let sleep_duration = Duration::from_millis(100);
|
||||
loop {
|
||||
let status = status_ptr.read()?;
|
||||
if status != GET_QUOTE_IN_FLIGHT {
|
||||
break;
|
||||
}
|
||||
let _ = sleep_queue.wait_until_or_timeout(|| -> Option<()> { None }, &sleep_duration);
|
||||
}
|
||||
|
||||
// Note: We cannot convert `DmaCoherent` to `USegment` here. When shared memory is converted back
|
||||
// to private memory in TDX, `TDG.MEM.PAGE.ACCEPT` will zero out all content.
|
||||
// TDX Module Specification - `TDG.MEM.PAGE.ACCEPT` Leaf:
|
||||
// "Accept a pending private page and initialize it to all-0 using the TD ephemeral private key."
|
||||
let out_len = field_ptr!(&report_ptr, TdxQuoteHdr, out_len).read()?;
|
||||
let mut outblob = vec![0u8; out_len as usize].into_boxed_slice();
|
||||
buf.read_bytes(size_of::<TdxQuoteHdr>(), outblob.as_mut())?;
|
||||
Ok(outblob)
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
struct TdxQuoteHdr {
|
||||
// Quote version, filled by TD
|
||||
version: u64,
|
||||
// Status code of quote request, filled by VMM
|
||||
status: u64,
|
||||
// Length of TDREPORT, filled by TD
|
||||
in_len: u32,
|
||||
// Length of quote, filled by VMM
|
||||
out_len: u32,
|
||||
}
|
||||
|
||||
fn handle_get_report(arg: usize) -> Result<i32> {
|
||||
const SHARED_BIT: u8 = 51;
|
||||
const SHARED_MASK: u64 = 1u64 << SHARED_BIT;
|
||||
let current_task = ostd::task::Task::current().unwrap();
|
||||
let user_space = CurrentUserSpace::new(current_task.as_thread_local().unwrap());
|
||||
let user_request: TdxReportRequest = user_space.read_val(arg)?;
|
||||
|
||||
let segment = FrameAllocOptions::new().alloc_segment(2).unwrap();
|
||||
let dma_coherent = DmaCoherent::map(segment.into(), false).unwrap();
|
||||
dma_coherent
|
||||
.write_bytes(0, &user_request.report_data)
|
||||
.unwrap();
|
||||
// 1024-byte alignment.
|
||||
dma_coherent
|
||||
.write_bytes(1024, &user_request.tdx_report)
|
||||
.unwrap();
|
||||
|
||||
if let Err(err) = get_report(
|
||||
((dma_coherent.paddr() + 1024) as u64) | SHARED_MASK,
|
||||
(dma_coherent.paddr() as u64) | SHARED_MASK,
|
||||
) {
|
||||
println!("[kernel]: get TDX report error: {:?}", err);
|
||||
return Err(err.into());
|
||||
}
|
||||
let report = tdx_get_report(&user_request.report_data)?;
|
||||
|
||||
let tdx_report_vaddr = arg + TDX_REPORTDATA_LEN;
|
||||
let mut generated_report = vec![0u8; TDX_REPORT_LEN];
|
||||
dma_coherent
|
||||
.read_bytes(1024, &mut generated_report)
|
||||
.unwrap();
|
||||
let report_slice: &[u8] = &generated_report;
|
||||
user_space.write_bytes(tdx_report_vaddr, &mut VmReader::from(report_slice))?;
|
||||
user_space.write_bytes(tdx_report_vaddr, &mut VmReader::from(report.as_ref()))?;
|
||||
Ok(0)
|
||||
}
|
||||
|
||||
fn tdx_get_report(inblob: &[u8]) -> Result<Box<[u8]>> {
|
||||
if inblob.len() != TDX_REPORTDATA_LEN {
|
||||
return_errno_with_message!(Errno::EINVAL, "Invalid inblob length");
|
||||
}
|
||||
|
||||
let segment = FrameAllocOptions::new().alloc_segment(2)?;
|
||||
let dma_coherent = DmaCoherent::map(segment.into(), false).unwrap();
|
||||
dma_coherent.write_bytes(0, &inblob).unwrap();
|
||||
|
||||
// FIXME: The `get_report` API from the `tdx_guest` crate should have been marked `unsafe`
|
||||
// because it has no way to determine if the input physical address is safe or not.
|
||||
get_report(
|
||||
((dma_coherent.paddr() + 1024) as u64) | SHARED_MASK,
|
||||
(dma_coherent.paddr() as u64) | SHARED_MASK,
|
||||
)?;
|
||||
|
||||
// Note: We cannot convert `DmaCoherent` to `USegment` here. When shared memory is converted back
|
||||
// to private memory in TDX, `TDG.MEM.PAGE.ACCEPT` will zero out all content.
|
||||
// TDX Module Specification - `TDG.MEM.PAGE.ACCEPT` Leaf:
|
||||
// "Accept a pending private page and initialize it to all-0 using the TD ephemeral private key."
|
||||
let mut generated_report = Box::new([0u8; TDX_REPORT_LEN]);
|
||||
dma_coherent
|
||||
.read_bytes(1024, generated_report.as_mut())
|
||||
.unwrap();
|
||||
|
||||
Ok(generated_report)
|
||||
}
|
||||
|
||||
fn alloc_dma_buf(buf_len: usize) -> Result<DmaCoherent> {
|
||||
let aligned_buf_len = buf_len.align_up(PAGE_SIZE);
|
||||
let segment = FrameAllocOptions::new().alloc_segment(aligned_buf_len / PAGE_SIZE)?;
|
||||
|
||||
let dma_buf = DmaCoherent::map(segment.into(), false).unwrap();
|
||||
Ok(dma_buf)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -66,6 +66,7 @@ mod net;
|
|||
mod prelude;
|
||||
mod process;
|
||||
mod sched;
|
||||
mod security;
|
||||
mod syscall;
|
||||
mod thread;
|
||||
mod time;
|
||||
|
|
@ -102,6 +103,7 @@ fn init() {
|
|||
sched::init();
|
||||
process::init();
|
||||
fs::init();
|
||||
security::init();
|
||||
}
|
||||
|
||||
fn init_on_each_cpu() {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
#[cfg(all(target_arch = "x86_64", feature = "cvm_guest"))]
|
||||
mod tsm;
|
||||
|
||||
pub(super) fn init() {
|
||||
#[cfg(all(target_arch = "x86_64", feature = "cvm_guest"))]
|
||||
tsm::init();
|
||||
}
|
||||
|
|
@ -0,0 +1,317 @@
|
|||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
//! Trusted Security Manager (TSM).
|
||||
//!
|
||||
//! This module provides a cross-vendor ConfigFS interface
|
||||
//! for generating confidential-computing attestation reports.
|
||||
//! Userspace writes a request blob under `/sys/kernel/config/tsm/report/$name/inblob`
|
||||
//! and reads back the signed report from `outblob`,
|
||||
//! with optional knobs like `provider` and `generation`.
|
||||
//!
|
||||
//! For more information about the interface,
|
||||
//! checkout Linux's [documentation](https://www.kernel.org/doc/Documentation/ABI/testing/configfs-tsm).
|
||||
|
||||
use alloc::{boxed::Box, string::ToString, sync::Arc};
|
||||
use core::fmt::Debug;
|
||||
|
||||
use aster_systree::{
|
||||
inherit_sys_branch_node, inherit_sys_leaf_node, BranchNodeFields, Error, NormalNodeFields,
|
||||
Result, SysAttrSetBuilder, SysObj, SysPerms, SysStr,
|
||||
};
|
||||
use aster_util::printer::VmPrinter;
|
||||
use inherit_methods_macro::inherit_methods;
|
||||
use ostd::{
|
||||
mm::{FallibleVmRead, FallibleVmWrite, VmReader, VmWriter},
|
||||
sync::RwMutex,
|
||||
};
|
||||
|
||||
use crate::{device::tdxguest::tdx_get_quote, fs::configfs};
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Tsm {
|
||||
fields: BranchNodeFields<ReportSet, Self>,
|
||||
}
|
||||
|
||||
impl Tsm {
|
||||
fn new() -> Arc<Self> {
|
||||
let name = SysStr::from("tsm");
|
||||
let attrs = SysAttrSetBuilder::new().build().unwrap();
|
||||
Arc::new_cyclic(|weak_self| {
|
||||
let fields = BranchNodeFields::new(name, attrs, weak_self.clone());
|
||||
fields.add_child(ReportSet::new()).unwrap();
|
||||
Tsm { fields }
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
inherit_sys_branch_node!(Tsm, fields, {
|
||||
fn perms(&self) -> SysPerms {
|
||||
SysPerms::DEFAULT_RW_PERMS
|
||||
}
|
||||
});
|
||||
|
||||
#[derive(Debug)]
|
||||
struct ReportSet {
|
||||
fields: BranchNodeFields<ReportNode, Self>,
|
||||
}
|
||||
|
||||
#[inherit_methods(from = "self.fields")]
|
||||
impl ReportSet {
|
||||
fn new() -> Arc<Self> {
|
||||
let name = SysStr::from("report");
|
||||
let attrs = SysAttrSetBuilder::new().build().unwrap();
|
||||
Arc::new_cyclic(|weak_self| ReportSet {
|
||||
fields: BranchNodeFields::new(name, attrs, weak_self.clone()),
|
||||
})
|
||||
}
|
||||
|
||||
fn add_child(&self, new_child: Arc<ReportNode>) -> Result<()>;
|
||||
}
|
||||
|
||||
inherit_sys_branch_node!(ReportSet, fields, {
|
||||
fn perms(&self) -> SysPerms {
|
||||
SysPerms::DEFAULT_RW_PERMS
|
||||
}
|
||||
|
||||
fn create_child(&self, name: &str) -> Result<Arc<dyn SysObj>> {
|
||||
let report = ReportNode::new(SysStr::from(name.to_string()));
|
||||
self.add_child(report.clone())?;
|
||||
Ok(report)
|
||||
}
|
||||
});
|
||||
|
||||
const TSM_INBLOB_MAX: usize = 64;
|
||||
|
||||
/// The state machine of a report directory.
|
||||
///
|
||||
/// The states and their transitions are summarized in the table below.
|
||||
///
|
||||
/// ```
|
||||
/// ┌────────────────┐
|
||||
/// │InblobNeeded │
|
||||
/// │(initial) │
|
||||
/// └────────┬───────┘
|
||||
/// │
|
||||
/// │ write inblob
|
||||
/// ┌────────▼───────┐
|
||||
/// │InblobProvided ◄─────────┐
|
||||
/// └────────┬───────┘ │
|
||||
/// │ │
|
||||
/// │ read outblob │ write inblob
|
||||
/// ┌────────▼───────┐ │
|
||||
/// │OutblobGenerated├─────────┘
|
||||
/// └────────────────┘
|
||||
/// ```
|
||||
#[derive(Debug)]
|
||||
enum TsmProviderState {
|
||||
/// Inblob is needed.
|
||||
///
|
||||
/// This is the initial state.
|
||||
///
|
||||
/// The behaviors of the files under a report directory
|
||||
/// are summarized in the table below:
|
||||
///
|
||||
/// | Files | Write behaviors| Read behaviors |
|
||||
/// |--------------|----------------|-----------------|
|
||||
/// | `inblob` | Transist to `InblobProvided` | Error due to WO |
|
||||
/// | `outblob` | Error due to RO| Error (-EINVAL) |
|
||||
/// | `generation` | Error due to RO| Returns 0 |
|
||||
///
|
||||
/// The initial value of `generation` is 0.
|
||||
InblobNeeded,
|
||||
/// Inblob is provided by the userspace, but outblob is not generated.
|
||||
///
|
||||
/// The behaviors of the files under a report directory
|
||||
/// are summarized in the table below:
|
||||
///
|
||||
/// | Files | Write behaviors| Read behaviors |
|
||||
/// |--------------|----------------|-----------------|
|
||||
/// | `inblob` | Update inblob | Error due to WO |
|
||||
/// | `outblob` | Error due to RO| Transist to `OutblobGenerated` |
|
||||
/// | `generation` | Error due to RO| Returns `generation` |
|
||||
///
|
||||
/// Whenever @inblob or any option is written, the value of `generation`
|
||||
/// increases by 1.
|
||||
InblobProvided {
|
||||
generation: u32,
|
||||
inblob: [u8; TSM_INBLOB_MAX],
|
||||
},
|
||||
/// Outblob is generated and up-to-date.
|
||||
///
|
||||
/// The behaviors of the files under a report directory
|
||||
/// are summarized in the table below:
|
||||
///
|
||||
/// | Files | Write behaviors| Read behaviors |
|
||||
/// |--------------|----------------|-----------------|
|
||||
/// | `inblob` | Transist to `InblobProvided` | Error due to WO |
|
||||
/// | `outblob` | Error due to RO| Returns `quote_buf` |
|
||||
/// | `generation` | Error due to RO| Returns `generation`|
|
||||
OutblobGenerated {
|
||||
generation: u32,
|
||||
quote_buf: Box<[u8]>,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct TsmProvider {
|
||||
provider: SysStr,
|
||||
state: TsmProviderState,
|
||||
}
|
||||
|
||||
impl TsmProvider {
|
||||
pub fn new() -> Self {
|
||||
TsmProvider {
|
||||
provider: SysStr::from("tdx_guest"),
|
||||
state: TsmProviderState::InblobNeeded,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct ReportNode {
|
||||
fields: NormalNodeFields<Self>,
|
||||
data: RwMutex<TsmProvider>,
|
||||
}
|
||||
|
||||
impl ReportNode {
|
||||
fn new(name: SysStr) -> Arc<Self> {
|
||||
let mut builder = SysAttrSetBuilder::new();
|
||||
builder.add(SysStr::from("inblob"), SysPerms::DEFAULT_RW_ATTR_PERMS);
|
||||
builder.add(SysStr::from("outblob"), SysPerms::DEFAULT_RO_ATTR_PERMS);
|
||||
builder.add(SysStr::from("provider"), SysPerms::DEFAULT_RO_ATTR_PERMS);
|
||||
builder.add(SysStr::from("generation"), SysPerms::DEFAULT_RO_ATTR_PERMS);
|
||||
let attrs = builder.build().unwrap();
|
||||
|
||||
Arc::new_cyclic(|weak_self| {
|
||||
let fields = NormalNodeFields::new(name, attrs, weak_self.clone());
|
||||
ReportNode {
|
||||
fields,
|
||||
data: RwMutex::new(TsmProvider::new()),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn read_inblob(&self, reader: &mut VmReader, inblob: &mut [u8]) -> Result<usize> {
|
||||
let mut writer = VmWriter::from(&mut inblob[..]);
|
||||
reader
|
||||
.read_fallible(&mut writer)
|
||||
.map_err(|_| Error::AttributeError)
|
||||
}
|
||||
|
||||
fn write_outblob(&self, writer: &mut VmWriter, outblob: &[u8]) -> Result<usize> {
|
||||
let mut reader = VmReader::from(outblob);
|
||||
writer
|
||||
.write_fallible(&mut reader)
|
||||
.map_err(|_| Error::AttributeError)
|
||||
}
|
||||
}
|
||||
|
||||
inherit_sys_leaf_node!(ReportNode, fields, {
|
||||
fn perms(&self) -> SysPerms {
|
||||
SysPerms::DEFAULT_RW_PERMS
|
||||
}
|
||||
|
||||
fn read_attr_at(&self, name: &str, offset: usize, writer: &mut VmWriter) -> Result<usize> {
|
||||
match name {
|
||||
"provider" => {
|
||||
let data = self.data.read();
|
||||
let mut printer = VmPrinter::new_skip(writer, offset);
|
||||
write!(printer, "{}\n", data.provider)?;
|
||||
Ok(printer.bytes_written())
|
||||
}
|
||||
|
||||
"outblob" => {
|
||||
let mut data = self.data.write();
|
||||
match data.state {
|
||||
TsmProviderState::InblobNeeded => Err(Error::InvalidOperation),
|
||||
|
||||
TsmProviderState::InblobProvided {
|
||||
ref inblob,
|
||||
generation,
|
||||
} => match tdx_get_quote(inblob) {
|
||||
Ok(quote) => {
|
||||
let res = self.write_outblob(writer, "e[offset..]);
|
||||
|
||||
// Transition to `OutblobGenerated`
|
||||
data.state = TsmProviderState::OutblobGenerated {
|
||||
generation,
|
||||
quote_buf: quote,
|
||||
};
|
||||
res
|
||||
}
|
||||
Err(_) => Err(Error::InvalidOperation),
|
||||
},
|
||||
|
||||
TsmProviderState::OutblobGenerated { ref quote_buf, .. } => {
|
||||
self.write_outblob(writer, "e_buf[offset..])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
"generation" => {
|
||||
let data = self.data.read();
|
||||
let mut printer = VmPrinter::new_skip(writer, offset);
|
||||
let generation = match data.state {
|
||||
TsmProviderState::InblobNeeded => 0,
|
||||
TsmProviderState::InblobProvided { generation, .. } => generation,
|
||||
TsmProviderState::OutblobGenerated { generation, .. } => generation,
|
||||
};
|
||||
write!(printer, "{}\n", generation)?;
|
||||
Ok(printer.bytes_written())
|
||||
}
|
||||
|
||||
_ => Err(Error::AttributeError),
|
||||
}
|
||||
}
|
||||
|
||||
fn write_attr(&self, name: &str, reader: &mut VmReader) -> Result<usize> {
|
||||
match name {
|
||||
"inblob" => {
|
||||
let mut data = self.data.write();
|
||||
match data.state {
|
||||
TsmProviderState::InblobNeeded => {
|
||||
let mut inblob = [0u8; TSM_INBLOB_MAX];
|
||||
let read_len = self.read_inblob(reader, &mut inblob)?;
|
||||
|
||||
// Transition to `InblobProvided`
|
||||
data.state = TsmProviderState::InblobProvided {
|
||||
generation: 1,
|
||||
inblob,
|
||||
};
|
||||
Ok(read_len)
|
||||
}
|
||||
|
||||
TsmProviderState::InblobProvided {
|
||||
ref mut inblob,
|
||||
ref mut generation,
|
||||
} => {
|
||||
let read_len = self.read_inblob(reader, inblob)?;
|
||||
*generation = generation.wrapping_add(1);
|
||||
Ok(read_len)
|
||||
}
|
||||
|
||||
TsmProviderState::OutblobGenerated {
|
||||
ref mut generation, ..
|
||||
} => {
|
||||
let mut inblob = [0u8; TSM_INBLOB_MAX];
|
||||
let read_len = self.read_inblob(reader, &mut inblob)?;
|
||||
*generation = generation.wrapping_add(1);
|
||||
|
||||
// Transition to `InblobProvided`
|
||||
data.state = TsmProviderState::InblobProvided {
|
||||
generation: *generation,
|
||||
inblob,
|
||||
};
|
||||
Ok(read_len)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_ => Err(Error::AttributeError),
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
pub(super) fn init() {
|
||||
configfs::register_subsystem(Tsm::new()).unwrap();
|
||||
}
|
||||
Loading…
Reference in New Issue