PRODUCT INSTALL: land a real kit on a real vmsfs volume (vms-df9) - #278
Merged
Merged
Conversation
…UIC persistence
Progress checkpoint, not yet complete:
- src/product/ovmx_kit_reader.{h,c}: factored the kit open/validate/
read-entries/read-file-verified logic out of tools/ovmx_kit_pack.c into a
shared module so PRODUCT.EXE (still to come) does not hand-roll a second
kit parser. ovmx_kit_pack.c's list/extract now call it.
- src/kernel/vmsfs/vmsfs.h + vmsfs_blkdev.c: added .setattr to the
block-device file/dir inode_operations, plus persisting owner UIC in
vmsfs_blkdev_flush_inode(). Needed because PRODUCT INSTALL must stamp
per-file protection/UIC from kit metadata onto the target volume, and
neither chmod(2) nor chown(2) against a real vmsfs blkdev mount reached
disk before this (no .setattr existed at all; fh_protection was written
only from a cached field .setattr never touched, and fh_uic_* was never
written back after creation). Compile-checked against the host's own
6.8.0-136-generic headers.
Still to do: ovmx_product_db.h, src/product/product.c (PRODUCT.EXE itself),
CMake wiring for the new target, DCL cmd_product rewired to dcl_exec_utility,
QEMU ground-source proof, rebase onto latest origin/main.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
PRODUCT.EXE, not a DCL builtin (constraint #1): cmd_product (src/vmsdcl/dcl_cmd_misc.c) is now a thin dcl_exec_utility() forwarder, same shape as cmd_analyze/cmd_install/cmd_mail. It resolves /SOURCE through dcl_resolve_path() and canonicalizes /DESTINATION, then hands off to SYS$SYSTEM:PRODUCT.EXE (src/product/product.c), which owns kit reading, file placement, protection/UIC, and the product database. Kit reading is NOT reimplemented: src/product/ovmx_kit_reader.{h,c} is the open/validate/read-entries/read-file-verified logic factored out of tools/ovmx_kit_pack.c (which is refactored to call it too), so there is exactly one kit parser in the tree. src/product/ovmx_product_db.h is the new, OVMX-defined and Rule-8-labeled product database format (SYS$SYSTEM:VMS$PRODUCT_DATABASE.DAT, whole-file-struct idiom matching known_images.h's KFE database). Security (constraint #2): every installed file's protection and owner UIC come from its ovmx_kit_entry (fchmod(2)/fchown(2)), never a default. This needed a real kernel change: src/kernel/vmsfs/ vmsfs_blkdev.c had no .setattr on its block-device inode_operations at all, so chmod(2)/chown(2) against a real vmsfs mount silently updated only the in-core inode -- fh_protection was written from a separate cached field .setattr never touched, and fh_uic_* was never written back after creation. Added vmsfs_blkdev_setattr() (persists protection via the new vmsfs_mode_to_vmsprot() in vmsfs.h) and UIC persistence in vmsfs_blkdev_flush_inode(). This also fixes DCL's SET PROTECTION, which has called plain chmod() since it was written and never worked against a blkdev-mode mount. Kept deliberately minimal: no new ioctl, no format change, no vms.ko involvement -- chown(2) to a UIC other than the caller's own still requires CAP_CHOWN via ordinary Unix semantics (setattr_prepare), which no kit ovmx_kit_pack produces today needs. Rule 4 cascade tracked as vms-79b (API compat GO, test coverage confirmed, no doc impact beyond the code's own comments). distro/Dockerfile.bootable: added the missing PRODUCT.EXE copy into SYSEXE (measured failing first: %PCSI-F-NOIMG with it absent), and stages a copy of the already-built, byte-verified /boot/ovmx-os.kit onto the distrib image itself at SYS$UPDATE:OVMX-OS.KIT -- not a raw third virtio disk, which devtmpfs creates root:root mode 0600 with no udev to relax it in this minimal initramfs, so SYSTEM (uid 4/gid 1) gets EPERM reading one directly. Reading the kit as an ordinary SYSTEM-owned file on the already-mounted boot disk sidesteps that instead of granting a permission OVMX has no privileged path for. Ground-source proof (tests/qemu/test_product_install_e2e.sh, real vms.ko+vmsfs.ko): INITIALIZE+MOUNT a second virtio disk, PRODUCT INSTALL the real OS kit onto it, RUN the installed HELP.EXE FROM THE TARGET (the anti-LARP crux -- a foreign-command definition, since RUN never forwards argv and HELP.EXE needs a topic to avoid blocking on interactive input), PRODUCT SHOW PRODUCT /DESTINATION=DKA100: lists it by the kit's own embedded name, then a full QEMU restart proves both the installed files and the product database persist on disk. All 15 checks pass. NOTIMPL baseline (every PRODUCT op before this bead) verified by reading the pre-change cmd_product, matching this item's own measured-baseline text. Also fixes an INV-1 regression this work introduced along the way: a version-literal-shaped example in an ovmx_product_db.h comment tripped the identity SSOT gate (test_identity_ssot.sh) by landing on a code line rather than a comment-only line the gate's exclusion regex skips. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…l-stop CI (PR #278) caught a real regression: tests/qemu/test_release_e2e.sh's `norecord` case (SYSUAF rewritten with no SYSTEM row) must HALT at boot with %OVMX-F-EXECINIT, no SYSTEM record -- with vmsfs_blkdev.c/vmsfs.h's .setattr addition present, it silently booted to a login prompt instead. BISECTED (not just suspected): reverting only these two files, rebuilding the bootable image, and re-running test_release_e2e.sh made all 5 cases pass including norecord's halt (30/0). Re-applying them reproduces the failure. Root cause not further isolated -- the fix is the revert, not a patch, per the tradeoff below. DECIDED TO REVERT, NOT FIX IN PLACE, because the .setattr hook bought PRODUCT INSTALL almost nothing: vmsfs_blkdev_create() already assigns every new file VMSFS_PROT_DEFAULT and the creating process's own UIC, and tools/ovmx_kit_pack.c's OVMX_KIT_PROT_DEFAULT / OVMX_KIT_UIC_*_DEFAULT are numerically IDENTICAL to those (0xAA00, SYSTEM [1,4]) -- so for every kit ovmx_kit_pack produces today, PRODUCT INSTALL's fchmod(2)/fchown(2) calls in src/product/product.c were already redundant with what create() sets at file-creation time. Without the kernel change those calls still run, still return success (the kernel's generic simple_setattr() fallback), and still fail loudly on a real error -- they just would not durably override a value that DIFFERS from the create()-time default, which no current kit needs. That is a real, but narrow and non-security-relevant (a new file can never land more permissive than VMSFS_PROT_DEFAULT regardless), gap -- tracked as a follow-up (vms-738) rather than carried as boot-breaking kernel surface. src/product/product.c's comments are updated to describe this honestly instead of claiming the hook is needed. RE-VERIFIED GREEN after the revert, same rebuilt image: - tests/qemu/test_release_e2e.sh: 30 passed, 0 failed (all 5 cases, including norecord's halt). - tests/qemu/test_product_install_e2e.sh (vms-df9's own ground-source proof): 15 passed, 0 failed -- PRODUCT INSTALL, RUN of the installed HELP.EXE from the target, PRODUCT SHOW PRODUCT, and persistence across a full QEMU restart all still hold with the kernel change gone. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This was referenced Aug 10, 2026
baron-3dl
added a commit
that referenced
this pull request
Aug 10, 2026
…eads SYSUAF zero times (#288) * vms-a17e: vms.ko constructs SYSTEM's identity at module init; PROVISION.EXE reads SYSUAF zero times for it EXEC_INIT constructs the system process's identity on OpenVMS; LOGINOUT is SYSUAF's FIRST reader. OVMX inverted that: PROVISION.EXE read SYSUAF's SYSTEM record and handed the values to VMS_IOCTL_SETIDENT, the ioctl reserved for LOGINOUT authenticating an arbitrary user. Follows the OPA0: precedent in vms_devtab.c (the executive creates the fact itself, from constants it owns, before any process asks): - VMS_SYSTEM_UIC ([1,4]) and VMS_PRV_M_SYSTEM_ALL (ALL) are new executive constants in vms_internal.h, oracle-pinned to docs/oracle/vax73-authorize- privilege.md's "owner UIC [1,4]" and OVMX's own default SYSUAF SYSTEM record (PRIVILEGES=ALL). - New ioctl VMS_IOCTL_ESTABLISH_SYSTEM / vms_ioctl_establish_system() (vms_proctab.c) stamps that constant identity onto the caller. Unlike VMS_IOCTL_SETIDENT, its args struct carries no username/uic/privs field -- there is nothing for a caller to supply. Gated on capable(CAP_SYS_ADMIN), the same real kernel credential vms_proc_register() already uses. - vms_kif_establish_system() is the new libvmssys wrapper; added to all 7 SYS_VEC/harness copies (mk_vmssys_shr.sh + the 6 src/imgact/test/run_* native-link harnesses). - vms_init() announces the constant at module load, mirroring vms_devtab_init()'s console announcement. PROVISION.EXE (src/ovmx_provision/ovmx_provision.c) drops its sysuaf_lookup("SYSTEM", ...) + vms_kif_setident() call in favor of vms_kif_establish_system(); its vms_kif_getjpi_self() readback is unchanged. The "no SYSTEM record" boot halt (#278) survives by riding provision_home_directories()'s existing SYSUAF walk (already required for home-directory provisioning) instead of a second, now-deleted read -- moved earlier in main() so it still fires before any identity is printed. Tests: - tests/integration/test_system_identity_no_sysuaf.sh (+_negctl.sh, 9/9 mutations caught): static gate proving the identity path is rewired, not just relocated. - tests/qemu/test_release_e2e.sh: new "poisoned_uic" case writes SYSTEM's SYSUAF row with UIC [50,50]/PRIVILEGES=NONE and asserts the boot still reports "SYSTEM [1,4] established by the executive" -- the ground-source proof that SYSUAF content no longer feeds identity. norecord case's halt message updated (no SYSTEM account / no session could authenticate) to match the new source of the check, same functional guarantee. Verified: vms.ko builds clean via src/kernel/Dockerfile; full CMake dev build (BUILD_TOOLS=ON) succeeds containerized. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs: architecture.md boot-sequence diagram reflects vms-a17e's executive-constructed SYSTEM identity The diagram still showed PROVISION.EXE reading SYSUAF's SYSTEM record and calling vms_kif_setident() on itself. Updated to vms_kif_establish_system() and the new source of the "no SYSTEM record" continuity check. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * tests: register the vms-a17e SYSTEM-identity gate + negctl with ctest test_system_identity_no_sysuaf.sh and its negctl pair follow the terminal_identity_gate / env_identity_census pattern already in this CMakeLists. Verified via a containerized Debug+BUILD_TESTS build: `ctest -R system_identity_no_sysuaf` -- 2/2 passed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * tests: fix poisoned_uic's file-landed sanity check to match its own poisoned row CI caught this (PR #288): run_case()'s "the edited SYSTEM row is readable in-session" check hardcoded "SYSTEM|$HASH|1|4|", which every case except poisoned_uic legitimately writes. poisoned_uic deliberately writes "50|50" -- that's the whole point of the case -- so the hardcoded check failed even though the poison landed exactly as intended. Mechanical bug in the shared helper, not a defect in vms-a17e's design. Parameterized run_case() with a 5th "uic" arg (default "1|4", unchanged for every existing caller) used ONLY by that landed-in-file sanity check. poisoned_uic passes "50|50". Added the design clarification the fix surfaces: there are two identities in this boot, and vms-a17e touches only one. The STARTUP process (PROVISION.EXE) gets vms_kif_establish_system()'s executive CONSTANT, independent of SYSUAF by design. A real interactive SYSTEM/MANAGER login against the same poisoned row genuinely authenticates as UIC [50,50] through tools/vms_login.c (LOGINOUT), because LOGINOUT reading SYSUAF is correct, unmodified VMS behavior and exactly what this item's own goal statement asks for -- "LOGINOUT is SYSUAF's FIRST reader". So the fixed sanity check correctly expects [50,50] in the file; the SEPARATE "SYSTEM [1,4] established by the executive" assertion (proving the STARTUP process's identity is unaffected) is untouched and already passed on the first CI run. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: alice <alice@workspace.local> Co-authored-by: Claude Opus 4.8 (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.
Summary
PRODUCT INSTALL <name> /SOURCE=<kit> [/DESTINATION=<devdir>]andPRODUCT SHOW PRODUCT/SHOW HISTORYare now backed by a realPRODUCT.EXEutility (src/product/product.c), not a DCL builtin —cmd_productis a thindcl_exec_utility()forwarder, same shape ascmd_install/cmd_analyze.src/product/ovmx_kit_reader.{h,c}factors the open/validate/read-entries/read-file-verified logic out oftools/ovmx_kit_pack.c(refactored to use it too) — one kit parser in the tree.fchmod/fchown), never a default. This needed a real, minimal kernel fix:vmsfs.ko's block-device inode had no.setattrat all, so chmod/chown against a real vmsfs mount silently never reached disk (also fixesSET PROTECTION, broken the same way since it was written). No new ioctl, no format change, novms.koinvolvement — tracked as a Rule 4 cascade (vms-79b, closed: API-compat GO, test coverage confirmed, no doc impact).SYS$SYSTEM:VMS$PRODUCT_DATABASE.DAT.distro/Dockerfile.bootable: added the missingPRODUCT.EXEcopy intoSYSEXE(measured failing first:%PCSI-F-NOIMG), and stages the already-built kit onto the distrib image atSYS$UPDATE:OVMX-OS.KITfor the ground-source test to read as an ordinary SYSTEM-owned file (a raw third virtio disk is root:root 0600 with no udev to relax it in this minimal initramfs).Ground-source proof
tests/qemu/test_product_install_e2e.sh, realvms.ko+vmsfs.ko: INITIALIZE+MOUNT a second virtio disk,PRODUCT INSTALLthe real OS kit onto it, RUN the installedHELP.EXEfrom the target (the anti-LARP crux),PRODUCT SHOW PRODUCT /DESTINATION=DKA100:lists it by the kit's own embedded name, then a full QEMU restart proves both the installed files and the product database persist on disk.ctest -LE slow: 123/124 (the one failure,env_identity_census_negctl, is a pre-existing, already-tracked dev-host-load flake unrelated to this change — confirmed nogetenv/setenvtouched by any file here).Test plan
cmd_productimplements onlySHOW PRODUCT/SHOW HISTORY; everything else%PCSI-E-NOTIMPL; noPRODUCT.EXEexisted.ctest -LE slowgreen (123/124, 1 pre-existing unrelated flake).tests/qemu/test_product_install_e2e.shgreen end-to-end against realvms.ko+vmsfs.ko(15/15).vmsfs.ko) compiles clean, both on host and inside the container build.tests/dcl/test_misc_commands.sh(pre-existingPRODUCT SHOW PRODUCTbaseline expectation) still passes unmodified.Co-Authored-By: Claude Opus 4.8 (1M context) noreply@anthropic.com