Skip to content

Commit 39200a6

Browse files
refactor: avoid TOCTOU by holding open output dir
1 parent 4f00abf commit 39200a6

1 file changed

Lines changed: 51 additions & 11 deletions

File tree

crates/wkg/src/main.rs

Lines changed: 51 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
use std::{io::Seek, path::PathBuf};
22

3-
use anyhow::{ensure, Context};
3+
use anyhow::{Context, ensure};
44
use clap::{Args, Parser, Subcommand, ValueEnum};
55
use futures_util::TryStreamExt;
66
use tokio::io::AsyncWriteExt;
77
use tracing::level_filters::LevelFilter;
88
use wasm_pkg_client::{
9-
caching::{CachingClient, FileCache},
109
Client, PublishOpts,
10+
caching::{CachingClient, FileCache},
1111
};
1212
use wasm_pkg_common::{
1313
self,
@@ -311,21 +311,61 @@ impl GetArgs {
311311
tracing::debug!(?release, "Fetched release details");
312312

313313
let output_trailing_slash = self.output.as_os_str().to_string_lossy().ends_with('/');
314-
let parent_dir = if output_trailing_slash {
315-
let parent_dir = self.output.as_path();
316-
if !tokio::fs::try_exists(parent_dir).await? {
317-
tokio::fs::create_dir_all(parent_dir).await
318-
.context("Failed to create output dir")?
314+
// SAFETY: to avoid TOCTOU bugs, we open the directory and keep it open
315+
let _parent_dir: tokio::fs::File;
316+
let parent_dir_path = if output_trailing_slash {
317+
let parent_dir_path = self.output.as_path();
318+
if !tokio::fs::try_exists(parent_dir_path).await? {
319+
tokio::fs::create_dir_all(parent_dir_path)
320+
.await
321+
.context("Failed to create output dir")?;
322+
_parent_dir = tokio::fs::OpenOptions::new()
323+
.read(true)
324+
.write(true)
325+
.create_new(true)
326+
.open(parent_dir_path)
327+
.await
328+
.with_context(|| {
329+
format!("failed to create new dir @ [{}]", parent_dir_path.display())
330+
})?;
331+
parent_dir_path
332+
} else {
333+
_parent_dir = tokio::fs::OpenOptions::new()
334+
.read(true)
335+
.write(true)
336+
.create(false)
337+
.open(parent_dir_path)
338+
.await
339+
.with_context(|| {
340+
format!(
341+
"failed to open existing dir @ [{}]",
342+
parent_dir_path.display()
343+
)
344+
})?;
345+
parent_dir_path
319346
}
320-
parent_dir
321347
} else {
322-
self.output
348+
let parent_dir_path = self
349+
.output
323350
.parent()
324-
.context("Failed to resolve output parent dir")?
351+
.context("Failed to resolve non-trailing-slash output parent dir")?;
352+
_parent_dir = tokio::fs::OpenOptions::new()
353+
.read(true)
354+
.write(true)
355+
.create(false)
356+
.open(parent_dir_path)
357+
.await
358+
.with_context(|| {
359+
format!(
360+
"failed to open existing parent dir @ [{}]",
361+
parent_dir_path.display()
362+
)
363+
})?;
364+
parent_dir_path
325365
};
326366

327367
let (tmp_file, tmp_path) =
328-
tempfile::NamedTempFile::with_prefix_in(".wkg-get", parent_dir)?.into_parts();
368+
tempfile::NamedTempFile::with_prefix_in(".wkg-get", parent_dir_path)?.into_parts();
329369
tracing::debug!(?tmp_path, "Created temporary file");
330370

331371
let mut content_stream = client.get_content(&package, &release).await?;

0 commit comments

Comments
 (0)