Script injection via data sections#86
Merged
Merged
Conversation
noise64
approved these changes
Mar 23, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Overview
BinarySlot is a dynamic JS injection mode for wasm-rquickjs that allows injecting JavaScript source into prebuilt WASM component templates without recompiling Rust. There is no capacity limit — the WASM component is structurally rewritten using
wasmparser+wasm-encoderto accommodate any size JS payload. Multiple JS modules are supported: the primary export module and any number of additional modules can each use BinarySlot mode independently.Marker Format (40 bytes)
Each BinarySlot module embeds a 40-byte marker file via
include_bytes!. Layout:SLOT_MAGICb"WASM_RQJS_SLOT\x01\x00"— identifies a slot markerMODULE_INDEXJS_OFFSETSLOT_END_MAGICb"WASM_RQJS_SLTND\x00"— footer for integrity validationConstants:
MARKER_SIZE = 40,WASM_PAGE_SIZE = 65536.create_marker_file(module_index: u32)creates these markers withJS_OFFSET=0.Template Compilation Flow
Crate generation (
copy_js_modulesinlib.rs): For eachBinarySlotmodule, a.slotfile is created with a uniqueMODULE_INDEX(assigned sequentially across all BinarySlot modules). The primary export module gets index 0, additional modules get 1, 2, etc.Code generation (
generate_module_defsinexports.rs):read_js_from_slot_bytes(slot: &[u8]) -> Stringfunction is emitted. This reads JS from the marker using volatile reads to prevent the optimizer from constant-folding the slot contents.JS_EXPORT_MODULE_SLOT: &[u8] = include_bytes!("name.slot")andjs_export_module()callsread_js_from_slot_bytes.include_bytes!static and a per-module reader function, used in theJS_ADDITIONAL_MODULESlazy vec.Compilation:
cargo-component buildcompiles the crate to a WASM component. The 40-byte markers end up in data segments withJS_OFFSET=0.Injection Flow
inject_js_into_component(input, output, js_sources: &[&str])— thejs_sourcesslice maps by position toMODULE_INDEX(first entry → MODULE_INDEX=0, second → MODULE_INDEX=1, etc.).Reencode Pass (
MarkerRewriter)The
MarkerRewriterstruct implementswasm_encoder::reencode::ReencodeandReencodeComponent:parse_data: For each data segment:max_data_end(highest memory address used by active segments on memory 0).is_marker_at()(checksSLOT_MAGICat offset 0 andSLOT_END_MAGICat offset 24).MODULE_INDEXand records it. Duplicate indices are an error.parse_data_section: After processing all existing segments:page_align(current_offset)containing[JS_LEN u32 LE][JS bytes].(module_index, mem_offset)pairs injs_mem_offsets.data_count: Returnscount + Nwhere N is the number of JS payloads (one per BinarySlot module).parse_memory_section: Grows the core module's memory minimum pages to fit all JS payloads. Upper bound:original_min_pages * PAGE_SIZE + total_payload_size + N * PAGE_SIZE(worst-case page alignment padding per payload).Post-Pass Binary Patch
patch_js_offsets_in_output(output, offsets):JS_OFFSET == 0.(module_index, js_mem_offset)pair, finds the marker with matchingMODULE_INDEXand writesjs_mem_offsetinto theJS_OFFSETfield at byte offset 20.Validation
After the reencode pass:
Runtime Reading
The generated
read_js_from_slot_bytes(slot: &[u8]) -> Stringfunction:SLOT_MAGICat offset 0 via volatile reads.JS_OFFSETat offset 20 (skippingMODULE_INDEXat offset 16) via volatile reads.JS_OFFSET > 0(panics if slot is empty — JS not injected).SLOT_END_MAGICat offset 24.JS_LEN(4 bytes) from linear memory atJS_OFFSET.JS_LENbytes from linear memory atJS_OFFSET + 4.String.Volatile reads prevent the compiler from constant-folding the marker contents at compile time, which is essential because the marker is patched post-compilation.
CLI Usage
The
--jsflag accepts multiple paths. Order must match the BinarySlot module order used duringgenerate-wrapper-crate(primary module first, then additional modules in order).