fix(cli): refuse an output destination that aliases the input - #4086
Draft
dmihalcik-virtru wants to merge 1 commit into
Draft
dmihalcik-virtru wants to merge 1 commit into
dmihalcik-virtru wants to merge 1 commit into
Conversation
|
Important Draft PR not reviewedDraft PRs are not automatically reviewed by default.
To automatically review draft PRs, update your CodeRabbit configuration: reviews:
auto_review:
drafts: trueThanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Contributor
Benchmark results, click to expandBenchmark authorization.GetDecisions Results:
Benchmark authorization.v2.GetMultiResourceDecision Results:
Benchmark Statistics
Bulk Benchmark Results
TDF3 Benchmark Results:
|
Contributor
dmihalcik-virtru
force-pushed
the
dspx-2604-10a-output-alias-guard
branch
from
September 21, 2026 14:38
146c56b to
229812f
Compare
Contributor
Benchmark results, click to expandBenchmark authorization.GetDecisions Results:
Benchmark authorization.v2.GetMultiResourceDecision Results:
Benchmark Statistics
Bulk Benchmark Results
TDF3 Benchmark Results:
|
Contributor
dmihalcik-virtru
marked this pull request as draft
September 21, 2026 15:45
Member
Author
|
Considering pulling this as it has a breaking change to the streamio API |
Contributor
|
@dmihalcik-virtru I agree that we can probably punt on this because if you're outputting to the same file you're decrypting, that seems like something that could pretty obviously break. |
A write-through destination is opened with O_TRUNC before the payload is read, so pointing -o at a symlink resolving to the input truncated the input out from under the already-open descriptor: the command then read an empty file, failed, and the source was gone. Both encrypt and decrypt open their input first and resolve the destination second, so both walked into it. NewOutputFile now takes the input file and refuses a write-through destination that os.SameFile says is that input. An ordinary destination naming the input is left alone -- temp-and-rename never truncates the source, and in-place decrypt is a deliberate thing to ask for. Also drops the destination basename from the temporary filename. A valid 255-byte destination name produced an overlong temporary component, and NewOutputFile failed before any payload was read. Signed-off-by: Dave Mihalcik <dmihalcik@virtru.com>
dmihalcik-virtru
force-pushed
the
dspx-2604-10a-output-alias-guard
branch
from
September 21, 2026 21:34
229812f to
437649c
Compare
Contributor
Benchmark results, click to expandBenchmark authorization.GetDecisions Results:
Benchmark authorization.v2.GetMultiResourceDecision Results:
Benchmark Statistics
Bulk Benchmark Results
TDF3 Benchmark Results:
|
Contributor
|
Contributor
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Proposed Changes
An output destination that aliases the input. A destination a rename cannot
stand in for — a symlink, a device, a fifo — is written through directly, which
means
os.OpenFilewithO_TRUNC. Bothencryptanddecryptopen theirinput first and resolve the destination second, so
-oat a symlink resolvingto the input truncated the input out from under the already-open descriptor.
The command then read an empty file, failed, and the source was gone:
NewOutputFilenow takes the caller's already-open input and refuses awrite-through destination that
os.SameFilesays is that input. The checkstats through the link, since that is what the open does; a dangling link
resolves to nothing and is not an alias.
The guard is deliberately confined to the write-through path. An ordinary
destination naming the input goes to a temp sibling and is renamed over it only
after
Decrypthas returned, so nothing is truncated and in-place decryptionkeeps working — that is a reasonable thing to ask for, not a footgun.
Passing the input is a required parameter rather than an option. The whole
failure mode is a caller not thinking about it, so the signature makes that
impossible to skip;
nilis available for a caller with no input to protect.Temporary filenames no longer carry the destination's basename. A component
name is capped at 255 bytes on most filesystems, and `"." + basename + ".tmp-"
added 22 bytes to it — so a destination whose own name was valid could fail the open before a single byte had been read. The prefix is now a fixed.otdfctl.tmp-`. The random suffix is what distinguishes concurrent writers; theprefix only has to be recognizable enough to sweep up after a crash.
Both fixes land in
streamio, so both reachencryptanddecrypt. The tempprefix was not introduced by #3939 — it came in with #3937 — but it lives in the
same function.
Checklist
Testing Instructions
New unit tests, each verified to fail against the unfixed code:
TestOutputFileRejectsSymlinkToInput— the destructive case, and that theinput is still readable afterwards.
TestOutputFileWritesThroughSymlinkToOtherFile,TestOutputFileWritesThroughDanglingSymlink— the guard stays narrow enoughto leave write-through working.
TestOutputFileAllowsRegularDestinationNamingInput— in-place decrypt is notrejected.
TestOutputFileAcceptsMaximumLengthDestinationName— a 255-byte destinationname opens. Probes the filesystem first and skips where a 255-byte component
is not allowed, so it reports the temp-name bug and nothing else.
e2e, against a running platform:
Two cases added to
streaming.bats(12 → 14), one per command, asserting thefailure and that the input survives it. The two
assert_no_leftoversglobs inthat file are updated for the new temp prefix.