mirror of
https://github.com/storytold/craft-anything.git
synced 2026-11-05 20:47:45 +01:00
Keep PyInstaller modules within their PYZ archive (from #707)
This commit is contained in:
@@ -218,6 +218,7 @@ struct PyzRead {
|
||||
/// How many modules its table holds, which may be more than `entries`.
|
||||
total: usize,
|
||||
encrypted: bool,
|
||||
outside: usize,
|
||||
}
|
||||
|
||||
fn looks_zlib(b: &[u8]) -> bool {
|
||||
@@ -254,6 +255,7 @@ fn read_pyz(data: &[u8], member: &PackedEntry, room: usize, key_seen: bool) -> R
|
||||
let unzipped = !samples.is_empty() && samples.iter().all(|b| !looks_zlib(b));
|
||||
let encrypted = key_seen || key_inside || unzipped;
|
||||
let kind = format!("{PYZ_KIND}{magic:04x}");
|
||||
let outside = modules.iter().filter(|m| !in_range(m)).count();
|
||||
let entries = modules
|
||||
.iter()
|
||||
.map(|m| PackedEntry {
|
||||
@@ -261,11 +263,11 @@ fn read_pyz(data: &[u8], member: &PackedEntry, room: usize, key_seen: bool) -> R
|
||||
offset: member.offset.saturating_add(m.at),
|
||||
stored_size: m.len,
|
||||
size: None,
|
||||
compression: if encrypted { Compression::Unreadable } else { Compression::Zlib },
|
||||
compression: if encrypted || !in_range(m) { Compression::Unreadable } else { Compression::Zlib },
|
||||
kind: Some(kind.clone()),
|
||||
})
|
||||
.collect();
|
||||
Ok(PyzRead { magic, entries, total, encrypted })
|
||||
Ok(PyzRead { magic, entries, total, encrypted, outside })
|
||||
}
|
||||
|
||||
impl PackedFormat for PyInstaller {
|
||||
@@ -312,6 +314,9 @@ impl PackedFormat for PyInstaller {
|
||||
"encrypted": read.encrypted,
|
||||
}));
|
||||
truncated |= read.total > read.entries.len();
|
||||
if read.outside > 0 {
|
||||
notes.push(format!("{} module(s) in {} lie outside that PYZ archive and cannot be read.", read.outside, m.entry.path));
|
||||
}
|
||||
nested.extend(read.entries);
|
||||
}
|
||||
Err(why) => {
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
use craft_anything_formats::packed::{Compression, PackedFormat};
|
||||
use craft_anything_formats::pyinstaller::PyInstaller;
|
||||
use craft_anything_testkit::packed::{code_311, marshal as m, pyinstaller};
|
||||
|
||||
fn sample() -> (Vec<u8>, Vec<u8>, Vec<u8>) {
|
||||
let code = code_311("<module>", "<module>", "good.py", 1, &[], &[], &[]);
|
||||
let compressed = miniz_oxide::deflate::compress_to_vec_zlib(&code, 6);
|
||||
let mut pyz = b"PYZ\0".to_vec();
|
||||
pyz.extend_from_slice(&3531u16.to_le_bytes());
|
||||
pyz.extend_from_slice(&[0x0d, 0x0a]);
|
||||
pyz.extend_from_slice(&[0; 8]);
|
||||
pyz.extend_from_slice(&compressed);
|
||||
let table_at = pyz.len();
|
||||
pyz[8..12].copy_from_slice(&(table_at as u32).to_be_bytes());
|
||||
let table = |outside: i32| {
|
||||
m::list(vec![
|
||||
m::tuple(vec![m::string("good"), m::tuple(vec![m::int(0), m::int(16), m::int(compressed.len() as i32)])]),
|
||||
m::tuple(vec![m::string("outside"), m::tuple(vec![m::int(0), m::int(outside), m::int(compressed.len() as i32)])]),
|
||||
])
|
||||
};
|
||||
let full_len = table_at + table(0).len();
|
||||
pyz.extend_from_slice(&table(full_len as i32));
|
||||
let container = pyinstaller(&[("PYZ.pyz", b'z', &pyz, false), ("neighbor", b'x', &compressed, false)], true, 312);
|
||||
(container, compressed, code)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_module_cannot_read_compressed_bytes_from_the_next_container_member() {
|
||||
let (data, _, _) = sample();
|
||||
let listing = PyInstaller.list(&data).unwrap();
|
||||
let bad = listing.entries.iter().find(|e| e.path == "PYZ.pyz.modules/outside.pyc").unwrap();
|
||||
assert_eq!(bad.compression, Compression::Unreadable);
|
||||
assert!(PyInstaller.read(&data, bad, 1000).is_err());
|
||||
assert!(listing.notes.iter().any(|n| n.contains("outside") || n.contains("bounds")));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sound_modules_and_neighboring_members_still_read() {
|
||||
let (data, compressed, code) = sample();
|
||||
let listing = PyInstaller.list(&data).unwrap();
|
||||
let good = listing.entries.iter().find(|e| e.path == "PYZ.pyz.modules/good.pyc").unwrap();
|
||||
let recovered = PyInstaller.read(&data, good, 1000).unwrap();
|
||||
assert_eq!(&recovered[16..], code);
|
||||
let neighbor = listing.entries.iter().find(|e| e.path == "neighbor").unwrap();
|
||||
assert_eq!(PyInstaller.read(&data, neighbor, 1000).unwrap(), compressed);
|
||||
}
|
||||
Reference in New Issue
Block a user