Skip to content

fix: shred only on this PC's drives, and remove only the file overwritten - #3231

Merged
laurentiu021 merged 1 commit into
mainfrom
fix/shredder-local-and-bound
Nov 10, 2026
Merged

laurentiu021 merged 1 commit into
mainfrom
fix/shredder-local-and-bound

Conversation

@laurentiu021

Copy link
Copy Markdown
Owner

Two changes to how File Shredder decides where it may write and what it removes.

What changes

  • No shredding on a network share. ThrowIfProtected compares against the drive-letter forms of Windows, System32 and Program Files, and a path through a share stayed in its UNC form through Path.GetFullPath, ResolveLinkTarget and GetFinalPathNameByHandle, so \\localhost\C$\Program Files\… (or \\127.0.0.1\C$\…, or a drive letter mapped to one) passed the check its C:\ form is refused by. ValidatePath now refuses a network path first, from the text alone, before anything reaches the share; it refuses again when the canonical path turns out to be on one, which a directory link in the middle of a local path can do; and the held-handle check refuses a handle that is open on a share. IsOnANetworkShare takes the UNC spellings (\\server\share, //server/share, \\?\UNC\, \\.\UNC\) and a drive letter Windows reports as a network drive, and leaves a device path to a local volume to the canonical check. Shredding over SMB is not a secure overwrite in any case, since the server decides where the writes land and can keep copies in its caches and snapshots.
  • The file removed is the file overwritten. The overwrite held one FileShare.None handle from the canonical check to the truncate, but the named streams were overwritten and the file deleted by path after that handle closed, so the one step that removes the file was not bound to the object that was checked and written. The named streams are now overwritten while the handle is still held (a named stream opens with FileShare.None while the unnamed one is held, as the method's remarks record), the file's identity is read from the held handle, and RemoveShreddedFile opens the name again with DELETE and FILE_FLAG_OPEN_REPARSE_POINT, checks that handle is open on the same file, and sets the delete disposition on it. A mismatch removes nothing and reports that the contents were overwritten and the name stayed; a refused disposition keeps the existing "overwritten but not removed" and "removal was denied" wording.
  • FileIdentity.OfAnyFileSystem falls back to the 64-bit file index where a file system has no 128-bit ID, a FAT or exFAT USB stick say, so the comparison works there too.
  • README: the File Shredder section says both.

How it was checked

  • Without the first check in ValidatePath, all four spellings of ShredFileAsync_APathOnANetworkShare_IsRefused failed. The files named do not exist, so nothing could be written had the refusal failed; the folder variants stayed refused by the canonical check, since those folders exist through the share.
  • ShredFileAsync_AFileReachedThroughALinkToAShare_IsRefused_AndLeftAsItWas goes through a directory link to this PC's temp folder by way of its administrative share, and skips when that share cannot be reached. With the canonical and the held-handle checks both taken out it failed, its file overwritten; with either one taken out alone it still passed, as it should, since each refuses the file by itself.
  • With the delete back to File.Delete(filePath), ShredFileAsync_RemovesOnlyTheFileItOverwrote_WhenItsNameLeadsElsewhereByThen failed: its write handle moves the file away on close and puts another at the name, and the path delete removed that other file.
  • With the classifier blind to \\, three rows of IsOnANetworkShare_SaysSoForAShare_InEverySpelling failed; IsOnANetworkShare_IsFalseForThisPCsOwnDrive_AlsoAsADevicePath covers the other side.
  • The existing shredder tests (named streams, hard links, links, cancellation, write faults) pass unchanged. Unit suite twice, the format check, and all four projects built with no warnings.

…tten

The refusal of Windows' own folders compared paths in their drive-letter
form, and a path through a network share kept its share form at every
resolution step, so \\localhost\C$\Program Files\..., this PC's own
folders under another name, passed it. A file on a network share is
refused now before the share is reached, and again when the canonical
path, or the file the overwrite holds open, leads to one through a link
or a mapped drive. An overwrite sent over the network is not a secure
one anyway: the computer holding the share decides where it lands and
can keep copies.

The overwrite holds one exclusive handle from the check of what it
opened to the truncate, and the file was deleted by its name after that
handle had closed, so the delete acted on whatever the path led to by
then. The named streams are overwritten while the handle is still held,
and the file is removed through a handle opened for deletion, a link at
its name opened as the link, and checked to be open on the file the
overwrite held. When it is not, nothing is removed and the caller hears
that the contents were overwritten and the name stayed.
@laurentiu021
laurentiu021 merged commit f5d8928 into main Nov 10, 2026
7 checks passed
@laurentiu021
laurentiu021 deleted the fix/shredder-local-and-bound branch November 10, 2026 09:02
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