NetHack on a VAX

NetHack on a VAX

NetHack 3.4.3, built from the official source on my VAXstations with the distribution’s own VMS build procedures and DEC C, installed for all users as a kit with an installer and an uninstaller, and set up so that players cannot read, delete, rename or overwrite each other’s save files without anyone being given a privilege. Version 3.4.3 (December 2003) is the last NetHack whose VMS port supports the VAX; 3.6 and 3.7 list Alpha and Itanium only.

Claude Code did much of the hard work.

It built without a single source change. The only thing I set is the one setting the distribution’s own VMS notes tell you to set: where the playground is (Local_HACKDIR in vmsconf.h). The interesting part turned out to be the security of the playground.

Downloads, with SHA-256 sums in SHA256SUMS.TXT:

What Was Built

  • Source: nethack-343-src.tgz, 3,497,458 bytes, SHA-256 bb39c3d2a9ee2df4a0c8fdde708fbc63740853a7608d2f4c560b488124866fe4, from the NetHack download area on SourceForge. A copy on my DECUS CD-ROMs (nethack-343-src-vms.tgz) contains exactly the same tar; I checked it, every file, rather than trusting the name.

  • Compiler and procedure: DEC C V6.4 on OpenVMS VAX V7.3, @VMSBUILD DECC (the procedure defaults to VAX C, which I do not have). No MMS, no lex or yacc (the pre-processed files in sys/share are used).

  • Measured on the VAXstation 4000/90 (a 71 MHz KA49): 132 progress lines in the procedure’s own log (compile steps, six links), 28 minutes 54 seconds elapsed as a batch job (27 min 33 s CPU), a 3,414-block NETHACK.EXE and a 15-block RECOVER.EXE; then the distribution’s own INSTALL.COM into a staging directory in 21 seconds, which builds the data library; the kit with BACKUP in 9.4 seconds. The build printed no compiler warnings. The 4000/60 only installed the kit.

  • The one change, as a patch in the source kit:

      -#define Local_HACKDIR "DISK$USERS:[GAMES.NETHACK.3_4_X.PLAY]\0..."
      +#define Local_HACKDIR "SYS$COMMON:[SYSLIB.NETHACK]\0..."
    

    (The HACKDIR logical name does the same at run time, and NETHACK_SETUP.COM defines it too.)

Installing and Playing

You need OpenVMS VAX 7.3, SYSTEM (or SYSPRV), and about 5,000 free blocks.

  1. Put the saveset and the two .COM files in one directory on the VAX (.BCK in binary, .COM in ASCII; the installer puts back the saveset’s record attributes that a binary transfer loses).
  2. As SYSTEM: $ @NETHACK_INSTALL. It restores the files, makes the playground, sets the protections, writes a manifest, and runs NETHACK -s as a smoke test. Measured: the restore takes 7 seconds on the 4000/90 and 12 on the 4000/60; the whole procedure including the smoke test is a few seconds longer.
  3. Each player: $ @SYS$COMMON:[SYSLIB.NETHACK]NETHACK_SETUP, then $ NETHACK (options in quotes: NETHACK "-uConan-B", NETHACK "-s"). S saves, NETHACK again restores.

@NETHACK_UNINSTALL CHECK shows what would go and @NETHACK_UNINSTALL does it: only files the manifest lists and that are still as installed. A score list that has entries and saved games are kept, and a reinstall adopts them. RECOVER is installed too.

The Unexpected: Making Players’ Files Private

NetHack on VMS wants one directory, the playground, that every player writes into: level files while a game runs, the lock file, the save file in [.SAVE], bones. The distribution’s own notes say its default is “world-writable files … NOT secure and not advisable in any environment where there are untrustworthy users”. The proper fix is to install the image with a rights identifier, which needs INSTALL, a privilege and a startup entry. I did not want to grant a game any of that, so I tried to do it with ACLs alone.

First attempt: a DEFAULT_PROTECTION ACE on the playground and [.SAVE], to make new files owner-only. It does not work. I started a game as USER1, saved, and as USER2 typed TYPE on the save file. It printed nothing, because it read it. The C run-time library creates the file with an explicit protection derived from the program’s creation mode (0660: the owner and the group may read and write), and an explicit protection wins over the directory’s default. On my systems every player is in the same UIC group, so everyone could read, replace and delete everyone’s game. The file showed (RWD,RWD,RWD,).

What works: an inheritable ACE that denies everybody:

$ SET ACL/ACL=((IDENTIFIER=[*,*],OPTIONS=DEFAULT,ACCESS=NONE), -
      (DEFAULT_PROTECTION,S:RWED,O:RWED,G,W)) SYS$COMMON:[SYSLIB.NETHACK]SAVE.DIR

(and the same on the playground directory itself). A file created there carries (IDENTIFIER=[*,*],ACCESS=NONE). I will not pretend to know VMS’s evaluation order by heart: what I measured is that its owner keeps full access (USER1 saved and restored its game over and over), that SYSTEM (which holds BYPASS) can still read the file and back it up with BACKUP, and that every other user is stopped. Measured, as USER2, against USER1’s files on both machines:

USER2 triesResult
DIRECTORY/SECURITY of the save fileinsufficient privilege or object protection violation
TYPE or COPY the save file%RMS-E-PRV, insufficient privilege or file protection violation
DELETE it%RMS-E-PRV ...
RENAME it%RMS-F-RMV, ACP remove function failed ... NOPRIV
APPEND NL: to it, SET FILE/PROTECTION, SET FILE/OWNERrefused
COPY NL: ...same.name or COPY LOGIN.COM ...same.name (a new version with the same name, which would otherwise be what NetHack opens first)%RMS-E-PRV ...
the same on the level files and lock file of a running gamerefused
DELETE or SET FILE/PROTECTION the program NETHACK.EXErefused
create and delete a file of its own in [.SAVE]allowed

USER1 saved and restored its game normally throughout, RECOVER rebuilt a save file from the level files of a game I killed with STOP/ID, and a second player’s game ended in the shared score list (“You made the top ten list!”), updated in place.

Two details in the installer matter: the ACEs are put on the directories last and taken off first. If they are on while the kit is restored, every program file inherits NONE and nobody can run the game, and the same would happen to the shared state files.

What this does not protect (residual risk)

  • The shared files are world-writable. RECORD, LOGFILE, PERM and PANICLOG are appended to by every player, so every local user can edit or delete the score list. Closing that needs the installed image with an identifier, which needs INSTALL, CMKRNL and a startup entry; this kit does not do that.
  • Bones files are private too, so one player never meets another’s ghost.
  • The playground is world-writable, so anyone can fill it (disk quota is the only limit).
  • SHELL (the ! command) and wizard mode are as the distribution ships them: ! is compiled in; wizard mode needs the user NHWIZARD, who does not exist.

Tests

These are all measured, not estimated, on the real machines, as the unprivileged accounts USER1 and USER2 over Telnet from a VT200-type terminal:

  • LIFECYCLE_TEST.COM: install; refused second install; uninstall CHECK; uninstall that keeps a changed file and a user’s file; uninstall and reinstall; REPLACE; a score list entry survives all of it; ends with a clean tree. 25 of 25 passed in 42 seconds on the 4000/90 and 69 seconds on the 4000/60.
  • Start a game, 10 turns, S, restore, S, restore, #quit; the log file (LOGFILE) gets one line per game; a game with points goes into RECORD and NETHACK "-s" "all" lists it for the other user.
  • The table above, on both machines.
  • SHOW USERS and SHOW SYSTEM after every session: no game process left behind, no level file, no save file.

Not Measured

  • I did not try the build with VAX C or GNU C (the distribution’s notes say VAX C 3.2 and GNU C 2.7.1 worked on V5.5-2), nor on a simulator.
  • I did not build the installed-image variant that would protect the score list; the paragraph above is why not, not a test result.

Licence

NetHack is under the NetHack General Public License (Copyright 1989 M. Stephenson, derived from the Bison GPL): you may copy and distribute it, source and binaries, if the licence and the means to get the source stay with it, and a modified version must say that it is modified. This kit is the unmodified source built with one configuration value; the source kit above is the corresponding source. The installer, uninstaller, setup procedure and test procedures are mine.