From 36c9693061f5bee4323bfe30958d1a1aa1966961 Mon Sep 17 00:00:00 2001 From: tommady Date: Mon, 8 Jun 2026 08:50:12 +0000 Subject: [PATCH] fix: ignore invalid unix permissions and setuid bits from zip Signed-off-by: tommady --- src/archive/zip.rs | 10 ++++++++- tests/integration.rs | 49 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+), 1 deletion(-) diff --git a/src/archive/zip.rs b/src/archive/zip.rs index eae9d327a..689f35ea1 100644 --- a/src/archive/zip.rs +++ b/src/archive/zip.rs @@ -315,7 +315,15 @@ fn unix_set_permissions(file_path: &Path, file: &ZipFile<'_, R>) -> Res use std::fs::Permissions; if let Some(mode) = file.unix_mode() { - fs::set_permissions(file_path, Permissions::from_mode(mode))?; + // Zip crate may return invalid Unix modes derived from MS-DOS attributes. + // Valid Unix modes contain the file type bits (S_IFMT, 0o170000). + // If these bits are missing, the mode is likely invalid and should be ignored. + // Also, we mask out the setuid, setgid, and sticky bits (0o7777 -> 0o0777) + // for security reasons when decompressing. + if mode & 0o170000 != 0 { + let safe_mode = mode & 0o777; + fs::set_permissions(file_path, Permissions::from_mode(safe_mode))?; + } } Ok(()) diff --git a/tests/integration.rs b/tests/integration.rs index 4c30533b7..de7d2ed2a 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -1569,3 +1569,52 @@ fn decompress_concatenated_lz4_frames() { encoder.finish().unwrap() }); } + +/// Ensure extracting zip archives lacking correct UNIX file type bits (e.g. MS-DOS attributes mapped poorly) +/// won't wrongly apply setuid/setgid bits. +#[test] +fn missing_file_type_unix_permissions() { + let (_tempdir, test_dir) = testdir().unwrap(); + + let bad_perms_zip = test_dir.join("bad_perms.zip"); + + { + use zip::write::SimpleFileOptions; + + let file = std::fs::File::create(&bad_perms_zip).unwrap(); + let mut zip = zip::ZipWriter::new(file); + + // Use an invalid mode: 0o7700 (missing S_IFMT) + let options = SimpleFileOptions::default().unix_permissions(0o7700); + zip.start_file("test_file.txt", options).unwrap(); + zip.write_all(b"hello world").unwrap(); + zip.finish().unwrap(); + } + + let out_dir = test_dir.join("out"); + + let mut cmd = crate::utils::cargo_bin(); + cmd.arg("d") + .arg(&bad_perms_zip) + .arg("-d") + .arg(&out_dir) + .assert() + .success(); + + #[cfg(unix)] + { + use std::os::unix::fs::PermissionsExt; + let file_path = out_dir.join("test_file.txt"); + let metadata = std::fs::metadata(&file_path).unwrap(); + let mode = metadata.permissions().mode(); + + // Default permission should be applied since 0o7700 does not have file type bits. + // It definitely shouldn't be 0o7700 or have sticky/setuid bits. + assert_ne!( + mode & 0o7777, + 0o7700, + "Should have fallen back to default file permissions" + ); + assert_eq!(mode & 0o7000, 0, "Should not have sticky, setuid, or setgid bits"); + } +}