Skip to content

Enforcer-style wild-pointer detection: report out-of-range guest data accesses #72

Description

@sidick

The other half of Enforcer's bug class (see #71 for NULL/low memory): a wild pointer into an address that isn't backed by guest RAM at all.

The evidence

LawBreaker (from the Enforcer package, see #70) does this deliberately:

207c aaaa 4444   movea.l #$AAAA4444,a4    ; a wild address, by design
2014             move.l  (a4),d0          ; read from it

volamos reports nothing, even with --sanitize. The reason is in AddressSpace's documented contract in memory.rs, which is deliberately total — no Result, no panics:

Out-of-range behavior is deliberately simple and total: reads past the end of the backing store return 0, and writes past the end of the backing store are silently ignored.

That total contract is the right call for the CPU hot path and should stay. But it means a wild read quietly yields 0 and a wild write quietly evaporates, so a guest that dereferences garbage gets plausible-looking zeroes instead of a diagnosis — and on real hardware this is exactly what Enforcer prints a report for.

Note this is distinct from the existing StopReason::PcOutOfBounds, which catches the program counter leaving guest memory (implemented already). This is about data accesses.

Proposal

Report an out-of-range guest data access as a sanitizer violation, while keeping the total read-as-0/drop-the-write semantics exactly as they are — a detector, not an enforcer, consistent with the rest of --sanitize.

Design notes:

  • The shadow map is sized to guest RAM, so an out-of-range address has no shadow byte to poison. The check therefore belongs in the access paths' existing out-of-range branches (the None arms of FlatMemory's read_u16/read_u32/write_* fast paths, and the get/get_mut misses in the byte paths) rather than in the shadow map's state lookup. ShadowMap::state already returns Valid for out-of-range addresses precisely because the total contract handled them; that becomes the thing to change.
  • A multi-byte access straddling the end of RAM already has partially-applied semantics (there is an existing test, straddling_out_of_range_write_partially_applies). Straddling should report once, consistent with the report-once-per-access rule from Extend --sanitize to stack bugs: below-SP accesses and return-address corruption (issue #65) #67.
  • Worth distinguishing "just past the end of RAM" (a plausible off-by-one on a large buffer) from "nowhere near RAM" ($AAAA4444, a garbage pointer) in the message, since they suggest very different bugs.

Scope

  • Report out-of-range guest data reads and writes as violations with their own PoisonReason/kind, preserving the total read-0/drop-write behaviour.
  • Handle the straddling case, reporting once per access.
  • Keep the sanitizer's own peek_u16/peek_u32 (from Extend --sanitize to stack bugs: below-SP accesses and return-address corruption (issue #65) #67) exempt, as they already are for every other check.
  • Fixture with a deliberate wild-pointer read and write.
  • False-positive sweep against PhxAss, pLhA and SAS/C sc — all must stay clean. Note there is real risk here: any program that probes memory by reading it (autoconfig-style scans, "how much RAM is there" loops) would legitimately trip this, which may be an argument for it being opt-in.
  • LawBreaker must report its $AAAA4444 read.

Related: #65, #70, #71, #73.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions