feat(pack): validate Wasm module magic bytes in check

check_package now reads the first 4 bytes of runtime.module and rejects
files that do not begin with the Wasm magic number (0x00 0x61 0x73 0x6D).
An unreadable or too-short file is treated as invalid.

is_wasm_module(path): opens the file, reads 4 bytes, compares to MAGIC.

Test added: check_package_bad_wasm_magic - writes NOT_WASM to app.wasm,
asserts check fails with a message containing bad magic bytes.
This commit is contained in:
Marco Allegretti 2026-03-11 10:21:43 +01:00
parent b2bb76125f
commit 5cff1f4412

View file

@ -98,6 +98,11 @@ fn check_package(dir: &Path) -> anyhow::Result<String> {
"runtime.module '{}' not found", "runtime.module '{}' not found",
wasm_path.display() wasm_path.display()
)); ));
} else if !is_wasm_module(&wasm_path) {
errors.push(format!(
"runtime.module '{}' is not a valid Wasm module (bad magic bytes)",
wasm_path.display()
));
} }
let ui_path = dir.join(&m.ui.entry); let ui_path = dir.join(&m.ui.entry);
@ -150,6 +155,20 @@ fn is_valid_app_id(id: &str) -> bool {
}) })
} }
fn is_wasm_module(path: &Path) -> bool {
const MAGIC: [u8; 4] = [0x00, 0x61, 0x73, 0x6D];
let mut buf = [0u8; 4];
match std::fs::File::open(path) {
Ok(mut f) => {
use std::io::Read;
f.read_exact(&mut buf)
.map(|_| buf == MAGIC)
.unwrap_or(false)
}
Err(_) => false,
}
}
fn resolve_install_root() -> anyhow::Result<PathBuf> { fn resolve_install_root() -> anyhow::Result<PathBuf> {
if let Ok(explicit) = std::env::var("WEFT_APP_STORE") { if let Ok(explicit) = std::env::var("WEFT_APP_STORE") {
return Ok(PathBuf::from(explicit)); return Ok(PathBuf::from(explicit));
@ -345,6 +364,37 @@ entry = "ui/index.html"
let _ = fs::remove_dir_all(&store); let _ = fs::remove_dir_all(&store);
} }
#[test]
fn check_package_bad_wasm_magic() {
use std::fs;
let tmp = std::env::temp_dir().join("weft_pack_test_bad_wasm");
let ui_dir = tmp.join("ui");
let _ = fs::create_dir_all(&ui_dir);
fs::write(tmp.join("app.wasm"), b"NOT_WASM").unwrap();
fs::write(ui_dir.join("index.html"), b"").unwrap();
fs::write(
tmp.join("wapp.toml"),
r#"
[package]
id = "com.example.badwasm"
name = "Bad Wasm"
version = "0.1.0"
[runtime]
module = "app.wasm"
[ui]
entry = "ui/index.html"
"#,
)
.unwrap();
let result = check_package(&tmp);
assert!(result.is_err());
let msg = result.unwrap_err().to_string();
assert!(msg.contains("bad magic bytes"), "got: {msg}");
let _ = fs::remove_dir_all(&tmp);
}
#[test] #[test]
fn check_package_invalid_app_id() { fn check_package_invalid_app_id() {
use std::fs; use std::fs;