mirror of https://github.com/astral-sh/uv
Use Astral-maintained `tokio-tar` fork (#11174)
## Summary I shipped one security fix here along with several significant performance improvements for large TAR files: - https://github.com/astral-sh/tokio-tar/pull/2 - https://github.com/astral-sh/tokio-tar/pull/4 - https://github.com/astral-sh/tokio-tar/pull/5 I also PR'd the security fix to `edera-dev` (https://github.com/edera-dev/tokio-tar/pull/4).
This commit is contained in:
parent
56684e4c24
commit
7b43baf251
|
|
@ -153,6 +153,22 @@ dependencies = [
|
||||||
"tempfile",
|
"tempfile",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "astral-tokio-tar"
|
||||||
|
version = "0.4.2"
|
||||||
|
source = "git+https://github.com/astral-sh/tokio-tar?rev=c06006a2cf6a6ca42e11775ddf1502dee8a8c688#c06006a2cf6a6ca42e11775ddf1502dee8a8c688"
|
||||||
|
dependencies = [
|
||||||
|
"filetime",
|
||||||
|
"futures-core",
|
||||||
|
"libc",
|
||||||
|
"portable-atomic",
|
||||||
|
"redox_syscall 0.3.5",
|
||||||
|
"rustc-hash",
|
||||||
|
"tokio",
|
||||||
|
"tokio-stream",
|
||||||
|
"xattr",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "async-channel"
|
name = "async-channel"
|
||||||
version = "2.3.1"
|
version = "2.3.1"
|
||||||
|
|
@ -1979,22 +1995,6 @@ dependencies = [
|
||||||
"windows-sys 0.52.0",
|
"windows-sys 0.52.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "krata-tokio-tar"
|
|
||||||
version = "0.4.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "e8bd5fee9b96acb5fc36b401896d601e6fdcce52b0e651ce24a3b21fb524e79f"
|
|
||||||
dependencies = [
|
|
||||||
"filetime",
|
|
||||||
"futures-core",
|
|
||||||
"libc",
|
|
||||||
"portable-atomic",
|
|
||||||
"redox_syscall 0.3.5",
|
|
||||||
"tokio",
|
|
||||||
"tokio-stream",
|
|
||||||
"xattr",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "kurbo"
|
name = "kurbo"
|
||||||
version = "0.8.3"
|
version = "0.8.3"
|
||||||
|
|
@ -5060,11 +5060,11 @@ dependencies = [
|
||||||
name = "uv-extract"
|
name = "uv-extract"
|
||||||
version = "0.0.1"
|
version = "0.0.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"astral-tokio-tar",
|
||||||
"async-compression",
|
"async-compression",
|
||||||
"async_zip",
|
"async_zip",
|
||||||
"fs-err 3.1.0",
|
"fs-err 3.1.0",
|
||||||
"futures",
|
"futures",
|
||||||
"krata-tokio-tar",
|
|
||||||
"md-5",
|
"md-5",
|
||||||
"rayon",
|
"rayon",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
|
|
@ -5344,6 +5344,7 @@ dependencies = [
|
||||||
name = "uv-publish"
|
name = "uv-publish"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"astral-tokio-tar",
|
||||||
"async-compression",
|
"async-compression",
|
||||||
"base64 0.22.1",
|
"base64 0.22.1",
|
||||||
"fs-err 3.1.0",
|
"fs-err 3.1.0",
|
||||||
|
|
@ -5351,7 +5352,6 @@ dependencies = [
|
||||||
"glob",
|
"glob",
|
||||||
"insta",
|
"insta",
|
||||||
"itertools 0.14.0",
|
"itertools 0.14.0",
|
||||||
"krata-tokio-tar",
|
|
||||||
"reqwest",
|
"reqwest",
|
||||||
"reqwest-middleware",
|
"reqwest-middleware",
|
||||||
"reqwest-retry",
|
"reqwest-retry",
|
||||||
|
|
|
||||||
|
|
@ -73,6 +73,7 @@ uv-workspace = { path = "crates/uv-workspace" }
|
||||||
anstream = { version = "0.6.15" }
|
anstream = { version = "0.6.15" }
|
||||||
anyhow = { version = "1.0.89" }
|
anyhow = { version = "1.0.89" }
|
||||||
arcstr = { version = "1.2.0" }
|
arcstr = { version = "1.2.0" }
|
||||||
|
astral-tokio-tar = { git = "https://github.com/astral-sh/tokio-tar", rev = "c06006a2cf6a6ca42e11775ddf1502dee8a8c688" }
|
||||||
async-channel = { version = "2.3.1" }
|
async-channel = { version = "2.3.1" }
|
||||||
async-compression = { version = "0.4.12", features = ["bzip2", "gzip", "xz", "zstd"] }
|
async-compression = { version = "0.4.12", features = ["bzip2", "gzip", "xz", "zstd"] }
|
||||||
async-trait = { version = "0.1.82" }
|
async-trait = { version = "0.1.82" }
|
||||||
|
|
@ -118,7 +119,6 @@ indoc = { version = "2.0.5" }
|
||||||
itertools = { version = "0.14.0" }
|
itertools = { version = "0.14.0" }
|
||||||
jiff = { version = "0.1.14", features = ["serde"] }
|
jiff = { version = "0.1.14", features = ["serde"] }
|
||||||
junction = { version = "1.2.0" }
|
junction = { version = "1.2.0" }
|
||||||
krata-tokio-tar = { version = "0.4.2" }
|
|
||||||
mailparse = { version = "0.15.0" }
|
mailparse = { version = "0.15.0" }
|
||||||
md-5 = { version = "0.10.6" }
|
md-5 = { version = "0.10.6" }
|
||||||
memchr = { version = "2.7.4" }
|
memchr = { version = "2.7.4" }
|
||||||
|
|
|
||||||
|
|
@ -20,11 +20,11 @@ uv-configuration = { workspace = true }
|
||||||
uv-distribution-filename = { workspace = true }
|
uv-distribution-filename = { workspace = true }
|
||||||
uv-pypi-types = { workspace = true }
|
uv-pypi-types = { workspace = true }
|
||||||
|
|
||||||
|
astral-tokio-tar = { workspace = true }
|
||||||
async-compression = { workspace = true, features = ["bzip2", "gzip", "zstd", "xz"] }
|
async-compression = { workspace = true, features = ["bzip2", "gzip", "zstd", "xz"] }
|
||||||
async_zip = { workspace = true }
|
async_zip = { workspace = true }
|
||||||
fs-err = { workspace = true, features = ["tokio"] }
|
fs-err = { workspace = true, features = ["tokio"] }
|
||||||
futures = { workspace = true }
|
futures = { workspace = true }
|
||||||
krata-tokio-tar = { workspace = true }
|
|
||||||
md-5 = { workspace = true }
|
md-5 = { workspace = true }
|
||||||
rayon = { workspace = true }
|
rayon = { workspace = true }
|
||||||
reqwest = { workspace = true }
|
reqwest = { workspace = true }
|
||||||
|
|
|
||||||
|
|
@ -5,5 +5,4 @@ mod error;
|
||||||
pub mod hash;
|
pub mod hash;
|
||||||
pub mod stream;
|
pub mod stream;
|
||||||
mod sync;
|
mod sync;
|
||||||
mod tar;
|
|
||||||
mod vendor;
|
mod vendor;
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ use std::pin::Pin;
|
||||||
|
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
use rustc_hash::FxHashSet;
|
use rustc_hash::FxHashSet;
|
||||||
|
use tokio_tar::EntryType;
|
||||||
use tokio_util::compat::{FuturesAsyncReadCompatExt, TokioAsyncReadCompatExt};
|
use tokio_util::compat::{FuturesAsyncReadCompatExt, TokioAsyncReadCompatExt};
|
||||||
use tracing::warn;
|
use tracing::warn;
|
||||||
|
|
||||||
|
|
@ -143,6 +144,16 @@ async fn untar_in(
|
||||||
mut archive: tokio_tar::Archive<&'_ mut (dyn tokio::io::AsyncRead + Unpin)>,
|
mut archive: tokio_tar::Archive<&'_ mut (dyn tokio::io::AsyncRead + Unpin)>,
|
||||||
dst: &Path,
|
dst: &Path,
|
||||||
) -> std::io::Result<()> {
|
) -> std::io::Result<()> {
|
||||||
|
// Like `tokio-tar`, canonicalize the destination prior to unpacking.
|
||||||
|
let dst = fs_err::tokio::canonicalize(dst).await?;
|
||||||
|
|
||||||
|
// Memoize filesystem calls to canonicalize paths.
|
||||||
|
let mut memo = FxHashSet::default();
|
||||||
|
|
||||||
|
// Delay any directory entries until the end, to ensure that directory permissions do not
|
||||||
|
// interfere with descendant extraction.
|
||||||
|
let mut directories = Vec::new();
|
||||||
|
|
||||||
let mut entries = archive.entries()?;
|
let mut entries = archive.entries()?;
|
||||||
let mut pinned = Pin::new(&mut entries);
|
let mut pinned = Pin::new(&mut entries);
|
||||||
while let Some(entry) = pinned.next().await {
|
while let Some(entry) = pinned.next().await {
|
||||||
|
|
@ -159,7 +170,15 @@ async fn untar_in(
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
file.unpack_in(dst).await?;
|
// Defer the creation of any directory entries.
|
||||||
|
if file.header().entry_type() == EntryType::Directory {
|
||||||
|
directories.push(file);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unpack the file into the destination directory.
|
||||||
|
#[cfg_attr(not(unix), allow(unused_variables))]
|
||||||
|
let unpacked_at = file.unpack_in_memo(&dst, &mut memo).await?;
|
||||||
|
|
||||||
// Preserve the executable bit.
|
// Preserve the executable bit.
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
|
|
@ -172,7 +191,7 @@ async fn untar_in(
|
||||||
let mode = file.header().mode()?;
|
let mode = file.header().mode()?;
|
||||||
let has_any_executable_bit = mode & 0o111;
|
let has_any_executable_bit = mode & 0o111;
|
||||||
if has_any_executable_bit != 0 {
|
if has_any_executable_bit != 0 {
|
||||||
if let Some(path) = crate::tar::unpacked_at(dst, &file.path()?) {
|
if let Some(path) = unpacked_at.as_deref() {
|
||||||
let permissions = fs_err::tokio::metadata(&path).await?.permissions();
|
let permissions = fs_err::tokio::metadata(&path).await?.permissions();
|
||||||
if permissions.mode() & 0o111 != 0o111 {
|
if permissions.mode() & 0o111 != 0o111 {
|
||||||
fs_err::tokio::set_permissions(
|
fs_err::tokio::set_permissions(
|
||||||
|
|
@ -186,6 +205,13 @@ async fn untar_in(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create any deferred directories in topological order.
|
||||||
|
directories.sort_by(|a, b| b.path_bytes().cmp(&a.path_bytes()));
|
||||||
|
for mut dir in directories {
|
||||||
|
dir.unpack_in_memo(&dst, &mut memo).await?;
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,40 +0,0 @@
|
||||||
use std::path::{Component, Path, PathBuf};
|
|
||||||
|
|
||||||
/// Determine the path at which the given tar entry will be unpacked, when unpacking into `dst`.
|
|
||||||
///
|
|
||||||
/// See: <https://github.com/vorot93/tokio-tar/blob/87338a76092330bc6fe60de95d83eae5597332e1/src/entry.rs#L418>
|
|
||||||
#[cfg_attr(not(unix), allow(dead_code))]
|
|
||||||
pub(crate) fn unpacked_at(dst: &Path, entry: &Path) -> Option<PathBuf> {
|
|
||||||
let mut file_dst = dst.to_path_buf();
|
|
||||||
{
|
|
||||||
for part in entry.components() {
|
|
||||||
match part {
|
|
||||||
// Leading '/' characters, root paths, and '.'
|
|
||||||
// components are just ignored and treated as "empty
|
|
||||||
// components"
|
|
||||||
Component::Prefix(..) | Component::RootDir | Component::CurDir => {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If any part of the filename is '..', then skip over
|
|
||||||
// unpacking the file to prevent directory traversal
|
|
||||||
// security issues. See, e.g.: CVE-2001-1267,
|
|
||||||
// CVE-2002-0399, CVE-2005-1918, CVE-2007-4131
|
|
||||||
Component::ParentDir => return None,
|
|
||||||
|
|
||||||
Component::Normal(part) => file_dst.push(part),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Skip cases where only slashes or '.' parts were seen, because
|
|
||||||
// this is effectively an empty filename.
|
|
||||||
if *dst == *file_dst {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Skip entries without a parent (i.e. outside of FS root)
|
|
||||||
file_dst.parent()?;
|
|
||||||
|
|
||||||
Some(file_dst)
|
|
||||||
}
|
|
||||||
|
|
@ -25,13 +25,13 @@ uv-pypi-types = { workspace = true }
|
||||||
uv-static = { workspace = true }
|
uv-static = { workspace = true }
|
||||||
uv-warnings = { workspace = true }
|
uv-warnings = { workspace = true }
|
||||||
|
|
||||||
|
astral-tokio-tar = { workspace = true }
|
||||||
async-compression = { workspace = true }
|
async-compression = { workspace = true }
|
||||||
base64 = { workspace = true }
|
base64 = { workspace = true }
|
||||||
fs-err = { workspace = true }
|
fs-err = { workspace = true }
|
||||||
futures = { workspace = true }
|
futures = { workspace = true }
|
||||||
glob = { workspace = true }
|
glob = { workspace = true }
|
||||||
itertools = { workspace = true }
|
itertools = { workspace = true }
|
||||||
krata-tokio-tar = { workspace = true }
|
|
||||||
reqwest = { workspace = true }
|
reqwest = { workspace = true }
|
||||||
reqwest-middleware = { workspace = true }
|
reqwest-middleware = { workspace = true }
|
||||||
reqwest-retry = { workspace = true }
|
reqwest-retry = { workspace = true }
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue