Skip to content

Commit 2e2e1f9

Browse files
authored
Merge pull request #782 from Dstack-TEE/feat/tsm-provider-env-support
feat(attest): TSM provider detection and path overrides
2 parents 913bd61 + 666a27f commit 2e2e1f9

8 files changed

Lines changed: 144 additions & 24 deletions

File tree

dstack/Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dstack/cc-eventlog/src/tcg.rs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,11 @@
77
use crate::{codecs::VecOf, tdx::TdxEvent};
88
use anyhow::{Context, Result};
99
use scale::Decode;
10+
use std::path::PathBuf;
1011

1112
/// The path to boottime ccel file.
1213
const CCEL_FILE: &str = "/sys/firmware/acpi/tables/data/CCEL";
14+
const CCEL_FILE_ENV: &str = "DSTACK_CCEL_FILE";
1315

1416
pub const TPM_ALG_ERROR: u16 = 0x0;
1517
pub const TPM_ALG_RSA: u16 = 0x1;
@@ -300,8 +302,12 @@ impl TcgEventLog {
300302
}
301303

302304
pub fn decode_from_ccel_file() -> Result<Self> {
303-
let data = fs_err::read(CCEL_FILE).context("Failed to read CCEL")?;
304-
Self::decode(&mut data.as_slice())
305+
let path = std::env::var_os(CCEL_FILE_ENV)
306+
.map(PathBuf::from)
307+
.unwrap_or_else(|| PathBuf::from(CCEL_FILE));
308+
let data = fs_err::read(&path)
309+
.with_context(|| format!("failed to read CCEL from {}", path.display()))?;
310+
Self::decode(&mut data.as_slice()).context("failed to decode CCEL")
305311
}
306312

307313
pub fn to_cc_event_log(&self) -> Result<Vec<TdxEvent>> {

dstack/cc-eventlog/src/tdx.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,14 @@ pub fn label_tdx_acpi_data_events(event_logs: &mut [TdxEvent]) {
129129
}
130130
}
131131

132+
/// Decode a raw CCEL byte stream into dstack's TDX event representation.
133+
pub fn decode_ccel(data: &[u8]) -> Result<Vec<TdxEvent>> {
134+
let mut input = data;
135+
let mut event_logs = TcgEventLog::decode(&mut input)?.to_cc_event_log()?;
136+
label_tdx_acpi_data_events(&mut event_logs);
137+
Ok(event_logs)
138+
}
139+
132140
/// Read both boottime and runtime event logs.
133141
pub fn read_event_log() -> Result<Vec<TdxEvent>> {
134142
let mut event_logs = TcgEventLog::decode_from_ccel_file()?.to_cc_event_log()?;
@@ -172,4 +180,12 @@ mod tests {
172180
assert_eq!(events[0].event, "");
173181
assert_eq!(events[4].event, "app-id");
174182
}
183+
184+
#[test]
185+
fn decodes_the_bundled_ccel() {
186+
let events = decode_ccel(include_bytes!("../samples/ccel.bin")).unwrap();
187+
assert!(!events.is_empty());
188+
assert!(events.iter().all(|event| event.imr <= 3));
189+
assert!(events.iter().all(|event| event.digest().len() == 48));
190+
}
175191
}

dstack/dstack-attest/src/attestation.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -280,7 +280,7 @@ fn choose_dstack_attestation_mode(has_tdx: bool, has_sev_snp: bool) -> Result<At
280280
impl AttestationMode {
281281
/// Detect attestation mode from system
282282
pub fn detect() -> Result<Self> {
283-
let has_tdx = std::path::Path::new("/dev/tdx_guest").exists();
283+
let has_tdx = tdx_attest::is_tdx_available();
284284
let has_sev_snp =
285285
std::path::Path::new("/dev/sev-guest").exists() || has_sev_snp_tsm_provider();
286286

dstack/tdx-attest/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,3 +32,4 @@ insta.workspace = true
3232
serde_json.workspace = true
3333
dcap-qvl.workspace = true
3434
tokio = { workspace = true, features = ["rt-multi-thread", "macros"] }
35+
tempfile.workspace = true

dstack/tdx-attest/src/dummy.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,3 +49,7 @@ pub fn get_report(_report_data: &TdxReportData) -> Result<TdxReport> {
4949
pub fn extend_rtmr(_index: u32, _event_type: u32, _digest: [u8; 48]) -> Result<()> {
5050
Err(TdxAttestError::NotSupported)
5151
}
52+
53+
pub fn is_tdx_available() -> bool {
54+
false
55+
}

dstack/tdx-attest/src/linux.rs

Lines changed: 99 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ const CONFIGFS_BASE: &str = "/sys/kernel/config/tsm/report";
3333
const CONFIGFS_DEFAULT: &str = "/sys/kernel/config/tsm/report/com.intel.dcap";
3434
const CONFIGFS_PATH_ENV: &str = "DCAP_TDX_QUOTE_CONFIGFS_PATH";
3535
const RTMR_SYSFS_BASE: &str = "/sys/devices/virtual/misc/tdx_guest/measurements";
36+
const RTMR_SYSFS_PATH_ENV: &str = "DCAP_TDX_RTMR_SYSFS_PATH";
3637
const TDX_ATTEST_CONFIG_PATH: &str = "/etc/tdx-attest.conf";
3738

3839
const TDX_REPORT_DATA_SIZE: usize = 64;
@@ -166,7 +167,7 @@ static CONFIGFS_MKDIR_TRIED: Mutex<bool> = Mutex::new(false);
166167
pub fn get_quote(report_data: &TdxReportData) -> Result<Vec<u8>> {
167168
let _guard = TDX_LOCK.lock().map_err(|_| TdxAttestError::Busy)?;
168169

169-
if is_configfs_available() {
170+
if should_try_configfs() {
170171
return get_quote_via_configfs(report_data);
171172
}
172173

@@ -179,8 +180,35 @@ pub fn get_quote(report_data: &TdxReportData) -> Result<Vec<u8>> {
179180
))
180181
}
181182

182-
fn is_configfs_available() -> bool {
183-
Path::new(CONFIGFS_DEFAULT).is_dir() || Path::new(CONFIGFS_BASE).is_dir()
183+
fn should_try_configfs() -> bool {
184+
// An explicit override is authoritative. Let prepare_configfs() report an
185+
// invalid path instead of silently falling back to another quote method.
186+
std::env::var_os(CONFIGFS_PATH_ENV).is_some()
187+
|| Path::new(CONFIGFS_DEFAULT).is_dir()
188+
|| Path::new(CONFIGFS_BASE).is_dir()
189+
}
190+
191+
/// Return whether a TDX guest provider is available through either the legacy
192+
/// misc device or the standard TSM report interface.
193+
pub fn is_tdx_available() -> bool {
194+
if Path::new(TDX_GUEST_DEVICE).exists() {
195+
return true;
196+
}
197+
198+
if let Some(path) = std::env::var_os(CONFIGFS_PATH_ENV) {
199+
return verify_configfs_provider(Path::new(&path)).unwrap_or(false);
200+
}
201+
202+
if verify_configfs_provider(Path::new(CONFIGFS_DEFAULT)).unwrap_or(false) {
203+
return true;
204+
}
205+
206+
fs::read_dir(CONFIGFS_BASE)
207+
.ok()
208+
.into_iter()
209+
.flatten()
210+
.filter_map(std::result::Result::ok)
211+
.any(|entry| verify_configfs_provider(&entry.path()).unwrap_or(false))
184212
}
185213

186214
fn is_vsock_available() -> bool {
@@ -223,8 +251,8 @@ pub fn extend_rtmr(index: u32, _event_type: u32, digest: [u8; 48]) -> Result<()>
223251
return Err(TdxAttestError::InvalidRtmrIndex(index));
224252
}
225253

226-
if is_rtmr_sysfs_available() {
227-
return extend_rtmr_via_sysfs(index, &digest);
254+
if let Some(base) = rtmr_sysfs_base()? {
255+
return extend_rtmr_via_sysfs(&base, index, &digest);
228256
}
229257

230258
if Path::new(TDX_GUEST_DEVICE).exists() {
@@ -236,24 +264,37 @@ pub fn extend_rtmr(index: u32, _event_type: u32, digest: [u8; 48]) -> Result<()>
236264
))
237265
}
238266

239-
fn is_rtmr_sysfs_available() -> bool {
240-
Path::new(RTMR_SYSFS_BASE).is_dir()
267+
fn rtmr_sysfs_base() -> Result<Option<std::path::PathBuf>> {
268+
if let Some(path) = std::env::var_os(RTMR_SYSFS_PATH_ENV) {
269+
let path = std::path::PathBuf::from(path);
270+
if path.as_os_str().len() < 240 && path.is_dir() {
271+
return Ok(Some(path));
272+
}
273+
return Err(TdxAttestError::NotSupported(format!(
274+
"invalid RTMR sysfs path from {RTMR_SYSFS_PATH_ENV}: {}",
275+
path.display()
276+
)));
277+
}
278+
279+
Ok(Path::new(RTMR_SYSFS_BASE)
280+
.is_dir()
281+
.then(|| std::path::PathBuf::from(RTMR_SYSFS_BASE)))
241282
}
242283

243-
fn extend_rtmr_via_sysfs(index: u32, digest: &[u8; 48]) -> Result<()> {
244-
let path = format!("{}/rtmr{}:sha384", RTMR_SYSFS_BASE, index);
284+
fn extend_rtmr_via_sysfs(base: &Path, index: u32, digest: &[u8; 48]) -> Result<()> {
285+
let path = base.join(format!("rtmr{index}:sha384"));
245286

246287
let mut file = OpenOptions::new()
247288
.write(true)
248289
.open(&path)
249-
.map_err(|e| TdxAttestError::ExtendFailure(format!("open {path}: {e}")))?;
290+
.map_err(|e| TdxAttestError::ExtendFailure(format!("open {}: {e}", path.display())))?;
250291

251292
file.write_all(digest).map_err(|e| match e.raw_os_error() {
252293
Some(libc::EINVAL) => TdxAttestError::InvalidRtmrIndex(index),
253294
Some(libc::EPERM) | Some(libc::EACCES) => {
254295
TdxAttestError::ExtendFailure(format!("permission denied for RTMR {index}"))
255296
}
256-
_ => TdxAttestError::ExtendFailure(format!("write {path}: {e}")),
297+
_ => TdxAttestError::ExtendFailure(format!("write {}: {e}", path.display())),
257298
})
258299
}
259300

@@ -334,7 +375,10 @@ fn get_quote_via_configfs(report_data: &TdxReportData) -> Result<Vec<u8>> {
334375

335376
fn prepare_configfs() -> Result<String> {
336377
if let Ok(path) = std::env::var(CONFIGFS_PATH_ENV) {
337-
if path.len() < 240 && Path::new(&path).is_dir() && verify_configfs_provider(&path)? {
378+
if path.len() < 240
379+
&& Path::new(&path).is_dir()
380+
&& verify_configfs_provider(Path::new(&path))?
381+
{
338382
return Ok(path);
339383
}
340384
return Err(TdxAttestError::NotSupported(format!(
@@ -343,7 +387,7 @@ fn prepare_configfs() -> Result<String> {
343387
}
344388

345389
let default_path = CONFIGFS_DEFAULT;
346-
if Path::new(default_path).is_dir() && verify_configfs_provider(default_path)? {
390+
if Path::new(default_path).is_dir() && verify_configfs_provider(Path::new(default_path))? {
347391
return Ok(default_path.to_string());
348392
}
349393

@@ -369,7 +413,7 @@ fn prepare_configfs() -> Result<String> {
369413
let provider_path = format!("{}/provider", default_path);
370414
for i in 0..5 {
371415
if Path::new(&provider_path).exists() {
372-
if verify_configfs_provider(default_path)? {
416+
if verify_configfs_provider(Path::new(default_path))? {
373417
return Ok(default_path.to_string());
374418
}
375419
break;
@@ -383,10 +427,11 @@ fn prepare_configfs() -> Result<String> {
383427
)))
384428
}
385429

386-
fn verify_configfs_provider(path: &str) -> Result<bool> {
387-
let provider_path = format!("{}/provider", path);
388-
let provider = fs::read_to_string(&provider_path)
389-
.map_err(|e| TdxAttestError::Unexpected(format!("read {provider_path}: {e}")))?;
430+
fn verify_configfs_provider(path: &Path) -> Result<bool> {
431+
let provider_path = path.join("provider");
432+
let provider = fs::read_to_string(&provider_path).map_err(|e| {
433+
TdxAttestError::Unexpected(format!("read {}: {e}", provider_path.display()))
434+
})?;
390435

391436
Ok(provider.trim().starts_with("tdx_guest"))
392437
}
@@ -647,3 +692,39 @@ fn parse_qgs_get_quote_response(data: &[u8]) -> Result<Vec<u8>> {
647692

648693
Ok(quote)
649694
}
695+
696+
#[cfg(test)]
697+
mod tests {
698+
use super::*;
699+
700+
#[test]
701+
fn recognizes_tdx_configfs_provider() {
702+
let temp = tempfile::tempdir().unwrap();
703+
fs::write(temp.path().join("provider"), "tdx_guest_sim\n").unwrap();
704+
assert!(verify_configfs_provider(temp.path()).unwrap());
705+
706+
fs::write(temp.path().join("provider"), "sev_guest\n").unwrap();
707+
assert!(!verify_configfs_provider(temp.path()).unwrap());
708+
}
709+
710+
#[test]
711+
fn invalid_configfs_override_does_not_fall_back() {
712+
let previous = std::env::var_os(CONFIGFS_PATH_ENV);
713+
std::env::set_var(
714+
CONFIGFS_PATH_ENV,
715+
"/definitely/not/a/real/tdx-configfs-provider",
716+
);
717+
718+
let result = get_quote(&[0u8; 64]);
719+
720+
match previous {
721+
Some(value) => std::env::set_var(CONFIGFS_PATH_ENV, value),
722+
None => std::env::remove_var(CONFIGFS_PATH_ENV),
723+
}
724+
assert!(matches!(
725+
result,
726+
Err(TdxAttestError::NotSupported(message))
727+
if message.contains("invalid configfs path from env")
728+
));
729+
}
730+
}

os/common/rootfs/dstack-prepare.sh

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -99,10 +99,21 @@ log "Syncing system time..."
9999
# Let the chronyd correct the system time immediately; keep booting if chronyd is not ready yet.
100100
chronyc makestep || log "Warning: chronyc makestep failed; continuing"
101101

102-
if [[ -e /dev/sev-guest ]] || grep -qw sev_guest /sys/kernel/config/tsm/report/*/provider 2>/dev/null; then
102+
has_tsm_provider() {
103+
local prefix="$1"
104+
local provider value
105+
for provider in /sys/kernel/config/tsm/report/*/provider; do
106+
[[ -r "$provider" ]] || continue
107+
value=$(<"$provider")
108+
[[ "$value" == "$prefix"* ]] && return 0
109+
done
110+
return 1
111+
}
112+
113+
if [[ -e /dev/sev-guest ]] || has_tsm_provider sev_guest; then
103114
log "SEV-SNP guest device/TSM provider detected"
104-
elif [[ -e /dev/tdx_guest ]]; then
105-
log "TDX guest device detected"
115+
elif [[ -e /dev/tdx_guest ]] || has_tsm_provider tdx_guest; then
116+
log "TDX guest device/TSM provider detected"
106117
elif modprobe sev-guest 2>/dev/null; then
107118
log "Loaded sev-guest module"
108119
elif modprobe tdx-guest 2>/dev/null; then

0 commit comments

Comments
 (0)