Skip to content

Enforcer-style NULL and low-memory access detection #71

Description

@sidick

--sanitize (#65) catches what Enforcer and MuForce structurally cannot — byte-granular heap redzones, use-after-free, return-address corruption. This issue covers the converse: the bug class Enforcer exists to catch and volamos currently absorbs in silence, NULL and low-memory access.

The evidence

LawBreaker (the deliberate-illegal-access test program from the Enforcer package, see #70) commits four violations on purpose. Under volamos with the sanitizer fully enabled, the entire run reports nothing:

$ volamos --sanitize LawBreaker
volamos: Alert(0x35000000): AT_Recovery -- SubSysId=0x35 GeneralError=0x00 SpecificError=0x0000
exit=0

Its disassembled violations (full listing in #70):

31c0 0000        move.w  d0,($0000).w     ; write to address 0
11c1 0101        move.b  d1,($0101).w     ; write to $0101
21c0 0102        move.l  d0,($0102).w     ; write to $0102

All three land inside volamos's own reserved region — TRAP_TABLE_BASE = 0x0000, TRAP_TABLE_SIZE = 0x2A00 (backend.rs) — which the shadow map treats as ordinary valid memory. So rather than being reported, these writes silently corrupt volamos's own fake library jump tables, ExecBase, and the library bases that live there. A guest bug that would produce an instant, obvious Guru on real hardware instead produces either nothing or an inexplicable failure much later, somewhere else entirely.

Worth stating plainly: this is the single most common Amiga crash class. A NULL pointer dereference writes to low memory, and that is precisely why Enforcer was written.

Proposal

Poison the reserved low-memory region in the shadow map so guest accesses to it are reported as violations:

  • A new PoisonReason (e.g. LowMemory / ReservedRegion) so the report distinguishes "you dereferenced NULL" from "you overran a heap block".
  • Address 0 specifically is worth calling out in the message as a NULL dereference, since that is the diagnosis the user actually wants.

The design problem to solve first, and the reason this needs thought rather than a quick patch: volamos's own runtime legitimately reads and writes this whole region constantly. It builds the trap table there, maintains a real struct ExecBase, keeps library bases with their jump tables, and the guest legitimately reads AbsExecBase from address 4 and jumps through library vectors in that range on every single library call. So the checking cannot be a blanket poison; it has to distinguish:

  • Guest data accesses to low memory → violation.
  • The guest reading AbsExecBase at address 4 → legitimate, and every real Amiga program does it.
  • The guest's JSR through a library base into the jump table → legitimate; this is the entire library-call mechanism.
  • volamos's own host-side construction and maintenance of those structures → legitimate.

The existing FlatMemory::peek_* methods (added in #67 for exactly this class of problem — the sanitizer inspecting memory on its own behalf) are the precedent for keeping runtime accesses out of the checked path. Instruction fetch and vector-jump reads will need similar care, or the mechanism reports every library call ever made.

Because of that, this is not a "poison [0, 0x2A00) and done" change, and it should be developed against a false-positive sweep (PhxAss, pLhA, SAS/C sc) the same way #66/#67 were — every false-positive class in those PRs was found by running real software, never by unit tests.

Scope

  • Decide and document how legitimate runtime/guest accesses to the reserved region are distinguished from guest bugs.
  • Poison the region with a distinct PoisonReason; special-case address 0 in the message as a NULL dereference.
  • Consider whether this should be its own flag or part of --sanitize — it may be noisier than the heap/stack detectors, in which case --sanitize-lowmem follows the --sanitize-uninit precedent set in Optional --sanitize-uninit: uninitialized-read detection as an opt-in extra (issue #65 increment 3) #68.
  • Fixture with a deliberate NULL write and a deliberate low-memory write, plus false-positive guards that do ordinary library calls.
  • False-positive sweep against PhxAss, pLhA and SAS/C sc; all must stay clean.
  • LawBreaker must report its three low-memory writes.

Related: #65 (the sanitizer), #70 (LawBreaker's provenance and disassembly), #72 (wild-pointer/out-of-range accesses), #73 (register snapshots in reports).

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