#256 Heap strings lack in-place NUL termination: any 8-aligned computed path can ENOENT on a real file (asdf perform root cause)
closedROOT CAUSE of the asdf perform SIGSEGV/hang (with cave#215's silent-spin as the amplifier), pinned 2026-07-08.
asm-resolve-path-rcx-to-rdi hands the kernel a pointer directly into the byte-vector data area, relying on IN-PLACE NUL termination. Quote-pool string literals carry an explicit trailing NUL (cave#196). Heap strings do NOT: concatenate results, cached pathname namestrings (%pn-ns-build), anything computed. When such a path's length is NOT a multiple of 8, the 8-alignment zero padding after it terminates the C string by luck; when the length IS 8-aligned, there is no pad byte, the kernel reads the nonzero neighbor byte(s), and open(2) sees path+garbage.
Observed: asdf load-source-op perform computed a perfectly correct 104-byte namestring (104 % 8 = 0) for an existing file; open returned -2 ENOENT; the pre-#215-fix loader then read from the negative fd, stamped -errno into the buffer length header, and the reader walked the garbage-length buffer — minutes of 100% CPU, then SIGSEGV. Shallow probes never reproduce because a quiet heap's neighbor bytes are virgin zeros; it takes a deep session (asdf-scale) for the byte after the string to be nonzero.
Fixed so far (this campaign):
- stdlib file functions were already protected via %c-path (copy + explicit NUL).
- The LOADER was not: ag-load-source-action / ag-file-is-fco-p passed raw strings to open-file. Fixed with ag-path-cstr, a warm-side twin of %c-path (the loader cannot call %c-path — stage2 boots stdlib THROUGH the loader).
Remaining exposure / proper fix candidates:
- Any other direct open-file/%sys-stat/delete callers on computed strings outside %c-path (audit).
- GENERAL FIX (preferred): NUL-terminate heap strings at allocation (concatenate / %pn-ns-build / make-string family allocate len+1 data bytes, keep length = len) — makes every string a valid C string like pool literals, kills the class.
- Alternative: harden asm-resolve-path-rcx-to-rdi to copy through a fixed scratch + NUL (costs a copy per syscall, no allocator change).
Comments (2)
FIXED (verified 2026-07-11): computed concatenated paths open real files — (file-exists-p (concatenate 'string "/etc/" "hostname")) => T; %c-path NUL-terminates (cave#196 note in stdlib). asdf perform now loads real component files end-to-end (alexandria, today), which was this issue's blocking symptom.
Loader fix LANDED (ag-path-cstr): asdf perform load-source-op now loads and RUNS a component end-to-end — (foo::hello) => 42 after perform. General allocator-level fix (NUL-terminate heap strings at construction) remains open as this issue. asdf-funcl.patch regenerated with the perform-lisp-load-source port.