Skip to content

shred: correctness and GNU-compatibility fixes#11513

Draft
can1357 wants to merge 7 commits intouutils:mainfrom
can1357:shred-correctness-and-gnu-compat
Draft

shred: correctness and GNU-compatibility fixes#11513
can1357 wants to merge 7 commits intouutils:mainfrom
can1357:shred-correctness-and-gnu-compat

Conversation

@can1357
Copy link
Copy Markdown
Contributor

@can1357 can1357 commented Mar 26, 2026

This PR fixes a cluster of bugs found while auditing shred. They break down into TOCTOU races, dir sync issues, missing truncate, degraded-media handling, direct I/O, and an unchecked close(fd).

TOCTOU + incorrect file-type gate

wipe_file performs path.is_file() and then opens the path in a separate syscall. That creates a TOCTOU window (e.g., symlink/target swap between check and open). It also rejects non-regular files up front, including block/character devices, which diverges from GNU shred behavior.

Non-atomic rename in wipe_name

wipe_name does new_path.exists() before fs::rename.

Incorrect fsync target for metadata durability

wipe_name syncs the renamed file descriptor, but rename durability is a directory metadata concern. Without syncing the parent directory, a crash can resurrect older directory entries.

do_remove unlinks without syncing the parent directory afterward at all, so directory-entry deletion is not crash-durable either.

Missing truncate-before-remove

With --remove, the code proceeds to rename/unlink without first truncating to zero. If removal fails partway, the remaining file can retain original length metadata and delayed-deallocation artifacts.

No bad-sector continuation on EIO

do_pass uses file.write_all(block)?; any EIO aborts the pass immediately. On degraded media, sectors after the first I/O failure are left unwiped.

No direct-I/O path

Writes are fully buffered through page cache. On large targets this causes cache pollution and can leave plaintext temporarily resident in cache.

Unchecked close before remove path

The writable fd is not closed (and checked) before entering name-wipe/unlink. On backends that report delayed write errors at close (e.g., NFS/FUSE), removal can proceed even though overwrite was not durably successful.


I apologize for the big PR, but it included a few reworks. Changes should be now mostly atomically scoped per-commit though.

@github-actions
Copy link
Copy Markdown

GNU testsuite comparison:

Skipping an intermittent issue tests/tty/tty-eof (passes in this run but fails in the 'main' branch)

@codspeed-hq
Copy link
Copy Markdown

codspeed-hq bot commented Mar 26, 2026

Merging this PR will degrade performance by 4.63%

❌ 1 regressed benchmark
✅ 299 untouched benchmarks
⏩ 46 skipped benchmarks1

⚠️ Please fix the performance issues or acknowledge them on CodSpeed.

Performance Changes

Mode Benchmark BASE HEAD Efficiency
Simulation sort_ascii_utf8_locale 15.4 ms 16.1 ms -4.63%

Comparing can1357:shred-correctness-and-gnu-compat (86616b5) with main (3dec656)

Open in CodSpeed

Footnotes

  1. 46 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

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