mirror of
https://github.com/storytold/craft-anything.git
synced 2026-11-05 20:47:45 +01:00
Only treat code/module CArchive members as PyInstaller crypto_key markers (#398)
Only code and module members named crypto_key mark PyInstaller modules as encrypted; a data file with that name no longer does. Fixes #334.
This commit is contained in:
@@ -281,6 +281,7 @@ impl PackedFormat for PyInstaller {
|
||||
find_cookie(data).is_some()
|
||||
}
|
||||
|
||||
/// List CArchive members and nested PYZ module entries with archive metadata.
|
||||
fn list(&self, data: &[u8]) -> Result<PackedListing, FormatError> {
|
||||
let cookie = find_cookie(data)
|
||||
.ok_or_else(|| FormatError::Malformed("no sound PyInstaller cookie (its magic, sizes and Python version must all agree)".into()))?;
|
||||
@@ -291,7 +292,7 @@ impl PackedFormat for PyInstaller {
|
||||
notes.push(format!("{}, so the members after it aren't listed.", capitalise(&why)));
|
||||
truncated = true;
|
||||
}
|
||||
let key_seen = members.iter().any(|m| m.entry.path.contains("crypto_key"));
|
||||
let key_seen = members.iter().any(|m| matches!(m.type_code, b'm' | b'M' | b's') && m.entry.path.contains("crypto_key"));
|
||||
let scripts: Vec<&str> = members.iter().filter(|m| m.type_code == b's').map(|m| m.entry.path.as_str()).collect();
|
||||
let entry_scripts: Vec<&str> = scripts.iter().copied().filter(|s| !s.starts_with("pyi_rth_") && !s.starts_with("pyiboot")).collect();
|
||||
let options: Vec<&str> = members.iter().filter(|m| m.type_code == b'o').map(|m| m.entry.path.as_str()).collect();
|
||||
@@ -348,9 +349,15 @@ impl PackedFormat for PyInstaller {
|
||||
Ok(PackedListing { facts, entries, notes, truncated })
|
||||
}
|
||||
|
||||
/// Extract a CArchive member or PYZ module within `max_bytes`, including any `.pyc` header.
|
||||
fn read(&self, data: &[u8], entry: &PackedEntry, max_bytes: u64) -> Result<Vec<u8>, FormatError> {
|
||||
const PYC_HEADER_BYTES: u64 = 16;
|
||||
let magic = entry.kind.as_deref().and_then(|k| k.strip_prefix(PYZ_KIND)).and_then(|m| u16::from_str_radix(m, 16).ok());
|
||||
let body = read_stored(data, entry, max_bytes)?;
|
||||
let body_limit = match magic {
|
||||
Some(_) => max_bytes.checked_sub(PYC_HEADER_BYTES).ok_or_else(|| FormatError::TooLarge(format!("{} needs a .pyc header", entry.path)))?,
|
||||
None => max_bytes,
|
||||
};
|
||||
let body = read_stored(data, entry, body_limit)?;
|
||||
match magic {
|
||||
Some(magic) => Ok(pyc::wrap_marshal(magic, &body)),
|
||||
None => Ok(body),
|
||||
@@ -429,6 +436,7 @@ mod tests {
|
||||
assert_eq!(PyInstaller.read(&data, find(&l, "libz.so.1"), 100).unwrap(), b"native");
|
||||
}
|
||||
|
||||
/// Verify that code/module crypto_key markers flag encryption while data members do not.
|
||||
#[test]
|
||||
fn encrypted_modules_are_flagged_and_listed_but_not_read() {
|
||||
// A key module in the archive.
|
||||
@@ -446,6 +454,15 @@ mod tests {
|
||||
let z = pyz(3531, &[("a", 0, &[0xee; 40])], false, false);
|
||||
let data = pyinstaller(&[("PYZ.pyz", b'z', &z, false)], true, 312);
|
||||
assert_eq!(PyInstaller.list(&data).unwrap().facts["pyz_archives"][0]["encrypted"], true);
|
||||
|
||||
// An ordinary data member named crypto_key.txt does not mark unencrypted PYZ modules unreadable.
|
||||
let z = pyz(3531, &[("a", 0, &code)], false, true);
|
||||
let data = pyinstaller(&[("crypto_key.txt", b'x', b"plain", false), ("PYZ.pyz", b'z', &z, false)], true, 312);
|
||||
let l = PyInstaller.list(&data).unwrap();
|
||||
assert_eq!(l.facts["encrypted_modules"], false);
|
||||
let a = find(&l, "PYZ.pyz.modules/a.pyc");
|
||||
assert_eq!(a.compression, Compression::Zlib);
|
||||
assert!(pyc::read_pyc(&PyInstaller.read(&data, a, 1000).unwrap()).unwrap().code.is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -562,11 +579,17 @@ mod tests {
|
||||
assert_eq!((kept.len(), total), (2, 3));
|
||||
}
|
||||
|
||||
/// Verify that `read` enforces `max_bytes` including the 16-byte `.pyc` header for PYZ modules.
|
||||
#[test]
|
||||
fn reads_stop_at_their_limit() {
|
||||
let data = pyinstaller_sample();
|
||||
let l = PyInstaller.list(&data).unwrap();
|
||||
assert!(matches!(PyInstaller.read(&data, find(&l, "struct"), 10), Err(FormatError::TooLarge(_))));
|
||||
let util = find(&l, "PYZ-00.pyz.modules/pkg/util.pyc");
|
||||
let full = PyInstaller.read(&data, util, 10_000).unwrap();
|
||||
assert!(matches!(PyInstaller.read(&data, util, 15), Err(FormatError::TooLarge(_))));
|
||||
assert!(matches!(PyInstaller.read(&data, util, (full.len() - 1) as u64), Err(FormatError::TooLarge(_))));
|
||||
assert_eq!(PyInstaller.read(&data, util, full.len() as u64).unwrap(), full);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user