From e4926f557eb6459927dc4e84a932b5cfe5df3e6b Mon Sep 17 00:00:00 2001 From: Kevin Wang Date: Wed, 15 Jul 2026 19:36:55 -0700 Subject: [PATCH 1/3] fix(attest): require absolute env path overrides Reject empty and relative values for configfs, RTMR, and CCEL path overrides so misconfiguration cannot silently use the process cwd. --- dstack/cc-eventlog/src/tcg.rs | 68 +++++++++++++++++++++++-- dstack/tdx-attest/src/linux.rs | 92 ++++++++++++++++++++++++++++------ 2 files changed, 142 insertions(+), 18 deletions(-) diff --git a/dstack/cc-eventlog/src/tcg.rs b/dstack/cc-eventlog/src/tcg.rs index f41f7b737..23e14a9ca 100644 --- a/dstack/cc-eventlog/src/tcg.rs +++ b/dstack/cc-eventlog/src/tcg.rs @@ -5,7 +5,7 @@ // SPDX-License-Identifier: Apache-2.0 use crate::{codecs::VecOf, tdx::TdxEvent}; -use anyhow::{Context, Result}; +use anyhow::{bail, Context, Result}; use scale::Decode; use std::path::PathBuf; @@ -302,9 +302,7 @@ impl TcgEventLog { } pub fn decode_from_ccel_file() -> Result { - let path = std::env::var_os(CCEL_FILE_ENV) - .map(PathBuf::from) - .unwrap_or_else(|| PathBuf::from(CCEL_FILE)); + let path = ccel_file_path()?; let data = fs_err::read(&path) .with_context(|| format!("failed to read CCEL from {}", path.display()))?; Self::decode(&mut data.as_slice()).context("failed to decode CCEL") @@ -320,6 +318,27 @@ impl TcgEventLog { } } +/// Resolve the CCEL path, honoring `DSTACK_CCEL_FILE` when set. +/// +/// Overrides must be non-empty absolute paths so relative values cannot silently +/// resolve against the process working directory. +fn ccel_file_path() -> Result { + let Some(value) = std::env::var_os(CCEL_FILE_ENV) else { + return Ok(PathBuf::from(CCEL_FILE)); + }; + if value.is_empty() { + bail!("empty path override from {CCEL_FILE_ENV}"); + } + let path = PathBuf::from(value); + if !path.is_absolute() { + bail!( + "path override from {CCEL_FILE_ENV} must be absolute: {}", + path.display() + ); + } + Ok(path) +} + fn parse_spec_id_event_log( input: &mut I, ) -> Result<(TcgEvent, TcgEfiSpecIdEvent)> { @@ -379,3 +398,44 @@ impl TryFrom for TdxEvent { }) } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn ccel_override_rejects_empty_and_relative() { + let previous = std::env::var_os(CCEL_FILE_ENV); + + std::env::set_var(CCEL_FILE_ENV, ""); + assert!(ccel_file_path() + .unwrap_err() + .to_string() + .contains("empty path override")); + + std::env::set_var(CCEL_FILE_ENV, "relative/ccel.bin"); + assert!(ccel_file_path() + .unwrap_err() + .to_string() + .contains("must be absolute")); + + std::env::set_var(CCEL_FILE_ENV, "/abs/ccel.bin"); + assert_eq!(ccel_file_path().unwrap(), PathBuf::from("/abs/ccel.bin")); + + match previous { + Some(value) => std::env::set_var(CCEL_FILE_ENV, value), + None => std::env::remove_var(CCEL_FILE_ENV), + } + } + + #[test] + fn ccel_defaults_without_override() { + let previous = std::env::var_os(CCEL_FILE_ENV); + std::env::remove_var(CCEL_FILE_ENV); + assert_eq!(ccel_file_path().unwrap(), PathBuf::from(CCEL_FILE)); + match previous { + Some(value) => std::env::set_var(CCEL_FILE_ENV, value), + None => std::env::remove_var(CCEL_FILE_ENV), + } + } +} diff --git a/dstack/tdx-attest/src/linux.rs b/dstack/tdx-attest/src/linux.rs index 9820ba792..420142ecc 100644 --- a/dstack/tdx-attest/src/linux.rs +++ b/dstack/tdx-attest/src/linux.rs @@ -12,10 +12,11 @@ //! 2. VSock - via QGS (Quote Generation Service) over vsock //! 3. TDVMCALL - via `/dev/tdx_guest` ioctl (legacy) +use std::ffi::OsStr; use std::fs::{self, File, OpenOptions}; use std::io::{Read, Write}; use std::os::unix::io::AsRawFd; -use std::path::Path; +use std::path::{Path, PathBuf}; use std::sync::Mutex; use std::thread; use std::time::{Duration, Instant}; @@ -188,6 +189,27 @@ fn should_try_configfs() -> bool { || Path::new(CONFIGFS_BASE).is_dir() } +/// Parse an environment path override. +/// +/// Overrides must be non-empty absolute paths so a mis-set relative value like +/// `"."` cannot silently operate on the process working directory. +fn absolute_env_path(var: &str, value: impl AsRef) -> Result { + let value = value.as_ref(); + if value.is_empty() { + return Err(TdxAttestError::NotSupported(format!( + "empty path override from {var}" + ))); + } + let path = PathBuf::from(value); + if !path.is_absolute() { + return Err(TdxAttestError::NotSupported(format!( + "path override from {var} must be absolute: {}", + path.display() + ))); + } + Ok(path) +} + /// Return whether a TDX guest provider is available through either the legacy /// misc device or the standard TSM report interface. pub fn is_tdx_available() -> bool { @@ -195,8 +217,11 @@ pub fn is_tdx_available() -> bool { return true; } - if let Some(path) = std::env::var_os(CONFIGFS_PATH_ENV) { - return verify_configfs_provider(Path::new(&path)).unwrap_or(false); + if let Some(value) = std::env::var_os(CONFIGFS_PATH_ENV) { + let Ok(path) = absolute_env_path(CONFIGFS_PATH_ENV, &value) else { + return false; + }; + return verify_configfs_provider(&path).unwrap_or(false); } if verify_configfs_provider(Path::new(CONFIGFS_DEFAULT)).unwrap_or(false) { @@ -264,9 +289,9 @@ pub fn extend_rtmr(index: u32, _event_type: u32, digest: [u8; 48]) -> Result<()> )) } -fn rtmr_sysfs_base() -> Result> { - if let Some(path) = std::env::var_os(RTMR_SYSFS_PATH_ENV) { - let path = std::path::PathBuf::from(path); +fn rtmr_sysfs_base() -> Result> { + if let Some(value) = std::env::var_os(RTMR_SYSFS_PATH_ENV) { + let path = absolute_env_path(RTMR_SYSFS_PATH_ENV, &value)?; if path.as_os_str().len() < 240 && path.is_dir() { return Ok(Some(path)); } @@ -278,7 +303,7 @@ fn rtmr_sysfs_base() -> Result> { Ok(Path::new(RTMR_SYSFS_BASE) .is_dir() - .then(|| std::path::PathBuf::from(RTMR_SYSFS_BASE))) + .then(|| PathBuf::from(RTMR_SYSFS_BASE))) } fn extend_rtmr_via_sysfs(base: &Path, index: u32, digest: &[u8; 48]) -> Result<()> { @@ -374,15 +399,14 @@ fn get_quote_via_configfs(report_data: &TdxReportData) -> Result> { } fn prepare_configfs() -> Result { - if let Ok(path) = std::env::var(CONFIGFS_PATH_ENV) { - if path.len() < 240 - && Path::new(&path).is_dir() - && verify_configfs_provider(Path::new(&path))? - { - return Ok(path); + if let Ok(value) = std::env::var(CONFIGFS_PATH_ENV) { + let path = absolute_env_path(CONFIGFS_PATH_ENV, &value)?; + if path.as_os_str().len() < 240 && path.is_dir() && verify_configfs_provider(&path)? { + return Ok(path.display().to_string()); } return Err(TdxAttestError::NotSupported(format!( - "invalid configfs path from env: {path}" + "invalid configfs path from env: {}", + path.display() ))); } @@ -727,4 +751,44 @@ mod tests { if message.contains("invalid configfs path from env") )); } + + #[test] + fn absolute_env_path_rejects_empty_and_relative() { + assert!(absolute_env_path("TEST_PATH", "").is_err()); + assert!(absolute_env_path("TEST_PATH", ".").is_err()); + assert!(absolute_env_path("TEST_PATH", "relative/path").is_err()); + assert_eq!( + absolute_env_path("TEST_PATH", "/abs/path").unwrap(), + PathBuf::from("/abs/path") + ); + } + + #[test] + fn relative_configfs_override_is_not_available() { + let previous = std::env::var_os(CONFIGFS_PATH_ENV); + std::env::set_var(CONFIGFS_PATH_ENV, "."); + let available = is_tdx_available(); + match previous { + Some(value) => std::env::set_var(CONFIGFS_PATH_ENV, value), + None => std::env::remove_var(CONFIGFS_PATH_ENV), + } + // relative overrides are ignored; only a real guest device can still pass + assert_eq!(available, Path::new(TDX_GUEST_DEVICE).exists()); + } + + #[test] + fn relative_rtmr_override_is_rejected() { + let previous = std::env::var_os(RTMR_SYSFS_PATH_ENV); + std::env::set_var(RTMR_SYSFS_PATH_ENV, "."); + let result = rtmr_sysfs_base(); + match previous { + Some(value) => std::env::set_var(RTMR_SYSFS_PATH_ENV, value), + None => std::env::remove_var(RTMR_SYSFS_PATH_ENV), + } + assert!(matches!( + result, + Err(TdxAttestError::NotSupported(message)) + if message.contains("must be absolute") + )); + } } From 2cd04148c3af7160e0686dbd8937771eea5e1a5d Mon Sep 17 00:00:00 2001 From: Kevin Wang Date: Thu, 16 Jul 2026 10:47:35 +0800 Subject: [PATCH 2/3] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- dstack/cc-eventlog/src/tcg.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/dstack/cc-eventlog/src/tcg.rs b/dstack/cc-eventlog/src/tcg.rs index 23e14a9ca..1e25d51c2 100644 --- a/dstack/cc-eventlog/src/tcg.rs +++ b/dstack/cc-eventlog/src/tcg.rs @@ -403,8 +403,11 @@ impl TryFrom for TdxEvent { mod tests { use super::*; + static ENV_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(()); + #[test] fn ccel_override_rejects_empty_and_relative() { + let _env_guard = ENV_LOCK.lock().unwrap(); let previous = std::env::var_os(CCEL_FILE_ENV); std::env::set_var(CCEL_FILE_ENV, ""); From 3842bd9f7421fbfb12c7ec7da320adbca77d8ef5 Mon Sep 17 00:00:00 2001 From: Kevin Wang Date: Thu, 16 Jul 2026 10:47:45 +0800 Subject: [PATCH 3/3] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- dstack/cc-eventlog/src/tcg.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/dstack/cc-eventlog/src/tcg.rs b/dstack/cc-eventlog/src/tcg.rs index 1e25d51c2..c6280abd6 100644 --- a/dstack/cc-eventlog/src/tcg.rs +++ b/dstack/cc-eventlog/src/tcg.rs @@ -433,6 +433,7 @@ mod tests { #[test] fn ccel_defaults_without_override() { + let _env_guard = ENV_LOCK.lock().unwrap(); let previous = std::env::var_os(CCEL_FILE_ENV); std::env::remove_var(CCEL_FILE_ENV); assert_eq!(ccel_file_path().unwrap(), PathBuf::from(CCEL_FILE));