diff --git a/Cargo.toml b/Cargo.toml index 2b1efcee..51f0122b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -57,6 +57,7 @@ walkdir = "2.5.0" time = { workspace = true, features = ["formatting", "macros"] } anyhow = "1" clap = { version = "=4.4.18", features = ["derive"] } +tempdir = "0.3.7" [features] aes-crypto = ["aes", "constant_time_eq", "hmac", "pbkdf2", "sha1", "rand", "zeroize"] diff --git a/src/read.rs b/src/read.rs index 053292ff..7fbe8422 100644 --- a/src/read.rs +++ b/src/read.rs @@ -13,6 +13,7 @@ use crate::types::{AesMode, AesVendorVersion, DateTime, System, ZipFileData}; use crate::zipcrypto::{ZipCryptoReader, ZipCryptoReaderValid, ZipCryptoValidator}; use indexmap::IndexMap; use std::borrow::Cow; +use std::fs::{create_dir_all, Permissions}; use std::io::{self, copy, prelude::*, sink}; use std::ops::Deref; use std::path::{Path, PathBuf}; @@ -661,7 +662,8 @@ impl ZipArchive { /// may be left on disk. pub fn extract>(&mut self, directory: P) -> ZipResult<()> { use std::fs; - + #[cfg(unix)] + let mut files_by_unix_mode = Vec::new(); for i in 0..self.len() { let mut file = self.by_index(i)?; let filepath = file @@ -671,25 +673,46 @@ impl ZipArchive { let outpath = directory.as_ref().join(filepath); if file.is_dir() { - fs::create_dir_all(&outpath)?; + Self::make_writable_dir_all(&outpath)?; } else { if let Some(p) = outpath.parent() { - if !p.exists() { - fs::create_dir_all(p)?; - } + Self::make_writable_dir_all(p)?; } let mut outfile = fs::File::create(&outpath)?; io::copy(&mut file, &mut outfile)?; } - // Get and Set permissions #[cfg(unix)] { - use std::os::unix::fs::PermissionsExt; + // Check for real permissions, which we'll set in a second pass if let Some(mode) = file.unix_mode() { - fs::set_permissions(&outpath, fs::Permissions::from_mode(mode))?; + files_by_unix_mode.push((outpath.clone(), mode)); } } } + #[cfg(unix)] + { + use std::cmp::Reverse; + use std::os::unix::fs::PermissionsExt; + + if files_by_unix_mode.len() > 1 { + // Ensure we update children's permissions before making a parent unwritable + files_by_unix_mode.sort_by_key(|(path, _)| Reverse(path.clone())); + } + for (path, mode) in files_by_unix_mode.into_iter() { + fs::set_permissions(&path, fs::Permissions::from_mode(mode))?; + } + } + Ok(()) + } + + fn make_writable_dir_all>(outpath: T) -> Result<(), ZipError> { + create_dir_all(outpath.as_ref())?; + #[cfg(unix)] + { + // Dirs must be writable until all normal files are extracted + use std::os::unix::fs::PermissionsExt; + std::fs::set_permissions(outpath.as_ref(), Permissions::from_mode(0o755))?; + } Ok(()) } diff --git a/tests/data/lin-ub_iwd-v11.zip b/tests/data/lin-ub_iwd-v11.zip new file mode 100644 index 00000000..d40aa4fe Binary files /dev/null and b/tests/data/lin-ub_iwd-v11.zip differ diff --git a/tests/repro_old423.rs b/tests/repro_old423.rs new file mode 100644 index 00000000..8aac8b7b --- /dev/null +++ b/tests/repro_old423.rs @@ -0,0 +1,12 @@ +use std::env::temp_dir; +use std::io; +use zip::result::ZipResult; +use zip::ZipArchive; + +#[test] +fn repro_old423() -> ZipResult<()> { + let mut v = Vec::new(); + v.extend_from_slice(include_bytes!("data/lin-ub_iwd-v11.zip")); + let mut archive = ZipArchive::new(io::Cursor::new(v)).expect("couldn't open test zip file"); + archive.extract(temp_dir()) +} \ No newline at end of file