The QEMU harness can finally see userspace: prove a public sys$ entry point against a real /dev/vms (vms-1d9) - #15
Merged
Merged
Conversation
…vms-1d9)
Phase 0.5 hard barrier: the merged Kernel Executive CI job (vms-e4d) only
COPYs src/kernel/ + tests/qemu/ into its initramfs, so every test drives
/dev/vms with raw ioctls. An adversary proved that reverting a userspace
syssvc file (src/libvms/syssvc/sys_event.c) to its pre-change stub left
the harness byte-identical green -- every Phase 1/3 item is userspace
wiring the harness cannot see at all.
Adds test_syssvc_lock, statically linked against the REAL src/libvms
(musl, same OVMX_STATIC mode distro/Dockerfile.bootable already uses) and
built into the initramfs alongside the raw-ioctl test_kmod_* programs. It
calls the public sys$enq/sys$enqw/sys$deq entry points across a real
fork()'d second process and, empirically (podman build+run against real
QEMU/vms.ko):
- GREEN with production sys_lock.c: 11 suites passed, 0 failed.
- RED after reverting sys$enq/sys$enqw to an always-succeeds stub (same
defect shape as the sys_event.c regression): test_syssvc_lock's
cross-process NOQUEUE-denial and post-release-grant assertions fail
(10 suites passed, 1 failed) while every test_kmod_* stays green --
proving the ioctl tests are structurally blind to this class of bug
and the new test is not.
- GREEN again after restoring sys_lock.c.
Also:
- src/libvms/include/lksdef.h: promotes sys_lock.c's private LKSB
struct to a public header (zero behavior change) -- external callers
had no way to build the lksb parameter sys$enq/sys$enqw/sys$deq
require.
- tests/qemu/CMakeLists.txt: builds test_syssvc_lock against real
/dev/vms when present; ctest SKIP (exit 77), never a fake PASS, when
it is not (every dev/CI container -- Rule 9, Docker is not a runtime).
- .github/workflows/ci.yml: updates the kernel-executive negative-
control job's exact suite-count assertion (3 passed/7 failed -> 3
passed/8 failed), empirically re-measured against a real
NEGATIVE_CONTROL=1 build+run -- test_syssvc_lock also depends on
/dev/vms and joins the "fails honestly when absent" bucket.
Found and flagged, not fixed (out of this item's scope): src/libvms/include/lckdef.h
duplicates starlet.h's LCK$M_* flag constants with DIFFERENT, stale
values (e.g. LCK$M_NOQUEUE 0x8 vs. starlet.h's oracle-pinned 0x4) --
see the file-header comment in test_syssvc_lock.c and this item's
returned findings.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Rebased onto current origin/main (past vms-e4d) and fixed the three
merge-blockers an adversary found against round 2, all verified against
real podman build+QEMU runs (never just code-read):
B1 - THE CAPABILITY WAS NOT GENERAL. tests/qemu/Dockerfile named exactly
one binary twice (`--target test_syssvc_lock`, `cp .../test_syssvc_lock`),
so the next test_syssvc_*.c a Phase 1/3 item adds would build, ctest-SKIP
fine locally, and never reach the QEMU initramfs. Fixed:
- tests/qemu/CMakeLists.txt now globs test_syssvc_*.c and registers each
automatically via qemu_syssvc_add_test(), collecting every target name
into a GLOBAL property and exposing a single `qemu_syssvc_tests` custom
target that depends on all of them.
- tests/qemu/Dockerfile builds `--target qemu_syssvc_tests` (not a named
binary) and copies build-static/bin/test_syssvc_* by glob into the
initramfs.
- Proved generality empirically: added a throwaway test_syssvc_dummy.c
with NO Dockerfile/CMakeLists.txt edit, podman-built, and confirmed it
ran inside QEMU ("test_syssvc_dummy: 1 passed, 0 failed", FINAL RESULTS
12/0). Removed the dummy and reran -- back to 11/0, matching the new
ci.yml assertion below. init.sh's existing `/tests/test_syssvc_*` glob
already handled the run side; only the build/copy side was hardcoded.
- Added a suite-count assertion to the POSITIVE kernel-executive CI job
(previously only the negative-control job pinned a count), so a test
that silently stops being built/staged/run can no longer stay green.
B2 - THE NO-SILENT-FALLBACK PROOF WAS CIRCULAR. test_syssvc_lock bailed at
its own vms_kif_open() bootstrap and exited SKIP(77) before any sys$ call
was made, so sys_lock.c's SS$_NOSUCHDEV return path (do_enq/sys$deq) was
never actually exercised -- constraint #2 was satisfied by code reading.
Fixed: when bootstrap fails, the test now calls the PUBLIC sys$enqw and
sys$deq entry points directly (vms_kif_open() is idempotent on failure, so
this drives the real ensure_kif_open()-fails branch in sys_lock.c) and
CHECKs the returned status AND the LKSB's own status field both equal
SS$_NOSUCHDEV. A failed check now returns exit 1 (real FAIL), not a masked
77. Proved the gate can go red: injected a defect in do_enq() (fake
SS$_NORMAL success instead of SS$_NOSUCHDEV when /dev/vms is absent),
rebuilt+ran the negative-control image, watched the new assertions FAIL
("test_syssvc_lock: 1 passed, 2 failed"), then reverted and reran green.
B3 - lksdef.h WAS PRESENTED AS VMS-AUTHENTIC. Rewrote the header comment
per CLAUDE.md Rule 8: explicitly labeled an OVMX design choice, not a VMS-
published layout, citing the oracle finding that SYS$LIBRARY:STARLET.MLB
has no $LKSB macro at all (%LIBRAR-W-NOMTCHFOU) -- there is nothing
authentic to pin the byte layout against.
Also (low priority, honesty over fixing): documented in
tests/qemu/CMakeLists.txt that the ctest registration of test_syssvc_lock
SKIPs in 100% of environments where ctest runs, and is invoked directly by
init.sh (not through ctest) inside QEMU -- it buys build-graph inclusion
and an honest SKIP, not coverage, despite appearing in the ctest listing.
Verified via podman (docker is absent on this host; podman reproduces CI
exactly):
- Positive job: FINAL RESULTS 11 suites passed, 0 suites failed.
- Negative-control job: FINAL RESULTS 3 suites passed, 8 suites failed;
new no-silent-fallback CHECKs all PASS (SS$_NOSUCHDEV asserted, not a
string this program authored).
- Injected-defect run: new CHECKs FAIL as expected, proving the gate can
trip; reverted and reran green.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…e-count floor)
F1: Dockerfile, distro/Dockerfile.bootable, and tests/qemu/Dockerfile all
`COPY . <dest>` the full build context with no .dockerignore, so any local
build-*/ tree, .git history, or stray artifact in the working copy lands in
the image byte for byte -- slow, fat, and a disclosure risk. Reproduced from
this exact working copy (build-ci/, build-docker/, build-test/,
build-verify/, .git, docs/, tracking/, third-party/ all present on disk).
Added a repo-root .dockerignore excluding VCS/agent-state/historical-doc
directories verified (by grep across all CMakeLists.txt and all three
Dockerfiles) to be unread by any build step. Confirmed via `podman build`
+ exec that /src/repo no longer contains build-ci, .git, docs, tracking, or
third-party, and that all three Dockerfiles (root, bootable, qemu) still
build and smoke-test clean.
F2: the round-3 kernel-executive CI job hard-pinned
'11 suites passed, 0 suites failed', so the very next item that legitimately
adds test_syssvc_event.c would raise the true count to 12 and turn CI red
for succeeding -- defeating the glob-based generality vms-1d9 exists to
build. Replaced the exact pin with three checks that only go red when a
suite is REMOVED or FAILS, never when one is ADDED: zero-failures, a floor
of >=11 passed, and presence of every named suite's init.sh header line.
Proved both directions against real QEMU runs (not just regex review):
- Removed tests/qemu/test_kmod_access.c, rebuilt, ran in QEMU for real:
output showed '10 suites passed, 0 suites failed'; new check goes RED
(floor check) and independently RED (named-suite check, verified with
the count artificially padded back to 11 to isolate that layer).
- Restored test_kmod_access.c; added a genuine throwaway
test_syssvc_throwaway.c (deleted before this commit), rebuilt with zero
Dockerfile/CMakeLists edits, ran in QEMU for real: output showed
'12 suites passed, 0 suites failed'; new check stays GREEN.
- Rebuilt the final tree (no throwaway file) and reran in QEMU: baseline
'11 suites passed, 0 suites failed' unchanged, new check GREEN.
- Reran the negative-control image (NEGATIVE_CONTROL=1): unchanged
'3 suites passed, 8 suites failed', RC=1 -- untouched by this change.
Everything else the round-3->4 adversarial review found (SS$_NOSUCHDEV
2680 vs oracle 2312, lckdef.h bit-value drift, zero production callers of
vms_kif_register(), the QEMU gate's blindness to src/vmsdcl, only the lock
manager reachable) is pre-existing debt, already filed separately, and is
untouched here.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… (vms-1d9)
Round 4 fixed two findings and introduced four. This undoes that damage and
closes the one gap that was never gated. Every claim below is proven by a real
podman build + QEMU boot, not by inspection.
G1 .dockerignore no longer excludes CLAUDE.md. Round 4's exclusion broke this
epic's OWN Rule 9 standing gate inside every image built from the repo root:
tests/integration/test_runtime_target.sh greps CLAUDE.md, and
`ctest -R runtime_target_gate` went red in the image while CI stayed green
(CI only ran ctest on the host checkout) -- a silently broken guardrail.
Now passes in-image.
G2 .dockerignore no longer excludes third-party/, which src/imgact/test/
run_tcc_{native,rms,object_native,selfhost}.sh and src/vmslink/mk_tcc.sh
hard-require. The whole list is narrowed to non-source state only, and it is
now validated by RUNNING the suite inside the resulting image rather than by
grepping the build files -- the method that missed both of the above.
G3 The suite gate is strong in both directions with nothing maintained by hand.
Round 3 pinned an exact tally (red on a legitimate addition); round 4
replaced it with a floor plus a hand-maintained name list whose own comment
said it is not updated on addition, leaving every future suite unprotected.
init.sh now prints a machine-readable per-suite verdict carrying the
binary's real exit status, and CI derives the expected suite set from
`ls tests/qemu/test_*.c`. Suite ADDED -> green; suite DROPPED -> red;
suite FAILS -> red. A monotone floor on the number of suite SOURCES catches
outright deletion, which a derived set cannot see.
G4 The negative-control job gets the same treatment. Its exact 3/8 tally pin
turned red when a legitimate test_syssvc_*.c was added (proven: 3/9).
G5 The decisive one. A real silent fallback in sys_lock.c (SS$_NORMAL instead
of SS$_NOSUCHDEV when /dev/vms is absent, in do_enq and sys$deq) left the
FINAL RESULTS accounting BYTE-IDENTICAL -- 3 passed / 8 failed, RC 1 -- and
every assertion in both jobs still passed. Cause: init.sh funnels exit 77
(honest skip) and exit 1 (assertion failure) into one counter, and CI pinned
only the total, so a per-process fake that reports success was invisible to
the entire gate. The negative control now asserts that every test_syssvc_*
suite exits exactly 77, which holds only when its device-absent
SS$_NOSUCHDEV assertions all passed. Re-injecting that exact fallback now
turns the job RED; reverting restores green (identical image SHA).
Unchanged: test_syssvc_lock.c, tests/qemu/CMakeLists.txt, sys_lock.c, lksdef.h.
The proven core -- a QEMU test linking the real libvms catching a userspace
defect all eight raw-ioctl suites miss -- was not touched, and was re-verified:
deleting one kstat_to_ss() line turns test_syssvc_lock red while all 8
test_kmod_* suites stay rc=0.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…(vms-1d9) GitHub runs `run:` steps under `bash -e`, and `grep -c` exits 1 on an empty set, so an empty derived suite list would have aborted the step with no diagnostic instead of reaching the explicit source-count floor below it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Main moved six times under this branch. Resolutions, all deliberate:
- tests/qemu/Dockerfile: keep main's libvmssys-linked devtab/procnam stage
AND this branch's cmake/musl libvms stage; widen the source COPY back to
test_*.c and dispatch build recipes BY PATTERN, never by literal name.
- tests/qemu/init.sh: main was unchanged; keep the per-suite verdict line.
- .github/workflows/ci.yml: drop main's exact tally pin ('3 suites passed,
10 suites failed') in favour of per-suite verdicts derived from the
checkout. The pin's comment argued the brittleness was the feature; it
also turns the job red for a legitimately ADDED suite, which is the thing
this item exists to make cheap for vms-ef1/vms-as1/vms-pv1.
- src/libvms/syssvc/sys_lock.c: reverted to main. This branch's promotion
of struct lksb to a public src/libvms/include/lksdef.h is DROPPED -- the
LKSB has no VMS-published byte layout (no $LKSB macro exists in the
oracle's STARLET.MLB), so per Rule 8 it is an OVMX design choice needing
operator sign-off, and the item says not to absorb that here. The test
declares its own LKSB storage, which is what OpenVMS callers do anyway.
- tests/qemu/test_syssvc_lock.c: device-absent assertions no longer pin
SS$_NOSUCHDEV. vms-0ff deleted that path as a fiction; pinning it would
freeze a superseded contract into a gate. They now assert the property
that survives the ruling -- no fabricated success.
- tests/qemu/CMakeLists.txt: add_test() removed. It reported Skipped in
100% of environments where ctest runs and was never invoked in the one
environment where it can pass. Rule 10: a permanently-skipped test is a
failing test.
…g (vms-1d9)
Round 6. Three blocking defects from the round-5 verdict, each closed and each
proven by its own MINIMAL mutation that trips that property and no other. All
runs are real podman builds + real QEMU boots on this host (aarch64, TCG, no
KVM); tools/replay_ci_kernel_executive.py executes the ci.yml assertion blocks
VERBATIM out of the YAML against the captured output, so a local proof cannot
drift from what CI runs.
1. THE GENERALIZATION IS NO LONGER FAKE.
tests/qemu/Dockerfile dispatches BY PATTERN everywhere a name was once
literal: `COPY tests/qemu/test_*.c` (was narrowed to test_kmod_*.c, so a
future non-kmod source was dropped), a case-based build dispatch, the
aggregate `--target qemu_syssvc_tests`, and a glob cp of every staged
test_syssvc_* binary. A `|| exit 1` was added to the gcc loop -- without it
a compile failure only broke that iteration and RUN exited with the LAST
iteration's status, so a suite that stopped compiling vanished silently.
The staging step now FAILS THE IMAGE BUILD if zero test_syssvc_* binaries
were staged.
PROOF: added a second suite (test_syssvc_evt.c) with ZERO Dockerfile edits
-> built, staged, RUN ("=== SUITE test_syssvc_evt rc=0 ==="), positive job
green at 13 derived suites, negative-control job green at 3/12. Then
narrowed the cp back to the literal test_syssvc_lock: the harness still
printed "ALL KERNEL MODULE TESTS PASSED" and exited 0, and the positive CI
job went RED -- "test_syssvc_evt: NEVER RAN (no verdict line)". Throwaway
suite removed.
2. THE POSITIVE JOB NOW HAS A GATE, NOT JUST AN EXIT CODE (vms-d2d).
It derives the expected suite set from `ls tests/qemu/test_*.c` at CI time
and asserts each suite's own "=== SUITE <name> rc= ===" verdict, plus a
suite-count FLOOR of 12 for the case the derived set cannot see: a source
deleted outright.
PROOF: deleted tests/qemu/test_kmod_ast.c. Harness exit 0, "ALL KERNEL
MODULE TESTS PASSED", 13 suites passed / 0 failed -- and the positive job
went RED: "only 11 suite sources under tests/qemu (expected at least 12)".
Restored.
3. THE NEGATIVE CONTROL NOW CALLS A PUBLIC sys$ ENTRY POINT AND JUDGES WHAT
IT RETURNS, not the test's own printf.
IMPORTANT SCOPE CORRECTION vs the dispatch: it does NOT assert
SS$_NOSUCHDEV. vms-0ff ruled OVMX has no executive-absent state and DELETED
sys_lock.c's per-call SS$_NOSUCHDEV returns; pinning that value would
freeze a superseded contract into a gate -- the exact failure this epic's
adversaries keep catching. What survives the ruling is a PROPERTY, not a
VMS behaviour: a public sys$ entry point must never report SUCCESS when it
did not reach the executive. The test asserts the odd/even success bit, an
empty lock ID, and prints the raw status for the record; CI pins the
suite's rc to exactly 77.
PROOF: injected a fabricated success into do_enq and sys$deq (SS$_NORMAL +
lock ID 0x1234 when the executive was unreachable). All four device-absent
assertions FAILED, rc 77 -> 1, and the negative-control job went RED naming
the cause -- while FINAL RESULTS stayed BYTE-IDENTICAL to the clean tree
("3 suites passed, 11 suites failed"), i.e. the tally pin this replaces
would have stayed green. Same mutation left the POSITIVE job green (12/12),
confirming it trips one property and not the others. Restored.
ALSO FIXED, found while proving #3: test_syssvc_lock could HANG the whole VM.
The child's post-release sys$enqw blocks in the kernel, and
src/kernel/vms_lock.c's enq_wait_sync re-arms on every signal wake without
returning to user mode -- so a child-side alarm(20) is swallowed. Measured: an
unreleased lock sat until run_tests.sh's 120s QEMU timeout, every later suite
never ran, and CI saw an unattributable timeout. The bound now lives in the
PARENT (poll-based read_bounded + WNOHANG reap), which is not blocked. With
sys$deq stubbed, the suite now fails in 20s with a named line and the harness
still reaches its own accounting (13 passed / 1 failed).
DELIBERATELY NOT DONE, and why:
- tests/qemu/CMakeLists.txt's add_test() is REMOVED. It reported Skipped in
100% of environments where ctest runs and was never invoked in the one
environment where it can pass (init.sh execs the binary directly), so it
never executed as a passing assertion anywhere. Rule 10: a permanently-
skipped test is a failing test, and a comment does not discharge it. No
coverage is lost -- add_executable keeps it in the default `all` target, so
it still breaks the host build if it stops compiling, and the QEMU job runs
and gates it. Host ctest: 40 tests, 40 passed, 0 skipped.
- src/libvms/include/lksdef.h is DROPPED and sys_lock.c is untouched. The
LKSB has no VMS-published byte layout (the oracle's STARLET.MLB has no
$LKSB macro), so per Rule 8 a shared header is an OVMX design choice
needing operator sign-off. The test declares its own LKSB storage, which is
what OpenVMS callers do anyway.
- SS$_NOSUCHDEV 2680-vs-2312, lckdef.h's nine wrong flag bits: untouched,
separately tracked.
baron-3dl
added a commit
that referenced
this pull request
Jul 30, 2026
The 12 -> 13 bump landed in this branch's conflict resolution against vms-1d9 (PR #15), which had REPLACED the exact-tally pin with per-suite verdicts plus this floor. Note in the file how the figure was obtained, so the next branch does not resolve a conflict by merging two numbers. The figure was verified by building the NEGATIVE_CONTROL=1 image on the merged tree and booting it under aarch64 QEMU TCG: === FINAL RESULTS: 3 suites passed, 12 suites failed === test_syssvc_lock rc=77, test_kmod_vmsfs* rc=0, all other test_kmod_* rc=1 and by replaying ci.yml's own verdict loop over that transcript: N_EXPECTED=13, N_SYSSVC=1, BAD empty. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
baron-3dl
added a commit
that referenced
this pull request
Jul 30, 2026
…s, so every /dev/vms ioctl failed (vms-9fc) (#16) * Bind the product to the executive: vms_kif_register() had zero callers (vms-9fc) vms_kif.h documented "open /dev/vms, then vms_kif_register()" from the day it was written. vms_kif_register() was defined and had ZERO CALLERS product- wide, and vms_module.c routes every ioctl except REGISTER through vms_proc_find_or_err(), which answers -ESRCH for an unregistered task. So every /dev/vms call OVMX made was rejected -- including sys$ENQ/$DEQ in sys_lock.c, the facility the executive-retrofit design named as "already wired" and told every implementer to copy. sys_lock.c's bind_to_executive() called vms_kif_open() and only vms_kif_open(), so the omission was propagated by design review rather than caught by it. The sequence now lives once, in kif_bind() at the kernel-interface layer, so every facility built on it inherits a registered process. It is keyed by task id, not a boolean: fork() copies TLS wholesale, so a child inherits both the parent's descriptor and the parent's "bound" mark while the executive knows it is not registered. The same comparison covers a new thread and a freshly activated image after execve(). REGISTER now ADOPTS a task the executive already knows, returning SS$_NORMAL instead of 0x1C, and does not re-apply the requested privileges. ORACLE PIN (reference lab VAX1, OpenVMS VAX V7.3, 2026-07-30): activating an image inside a process does not recreate the process and is not an error -- SHOW PROCESS/ACCOUNTING across two further activations reports the same Process ID 2020021D and name "SYSTEM" with Images activated 19 -> 21. Failed ioctls no longer all report SS$_BADPARAM ("your parameters were bad") to callers whose parameters were fine. The errno set is closed because both sides of /dev/vms are ours, and each mapped status is oracle- pinned by $SSDEF extraction plus F$MESSAGE round-trip: -EFAULT -> SS$_ACCVIO 12, -ENOMEM -> SS$_INSFMEM 292, -ENOTTY -> SS$_ILLIOFUNC 244. -ESRCH gets no status of its own (Rule 10: VMS is never without a PCB, so kif_bind makes it unreachable); if seen anyway it is SS$_BUGCHECK 676, "internal consistency failure". Same oracle run corrects SS$_ILLIOFUNC from 580 to 244 in ssdef.h and vms_errno.h: F$MESSAGE(580) is %SYSTEM-F-VASFULL, so every sys$qio that rejected an unimplemented function code was reporting address-space exhaustion. tests/qemu/test_kmod_bind.c proves it against a real /dev/vms in QEMU. Its positive path never calls vms_kif_open() or vms_kif_register() before using a facility -- supplying the step the product forgets is exactly how this defect stayed invisible. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Open /dev/vms unconditionally in kif_bind, and re-anchor the Rule 9 controls (vms-9fc) Two things the standing gates caught in the first commit, both correctly. 1. tests/integration/test_runtime_target.sh flagged `if (vms_kif_open() < 0) return;` in kif_bind(): branching on whether the executive could be opened is the deleted fallback in a new place. The condition is unreachable by construction -- PID 1 refuses to bring the system up without /dev/vms -- so under Rule 10 it must not be handled at all. The open is now unconditional and its result discarded. If a descriptor somehow is not available, REGISTER fails, vms_bound_tid stays 0, and the caller gets SS$_BUGCHECK; nothing is ever told it is bound when it is not. 2. tests/integration/test_runtime_target_negctl.sh went 13/20: all seven of its check-3c controls reported "the evasion was CERTIFIED, not caught". The gate was fine. The CONTROL was broken: its fixture injects an evasion into sys_lock.c's bind_to_executive(), a function this branch deletes, so the injection silently became a no-op and the gate was being run against an unmutated tree. Re-anchored to src/libvmssys/vms_kif.c's kif_bind(), which is where binding lives now. 20/20 again, with the same seven evasions genuinely caught. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * One PCB per PROCESS, not per Linux thread (vms-9fc) Round 1 wired the product to the executive but keyed the binding on the THREAD: kif_bind() marked itself with vms_sys_gettid() and vms_module.c registered and looked up on current->pid. Every thread that touched any vms_kif_* entry point therefore minted its own executive process. Measured in QEMU by the veracity adversary: a pthread of one image saw linux_pid=119 while getpid()=117, saw prcnam='' after the main thread's $SETPRN returned SS$_NORMAL, saw event flag 41 CLEAR after the main thread SET it, and could not $DEQ its own process's lock. That is not VMS. A VMS process has ONE PCB and its kernel threads SHARE it -- that shared residency is the entire meaning of a process-wide event flag cluster, a process name and a process's lock ids. It is Rule 11's facade shape inverted: per-thread state pretending to be per-process. It was also newly reachable, because before round 1 nothing registered at all. Fixed at the identifier, on both sides of /dev/vms: - src/kernel/vms_module.c keys the process table on current->tgid and pins task_tgid(current), the Linux process-wide identity (== getpid(2)). - src/libvmssys/vms_kif.c's TLS mark records vms_sys_getpid(), so userspace and the executive agree on what the registration is a property of. The fork/exec/new-thread cases the mark exists to catch all still work: a forked child's inherited mark mismatches its new pid, and fresh or wiped TLS reads 0. - vms_dev_release() no longer deletes a live process's PCB when one of its threads exits: it now requires the last member of the thread group. Anything that misses is reaped lazily as before, and the reaper's liveness test is now whole-process (the pinned tgid resolves to the group leader, which the kernel does not release while any thread runs). PROVED, not asserted. tests/qemu/test_kmod_bind.c gains suite 7: the main thread names the process, sets a local event flag and takes a lock, then a pthread -- using only public entry points, no explicit open or register -- must report the SAME executive entry, the SAME name, the SAME flag state and must be able to release the main thread's lock. Against a real /dev/vms with vms.ko insmod'd: 42 passed, 0 failed; whole harness 14 suites passed, 0 failed. MINIMAL MUTATION for this property and no other -- vms_module.c current->tgid -> current->pid, rebuilt and rerun in QEMU: exactly 5 assertions red, ALL in suite 7 (37 passed / 5 failed), every other suite still green including vms-8019's test_kmod_procnam (30 passed, 0 failed). No single-threaded test can see this defect, which is why suites 1-6 and every other kernel suite pass either way. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Finish the SS$_ILLIOFUNC correction: F$MESSAGE could not name it (vms-9fc) Round 1 corrected SS$_ILLIOFUNC 580 -> 244 in ssdef.h and vms_errno.h and claimed "consumers name the symbol, so none breaks". That was FALSE. DCL's F$MESSAGE table in dcl_lexical.c is keyed by NUMBER and hard-coded { 580, 'E', "ILLIOFUNC" }, so on the corrected tree: X = F$MESSAGE(244) -> "%SYSTEM-?-UNKNOWN, message code %X000000F4" X = F$MESSAGE(580) -> "%SYSTEM-E-ILLIOFUNC, illegal I/O function" OVMX could not name the status its own sys$qio returns, and still rendered "illegal I/O function" for what the oracle calls VASFULL. Severity was wrong too ('E' where the oracle says 'F'). ORACLE, RE-RUN THIS SESSION on the reference lab (OpenVMS VAX V7.3, node VAX1 -- not recalled from the previous round's transcript), two ways: LIBRARY/EXTRACT=$SSDEF/OUTPUT=... SYS$LIBRARY:STARLET.MLB + SEARCH $EQU SS$_ILLIOFUNC 244 $EQU SS$_VASFULL 580 F$MESSAGE 244 -> %SYSTEM-F-ILLIOFUNC, illegal I/O function code 580 -> %SYSTEM-F-VASFULL, virtual address space is full So: a 244/'F'/ILLIOFUNC row is added, the 580 row becomes VASFULL/'F', status.c's ILLIOFUNC text gains the oracle's "code", and docs/api-system-services.md's 580 row is corrected. BOUND SO IT CANNOT DESYNCHRONISE AGAIN. A _Static_assert in dcl_lexical.c ties the table's 244 row to SS$_ILLIOFUNC itself, so moving the constant again breaks the build instead of silently leaving F$MESSAGE unable to name a status OVMX hands out. TESTS, run not asserted (ctest 40/40 pass, 0 failed): - new tests/dcl/test_lexical_message.sh asserts the round trip through the built DCL. - three mutations, each rebuilt and rerun: dropping the 244 row reproduces the adversary's exact "%SYSTEM-?-UNKNOWN, message code %X000000F4"; restoring 580/'E'/ILLIOFUNC turns the two VASFULL assertions red and nothing else; setting SS$_ILLIOFUNC back to 580 fails the build on the static assertion. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * A negative control that silently no-ops must fail loudly (vms-9fc) Round 1's re-anchor of check 3c was correct but treated the symptom. The COUPLING is structural: every control here is a sed/awk injection anchored to a source line, and an anchor that stops matching -- a renamed, reshaped or deleted function -- makes the injection a SILENT NO-OP. The sandbox stays unmutated, the gate correctly stays GREEN, and every downstream control then reports "the evasion was CERTIFIED, not caught", blaming the gate for a broken fixture. That is exactly what happened when this branch deleted sys_lock.c's bind_to_executive(): 13/20, all seven 3c controls red against a perfectly healthy gate. A future rename reproduces it verbatim. So expect_red now takes the file its mutation was supposed to change and refuses to interpret the gate's verdict until that file demonstrably differs from its pristine copy, naming the FIXTURE rather than the gate when it does not. A control that silently tests nothing is worse than none: it reports that evasions are caught. The detector gets its own control, using the real anchor that broke (sys_lock.c's deleted bind_to_executive). RUN, not asserted: 21 passed / 0 failed (was 20 + the new meta-control). And verified the detector actually fires -- a copy of this script with 3c's awk anchor pointed at a function that does not exist reports "BROKEN FIXTURE (not a broken gate)" for all seven 3c cases instead of certifying them. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Record how the negative-control floor was recomputed (vms-9fc) The 12 -> 13 bump landed in this branch's conflict resolution against vms-1d9 (PR #15), which had REPLACED the exact-tally pin with per-suite verdicts plus this floor. Note in the file how the figure was obtained, so the next branch does not resolve a conflict by merging two numbers. The figure was verified by building the NEGATIVE_CONTROL=1 image on the merged tree and booting it under aarch64 QEMU TCG: === FINAL RESULTS: 3 suites passed, 12 suites failed === test_syssvc_lock rc=77, test_kmod_vmsfs* rc=0, all other test_kmod_* rc=1 and by replaying ci.yml's own verdict loop over that transcript: N_EXPECTED=13, N_SYSSVC=1, BAD empty. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Removes the Phase-0.5 barrier. The kernel-executive CI job proved the kernel side and was structurally blind to userspace — which is the half every Phase 1 and Phase 3 item delivers.
The defect
tests/qemu/Dockerfilecopiedsrc/kernel/and the test sources and nothing else. The initramfs contained zero bytes of the userspace system-service layer. Proven empirically: an adversary revertedsrc/libvms/syssvc/sys_event.cwholesale to its pre-change stub, rebuilt, reran — byte-identical output, exit 0. Deleting 100% of an item's production code changed nothing the gate could see.What landed
test_syssvc_lock.cexercisessys$enq/sys$enqw/sys$deq— public entry points through the reallibvms— against a real/dev/vmsin QEMU, with a genuine two-process cross-release assertion.The decisive proof is a facade injection. A reviewer replaced
do_enq/sys$deqwith a per-process userspace lock table — the exact Rule 11 facade this epic exists to kill — and the first five assertions passed. It failed only at "child:sys$enqwEX granted after parent'ssys$deq". A single-process-shaped test would have been fooled; the cross-process release assertion is what catches it. Kernel-side corroboration:dmesgshowsregistered process pid=112andpid=113— two genuinely distinct executive processes.Three structural fixes, each with its own minimal mutation
test_syssvc_event.cwould have built locally, ctest'd fine, and then been silently not built, not staged and not run, while CI stayed green. Now pattern-dispatched throughout. Proven both ways: a second suite added with zero Dockerfile edits was built, staged and ran; narrowing thecpback to a literal name turned the job RED with "NEVER RAN (no verdict line — not built, or not staged into the initramfs)".vms-d2d). It derives the suite set from the tree and asserts each suite's own verdict line, plus a source-count floor. Proven by deletingtest_kmod_ast.c: the harness still printedALL KERNEL MODULE TESTS PASSEDand exited 0 — and the job went RED with "only 11 suite sources (expected at least 12)".cannot open /dev/vms, which matched the test's own bootstrap message — an assertion satisfiable by something other than the behaviour under test. It now calls a publicsys$entry point with the device absent and judges what comes back. Proven by injecting a fabricated success: all four assertions failed and the job went RED, whileFINAL RESULTSstayed byte-identical — i.e. the hand-maintained tally pin this replaces would have stayed green.Also fixed
sys$enqwblocks in the kernel, andenq_wait_syncre-arms on every signal wake without returning to user mode, so a child-sidealarm()is swallowed — measured, not assumed. An unreleased lock sat until the 120 s QEMU timeout, every later suite never ran, and CI saw an unattributable timeout indistinguishable from a boot panic. The bound now lives in the parent, which isn't blocked.tools/replay_ci_kernel_executive.pyextracts theci.ymlassertion blocks verbatim and runs them against captured output, so a local proof cannot drift from what CI runs.Deliberate removals
tests/qemu/CMakeLists.txt'sadd_test()— it reported Skipped in 100% of environments where ctest runs and never ran in the one where it can pass. Rule 10: a permanently-skipped test is a failing test. No coverage lost:add_executablekeeps it inall(it still breaks the host build if it stops compiling) and the QEMU job runs and gates it. Host ctest: 40/40, zero skips.lksdef.hdropped — the LKSB has no VMS-published byte layout, so a public header would be an OVMX design choice needing sign-off (Rule 8). The test declares its own LKSB storage, which is what OpenVMS callers do anyway.Known, disclosed
Production code still has zero callers of
vms_kif_register()(vms-9fc, in flight), so this proves the lock plumbing works through the public entry points, not that a normally-activated image can use them yet. Stated loudly in the file header and inci.yml.🤖 Generated with Claude Code