-
Notifications
You must be signed in to change notification settings - Fork 74
Description
The KMS app key endpoint in dstack/kms/src/main_service.rs accepts a caller-supplied app_id path component without sanitizing for directory traversal characters, potentially allowing access to keys belonging to other apps.
Root Cause
The remove_cache admin endpoint joins a user-supplied sub_dir parameter with a parent directory path without sanitizing path traversal sequences (../). While the endpoint requires an admin token, an attacker who has obtained the admin token (e.g., via a timing side-channel) can use this to read or delete arbitrary files on the KMS filesystem.
// main_service.rs:122-137
let path = parent_dir.join(sub_dir); // sub_dir not sanitized
fs::remove_dir_all(&path)?;Attack Path
- Attacker obtains the KMS admin token (e.g., via timing attack on a related vulnerability, or from leaked config)
- Attacker calls
remove_cachewithsub_dir = "../../etc"or similar traversal path - The unsanitized path resolves to a directory outside the intended cache directory
fs::remove_dir_alldeletes the traversed path- Attacker can delete KMS state files, configuration, or root key storage
Impact
Arbitrary file/directory deletion on the KMS filesystem. An attacker with the admin token can destroy KMS state, including root keys, configuration, and cached certificates. This could cause permanent data loss or denial of service for all CVMs depending on the KMS.
Suggested Fix
Sanitize the sub_dir parameter to prevent path traversal. Reject absolute paths and strip non-normal components, then verify the resolved path is within the expected directory:
let sub_path = std::path::Path::new(sub_dir);
if sub_path.is_absolute() {
return Err(Error::InvalidPath);
}
// Strip ".." and other non-normal components
let cleaned: std::path::PathBuf = sub_path
.components()
.filter_map(|c| match c {
std::path::Component::Normal(part) => Some(part),
_ => None,
})
.collect();
let path = parent_dir.join(&cleaned);
let canonical_parent = parent_dir.canonicalize()?;
// Verify resolved path is within the expected directory.
// Use canonical parent + cleaned path as fallback if target doesn't exist yet.
let canonical = path
.canonicalize()
.unwrap_or_else(|_| canonical_parent.join(&cleaned));
if !canonical.starts_with(&canonical_parent) {
return Err(Error::InvalidPath);
}Note: This issue was created automatically. The vulnerability report was generated by Claude and has not been verified by a human.