Skip to content

Verify a device node carries the number it was created with - #405

Merged
folbricht merged 1 commit into
masterfrom
device-verify
Sep 2, 2026
Merged

folbricht merged 1 commit into
masterfrom
device-verify

Conversation

@folbricht

@folbricht folbricht commented Sep 2, 2026 •

Copy link
Copy Markdown
Owner

CreateDevice passed on whatever error mknod returned and never checked the result:

if err := fs.createDeviceNode(r, n); err != nil {
    return fmt.Errorf("mknod %s: %w", n.Name, err)
}
// nothing verified n.Major/n.Minor actually landed

So a filesystem that accepts a device number without recording it produced a node pointing at the wrong device — silently, with a successful exit.

This isn't hypothetical, and it isn't one platform

Two are known to do exactly that:

  • DragonFly — mknod reports success and stores NODEV.
  • NetBSD — did the same through mknodat until Support NetBSD, OpenBSD and DragonFly #403 switched it to mknod. Before that fix, untar there wrote rdev-0 nodes and reported success. That is fixed on master; it is listed here because it is the second independent instance of the same silent failure, not because it is still broken.

Neither reports anything. NetBSD was caught only because TestCreateDeviceRoundTrip runs as root in a VM job. A filesystem behaving the same way under Linux or macOS would go unnoticed, because that test never runs there — GitHub's hosted runners are not root, so it skips:

platform round-trip exercised in CI?
FreeBSD, OpenBSD, NetBSD, DragonFly yes — VM jobs run as root
Linux, macOS no — test skips

Whether a device number survives depends on the kernel and the filesystem underneath, not on the operating system, so it isn't something a build tag can express. Overlay, network and FUSE filesystems are the obvious places this could bite on Linux.

The change

Read the node back after creating it and compare major/minor, in the shared CreateDevice rather than the per-platform files. One lstat per device node, and device nodes are rare in archives.

On DragonFly untar now refuses device entries instead of writing wrong ones. That's the intended trade — an extraction that stops and says so beats a node pointing somewhere else — and the platform table says so.

Verification

Run as root, which no GitHub-hosted runner does:

  • TestCreateDeviceRoundTrip passes as root on Linux, where it is otherwise always skipped. Linux records device numbers faithfully and the check raises no false failures.
  • TestCreateDeviceVerifiesNumber (new, portable) creates a node as 1:3 and verifies it against 9:9, standing in for a filesystem that ignores the number. It's caught with the expected message. This covers detection of a mismatch; the round-trip test alone only shows the check does not trip over a correct node.
  • TestCreateDeviceRejectsUnrecordedNumber (new, DragonFly-only) covers the real integration case in the DragonFly VM job. Should DragonFly's mknod ever start recording the number, it fails, flagging that the skip can be removed.
  • Full suite, golangci-lint, and go vet across linux/darwin/windows and all four BSDs.

Builds on #403, now merged — that is where both instances surfaced, and its VM jobs are what give the BSD coverage this relies on. This branch is cut from master with #403 in it, so it needs nothing else first.

CreateDevice passed on whatever error mknod returned and never checked
the result, so a filesystem that accepts a device number without
recording it produced a node pointing at the wrong device, silently and
with a successful exit.

Two platforms are known to do exactly that. DragonFly's mknod stores
NODEV. NetBSD's did the same through mknodat until #403 switched it to
mknod. Neither reported anything: what gave NetBSD away was a test that
happens to run as root in a VM job, and nothing would have given away a
filesystem that behaved the same way under Linux or macOS, because that
test never runs there - GitHub's hosted runners aren't root.

So read the node back and compare. Whether a device number survives
depends on the kernel and on the filesystem underneath rather than on
the operating system, so this isn't something a build tag can express.
An extraction that stops and says so beats a device node pointing
somewhere else.

On DragonFly untar now refuses device entries instead of writing wrong
ones, which is the intended trade and is noted in the platform table.

Verified as root on Linux, where the round-trip test had never actually
run before: it passes, and a deliberate mismatch is caught with the
expected message.
@folbricht
folbricht merged commit 7a75f5a into master Sep 2, 2026
9 checks passed
@folbricht
folbricht deleted the device-verify branch September 2, 2026 10:20
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

⌂ 换网址