Fix EXECUTE/?/DUMP/TYPE/DECIMAL-HEX-OCTAL/ALIGN defects from proof sweep

proof/FINDINGS.md's Isabelle/HOL word-source sweep (§4) flagged five real
defects; this fixes all five and records resolution in that doc:

- EXECUTE (system_words.c): cast a popped cell straight to a DictEntry*
  and called through it with only a null check. Now validates via a new
  shared vm_dict_entry_ok(), promoted out of starforth_words.c's
  ENTROPY@/ENTROPY! guard (dictionary_management.c) so EXECUTE gets the
  same live-entry check.

- ? and DUMP (format_words.c): dereferenced the popped cell as a raw host
  pointer, bypassing vm_addr_ok entirely (out-of-VM-bounds read). Both now
  go through VM_ADDR/vm_addr_ok/vm_load_cell/vm_ptr like every other
  memory word (@, `,`, editor_words.c).

- TYPE (io_words.c): bounds check computed addr+count in signed 64-bit
  arithmetic, which can overflow and bypass the check on large operands.
  Replaced with vm_addr_ok(), which is written to avoid that overflow.

- DECIMAL/HEX/OCTAL (format_words.c): wrote only the BASE memory cell,
  never vm->base, the host-mirror field number-output words actually read
  via current_base() -- so these words silently affected number parsing
  but never printing. Now call the existing vm_set_base() (previously
  only used at boot init), which updates both. vm_get_base/vm_set_base
  promoted to public declarations in include/vm.h.

- ALIGN vs ALLOT/,/C,/2, (dictionary_words.c): disagreed on dictionary
  growth ceiling (2MB vs 5MB). Investigated which was correct rather than
  blindly widening: vm_get_block_addr() maps block N to
  vm->memory + N*BLOCK_SIZE across the full 5MB arena, and
  USER_BLOCKS_START (block 2048) lines up exactly with
  DICTIONARY_MEMORY_SIZE -- so ALLOT/,/C,/2, letting `here` grow past 2MB
  could silently corrupt live block/user data sharing that memory.
  Tightened ALLOT/,/C,/2, to DICTIONARY_MEMORY_SIZE to match ALIGN.

Verified: hosted (amd64) and kernel (amd64, __STARKERNEL__) both build
clean with -Wall -Werror; hosted POST suite 1012/1012 passing (0
regressions); manually exercised EXECUTE, ?/DUMP, TYPE, HEX/DECIMAL/OCTAL,
and large-ALLOT rejection in the REPL.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014Qf6YcnHgaEtEygq3knx19
This commit is contained in:
Claude
2026-09-05 14:07:11 +00:00
parent 70dc8beba4
commit c36bd99e1e
9 changed files with 85 additions and 48 deletions
+21
View File
@@ -147,22 +147,43 @@ kernel word-source) — see this document's closing status line for result.
since `EXECUTE` is a core, ubiquitous primitive rather than a diagnostic
word. **Flagged as the highest-severity single-word finding in the
sweep.**
**RESOLVED 2026-09-05:** now validates via the (newly shared)
`vm_dict_entry_ok()` — the same dictionary-walk check `ENTROPY@`/`ENTROPY!`
already used, promoted out of `starforth_words.c` into
`dictionary_management.c` so `EXECUTE` can call it too.
- **`?` and `DUMP`** (`format_words.c`) cast the popped cell straight to a
host pointer and dereference it, bypassing `vm_addr_ok` — an
out-of-VM-bounds read.
**RESOLVED 2026-09-05:** both now go through `VM_ADDR`/`vm_addr_ok`/
`vm_load_cell`/`vm_ptr`, matching `@`/`,`/editor_words.c`'s pattern.
- **`TYPE`** (`io_words.c`) has a signed-overflow bypass in its bounds
check (machine-checked witness in the proof).
**RESOLVED 2026-09-05:** replaced the manual `addr + count > VM_MEMORY_SIZE`
sum with `vm_addr_ok(vm, addr, count)`, which is written to avoid exactly
this overflow.
- **`DECIMAL`/`HEX`/`OCTAL`** (`format_words.c`) write only the memory cell
at `base_addr`, never `vm->base` (the separate host-mirror field number-
*output* words actually read via `current_base()`) — proved as
`decimal_does_not_change_vm_base` et al. Net effect: these words silently
affect number *parsing* but never number *printing*.
**RESOLVED 2026-09-05:** all three now call the existing `vm_set_base()`
(previously only used at boot init), which updates both `base_addr` and
`vm->base`; `vm_get_base()`/`vm_set_base()` promoted to public
declarations in `include/vm.h` so word-source files can reach them.
- **`LATEST`** (`dictionary_words.c`) has a body identical to `HERE` (both
push `vm->here`) — does not consult `vm->latest` despite its doc comment
claiming otherwise.
- **`ALIGN`** bounds-checks `here` against `DICTIONARY_MEMORY_SIZE` (2MB)
while `ALLOT`/`,`/`C,`/`2,` check against `VM_MEMORY_SIZE` (5MB) instead —
two different ceilings for the same pointer.
**RESOLVED 2026-09-05:** the two ceilings disagreeing was real, but
`ALIGN`'s 2MB was the correct one, not `ALLOT`'s 5MB — `vm_get_block_addr()`
maps block N directly to `vm->memory + N*BLOCK_SIZE` for the *entire* 5MB
arena, and `USER_BLOCKS_START` (block 2048) lines up exactly with
`DICTIONARY_MEMORY_SIZE`, so letting dictionary growth run past 2MB (as
`ALLOT`/`,`/`C,`/`2,` previously allowed) would silently corrupt live
block/user data sharing that same memory. Tightened `ALLOT`/`,`/`C,`/`2,`
to `DICTIONARY_MEMORY_SIZE` to match `ALIGN`, not the other way around.
- **`INFER-*` (`array_ptr`, `inference_words.c`)** sets `vm->error` *and*
still pushes a placeholder value anyway — violates the "error or push,
never both" shape essentially every other word in the sweep follows.