Skip to content

Commit

Permalink
attester: add tsm_report module
Browse files Browse the repository at this point in the history
Linux 6.7 added a common ABI for CVMs to provide their attestation
reports. It's based on configfs and the quotes can be generated
using the 'TSM reports'.

Documentation:
https://www.kernel.org/doc/Documentation/ABI/testing/configfs-tsm

Add a tsm_report module that CoCo attesters can use to generate
quotes to be included in their attestation evidence.

Signed-off-by: Mikko Ylinen <[email protected]>
  • Loading branch information
mythi committed Jan 19, 2024
1 parent 178a689 commit 4178539
Show file tree
Hide file tree
Showing 4 changed files with 133 additions and 0 deletions.
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions attestation-agent/attester/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ codicon = { version = "3.0", optional = true }
hyper = { version = "0.14", features = ["full"], optional = true }
hyper-tls = { version = "0.5", optional = true }
tokio = { version = "1", features = ["full"], optional = true }
tempfile = { workspace = true, optional = true }
thiserror.workspace = true

[dev-dependencies]
tokio.workspace = true
Expand All @@ -46,4 +48,5 @@ snp-attester = ["sev"]
csv-attester = ["csv-rs", "codicon", "hyper", "hyper-tls", "tokio"]
cca-attester = ["nix"]

tsm-report = ["tempfile"]
bin = ["tokio/rt", "tokio/macros", "all-attesters"]
3 changes: 3 additions & 0 deletions attestation-agent/attester/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ pub mod snp;
#[cfg(feature = "csv-attester")]
pub mod csv;

#[cfg(feature = "tsm-report")]
pub mod tsm_report;

pub type BoxedAttester = Box<dyn Attester + Send + Sync>;

impl TryFrom<Tee> for BoxedAttester {
Expand Down
125 changes: 125 additions & 0 deletions attestation-agent/attester/src/tsm_report/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
// Copyright (c) 2024 Intel Corporation
//
// SPDX-License-Identifier: Apache-2.0
//

use log::error;
use std::path::{Path, PathBuf};
use tempfile::tempdir_in;
use thiserror::Error;

const TSM_REPORT_PATH: &str = "/sys/kernel/config/tsm/report";
pub const TSM_REPORT_PROVIDER_TDX: &str = "tdx_guest";
pub const TSM_REPORT_PROVIDER_SEV: &str = "sev_guest";

#[derive(Error, Debug)]
pub enum TsmReportError {
#[error("Failed to create TSM Report path: {0}")]
Open(#[from] std::io::Error),
#[error("Failed to access TSM Report attribute: {0}: {1}")]
Access(&'static str, #[source] std::io::Error),
#[error("TSM Report inblob write conflict: generation {0}, expected 1")]
InblobConflict(u32),
#[error("Failed to generate TSM Report: missing inblob (len=0)")]
InblobLen,
}

pub enum TsmReportProvider {
Tdx(Vec<u8>),
Sev(u8, Vec<u8>),
}

pub struct TsmReportPath {
path: PathBuf,
pub provider: String,
}

impl TsmReportPath {
pub fn open() -> Result<Self, TsmReportError> {
let p = tempdir_in(TSM_REPORT_PATH).map_err(TsmReportError::Open)?;

let path = p.into_path();

let provider = match std::fs::read_to_string(path.as_path().join("provider")) {
Ok(tsm) => tsm.trim_matches('\n').to_string(),
Err(e) => {
let _ = std::fs::remove_dir(path.as_path())
.map_err(|r| log::error!("Failed to remove TSM Report directory: {}", r));
return Err(TsmReportError::Access("provider", e));
}
};

Ok(Self { path, provider })
}
pub fn close(&self) {
let _ = std::fs::remove_dir(self.path.as_path())
.map_err(|e| log::error!("Failed to remove TSM Report directory: {}", e));
}
pub fn attestation_report(
&self,
provider: TsmReportProvider,
) -> Result<Vec<u8>, TsmReportError> {
let report_path = self.path.as_path();

let report_data = match provider {
TsmReportProvider::Tdx(inblob) => inblob,
TsmReportProvider::Sev(privlevel, inblob) => {
// TODO: untested
std::fs::write(report_path.join("privlevel"), vec![privlevel])
.map_err(|e| TsmReportError::Access("privlevel", e))?;
inblob
}
};

if report_data.is_empty() {
return Err(TsmReportError::InblobLen);
}

std::fs::write(report_path.join("inblob"), report_data)
.map_err(|e| TsmReportError::Access("inblob", e))?;

let q = std::fs::read(report_path.join("outblob"))
.map_err(|e| TsmReportError::Access("outblob", e))?;

check_inblob_write_race(report_path)?;

Ok(q)
}
pub fn supplemental_data(&self) -> Result<Vec<u8>, TsmReportError> {
let report_path = self.path.as_path();

let aux = std::fs::read(report_path.join("auxblob"))
.map_err(|e| TsmReportError::Access("auxblob", e))?;

check_inblob_write_race(report_path)?;

Ok(aux)
}
}

fn check_inblob_write_race(report_path: &std::path::Path) -> Result<(), TsmReportError> {
let g = std::fs::read_to_string(report_path.join("generation"))
.map_err(|e| TsmReportError::Access("generation", e))?;

let generation = g.trim_matches('\n').to_string().parse::<u32>().unwrap();

if generation > 1 {
return Err(TsmReportError::InblobConflict(generation));
}

Ok(())
}

pub fn provider_is(wanted: &str) -> bool {
if !Path::new(TSM_REPORT_PATH).exists() {
return false;
}

match TsmReportPath::open() {
Ok(report_path) => {
report_path.close();
report_path.provider == wanted
}
Err(_) => false,
}
}

0 comments on commit 4178539

Please sign in to comment.