Skip to content

macOS hardware audit: close the four checks, fix R-069 and R-070 - #82

Merged
danifunker merged 4 commits into
mainfrom
vcfmwfixup
Sep 10, 2026
Merged

danifunker merged 4 commits into
mainfrom
vcfmwfixup

Conversation

@danifunker

Copy link
Copy Markdown
Owner

Closes the four open macOS hardware checks, fixes two High regressions found while running
them, and removes the instrumentation that was kept only until they were confirmed.

Verified on hardware; CI green on the branch head.

R-070 — a restore without --device wrote a device as if it were a file

RestoreConfig::target_is_device came straight from the --device flag, so
rb-cli restore <backup> /dev/diskN without it took run_restore's image-file arm and
opened the device with create(true).truncate(true). O_TRUNC is ignored on a device node,
so on writable media — or under sudo — that arm wrote to the raw device while skipping
every guard that path exists to provide:

  • device_safety::preflight and its system-disk guard
  • the DiskArbitration unmount and the disk claim
  • the sector-aligned writer
  • open_target_for_writing's write-protect bail

Found on hardware, where only the card's physical lock switch stopped the write.

A device-shaped target without --device is now refused, naming the flag. Auto-enabling
device mode was considered and rejected: it turns a typo into a whole-disk overwrite.
Covered by device_target_needs_flag's tests.

R-069 — the raw-device check ran after a content probe

R-068 moved the raw-device check above the File::open that had preceded it, but only the
one at the end of open_peeled_read_with_entry. The encrypted-DMG probe sits earlier in the
same function, inside the default-on crypto feature, and opened the source
unconditionally — so an unprivileged rb-cli inspect /dev/rdiskN still returned a bare
EACCES with no dialog and no log output.

The check now sits immediately after is_container_path, above every content probe: a
raw device is never a container, an encrypted DMG, or an NDIF fork carrier. try_decode_dart
escaped the same bug only by accident, reading a metadata length that is 0 for a character
device.

Four error messages had lost their line continuations

A \-newline escape had gone missing from four string literals, so the source indentation
became part of the message — 14 to 22 spaces sitting mid-sentence in text the user reads:

File Message
src/cli/verbs/new_partitioned_hd.rs the HUNK_HEADER rejection
src/fs/ufs.rs the triple-indirect size ceiling
src/fs/ofs_write.rs the too-small-volume bail
src/fs/affs.rs the root-block search failure (three runs in one string)

Each now ends the line with a space then \, matching the convention in src/os/macos.rs
the escape strips the newline and all leading whitespace on the next line, so the
separating space has to precede the backslash. Same defect as the two repaired on the macOS
elevation path in d7af8f41; these are the rest. Verified by reading the strings back out
of the compiled binary, and with the vintage 1.73 proxy build, since three of the four are
engine code.

TEMP-DIAG removed

describe_device_access and its seven call sites are gone. They were kept only until the
2026-08-05 EACCES fix was confirmed on hardware, which R6 did.

Note on overlap with #81

src/os/mod.rs is the only file both branches touch, in different regions — this one removes
describe_device_access around line 192, #81 adds a sync helper around line 544. They merge
clean.

Worth being explicit about one thing, since the symptoms look alike: the TEMP-DIAG here was
chasing a macOS restore failing with Permission denied (os error 13) at the end of an
otherwise successful run, and that is fixed. #81 fixes a different end-of-restore failure,
Inappropriate ioctl for device (os error 25)ENOTTY from F_FULLFSYNC on a raw
device, after the data is already written. Different errno, different mechanism, both needed.

🤖 Generated with Claude Code

danifunker and others added 4 commits September 8, 2026 14:47
R6, R11, R19 and the read-only escalation fallback have survived two
sessions because each needs removable media and a human at the keyboard.
The 2026-09-08 attempt got no further: the USB floppy drive dropped off
the bus mid-session, so an absent device answered ENXIO and nothing was
verified.

Collects what the next session needs in one place: that R-068 is the
enabler (before it the documented R19 command could not work
unprivileged), the exact commands and the verbatim log lines each check
must produce, the hardware traps that cost the last attempt (a
disappearing device reads as ENXIO, not EIO; an 800K Mac floppy is GCR
and unreadable in a PC USB drive whatever size the drive reports), and
the eight TEMP-DIAG sites to delete once R6 passes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
R-068 moved the raw-device check above the File::open that had preceded it,
but only the one at the end of open_peeled_read_with_entry. The encrypted-DMG
probe sits earlier in the same function, inside the default-on `crypto`
feature, and opens the source unconditionally — so an unprivileged
`rb-cli inspect /dev/rdiskN` still returned a bare EACCES before
open_source_for_reading could run, with no dialog and no log output.

The check now sits immediately after is_container_path, above every content
probe: a raw device is never a container, an encrypted DMG or an NDIF fork
carrier. try_decode_dart escaped the same bug only by accident, reading a
metadata length that is 0 for a character device.

Found on hardware while running the R-053 floppy check; with this in, the
macOS device path reaches DA claim, elevation and the R6 write-protect
warning on a physically locked disk for the first time from the CLI.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Closes the four macOS hardware checks and removes the instrumentation
that was gated on one of them.

R-070: RestoreConfig::target_is_device comes straight from the --device
flag, so `rb-cli restore <backup> /dev/diskN` without it took run_restore's
image-file arm and opened the device with create(true).truncate(true).
O_TRUNC is ignored on a device node, so on writable media — or under sudo —
that arm writes to the raw device while skipping device_safety::preflight
and its system-disk guard, the DiskArbitration unmount, the disk claim, the
sector-aligned writer and open_target_for_writing's write-protect bail.
Found on hardware: only the card's lock switch stopped the write. A
device-shaped target without --device is now refused, naming the flag;
auto-enabling device mode was rejected because it turns a typo into a
whole-disk overwrite. Covered by device_target_needs_flag's tests.

TEMP-DIAG: describe_device_access and its seven call sites are gone. They
were kept only until the 2026-08-05 EACCES fix was confirmed on hardware,
which is what R6 did today.

Verification, all on hardware, 2026-09-08:
  R6        write-protected media — read path escalates read-only and says
            why with no second prompt; restore refuses with the write-path
            message without touching the device (lock-switched SD card)
  R11       a cancelled dialog — one prompt, no read-only retry, on the
            O_RDWR path where the !is_authorization_cancelled guard runs
  R19       raw-device reads — 1474560 bytes and an HFS superfloppy, not 0 B
  Section 3 read-only fallback after a refused read-write escalation

Two log messages had lost their `\` line continuations, embedding 25 spaces
of source indentation into user-visible text; both are on the audited path.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A Rust `\`-newline escape had gone missing from four string literals, so
the source indentation became part of the message: 14 to 22 spaces sitting
mid-sentence in text the user reads.

  src/cli/verbs/new_partitioned_hd.rs  the HUNK_HEADER rejection
  src/fs/ufs.rs                        the triple-indirect size ceiling
  src/fs/ofs_write.rs                  the too-small-volume bail
  src/fs/affs.rs                       the root-block search failure (three
                                       runs in the one string)

Each now ends the line with a space then `\`, matching the convention in
src/os/macos.rs — the escape strips the newline and all leading whitespace
on the next line, so the separating space has to precede the backslash.
Same defect as the two repaired on the macOS elevation path in d7af8f4;
these were the rest of them. Verified by reading the strings back out of
the compiled binary, and with the vintage 1.73 proxy build since three of
the four are engine code.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@danifunker
danifunker merged commit 99e46a4 into main Sep 10, 2026
29 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