Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion src/archive/zip.rs
Original file line number Diff line number Diff line change
Expand Up @@ -315,7 +315,15 @@ fn unix_set_permissions<R: Read>(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(())
Expand Down
49 changes: 49 additions & 0 deletions tests/integration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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");
}
}
Loading