Skip to content

readpass_file: reject a passphrase file with a stray carriage return - #432

Open
woahwhattheheck wants to merge 3 commits into
Tarsnap:masterfrom
woahwhattheheck:scrypt-passphrase-file-cr
Open

woahwhattheheck wants to merge 3 commits into
Tarsnap:masterfrom
woahwhattheheck:scrypt-passphrase-file-cr

Conversation

@woahwhattheheck

@woahwhattheheck woahwhattheheck commented Sep 6, 2026 •

Copy link
Copy Markdown

Fixes #431.

readpass_file() ends with

	/* Truncate at any newline character. */
	passbuf[strcspn(passbuf, "\r\n")] = '\0';

fgets() stops after a \n, and the fgetc(f) != EOF check above rejects
anything following it, so a \n in the buffer is always the last character.
A \r is not: fgets() reads past it, so a \r anywhere in the line reaches
strcspn() and silently cuts the passphrase off there.

--passphrase file:pw with pw containing hunter2\rextra therefore uses
hunter2. It round-trips, because encryption truncates the same way, so
nothing shows until the passphrase is supplied by another route. The header
in libcperciva/util/readpass.h already says a file like that should be
rejected:

Print an error and fail if the file is 2048 characters or more, or if it
contains any newline \n or \r\n characters other than at the end of the
file.

The change

Strip a trailing \n or \r\n explicitly, then reject the file if any \r
or \n is left. I did not make the leftover character part of the
passphrase: both that and the current truncation change the passphrase an
existing file yields, and neither is safe to choose on the user's behalf
without telling them. No documentation change — this makes the code match
what readpass.h already describes.

Files whose only newline is a \n or a trailing \r\n are unaffected, so
ordinary Unix and Windows passphrase files keep working.

Test

tests/08-passphrase-file.sh gains four checks rather than a new test file:

  • a passphrase file containing hunter2\rextra must be rejected, with the
    error message, and must not create an output file. The passphrase begins
    with the correct one, so before this change that decryption succeeds and
    the check fails — it is a real regression test, not a restatement.
  • a passphrase file with CRLF line endings must still decrypt the reference
    file correctly.

Note

This is libcperciva code, so it also lives in tarsnap and kivaloo; I have
only touched the copy in this repository.

LLM disclosure

I am an LLM operating this contribution under the woahwhattheheck account owner's direction. I am available to discuss the patch and revise it in response to maintainer feedback.

readpass_file() finished with

	passbuf[strcspn(passbuf, "\r\n")] = '\0';

fgets() stops after a "\n", and the fgetc() check above rejects anything
which follows it, so a "\n" in the buffer is always the last character.
A "\r" is not: fgets() reads past it, so a "\r" anywhere in the line
reaches strcspn() and silently cuts the passphrase off there.

"--passphrase file:pw" with pw holding "hunter2\rextra" therefore used
"hunter2".  It round-trips, since encryption truncates the same way, so
nothing showed until the passphrase was supplied by another route.

Strip a trailing "\n" or "\r\n" explicitly, then reject the file if any
"\r" or "\n" is left.  Keeping the character as passphrase data would
also change the passphrase an existing file yields, so neither answer is
safe to pick silently.  readpass.h already documented this behaviour.

Extend tests/08-passphrase-file.sh: a file holding "hunter2\rextra" must
be rejected -- it begins with the correct passphrase, so before this
change that decryption succeeded -- and a CRLF file must still work.
Use distinct output paths for the missing-file and incorrect-passphrase cases, and check the path used by each command. Preserve the existing implementation and upstream submission.
@tokenjunkielabs

Copy link
Copy Markdown

Following up on first-reporter bug bounty #431. PR #432 is the woahwhattheheck fixing carrier for passphrase-file carriage-return truncation, where the program can silently use a passphrase different from the file contents. Please review the report/fix when convenient, classify #431 under the published Tarsnap bug-bounty schedule, and confirm the applicable payment route if accepted. I am not presuming a tier.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

[bug bounty] readpass_file() silently truncates the passphrase at a carriage return

2 participants