Skip to content

Console login creates a real interactive VMS process another process can see (vms-72c) - #40

Merged
baron-3dl merged 1 commit into
mainfrom
work/vms-72c
Aug 1, 2026
Merged

baron-3dl merged 1 commit into
mainfrom
work/vms-72c

Conversation

@baron-3dl

Copy link
Copy Markdown
Contributor

⚠️ This fixes an authentication bypass on main

distro/rootfs/.../SYSUAF.DAT shipped every account with an empty password hash, and
sysuaf_authenticate() treats an empty hash as "no password required."

Measured on the unmodified image, not inferred: booting origin/main and logging in as SYSTEM with
a deliberately wrong password reached Welcome to OVMX and a $ prompt.

Username: SYSTEM
Password:
Welcome to OVMX V0.1 - OpenVMS-compatible
$

SYSTEM and GUEST now carry real SHA256 hashes, and the refusal message User authorization failure
is pinned to public OpenVMS documentation (VMS Help "Login", Example 6) — the ~/vax lab was
mid-experiment for vms-760 for this item's entire duration, checked via ps aux and never touched.

The other two facades

The login session had no VMS process name. tools/vms_login.c never called vms_kif_setprn() — a
wrapper that already existed and already had a product caller. Now called after identity is stamped,
pinned to the VAX1 oracle capture showing an interactive SYSTEM session's own SHOW SYSTEM row named
literally SYSTEM.

SHOW USERS was a per-process fabrication. Its terminal-table reader had its only writer deleted by
vms-fb9, so the table was permanently empty and the "no entries" branch invented a row from the
calling process's getpid(). Measured: SHOW USERS printed PID 00000049 for the same session whose
SHOW PROCESS, one command earlier, reported VMS pid 10000003. It now reads the executive process
table through vms_kif_procscan() — the same source SHOW SYSTEM and SHOW PROCESS already use — and
the dead file-based reader is deleted.

Rule 10

The "LOGINOUT failed to exec" path fell back to a raw, unauthenticated DCL shell. LOGINOUT.EXE is a
required, always-provisioned system file, so the condition is made unreachable rather than degraded into
a shell — the same illegal-third-answer shape as the empty-hash default.

The UAT was extended, not weakened

52/52, up from 47, reproduced twice for determinism. A wrong-password negative test with an anchored
refusal; a SHOW USERS assertion cross-checked against the session's own real VMS pid extracted from
its SHOW PROCESS response rather than re-typed as a literal; and the pre-existing vms-d0e divergence
tripwire converted into a real VMS-matching assertion now that the login-session case is answered.

Verification

ctest 50/50 · QEMU 28 suites / 0 failed · executive-absent control red for the right reason · product-boot
UAT 52/52 twice.

Filed, not fixed: vms-d0f5 — SPAWN fails on the second invocation in a session regardless of command
content, proven by a minimal probe (three SPAWN SHOW TIME: #1 succeeds, #2 and #3 fail identically).
That blocked a cross-process $GETJPI-by-name check at the command layer; the mechanism is proven instead
at the executive layer by test_syssvc_procnam.c.

Closes vms-72c.

🤖 Generated with Claude Code

…can see (vms-72c)

Measured the gap on a real QEMU boot before changing anything: SHOW SYSTEM/
SHOW PROCESS already read the executive process table honestly (vms-8019),
and SHOW TERMINAL/job-terminal binding already worked (vms-d0b) -- but three
real facades remained.

1. Authentication was vacuous. distro/rootfs/.../SYSUAF.DAT shipped every
   account with an EMPTY password hash, and sysuaf_authenticate() treats an
   empty hash as "no password required" -- so any password, including a
   deliberately wrong one, authenticated successfully. Reproduced directly
   against a real QEMU boot (SYSTEM + a wrong password reached a DCL prompt).
   Fixed by giving SYSTEM and GUEST (the accounts the UAT actually drives)
   real SHA256 hashes; refusal message ("User authorization failure") pinned
   to a public OpenVMS documentation transcript since the ~/vax lab was
   mid-use for an unrelated cluster experiment for the whole of this item.

2. The login session had no VMS process name. tools/vms_login.c never
   called vms_kif_setprn() (which already existed and already had a
   product caller for $CREPRC), so SHOW PROCESS printed Process name: ""
   for a real, authenticated, terminal-owning session. Closed by calling
   vms_kif_setprn(rec->username) after identity is stamped, pinned to the
   VAX1 oracle capture showing the interactive SYSTEM session named
   literally SYSTEM (docs/oracle/vax73-show-system-process.md Section 1).
   Distinct from vms-d0e's still-open, broader question (default naming
   for a bare $CREPRC with no explicit prcnam).

3. SHOW USERS was a per-process facade. Its terminal-allocation-table
   reader (vms_term_list()) had had its only writer deleted by vms-fb9,
   so the table was permanently empty and the "no entries" branch
   fabricated a row from the CALLING process's own context -- getpid(),
   not the VMS pid. Measured: SHOW USERS printed PID 00000049 for the
   same session SHOW PROCESS reported as VMS pid 10000003. Rewritten to
   read the executive process table via vms_kif_procscan(), the same
   source SHOW SYSTEM/SHOW PROCESS already use; the dead file-based
   reader is deleted along with its now-unused writer-less table.

4. The LOGINOUT-exec-fails fallback to raw, unauthenticated DCL is
   deleted (Rule 10): LOGINOUT.EXE is a required, always-provisioned
   system file, so the condition is made unreachable rather than handled
   by a plausible-looking degraded shell.

Extended tests/uat/vms_session_qemu.sh (not weakened): a wrong-password
negative test with anchored refusal/non-grant checks, a SHOW USERS
assertion cross-checked against the session's own real VMS pid, and the
vms-d0e known-divergence tripwire for SHOW PROCESS's Process name field
converted to a real VMS-matching assertion now that this item lands it
for the login path. 52/52 UAT checks pass (up from 47), reproduced twice.

A cross-process $GETJPI-by-name UAT check (SPAWN a second time in the
same session) was written, built, and run against a real boot -- and hit
a pre-existing, unrelated DCL defect instead: the second SPAWN in a
session fails outright (%DCL-E-CREPRC) regardless of command content,
reproduced three ways including a minimal isolated probe. Not this item's
to fix; reported as a finding. The cross-process $GETJPI-by-name
mechanism itself is proven at the kernel-executive layer independent of
DCL/SPAWN (tests/qemu/test_syssvc_procnam.c, pre-existing).

Verified: host ctest 50/50, QEMU kernel-executive test suite 28/28
suites, executive-absent negative control (3 passed / 25 failed exactly
as its own harness expects), product-boot UAT 52/52 (x2).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@baron-3dl
baron-3dl merged commit 790dc22 into main Aug 1, 2026
35 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant