Skip to content

Commit c86ea90

Browse files
committed
vmm: Prevent path traveral attack
1 parent 010d084 commit c86ea90

File tree

3 files changed

+48
-20
lines changed

3 files changed

+48
-20
lines changed

vmm/rpc/proto/vmm_rpc.proto

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -302,7 +302,7 @@ service Vmm {
302302
rpc BackupDisk(BackupDiskRequest) returns (google.protobuf.Empty);
303303

304304
// List backups for a VM
305-
rpc ListBackups(Id) returns (ListBackupsResponse);
305+
rpc ListBackups(BackupDiskRequest) returns (ListBackupsResponse);
306306

307307
// Delete a backup
308308
rpc DeleteBackup(DeleteBackupRequest) returns (google.protobuf.Empty);

vmm/src/app.rs

Lines changed: 7 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -115,13 +115,6 @@ pub struct App {
115115
state: Arc<Mutex<AppState>>,
116116
}
117117

118-
fn validate_filename(s: &str) -> Result<()> {
119-
if s.contains("/") || s.contains("\\") {
120-
bail!("Invalid filename");
121-
}
122-
Ok(())
123-
}
124-
125118
impl App {
126119
fn lock(&self) -> MutexGuard<AppState> {
127120
self.state.lock().unwrap()
@@ -139,15 +132,12 @@ impl App {
139132
self.config.cvm.backup.path.join(id).join("backups")
140133
}
141134

142-
fn backup_dir(&self, id: &str, backup_id: &str) -> Result<PathBuf> {
143-
validate_filename(backup_id)?;
144-
let backup_dir = self.backups_dir(id).join(backup_id);
145-
Ok(backup_dir)
135+
fn backup_dir(&self, id: &str, backup_id: &str) -> PathBuf {
136+
self.backups_dir(id).join(backup_id)
146137
}
147138

148-
fn backup_file(&self, id: &str, backup_id: &str, snapshot_id: &str) -> Result<PathBuf> {
149-
validate_filename(snapshot_id)?;
150-
Ok(self.backup_dir(id, backup_id)?.join(snapshot_id))
139+
fn backup_file(&self, id: &str, backup_id: &str, snapshot_id: &str) -> PathBuf {
140+
self.backup_dir(id, backup_id).join(snapshot_id)
151141
}
152142

153143
pub fn new(config: Config, supervisor: SupervisorClient) -> Self {
@@ -837,7 +827,7 @@ impl App {
837827
if !self.config.cvm.backup.enabled {
838828
bail!("Backup is not enabled");
839829
}
840-
let backup_dir = self.backup_dir(vm_id, backup_id)?;
830+
let backup_dir = self.backup_dir(vm_id, backup_id);
841831
if !backup_dir.exists() {
842832
bail!("Backup does not exist");
843833
}
@@ -863,7 +853,7 @@ impl App {
863853
bail!("VM is not stopped: status={}", info.status);
864854
}
865855

866-
let backup_file = self.backup_file(vm_id, backup_id, snapshot_id)?;
856+
let backup_file = self.backup_file(vm_id, backup_id, snapshot_id);
867857
if !backup_file.exists() {
868858
bail!("Backup file not found");
869859
}
@@ -873,7 +863,7 @@ impl App {
873863
// Just copy the file
874864
tokio::fs::copy(&backup_file, &hda_img).await?;
875865
} else {
876-
let backup_dir = self.backup_dir(vm_id, backup_id)?;
866+
let backup_dir = self.backup_dir(vm_id, backup_id);
877867
let snapshot_id = snapshot_id.to_string();
878868
// Rename the current hda file to *.bak
879869
let bak_file = hda_img.display().to_string() + ".bak";

vmm/src/main_service.rs

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,30 @@ fn resolve_gpus(gpu_cfg: &rpc::GpuConfig) -> Result<GpuConfig> {
106106
}
107107
}
108108

109+
fn check_path_traversal(input: &str) -> Result<()> {
110+
if input.is_empty() {
111+
bail!("path is empty");
112+
}
113+
114+
if input.len() > 255 {
115+
bail!("path is too long");
116+
}
117+
118+
if input.contains('/') {
119+
bail!("path contains '/'");
120+
}
121+
122+
if input.contains('\0') {
123+
bail!("path contains '\0'");
124+
}
125+
126+
if input == "." || input == ".." {
127+
bail!("path is '.' or '..'");
128+
}
129+
130+
Ok(())
131+
}
132+
109133
impl RpcHandler {
110134
fn resolve_gpus(&self, gpu_cfg: &rpc::GpuConfig) -> Result<GpuConfig> {
111135
let gpus = resolve_gpus(gpu_cfg)?;
@@ -209,6 +233,7 @@ impl VmmRpc for RpcHandler {
209233
}
210234

211235
async fn start_vm(self, request: Id) -> Result<()> {
236+
check_path_traversal(&request.id)?;
212237
self.app
213238
.start_vm(&request.id)
214239
.await
@@ -217,6 +242,7 @@ impl VmmRpc for RpcHandler {
217242
}
218243

219244
async fn stop_vm(self, request: Id) -> Result<()> {
245+
check_path_traversal(&request.id)?;
220246
self.app
221247
.stop_vm(&request.id)
222248
.await
@@ -225,6 +251,7 @@ impl VmmRpc for RpcHandler {
225251
}
226252

227253
async fn remove_vm(self, request: Id) -> Result<()> {
254+
check_path_traversal(&request.id)?;
228255
self.app
229256
.remove_vm(&request.id)
230257
.await
@@ -253,6 +280,7 @@ impl VmmRpc for RpcHandler {
253280
}
254281

255282
async fn upgrade_app(self, request: UpgradeAppRequest) -> Result<Id> {
283+
check_path_traversal(&request.id)?;
256284
let new_id = if !request.compose_file.is_empty() {
257285
// check the compose file is valid
258286
let _app_compose: AppCompose =
@@ -322,6 +350,7 @@ impl VmmRpc for RpcHandler {
322350
}
323351

324352
async fn get_info(self, request: Id) -> Result<GetInfoResponse> {
353+
check_path_traversal(&request.id)?;
325354
if let Some(vm) = self.app.vm_info(&request.id).await? {
326355
Ok(GetInfoResponse {
327356
found: true,
@@ -337,6 +366,7 @@ impl VmmRpc for RpcHandler {
337366

338367
#[tracing::instrument(skip(self, request), fields(id = request.id))]
339368
async fn resize_vm(self, request: ResizeVmRequest) -> Result<()> {
369+
check_path_traversal(&request.id)?;
340370
info!("Resizing VM: {:?}", request);
341371
let vm = self
342372
.app
@@ -397,6 +427,7 @@ impl VmmRpc for RpcHandler {
397427
}
398428

399429
async fn shutdown_vm(self, request: Id) -> Result<()> {
430+
check_path_traversal(&request.id)?;
400431
self.guest_agent_client(&request.id)?.shutdown().await?;
401432
Ok(())
402433
}
@@ -463,21 +494,28 @@ impl VmmRpc for RpcHandler {
463494
}
464495

465496
async fn backup_disk(self, request: BackupDiskRequest) -> Result<()> {
497+
check_path_traversal(&request.vm_id)?;
466498
self.app.backup_disk(&request.vm_id, &request.level).await
467499
}
468500

469-
async fn list_backups(self, request: Id) -> Result<rpc::ListBackupsResponse> {
470-
let backups = self.app.list_backups(&request.id).await?;
501+
async fn list_backups(self, request: BackupDiskRequest) -> Result<rpc::ListBackupsResponse> {
502+
check_path_traversal(&request.vm_id)?;
503+
let backups = self.app.list_backups(&request.vm_id).await?;
471504
Ok(rpc::ListBackupsResponse { backups })
472505
}
473506

474507
async fn delete_backup(self, request: DeleteBackupRequest) -> Result<()> {
508+
check_path_traversal(&request.vm_id)?;
509+
check_path_traversal(&request.backup_id)?;
475510
self.app
476511
.delete_backup(&request.vm_id, &request.backup_id)
477512
.await
478513
}
479514

480515
async fn restore_backup(self, request: RestoreBackupRequest) -> Result<()> {
516+
check_path_traversal(&request.vm_id)?;
517+
check_path_traversal(&request.backup_id)?;
518+
check_path_traversal(&request.snapshot_id)?;
481519
self.app
482520
.restore_backup(&request.vm_id, &request.backup_id, &request.snapshot_id)
483521
.await

0 commit comments

Comments
 (0)