Zip and UnZip for VAX OpenVMS 7.3

Zip and UnZip for VAX OpenVMS 7.3

My VAXstations now have Zip 3.0 and UnZip 6.0, the last official Info-ZIP releases, with the security fixes they never got. It was built on the VAX itself, with the compiler and build procedures that come with OpenVMS. These ZIP files keep everything that makes a VMS file a VMS file: with -V, a variable-length text file, a fixed-512 file and an indexed file go into a zip file and come out again with the same organisation, record format, key definitions and creation date. The kit is a BACKUP saveset with a DCL installer and a DCL uninstaller.

Claude Code did most of the hard work.

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

Why bother with Zip on a VAX

Every file I move to the VAXen typically goes as a zip, and every file I want off them has to travel as something. VMS has BACKUP, which keeps everything but is understood only by VMS. Zip is understood by everything: the laptop, a phone, the web. With the Info-ZIP -V option a zip file is also a faithful VMS backup of a file, and still opens with any unzip program.

What is built

Info-ZIP’s two programs are plain C with DCL build procedures (BUILD_UNZIP.COM, BUILD_ZIP.COM) that know about VAX and the Compaq C compiler: no make, no MMS, no configure script. Their own notes list “VMS VAX V7.3, DEC/Compaq C” as tested. So the build procedures and the VMS code needed no change; the only source changes are the patches listed below, and the work was to put it on a real machine in a way that could be repeated:

  • The sources are the real ones. Zip 3.0 (5 July 2008) and UnZip 6.0 (20 April 2009) from SourceForge, checked against the hashes in Debian’s signed source descriptions.
  • 34 patches, 32 of them Debian’s fixes for the two programs (see below), one-line marker patches of ours (the Info-ZIP licence says an altered version must say so, so unzip -v now reads “altered build with security patches”), and nothing else. Each patch keeps its author’s header.
  • No bzip2 (there is no bzip2 library on the machine) and no Zip64 (the VAX build has no 64-bit file support): the archives are limited to 2 GB and to deflate, which is what everything uses.
  • Bundles instead of a tarball. The VAX has no tar or unzip yet; that is what is being built. The two source trees travel as ASCII bundle files (!!!FILE name ... !!!END) that a 50-line C program splits.
  • Two command styles. Both programs exist in two versions each: UNZIP -l file.zip as on Linux, and UNZIP_VMS /LIST file.zip with VMS qualifiers. ZIPINFO is UNZIP -Z. On a VAX, DCL makes unquoted arguments lower case and no C run-time switch changes that, so -V (keep VMS attributes) and -P must be written in double quotes: ZIP "-V" ....

The security fixes, and what is not fixed

Debian’s unzip 6.0-31 and zip 3.0-16 carry the fixes that the 2009 releases never got. All the ones that touch code that runs on VMS are in the kit (the full table, with each patch’s own description, is in PATCHES.TXT): UnZip CVE-2014-8139, -8140, -8141, -9636, -9913, CVE-2015-7696, -7697, CVE-2016-9844, CVE-2018-1000035, CVE-2019-13232 (five patches, by Mark Adler, against overlapping-entry “zip bombs”), CVE-2022-0529 and -0530 (by Steven Schweda, a long-time Info-ZIP VMS contributor; his name is on the patch), three 2026 fixes Debian made this year, and for Zip CVE-2018-13410 (a buffer overflow with -T -TT), a buffer overflow with Unicode file names, a format-string fix and a command-injection fix. Not fixed: CVE-2021-4217, a null-pointer dereference in UnZip’s Unicode string handling. No patch exists for it; Debian rates it “unimportant”. The post says so; do not run these programs on archives from people you do not trust any more than you would the originals.

(M) The build, with Compaq C V6.4-005 on the real VAXstation 4000/90: UnZip 6.0 gave one informational message (%CC-I-RIGHTSHIFTOVR, extract.c: a shift by 32 of a 32-bit value that only a Zip64 entry could reach), Zip 3.0 none.

Examples

RUN_TESTS.COM makes 48 checks and prints PASS n of n; it needs no privilege, works in its own scratch directory and removes it. Expected values never come from Info-ZIP:

WhatHow it is checked
A zip made by Info-ZIP 3.0 on Linux and one made by Python’s zipfile (10 files each, a nested directory, stored and deflated entries, a 319 KB text, a 100 KB run of zeros, random binary)UNZIP -t, extraction, then every file zipped again and its length and CRC-32 compared with the values computed by Python’s zlib.crc32
-a on a 3000-line text whose line lengths run from 0 to 199, with and without a final line end, and on CR LF textno record lost (3000 records, 2 records of 12 characters)
A variable-length text file, a fixed-512 file and an indexed file, through ZIP -V and UNZIP -Vorganisation, record format, record size, size in blocks, creation date equal; DIFFERENCES finds nothing; CHECKSUM equal; the indexed file is indexed again. The control: without -V the fixed-512 file comes back as Stream_LF
Encrypted archives, ZIP -d, ZIPINFOwrong password refused, right password accepted
UNZIP_VMS /LIST, ZIP_VMS /VMS, a self-extracting executable (UNZIPSFX.EXE joined to a zip)list shows the file; the executable unpacks all 10 files with the right CRC-32
The kit’s own BACKUP saveset (6.2 MB, fixed 32256-byte records) through ZIP -V / UNZIP -Vsame format, BACKUP/LIST reads it, CHECKSUM equal; without -V BACKUP says “not a BACKUP save set”
ZIPDIR.COM / UNZIPALL.COM, example procedures (a tree to a dated archive with every attribute, and back)all 10 files come back with the right length and CRC-32

On the host: a zip made on the VAX unpacks on Linux with unzip -t clean and the SHA-256 of every file equal to the original’s (10 files, with and without -V); and the 6,225,408-byte saveset, zipped on the VAX with -V (2,462,042 bytes) and unpacked on Linux, has the same SHA-256 as the saveset itself. (The check script is in the project repository, examples/infozip/tests/.)

Results

(M = measured on the real VAXstation 4000/90 which I named VAX90)

  • (M) Clean rebuild from the bundle files, on the real VAXstation 4000/90 (KA49-A, 71 MHz, 128 MB, otherwise idle): 11 min 41 s elapsed, 9 min 58 s CPU: unpacking 25 s, UnZip 6 min 08 s, Zip 4 min 24 s, kit (stage, two help libraries, BACKUP/VERIFY) 44 s. Result: 6,225,408-byte saveset, 12,159 blocks.
  • (M) Installed: 248 files, 10,640 blocks, 37 s for the restore, 42 s for the whole installer including its own smoke test.
  • (M) Lifecycle test (install, refuse a second install, CHECK, keep-changed, uninstall, reinstall, REPLACE): 21 of 21, 3 min 12 s on VAX90 itself: I chose real VAXen for this series, so there was no simulator first.
  • (M) RUN_TESTS: 48 of 48 as SYSTEM (4 min 18 s) and 44 of 44 as a fresh, unprivileged USER1 (1 min 37 s; the saveset test needs a file USER1 can read and is skipped).
  • (M) Second machine, VAX60 (VAXstation 4000/60, the slower one): kit B installed in 68 s, lifecycle test 21 of 21 (13 min 24 s with RUN_TESTS), RUN_TESTS 48 of 48 as SYSTEM (7 min 48 s) and 44 of 44 as a fresh USER1 (2 min 40 s). VAX90 was then switched to the same kit B with the installer’s REPLACE (64 s, 248 files out, 248 in) and passed again: 44 of 44 as USER1 (1 min 37 s), 48 of 48 as SYSTEM.
  • (M) Cross-checks on the host (host_check.py): a zip made on the VAX (with and without -V) passes the host’s unzip -t and every file has the SHA-256 of the original; the kit’s 6.2 MB saveset zipped on the VAX with -V and unpacked on Linux has the same SHA-256 as the saveset.
  • (M) -a conversion: a 319 KB text of 3000 lines of every length comes back with 3000 records, with and without a final line end; the later “last line lost” fix in Steven Schweda’s newer vms.c was therefore not needed.
  • (M) Sizes: UNZIP.EXE 264 blocks, UNZIP_CLI.EXE 287, UNZIPSFX.EXE 132, ZIP.EXE 310, ZIP_CLI.EXE 351, ZIPCLOAK.EXE 115, ZIPNOTE.EXE 107, ZIPSPLIT.EXE 103.
  • (E) None of the timing figures is a benchmark: the VAX was idle but its batch queue ran a single job at a time. The speed of ZIP on a 71 MHz VAX was not measured separately.

Issues encountered

  • An 8-level directory limit: the kit’s own staging tree, SRC.UNZIP60.VMS under a home-directory project, is nine levels deep as a full name, and RMS refuses it; the procedures go through a concealed-root logical name.
  • A command line typed over Telnet is cut at about 250 characters (“terminator not seen”).
  • A test that “passed” for the wrong reason: the verification routine accepted a missing listing as “all files found”; a DEFINE/USER_MODE SYS$OUTPUT left pending hid a failure message. Both are fixed and described in the rebuild notes in the project repository (INFOZIP_VAX_REBUILD.md, section 6).
  • DIFFERENCES cannot compare binary Stream_LF files, SEARCH/STATISTICS prints on the terminal and not on SYS$OUTPUT: neither can be captured the way one would expect.