/
githubmirror
/
deno
Обзор
Документация
Войти
/
githubmirror
/
deno
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
ext/napi/pe.rs
164 строки
6 KB
em
chore: test cover PE32 node.exe addon imports (#34848)
05 июн 2026, 12:41
Не верифицирован
05 июн 2026, 12:41
d78c7ac
Код
Авторство
О чём код?
// Copyright 2018-2026 the Deno authors. MIT license. //! Detects Windows `.node` addons that link *directly* against the Node.js //! binary (`node.exe`) via a regular import. //! //! Such addons depend on the V8 C++ ABI, Node internal APIs and/or libuv that //! are exported by the `node.exe` executable — none of which Deno provides — //! and they fail to load into any host that isn't literally named `node.exe` //! with the opaque Windows loader error `LoadLibraryExW failed`. //! //! Delay-loaded imports are intentionally ignored: a delay-loaded `node.exe` //! import is the node-gyp blessed pattern whose `win_delay_load_hook` redirects //! symbol resolution to the host process at runtime, so it works fine in Deno. //! `object`'s import table only covers the regular import directory, so that //! distinction comes for free. use object::FileKind; use object::LittleEndian as LE; use object::pe; use object::read::pe::ImageNtHeaders; use object::read::pe::PeFile; /// Returns `true` if the PE image in `bytes` has a regular (non delay-load) /// import of `node.exe`, meaning it expects the Node.js executable to satisfy /// its symbols at load time. Returns `false` for anything that isn't a /// well-formed PE image. pub fn imports_node_executable(bytes: &[u8]) -> bool { imported_dll_names(bytes).is_some_and(|names| { names .iter() .any(|name| name.eq_ignore_ascii_case(b"node.exe")) }) } /// Reads the names of the DLLs referenced by the regular import directory of a /// PE image. Returns `None` if the bytes are not a well-formed PE image. fn imported_dll_names(bytes: &[u8]) -> Option<Vec<Vec<u8>>> { match FileKind::parse(bytes).ok()? { FileKind::Pe32 => dll_names::<pe::ImageNtHeaders32>(bytes), FileKind::Pe64 => dll_names::<pe::ImageNtHeaders64>(bytes), _ => None, } } fn dll_names<Nt: ImageNtHeaders>(bytes: &[u8]) -> Option<Vec<Vec<u8>>> { let file = PeFile::<Nt>::parse(bytes).ok()?; let import_table = file.import_table().ok()??; let mut descriptors = import_table.descriptors().ok()?; let mut names = Vec::new(); while let Some(descriptor) = descriptors.next().ok()? { if let Ok(name) = import_table.name(descriptor.name.get(LE)) { names.push(name.to_vec()); } } Some(names) } #[cfg(test)] mod tests { use super::*; /// Builds a minimal but well-formed PE image whose import directory /// references each of `dlls`. A single section maps RVA == file offset /// (both based at 0x1000) to keep the fixture simple. fn build_pe(is_pe64: bool, dlls: &[&str]) -> Vec<u8> { let mut buf = vec![0u8; 0x2000]; // DOS header. buf[0..2].copy_from_slice(b"MZ"); let e_lfanew: u32 = 0x80; buf[0x3C..0x40].copy_from_slice(&e_lfanew.to_le_bytes()); let pe = e_lfanew as usize; buf[pe..pe + 4].copy_from_slice(b"PE\0\0"); let coff = pe + 4; // Machine = IMAGE_FILE_MACHINE_AMD64 or IMAGE_FILE_MACHINE_I386. let machine = if is_pe64 { 0x8664u16 } else { 0x14cu16 }; buf[coff..coff + 2].copy_from_slice(&machine.to_le_bytes()); // NumberOfSections = 1. buf[coff + 2..coff + 4].copy_from_slice(&1u16.to_le_bytes()); // SizeOfOptionalHeader (standard/windows fields + 16 data dirs). let size_opt: u16 = if is_pe64 { 0xF0 } else { 0xE0 }; buf[coff + 16..coff + 18].copy_from_slice(&size_opt.to_le_bytes()); let opt = coff + 20; // Magic = PE32+ or PE32. let magic = if is_pe64 { 0x20bu16 } else { 0x10bu16 }; buf[opt..opt + 2].copy_from_slice(&magic.to_le_bytes()); // NumberOfRvaAndSizes = 16 (so the import data directory is valid). let number_of_rva_and_sizes = if is_pe64 { opt + 108 } else { opt + 92 }; buf[number_of_rva_and_sizes..number_of_rva_and_sizes + 4] .copy_from_slice(&16u32.to_le_bytes()); let data_dir = if is_pe64 { opt + 112 } else { opt + 96 }; let import_rva: u32 = 0x1000; let descriptors_size = ((dlls.len() + 1) * 20) as u32; buf[data_dir + 8..data_dir + 12].copy_from_slice(&import_rva.to_le_bytes()); buf[data_dir + 12..data_dir + 16] .copy_from_slice(&descriptors_size.to_le_bytes()); // Single section: VirtualAddress == PointerToRawData == 0x1000. let sec = opt + size_opt as usize; buf[sec..sec + 6].copy_from_slice(b".idata"); buf[sec + 8..sec + 12].copy_from_slice(&0x1000u32.to_le_bytes()); buf[sec + 12..sec + 16].copy_from_slice(&0x1000u32.to_le_bytes()); buf[sec + 16..sec + 20].copy_from_slice(&0x1000u32.to_le_bytes()); buf[sec + 20..sec + 24].copy_from_slice(&0x1000u32.to_le_bytes()); // Import descriptors followed by the null-terminated name strings. let descriptors_off = 0x1000usize; let mut names_off = descriptors_off + (dlls.len() + 1) * 20; for (i, dll) in dlls.iter().enumerate() { let desc = descriptors_off + i * 20; buf[desc + 12..desc + 16] .copy_from_slice(&(names_off as u32).to_le_bytes()); buf[names_off..names_off + dll.len()].copy_from_slice(dll.as_bytes()); names_off += dll.len() + 1; // keep the trailing NUL (already zeroed) } buf } #[test] fn detects_regular_node_exe_import() { let pe = build_pe(true, &["node.exe", "KERNEL32.dll", "WS2_32.dll"]); assert!(imports_node_executable(&pe)); let names = imported_dll_names(&pe).unwrap(); assert_eq!( names, vec![ b"node.exe".to_vec(), b"KERNEL32.dll".to_vec(), b"WS2_32.dll".to_vec(), ] ); } #[test] fn detects_regular_node_exe_import_pe32() { let pe = build_pe(false, &["NoDe.ExE"]); assert!(imports_node_executable(&pe)); } #[test] fn node_exe_match_is_case_insensitive() { assert!(imports_node_executable(&build_pe(true, &["NODE.EXE"]))); assert!(imports_node_executable(&build_pe(true, &["Node.Exe"]))); } #[test] fn ignores_addons_without_node_exe_import() { let pe = build_pe(true, &["KERNEL32.dll", "WS2_32.dll", "VCRUNTIME140.dll"]); assert!(!imports_node_executable(&pe)); } #[test] fn malformed_input_is_not_flagged() { assert!(!imports_node_executable(b"")); assert!(!imports_node_executable(b"MZ")); assert!(!imports_node_executable(&[0u8; 256])); assert!(!imports_node_executable(&[0xFFu8; 4096])); assert!(imported_dll_names(b"not a pe file").is_none()); } }