Skip to content

Commit 4fec3b9

Browse files
yeetypetecgwalters
authored andcommitted
tar: Add regression test for PAX path remap with non-ASCII filenames
Verifies that PAX `path` headers (as produced by Docker/BuildKit for non-ASCII filenames) do not bypass the /etc -> /usr/etc remap. Checks both that no unremapped /etc PAX headers remain in the output and that the remapped file appears under usr/etc. Signed-off-by: Peter Siegel <psiegel2000@icloud.com>
1 parent ca540f5 commit 4fec3b9

1 file changed

Lines changed: 69 additions & 0 deletions

File tree

crates/ostree-ext/src/tar/write.rs

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -630,4 +630,73 @@ mod tests {
630630
assert!(!destdir.join("blah").exists());
631631
Ok(())
632632
}
633+
634+
/// Regression test: PAX `path` headers (used for non-ASCII filenames)
635+
/// must not bypass the /etc -> /usr/etc remap, since PAX takes
636+
/// precedence over basic tar headers per POSIX.
637+
#[tokio::test]
638+
async fn tar_filter_pax_etc_remap() -> Result<()> {
639+
let tempd = tempfile::tempdir()?;
640+
let src_tar_path = tempd.path().join("src.tar");
641+
let pax_path = "etc/ssl/certs/Főtanúsítvány.pem";
642+
643+
// Build a tar with an explicit PAX `path` under etc/, matching how
644+
// Docker/BuildKit produces layers for non-ASCII filenames.
645+
{
646+
let mut builder = tar::Builder::new(std::fs::File::create(&src_tar_path)?);
647+
let data = b"cert";
648+
let mut header = tar::Header::new_gnu();
649+
header.set_size(data.len() as u64);
650+
header.set_mode(0o644);
651+
header.set_entry_type(tar::EntryType::Regular);
652+
header.set_cksum();
653+
builder.append_pax_extensions([("path", pax_path.as_bytes())].into_iter())?;
654+
builder.append_data(&mut header, pax_path, &data[..])?;
655+
builder.into_inner()?;
656+
}
657+
658+
let mut dest = Vec::new();
659+
let src = tokio::io::BufReader::new(tokio::fs::File::open(&src_tar_path).await?);
660+
let cap_tmpdir = Dir::open_ambient_dir(&tempd, cap_std::ambient_authority())?;
661+
filter_tar_async(
662+
src,
663+
oci_image::MediaType::ImageLayer,
664+
&mut dest,
665+
&Default::default(),
666+
cap_tmpdir,
667+
)
668+
.await?;
669+
670+
// Check the raw PAX headers in the output. We cannot use unpack()
671+
// because the Rust tar crate resolves PAX-vs-GNU conflicts
672+
// differently than libarchive/ostree (which gives PAX precedence).
673+
let mut found_remapped = false;
674+
let mut archive = tar::Archive::new(Cursor::new(dest.as_slice()));
675+
for entry in archive.entries()? {
676+
let mut entry = entry?;
677+
let entry_path = entry.path()?;
678+
let entry_path = entry_path.to_string_lossy();
679+
let entry_path = entry_path.trim_start_matches("./");
680+
if entry_path == format!("usr/{pax_path}") {
681+
found_remapped = true;
682+
}
683+
if let Some(pax) = entry.pax_extensions()? {
684+
for ext in pax.flatten() {
685+
if let Ok("path" | "linkpath") = ext.key() {
686+
let value = String::from_utf8_lossy(ext.value_bytes());
687+
let clean = value.trim_start_matches("./").trim_end_matches('\0');
688+
assert!(
689+
!clean.starts_with("etc/") && clean != "etc",
690+
"PAX header still contains unremapped /etc path: {value}"
691+
);
692+
}
693+
}
694+
}
695+
}
696+
assert!(
697+
found_remapped,
698+
"Expected remapped file at usr/{pax_path} not found in output"
699+
);
700+
Ok(())
701+
}
633702
}

0 commit comments

Comments
 (0)