VYPR
Moderate severityNVD Advisory· Published Mar 20, 2026· Updated Mar 20, 2026

tar-rs: unpack_in can chmod arbitrary directories by following symlinks

CVE-2026-33056

Description

tar-rs is a tar archive reading/writing library for Rust. In versions 0.4.44 and below, when unpacking a tar archive, the tar crate's unpack_dir function uses fs::metadata() to check whether a path that already exists is a directory. Because fs::metadata() follows symbolic links, a crafted tarball containing a symlink entry followed by a directory entry with the same name causes the crate to treat the symlink target as a valid existing directory — and subsequently apply chmod to it. This allows an attacker to modify the permissions of arbitrary directories outside the extraction root. This issue has been fixed in version 0.4.45.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

In tar-rs 0.4.44 and below, a crafted tarball can trick the library into changing permissions on arbitrary directories outside the extraction root via symlink following.

Vulnerability

Overview

The tar-rs library (a tar archive reader/writer for Rust) in versions up to 0.4.44 contains a symlink-following flaw in its unpack and unpack_in functions. When unpacking a tar archive, the library uses fs::metadata() to check whether a path that already exists is a directory. Because fs::metadata() follows symbolic links, a specially crafted tarball containing a symlink entry and then a directory entry with the same name causes the library to treat the symlink's target as a valid existing directory, leading to a subsequent chmod call affecting the target path. [1][3]

Attack

Vector and Prerequisites

An attacker can craft a tarball that includes a symlink pointing to an arbitrary directory outside the extraction root (e.g., /etc/ssh), followed by a directory entry with the same name. The library then resolves the symlink and applies the directory entry's metadata (file mode) to the symlink target. Exploitation requires only that a victim extracts the malicious archive using an affected version of the library. No authentication or elevated privileges are needed beyond the ability to cause extraction, and the attack can be delivered remotely via email, web download, or similar methods. The CVSS vector string CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:A/VC:N/VI:L/VA:N/SC:N/SI:N/SA:N confirms low attack complexity and no privileges required, but user interaction (extracting the archive) is required. [3]

Impact

An attacker can modify the permissions (chmod) of arbitrary directories already present on the filesystem, outside the intended extraction directory. This integrity violation can lead to unauthorized access or privilege escalation, for instance by making a sensitive directory world-writable. The vulnerability does not directly allow file writing or reading, but permission changes can weaken the security posture of the system. [1]

Mitigation

The issue has been fixed in tar-rs version 0.4.45. Users should update to this patched version immediately. There is no known workaround for the vulnerability, though the advisory notes that using stronger sandboxing (e.g., the cap-std crate or OS-level containerization) is encouraged when processing untrusted archives. The commit implementing the fix (17b1fd84e632071cb8eef9d3709bf347bd266446) improves documentation and adds checks to prevent the symlink-directory collision. [1][2][3]

AI Insight generated on May 18, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
tarcrates.io
< 0.4.450.4.45

Affected products

1
  • alexcrichton/tar-rsv5
    Range: < 0.4.45

Patches

1
17b1fd84e632

archive: Prevent symlink-directory collision chmod attack (#442)

https://github.com/alexcrichton/tar-rsAlex CrichtonMar 19, 2026via ghsa
3 files changed · +75 6
  • src/archive.rs+15 3 modified
    @@ -93,9 +93,21 @@ impl<R: Read> Archive<R> {
         /// extracting each file in turn to the location specified by the entry's
         /// path name.
         ///
    -    /// This operation is relatively sensitive in that it will not write files
    -    /// outside of the path specified by `dst`. Files in the archive which have
    -    /// a '..' in their path are skipped during the unpacking process.
    +    /// # Security
    +    ///
    +    /// A best-effort is made to prevent writing files outside `dst` (paths
    +    /// containing `..` are skipped, symlinks are validated). However, there
    +    /// have been historical bugs in this area, and more may exist. For this
    +    /// reason, when processing untrusted archives, stronger sandboxing is
    +    /// encouraged: e.g. the [`cap-std`] crate and/or OS-level
    +    /// containerization/virtualization.
    +    ///
    +    /// If `dst` does not exist, it is created. Unpacking into an existing
    +    /// directory merges content. This function assumes `dst` is not
    +    /// concurrently modified by untrusted processes. Protecting against
    +    /// TOCTOU races is out of scope for this crate.
    +    ///
    +    /// [`cap-std`]: https://docs.rs/cap-std/
         ///
         /// # Examples
         ///
    
  • src/entry.rs+4 3 modified
    @@ -212,8 +212,9 @@ impl<'a, R: Read> Entry<'a, R> {
         /// also be propagated to the path `dst`. Any existing file at the location
         /// `dst` will be overwritten.
         ///
    -    /// This function carefully avoids writing outside of `dst`. If the file has
    -    /// a '..' in its path, this function will skip it and return false.
    +    /// # Security
    +    ///
    +    /// See [`Archive::unpack`].
         ///
         /// # Examples
         ///
    @@ -446,7 +447,7 @@ impl<'a> EntryFields<'a> {
             // If the directory already exists just let it slide
             fs::create_dir(dst).or_else(|err| {
                 if err.kind() == ErrorKind::AlreadyExists {
    -                let prev = fs::metadata(dst);
    +                let prev = fs::symlink_metadata(dst);
                     if prev.map(|m| m.is_dir()).unwrap_or(false) {
                         return Ok(());
                     }
    
  • tests/entry.rs+56 0 modified
    @@ -408,3 +408,59 @@ fn modify_symlink_just_created() {
             .unwrap();
         assert_eq!(contents.len(), 0);
     }
    +
    +/// Test that unpacking a tarball with a symlink followed by a directory entry
    +/// with the same name does not allow modifying permissions of arbitrary directories
    +/// outside the extraction path.
    +#[test]
    +#[cfg(unix)]
    +fn symlink_dir_collision_does_not_modify_external_dir_permissions() {
    +    use ::std::fs;
    +    use ::std::os::unix::fs::PermissionsExt;
    +
    +    let td = Builder::new().prefix("tar").tempdir().unwrap();
    +
    +    let target_dir = td.path().join("target-dir");
    +    fs::create_dir(&target_dir).unwrap();
    +    fs::set_permissions(&target_dir, fs::Permissions::from_mode(0o700)).unwrap();
    +    let before_mode = fs::metadata(&target_dir).unwrap().permissions().mode() & 0o7777;
    +    assert_eq!(before_mode, 0o700);
    +
    +    let extract_dir = td.path().join("extract-dir");
    +    fs::create_dir(&extract_dir).unwrap();
    +
    +    let mut ar = tar::Builder::new(Vec::new());
    +
    +    let mut header = tar::Header::new_gnu();
    +    header.set_size(0);
    +    header.set_entry_type(tar::EntryType::Symlink);
    +    header.set_path("foo").unwrap();
    +    header.set_link_name(&target_dir).unwrap();
    +    header.set_mode(0o777);
    +    header.set_cksum();
    +    ar.append(&header, &[][..]).unwrap();
    +
    +    let mut header = tar::Header::new_gnu();
    +    header.set_size(0);
    +    header.set_entry_type(tar::EntryType::Directory);
    +    header.set_path("foo").unwrap();
    +    header.set_mode(0o777);
    +    header.set_cksum();
    +    ar.append(&header, &[][..]).unwrap();
    +
    +    let bytes = ar.into_inner().unwrap();
    +    let mut ar = tar::Archive::new(&bytes[..]);
    +
    +    let result = ar.unpack(&extract_dir);
    +    assert!(result.is_err());
    +
    +    let symlink_path = extract_dir.join("foo");
    +    assert!(symlink_path
    +        .symlink_metadata()
    +        .unwrap()
    +        .file_type()
    +        .is_symlink());
    +
    +    let after_mode = fs::metadata(&target_dir).unwrap().permissions().mode() & 0o7777;
    +    assert_eq!(after_mode, 0o700);
    +}
    

Vulnerability mechanics

Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

5

News mentions

0

No linked articles in our index yet.