Skip to content

dcl: MOUNT/DISMOUNT really mount(2)/umount(2) vmsfs volumes (vms-651) - #277

Merged
baron-3dl merged 3 commits into
mainfrom
worktree-agent-a5187fa021a23a823
Aug 10, 2026
Merged

baron-3dl merged 3 commits into
mainfrom
worktree-agent-a5187fa021a23a823

Conversation

@baron-3dl

Copy link
Copy Markdown
Contributor

Summary

  • Deletes the cmd_mount/cmd_dismount facade (src/vmsdcl/dcl_cmd_misc.c, plus the process-local vms_device_table[] in dcl_builtin.c) that never called mount(2), faked its device table, and used getcwd() as the "mount path".
  • MOUNT DKA100: now checks PRV$M_MOUNT through the executive (new oracle-pinned privilege bit, wired into VMS_PRV_M_ENFORCED), resolves the unit's backing block device via vms_kif_disk_resolve, claims the unit in the executive's device table (vms_kif_alloc, wired for the first time), and mount(2)s the backing device as vmsfs. DISMOUNT mirrors this with umount(2). "Already mounted" is answered from /proc/mounts, not a per-process struct.
  • Privilege-drop wrinkle (measured, not assumed): LOGINOUT drops every VMS session to its SYSUAF UIC (no Linux capability survives that), so the calling process cannot itself call mount(2)/umount(2) (both require CAP_SYS_ADMIN) regardless of VMS privilege. A kernel-mediated mount was attempted first and does not work on this platform — do_mount/path_mount/do_umount/path_umount are absent from the kernel's exported-symbol table entirely (verified against the real Module.symvers). Settled on the classic Unix shape instead: tools/vms_mount_helper.c, a small setuid-root binary that re-derives its own PRV$M_MOUNT authorization from the executive before doing anything privileged, so it never trusts its caller.
  • Mount points are pre-created as root by a new provision_disk_mount_points() in ovmx_init.c, before any session drops privilege.

Test plan

  • tests/dcl/test_mount.sh / test_show_device.sh rewritten for the honest-failure path (ctest, no /dev/vms) — both pass.
  • ctest full suite: 124/125 pass (the one pre-existing failure, env_identity_census_negctl, is an unrelated timeout on this host, unchanged by this PR).
  • kif_caller_census, terminal_identity_gate, show_device_rows_gate/negctl all green (privilege bit + new wrapper wiring correctly detected).
  • Ground-source QEMU e2e (tests/qemu/test_mount_e2e.sh, opt-in OVMX_QEMU_FULL_E2E=1, real vms.ko+vmsfs.ko): 17/17 assertions pass — MOUNT/COPY/DISMOUNT/re-MOUNT round-trip, /proc/mounts reflects mount state correctly, a full QEMU process restart against the same disk file proves the file persisted to disk (not tmpfs), and a nonexistent-unit negative control fails honestly with no %MOUNT-I-MOUNTED.
  • Native VMS-toolchain build (distro/Dockerfile.bootable, LINK.EXE graph) builds clean with the new symbol-vector entries.

🤖 Generated with Claude Code

Co-Authored-By: Claude Opus 4.8 (1M context) noreply@anthropic.com

alice and others added 3 commits August 10, 2026 11:21
KILLED the cmd_mount/cmd_dismount facade (src/vmsdcl/dcl_cmd_misc.c:1597):
it never called mount(2), wrote a per-process userspace device table
(struct vms_device / vms_device_table[], src/vmsdcl/dcl_builtin.c -- deleted,
its only two readers), used getcwd() as the "mount path", and printed
%MOUNT-I-MOUNTED unconditionally.

MOUNT DKA100: now: checks PRV$M_MOUNT through the executive (vms_kif_chkpriv,
not getuid() -- PRV$M_MOUNT is a new privilege bit, oracle-pinned public
$PRVDEF position 17, wired into VMS_PRV_M_ENFORCED); resolves the unit's
backing block device through the executive (vms_kif_disk_resolve, vms-3e8);
claims the unit in the executive's device table (vms_kif_alloc, wired here
for the first time, so a second process cannot mount the same unit
concurrently); and mount(2)s "/dev/<backing>" as vmsfs at the unit's mount
point. DISMOUNT is the mirror image with umount(2). "Already mounted" is
answered from the kernel's own /proc/mounts, not a per-process field.

THE PRIVILEGE-DROP WRINKLE, measured rather than assumed: LOGINOUT
setuid()/setgid()'s every VMS session onto its SYSUAF UIC (tools/
vms_login.c), so by the time MOUNT runs the calling process holds no Linux
capability regardless of PRV$M_MOUNT -- mount(2)/umount(2) require
CAP_SYS_ADMIN unconditionally. A kernel-mediated mount (a vms.ko ioctl
calling the mount-syscall internals with elevated creds) was tried and does
NOT work on this platform: fs/namespace.c's do_mount()/path_mount()/
do_umount()/path_umount() are absent from this kernel's exported-symbol
table entirely (confirmed against the real Module.symvers, not guessed),
and the public fs_context replacement API has no exported way to graft a
mount onto a path in a process's namespace. The settled design is the
classic Unix shape instead: tools/vms_mount_helper.c, a small setuid-root
binary that does only mount(2)/umount(2) and re-derives ITS OWN
authorization from the executive (vms_kif_register_continue() +chkpriv)
before doing anything privileged, so it never trusts its caller -- direct
invocation registers as a fresh, unauthenticated process and is refused.
cmd_mount/cmd_dismount fork, continue this session's identity onto the
child, and exec the helper.

Mount points are pre-created as root by src/ovmx_init/ovmx_init.c's new
provision_disk_mount_points() (enumerating DC$_DISK units via the SAME
vms_kif_devscan() table MOUNT reads) before any session drops privilege --
mkdir(2) is a DAC check a de-privileged session can't satisfy against
root-owned /mnt either.

Ground-source proof (tests/qemu/test_mount_e2e.sh + run_mount_e2e.sh, real
vms.ko+vmsfs.ko, opt-in OVMX_QEMU_FULL_E2E=1): MOUNT DKA100:, COPY a real
file onto it, DISMOUNT, re-MOUNT -> file still there; /proc/mounts shows
the vmsfs mount while mounted and not after DISMOUNT; a full QEMU process
restart against the same disk file -> the file survives, byte-identical
(proves the disk, not a tmpfs); MOUNT of a nonexistent unit (DKA200:) fails
honestly with no %MOUNT-I-MOUNTED. 17/17 assertions pass. tests/dcl/
test_mount.sh and test_show_device.sh (ctest, no /dev/vms) are rewritten to
assert the honest-failure path instead of the deleted facade's success
message -- MOUNT's own liveness anchor changed from "the facade always
succeeds" to a bare WRITE, since MOUNT now asks the executive too and fails
the same way SHOW DEVICE does with no executive present.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
VMS_PRV_M_ENFORCED gained PRV$M_MOUNT (bit 17, right after WORLD's bit 16)
in the previous commit -- cmd_mount/cmd_dismount really gate on it now, so
it belongs in the enforced set F$GETJPI CURPRIV/AUTHPRIV render (they walk
VMS_PRV_M_ENFORCED bit by bit, ascending). SYSTEM/ALL's rendered string
gains ",MOUNT" at the end. Measured against a real CI run (Kernel Executive
+ VMS User Acceptance Test jobs both failed on this exact string before the
fix): three hardcoded expectations updated to match --
tests/qemu/test_syssvc_ident.c scenario F, its facility_defects.sh manifest
line, and tests/uat/vms_session_qemu.sh's CURPRIV/AUTHPRIV checks (whose
own comment already documented the update rule this follows).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… fixup)

The previous commit inserted an 8-line explanatory comment BETWEEN
test_syssvc_ident.c's `/* negctl-knockon: bind-client-no-register */`
marker and the CHECK() it anchors -- every other marker in this file (and
the anchoring reader in facility_defects.sh) assumes the marker sits
DIRECTLY above the statement it names, with nothing in between. Moved the
explanatory comment above the marker instead, restoring that adjacency.

Measured against a real CI run (Kernel Executive — Per-Facility Negative
Controls, shard 0/6): it reported "test_syssvc_ident.c:1851 anchors
'bind-client-no-register' but the statement under it names none of that
defect's require_fail/knock_on_fail texts" -- the reader had walked into
the explanatory comment instead of the CHECK() call two names anchors away.
The same run's "derived suite(s) NAMED BY NO defect's suites_red" report
(unrelated-looking suites: test_kmod_resdir, test_syssvc_rightslist,
test_syssvc_sysuaf_uic_base) is suspected fallout from the same desync --
this fix's own next run tells us whether that suspicion holds.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@baron-3dl
baron-3dl force-pushed the worktree-agent-a5187fa021a23a823 branch from 5b608bf to f8e5063 Compare August 10, 2026 11:32
@baron-3dl
baron-3dl merged commit 7ce6267 into main Aug 10, 2026
49 checks passed
baron-3dl added a commit that referenced this pull request Aug 12, 2026
… self-emitted tokens (#375)

A family of tests in tests/dcl/ traced to the self-emitted-token framework
(f7a0169) asserted only tokens the test script itself always printed, or
grepped the product's own hardcoded banners — so they could never fail no
matter how broken or faked the underlying behavior was. Each is re-authored
to assert a real, product-independent property with a genuine failure mode,
proven red-on-defect and green-on-correct.

- test_no_unix_leaks.sh (priority): the real guard (EXPECT_NOT on
  UNIX_LEAK_DETECTED) was written but commented out as FUTURE_EXPECT_NOT,
  and a detected leak was downgraded to a printed WARNING instead of a
  failure. Restored: any leak now sets UNIX_LEAK_DETECTED and the script
  exits non-zero.

- test_vms_messages.sh: absence of "%" output used to read as OK. Now each
  of 11 scenarios (IVVERB, NOKEYW, DIRECT, RMS FNF/RNF, NOIFBLK x2, NOLAB,
  NOGOSUB, IVKEYW) asserts the SPECIFIC ident VMS raises for that condition,
  grounded in the source that raises it. This surfaced a real bug: RENAME of
  a nonexistent file segfaulted (dcl_cmd_file.c never included
  dcl/vms_messages.h, so vms_strerror() had no prototype and its pointer
  return value was truncated under implicit int-return assumption) — fixed
  with the missing include.

- test_help_content.sh: HELP's listing does come from the live verb table,
  but the test never checked HELP actually dispatches on its argument. Added
  differential checks: HELP SHOW must produce the SHOW-specific
  Subcommands: block (not the generic listing), and an unknown topic must
  produce the real %DCL-W-NOHELP.

- test_show_memory.sh: cross-checks SHOW MEMORY's page count (converted
  back to KB) against /proc/meminfo, read independently by the script, 2%
  tolerance.

- test_sysgen.sh: removed the self-fulfilling fallback that ECHOED the
  exact literals ("MAXPROCESSCNT ... 64 ... 64") its own assertions checked
  for when SYSGEN.EXE wasn't found. Real check now: SET changes the value,
  SHOW reflects it (round-trip through the live working set), and an
  out-of-range SET is rejected with the real ident and leaves the value
  unchanged. Honest SKIP (SYSGEN_SKIPPED, not a fabricated pass) if the
  binary is genuinely absent.

- test_tcpip_show_version.sh: was a regex matching the command's own printf
  template. Now cross-checks the version against SHOW SYSTEM, an
  independently-coded call site that reads the same SSOT — catches drift
  between the two.

- test_mount.sh (vms-3bb): already re-armed by vms-651 (#277) with a real
  $STATUS-based check; confirmed it still fails on an injected facade
  success and needed no further change.

All 152 ctest tests pass (9 e2e intentionally skipped, unrelated to this
work); the dcl-integration suite (121 test_*.sh files) passes clean.

Co-authored-by: alice <alice@workspace.local>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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