tar-rs incorrectly ignores PAX size headers if header size is nonzero
Description
tar-rs is a tar archive reading/writing library for Rust. Versions 0.4.44 and below have conditional logic that skips the PAX size header in cases where the base header size is nonzero. As part of CVE-2025-62518, the astral-tokio-tar project was changed to correctly honor PAX size headers in the case where it was different from the base header. This is almost the inverse of the astral-tokio-tar issue. Any discrepancy in how tar parsers honor file size can be used to create archives that appear differently when unpacked by different archivers. In this case, the tar-rs (Rust tar) crate is an outlier in checking for the header size - other tar parsers (including e.g. Go archive/tar) unconditionally use the PAX size override. This can affect anything that uses the tar crate to parse archives and expects to have a consistent view with other parsers. This issue has been fixed in version 0.4.45.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
tar-rs crate versions ≤0.4.44 ignore PAX size headers when the base header size is nonzero, enabling archive differentials that can hide or expose files across parsers.
Vulnerability
The Rust tar crate (tar-rs) versions 0.4.44 and below contain a conditional logic flaw in PAX extended header handling. When a tar entry has both a base header size field and a PAX extended header that overrides the size, the crate incorrectly skips the PAX size field is nonzero. This is the inverse of the issue addressed in CVE-2025-62518 for the astral-tokio-tar project [1][3]. Most other tar parsers, including Go's archive/tar, unconditionally honor the PAX size override [1].
Exploitation
An attacker can craft a malicious tar archive where a PAX extended header declares a larger file size than the base header. The inflated region can contain hidden entries (e.g., symlinks or other files) that are skipped by tar-rs but would be extracted by other parsers that correctly honor the PAX size [2]. The attack requires no authentication and can be delivered over the network, though user interaction (e.g., extracting an archive) is needed [4].
Impact
This discrepancy creates a "tar differential" that can be used to smuggle content past tar-rs-based scanners or validators while remaining visible to other tools. For example, a symlink hidden in the inflated region would be ignored by tar-rs but extracted by a compliant parser, potentially leading to file overwrites or path traversal [2][3]. The CVSS score is 5.1 (Medium) with low confidentiality and integrity impacts [4].
Mitigation
The issue is fixed in tar-rs version 0.4.45, which unconditionally honors the PAX size header [1][2]. Users should update to this version or later. No workarounds are documented; the crate is widely used in the Rust ecosystem, including indirectly through tools like Cargo (though crates.io rejects symlinks and hard links, limiting direct exploitation there) [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 products
2- alexcrichton/tar-rsv5Range: < 0.4.45
Patches
1de1a5870e603archive: Unconditionally honor PAX size (#441)
3 files changed · +159 −4
Cargo.toml+3 −0 modified@@ -23,8 +23,11 @@ contents are never required to be entirely resident in memory all at once. filetime = "0.2.8" [dev-dependencies] +astral-tokio-tar = "0.5" rand = { version = "0.8", features = ["small_rng"] } tempfile = "3" +tokio = { version = "1", features = ["macros", "rt"] } +tokio-stream = "0.1" [target."cfg(unix)".dependencies] xattr = { version = "1.1.3", optional = true }
src/archive.rs+5 −4 modified@@ -337,10 +337,11 @@ impl<'a> EntriesFields<'a> { let file_pos = self.next; let mut size = header.entry_size()?; - if size == 0 { - if let Some(pax_size) = pax_size { - size = pax_size; - } + // If this exists, it must override the header size. Disagreement among + // parsers allows construction of malicious archives that appear different + // when parsed. + if let Some(pax_size) = pax_size { + size = pax_size; } let ret = EntryFields { size,
tests/all.rs+151 −0 modified@@ -1871,3 +1871,154 @@ fn append_data_error_does_not_corrupt_subsequent_entries() { assert_eq!(entries.len(), 1); assert_eq!(entries[0].path().unwrap().to_str().unwrap(), "clean.txt"); } + +/// Build the PAX size smuggling archive described in the original report. +/// +/// A PAX extended header declares `size=2048` for a regular file whose +/// actual header size field is 8. A symlink entry is hidden inside the +/// inflated region. A correct parser honours the PAX size and skips over +/// the symlink; a buggy one reads only the header size and exposes it. +fn build_pax_smuggle_archive() -> Vec<u8> { + const B: usize = 512; + const INFLATED: usize = 2048; + let end_of_archive = || std::iter::repeat(0u8).take(B * 2); + + let mut ar: Vec<u8> = Vec::new(); + + // PAX extended header declaring size=2048 for the next entry. + let pax_rec = format!("13 size={INFLATED}\n"); + let mut pax_hdr = Header::new_ustar(); + pax_hdr.set_path("./PaxHeaders/regular").unwrap(); + pax_hdr.set_size(pax_rec.as_bytes().len() as u64); + pax_hdr.set_entry_type(EntryType::XHeader); + pax_hdr.set_cksum(); + ar.extend_from_slice(pax_hdr.as_bytes()); + ar.extend_from_slice(pax_rec.as_bytes()); + ar.resize(ar.len().next_multiple_of(B), 0); + + // Regular file whose header says size=8, but PAX says 2048. + let content = b"regular\n"; + let mut file_hdr = Header::new_ustar(); + file_hdr.set_path("regular.txt").unwrap(); + file_hdr.set_size(content.len() as u64); + file_hdr.set_entry_type(EntryType::Regular); + file_hdr.set_cksum(); + ar.extend_from_slice(file_hdr.as_bytes()); + let mark = ar.len(); + ar.extend_from_slice(content); + ar.resize(ar.len().next_multiple_of(B), 0); + + // Smuggled symlink hidden in the inflated region. + let mut sym_hdr = Header::new_ustar(); + sym_hdr.set_path("smuggled").unwrap(); + sym_hdr.set_size(0); + sym_hdr.set_entry_type(EntryType::Symlink); + sym_hdr.set_link_name("/etc/shadow").unwrap(); + sym_hdr.set_cksum(); + ar.extend_from_slice(sym_hdr.as_bytes()); + ar.extend(end_of_archive()); + + // Pad to fill the inflated window. + let used = ar.len() - mark; + let pad = INFLATED.saturating_sub(used); + ar.extend(std::iter::repeat(0u8).take(pad.next_multiple_of(B))); + + // End-of-archive. + ar.extend(end_of_archive()); + ar +} + +/// Regression test for PAX size smuggling. +/// +/// A crafted archive uses a PAX extended header to declare a file size (2048) +/// larger than the header's octal size field (8). Before the fix, `tar-rs` +/// only applied the PAX size override when the header size was 0, so it would +/// read the small header size, advance too little, and expose a symlink entry +/// hidden in the "padding" area. After the fix, the PAX size unconditionally +/// overrides the header size, causing the parser to skip over the smuggled +/// symlink — matching the behavior of compliant parsers. +#[test] +fn pax_size_smuggled_symlink() { + let data = build_pax_smuggle_archive(); + + let mut archive = Archive::new(random_cursor_reader(&data[..])); + let entries: Vec<_> = archive + .entries() + .unwrap() + .map(|e| { + let e = e.unwrap(); + let path = e.path().unwrap().to_path_buf(); + let kind = e.header().entry_type(); + let link = e.link_name().unwrap().map(|l| l.to_path_buf()); + (path, kind, link) + }) + .collect(); + + // With the fix applied, only "regular.txt" should be visible. + // The smuggled symlink must NOT appear. + let expected: Vec<(PathBuf, EntryType, Option<PathBuf>)> = + vec![(PathBuf::from("regular.txt"), EntryType::Regular, None)]; + assert_eq!( + entries, expected, + "smuggled symlink visible or unexpected entries\n\ + got: {entries:?}" + ); +} + +/// Cross-validate that `tar` and `astral-tokio-tar` parse the PAX size +/// smuggling archive identically, guarding against parsing differentials. +#[tokio::test] +async fn pax_size_smuggle_matches_astral_tokio_tar() { + use tokio_stream::StreamExt; + + let data = build_pax_smuggle_archive(); + + // Parse with sync tar. + let sync_entries: Vec<_> = { + let mut ar = Archive::new(&data[..]); + ar.entries() + .unwrap() + .map(|e| { + let e = e.unwrap(); + let path = e.path().unwrap().to_path_buf(); + let kind = e.header().entry_type(); + let link = e.link_name().unwrap().map(|l| l.to_path_buf()); + (path, kind, link) + }) + .collect() + }; + + // Parse with async astral-tokio-tar. + let async_entries: Vec<_> = { + let mut ar = tokio_tar::Archive::new(&data[..]); + let mut entries = ar.entries().unwrap(); + let mut result = Vec::new(); + while let Some(e) = entries.next().await { + let e = e.unwrap(); + let entry_type = e.header().entry_type(); + result.push(( + e.path().unwrap().to_path_buf(), + // Map through the raw byte so the two crates' EntryTypes compare. + EntryType::new(entry_type.as_byte()), + e.link_name().unwrap().map(|l| l.to_path_buf()), + )); + } + result + }; + + // Assert exact expected content for both parsers independently, + // so we verify correctness — not just mutual agreement. + let expected: Vec<(PathBuf, EntryType, Option<PathBuf>)> = + vec![(PathBuf::from("regular.txt"), EntryType::Regular, None)]; + + assert_eq!( + sync_entries, expected, + "tar-rs produced unexpected entries (smuggled symlink visible?)\n\ + got: {sync_entries:?}" + ); + assert_eq!( + async_entries, expected, + "astral-tokio-tar produced unexpected entries (smuggled symlink visible?)\n\ + got: {async_entries:?}" + ); +}
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
7- github.com/advisories/GHSA-gchp-q4r4-x4ffghsaADVISORY
- github.com/alexcrichton/tar-rs/commit/de1a5870e603758f430073688691165f21a33946ghsax_refsource_MISC
- github.com/alexcrichton/tar-rs/security/advisories/GHSA-gchp-q4r4-x4ffghsax_refsource_CONFIRM
- www.cve.org/CVERecordghsax_refsource_MISC
- github.com/composefs/tar-rs/security/advisories/GHSA-gchp-q4r4-x4ffghsa
- nvd.nist.gov/vuln/detail/CVE-2026-33055ghsa
- rustsec.org/advisories/RUSTSEC-2026-0068.htmlghsa
News mentions
0No linked articles in our index yet.