Skip to content

Commit 0e7de76

Browse files
committed
fix(verifier): bound the measurement cache directory
1 parent 9db0b6f commit 0e7de76

1 file changed

Lines changed: 73 additions & 0 deletions

File tree

‎dstack/verifier/src/verification.rs‎

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,10 @@ struct CachedMeasurement {
101101
measurements: TdxMeasurements,
102102
}
103103

104+
/// Cap on `<image_cache_dir>/measurements/` (~400 B per entry). Entries never go
105+
/// stale, but a caller varying the VM shape adds one per shape.
106+
const MEASUREMENT_CACHE_MAX_ENTRIES: usize = 1024;
107+
104108
struct ImagePaths {
105109
image_dir: PathBuf,
106110
fw_path: PathBuf,
@@ -224,6 +228,39 @@ impl CvmVerifier {
224228
)
225229
})?;
226230
debug!("Stored measurement cache entry {}", cache_key);
231+
if let Err(e) = Self::prune_measurement_cache(&cache_dir, MEASUREMENT_CACHE_MAX_ENTRIES) {
232+
warn!("failed to prune the measurement cache: {e:?}");
233+
}
234+
Ok(())
235+
}
236+
237+
/// Best effort: drop the oldest `.json` entries (not in-flight temp files)
238+
/// until at most `max_entries` remain.
239+
fn prune_measurement_cache(cache_dir: &Path, max_entries: usize) -> Result<()> {
240+
let mut entries = Vec::new();
241+
for entry in fs_err::read_dir(cache_dir).context("failed to read measurement cache")? {
242+
let entry = entry.context("failed to read measurement cache entry")?;
243+
let path = entry.path();
244+
if path.extension() != Some(OsStr::new("json")) {
245+
continue;
246+
}
247+
let modified = entry
248+
.metadata()
249+
.and_then(|metadata| metadata.modified())
250+
.unwrap_or(std::time::UNIX_EPOCH);
251+
entries.push((modified, path));
252+
}
253+
if entries.len() <= max_entries {
254+
return Ok(());
255+
}
256+
let excess = entries.len() - max_entries;
257+
entries.sort();
258+
for (_, path) in entries.iter().take(excess) {
259+
if let Err(e) = fs_err::remove_file(path) {
260+
debug!("failed to evict measurement cache entry: {e:?}");
261+
}
262+
}
263+
debug!("evicted {excess} measurement cache entries");
227264
Ok(())
228265
}
229266

@@ -1636,6 +1673,42 @@ mod tests {
16361673
.is_none());
16371674
}
16381675

1676+
#[test]
1677+
fn measurement_cache_evicts_the_oldest_entries_and_spares_temporaries() {
1678+
let dir = tempfile::tempdir().expect("temp cache dir");
1679+
let cache_dir = dir.path();
1680+
let base = std::time::UNIX_EPOCH + Duration::from_secs(1_700_000_000);
1681+
for index in 0..8u64 {
1682+
let path = cache_dir.join(format!("{index:02}.json"));
1683+
fs_err::write(&path, b"{}").unwrap();
1684+
let file = fs_err::File::open(&path).unwrap();
1685+
file.set_times(
1686+
std::fs::FileTimes::new().set_modified(base + Duration::from_secs(index)),
1687+
)
1688+
.unwrap();
1689+
}
1690+
// An in-flight `NamedTempFile`: no extension until `persist`.
1691+
let in_flight = cache_dir.join(".tmpABCDEF");
1692+
fs_err::write(&in_flight, b"partial").unwrap();
1693+
1694+
CvmVerifier::prune_measurement_cache(cache_dir, 3).unwrap();
1695+
1696+
let mut remaining: Vec<String> = fs_err::read_dir(cache_dir)
1697+
.unwrap()
1698+
.map(|entry| entry.unwrap().file_name().to_string_lossy().into_owned())
1699+
.collect();
1700+
remaining.sort();
1701+
assert_eq!(
1702+
remaining,
1703+
vec![
1704+
".tmpABCDEF".to_string(),
1705+
"05.json".to_string(),
1706+
"06.json".to_string(),
1707+
"07.json".to_string(),
1708+
]
1709+
);
1710+
}
1711+
16391712
#[test]
16401713
fn concurrent_measurement_cache_writes_are_atomic() {
16411714
let directory = tempfile::tempdir().unwrap();

0 commit comments

Comments
 (0)