Skip to content

encoder: load/store imm12 offset truncated (& 0xFFF) with no bounds check — silent wrong-address class (cf #253/#255) #259

Description

@avrabe

Load/store imm12 encoders truncate offset & 0xFFF with no bounds check — silent wrong-address miscompile

Continuing the encoder audit that surfaced #255 (cmp/adds/subs ThumbExpandImm). The thumb32 load/store immediate-offset encoders in crates/synth-backend/src/arm_encoder.rs all mask the offset to 12 bits and emit unconditionally:

let hw2: u16 = ((rd_bits << 12) | (offset & 0xFFF)) as u16;   // no check that offset <= 0xFFF
Ok(bytes)

Affected (all the _imm forms): encode_thumb32_ldr (L6256), str (L6270), ldrb_imm (L6320), ldrsb_imm, ldrh_imm (L6370), ldrsh_imm (L6395), strb_imm, strh_imm (L6445). A grep for return Err/bounds checks across L6256–6470 returns 0.

The bug

LDR.W/STR.W Rd, [Rn, #imm12] (T3/T4) supports imm12 ∈ 0..=4095. For offset >= 0x1000, offset & 0xFFF silently wraps → the access targets the wrong address (offset mod 4096). Same silent-truncation class as #253 (add sp,#256 → #0), but on a memory access — so the failure mode is a wrong load/store (corruption or fault) rather than wrong arithmetic.

Live vs. latent

Likely latent for the current gale workload (host-pointer struct fields and wasm frame offsets are all < 4096), exactly like cmp_imm was before #256. But it bites for: a struct/local frame field at offset ≥ 4096, a static/global placed high in linear memory, or any larger wasm module. Given #251/#253/#256 were all fixed defensively even while latent, this one is worth the same guard — and the consequence here is worse (silent memory corruption).

Suggested fix (mirrors #253)

Guard each: if offset > 0xFFF { return Err(...) } so the selector is forced to materialize the offset and use the register-offset form (encode_thumb32_ldr_reg/str_reg, which already exist right below). A shared helper would cover all eight _imm encoders. (ARM also defines an LDR/STR T4 imm8 index form for a small negative/writeback range, but the clean, uniform fix is the Err + register-offset fallback.)

Found via the wasm-cross-LTO silicon work; happy to add an on-target test vector (a [Rn, #4096] load round-trip on the G474RE) once a fix lands. Close as dup/wontfix if the selector already guarantees offset < 4096 upstream — but a defensive Err still seems warranted given the class history.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions