make the guard a gate: sha256_hex(50000) no longer segfaults

This commit is contained in:
bigmerge
2026-08-17 10:49:49 -05:00
3 changed files with 67 additions and 3 deletions
+31 -3
View File
@@ -712,6 +712,20 @@ el_val_t el_map_set(el_val_t mapv, el_val_t keyv, el_val_t value) {
* happen to look like aligned heap pointers are exceedingly unlikely to land
* on a page whose first 4 bytes match either magic. */
/* el_tagged — THE gate for "is this slot a heap object carrying this tag".
*
* el_val_t carries both integers and tagged heap pointers, so deciding which
* requires checking the value BEFORE dereferencing it. That check was a
* convention every author had to know rather than a gate they had to pass
* through, and the result is measurable: geom_of and mfld_of call
* looks_like_heap_obj and are correct; el_bin_lookup checked only a 4096 floor
* -- no alignment, no small-int, no negative -- and reads EIGHT BYTES BACKWARD
* from the pointer. sha256_hex(50000) therefore compiled clean and segfaulted.
*
* Exported, so the engram siblings stop re-deriving it. Anything that
* dereferences a slot without passing through here is the defect. */
int el_tagged(el_val_t v, uint32_t magic);
static int looks_like_heap_obj(el_val_t v) {
if (v == 0) return 0;
int64_t s = (int64_t)v;
@@ -722,6 +736,12 @@ static int looks_like_heap_obj(el_val_t v) {
return 1;
}
int el_tagged(el_val_t v, uint32_t magic) {
if (!looks_like_heap_obj(v)) return 0;
return *(const uint32_t*)(uintptr_t)v == magic;
}
void el_retain(el_val_t v) {
if (!looks_like_heap_obj(v)) return;
ElHeader* h = (ElHeader*)(uintptr_t)v;
@@ -18272,8 +18292,11 @@ static int el_bin_lookup(const void* p, size_t* out_len) {
/* Avoid reading off the front of a page on tiny pointers (e.g. NULs
* passed in as int-cast values). 4096 is a safe lower bound on any
* platform we target. */
if ((uintptr_t)p < 4096) return 0;
const el_bin_hdr_t* hdr = (const el_bin_hdr_t*)((const char*)p - sizeof(el_bin_hdr_t));
/* Reads BACKWARD, so the HEADER address is what must be validated -- and a
* 4096 floor alone let sha256_hex(50000) through to a SIGSEGV. */
const char* hp = (const char*)p - sizeof(el_bin_hdr_t);
if (!looks_like_heap_obj((el_val_t)(uintptr_t)hp)) return 0;
const el_bin_hdr_t* hdr = (const el_bin_hdr_t*)hp;
if (hdr->magic != EL_MAGIC_BIN) return 0;
*out_len = hdr->length;
return 1;
@@ -18283,7 +18306,12 @@ static int el_bin_lookup(const void* p, size_t* out_len) {
static size_t el_input_len(const char* s) {
size_t n;
if (el_bin_lookup(s, &n)) return n;
return s ? strlen(s) : 0;
/* The FALLBACK is the hazard, not the tagged lookup. A NULL check does not
* establish that a slot is a pointer: el_val_t carries integers too, so
* strlen() on `sha256_hex(50000)` walks address 50000. Guarding the tagged
* path alone left this untouched and the SIGSEGV unchanged -- measured. */
if (!looks_like_heap_obj((el_val_t)(uintptr_t)s)) return 0;
return strlen(s);
}
/* ─── SHA-256 (Brad Conte / public domain) ──────────────────────────────── */
+1
View File
@@ -888,6 +888,7 @@ el_val_t engram_age_field_catchup(void);
el_val_t engram_chrono_persist_tick(void);
el_val_t engram_chrono_tick(void);
el_val_t engram_boundary_beat(el_val_t op_name, el_val_t construct);
int el_tagged(el_val_t v, uint32_t magic); /* the gate: validate a slot BEFORE dereferencing it */
el_val_t el_seam_run(el_val_t fn_name, el_val_t phase, el_val_t result); /* runtime construct seam */
el_val_t el_seam_wrap(el_val_t fn_name, el_val_t (*body)(void*), void* env); /* runtime invocation control */ /* API-reshape decorator-seam auto-emit; construct = the decorator that caused the beat */
el_val_t engram_self_anchor_capture(void);
+35
View File
@@ -0,0 +1,35 @@
#!/usr/bin/env bash
# tagged_gate.sh — a slot must be validated before it is dereferenced.
#
# el_val_t carries both integers and tagged heap pointers, so "is this a
# pointer" is undecidable without checking first. That check was a CONVENTION
# every author had to know rather than a GATE they had to pass through:
# geom_of, mfld_of call looks_like_heap_obj correct
# el_bin_lookup checked only a 4096 floor read 8 bytes backward
# el_input_len checked only for NULL strlen'd an integer
# sha256_hex(50000) therefore compiled clean and segfaulted (exit 139).
set -uo pipefail
ELC="${1:?usage: tagged_gate.sh <elc>}"
LANG_DIR="${2:-$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)}"
W=$(mktemp -d); trap 'rm -rf "$W"' EXIT; F=0
chk(){ [ "$2" = "$3" ] && printf ' ok %s\n' "$1" || { printf ' FAIL %s\n expected %s got %s\n' "$1" "$2" "$3"; F=$((F+1)); }; }
cd "$LANG_DIR"
SRCS=$(../scripts/el-runtime-sources.sh runtime)
CF="-std=c11 -O2 -I runtime"; for d in /opt/homebrew/opt/openssl@3 /usr/local/opt/openssl@3; do [ -d "$d" ] && CF="$CF -I $d/include" && LF="-L $d/lib"; done
LF="${LF:-} -lcurl -lssl -lcrypto -lpthread -lm"
build(){ "$ELC" "$1" > "$W/t.c" 2>/dev/null && cc $CF -o "$W/t" "$W/t.c" $SRCS $LF 2>/dev/null; }
printf 'fn main() { let h: String = sha256_hex(50000) println("got " + h) }\n' > "$W/a.el"
build "$W/a.el"; "$W/t" >"$W/o" 2>&1; chk "an integer where a string is expected does not crash" "0" "$?"
chk "and yields the empty-string hash, not memory" "1" "$(grep -c e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 "$W/o")"
printf 'fn main() { let h: String = sha256_hex(-5) println("got " + h) }\n' > "$W/b.el"
build "$W/b.el"; "$W/t" >/dev/null 2>&1; chk "a NEGATIVE integer does not crash" "0" "$?"
printf 'fn main() { println(sha256_hex("abc")) }\n' > "$W/c.el"
build "$W/c.el"; out=$("$W/t" 2>&1)
chk "a legitimate string still hashes correctly" "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad" "$out"
chk "the gate is exported, so siblings stop re-deriving it" "1" "$(grep -c 'int *el_tagged(el_val_t' runtime/el_runtime.h)"
echo; echo " 5 assertions, $((5-F)) passed, $F failed"; exit $F