From 0f8dd1faea639ec12b36a8af91e21aa44b87bf5c Mon Sep 17 00:00:00 2001 From: Jaime Alfaro Salazar <62313620+jialfaro@users.noreply.github.com> Date: Sun, 6 Sep 2026 11:58:01 -0500 Subject: [PATCH] Fix #431: [bug bounty] readpass_file() silently truncates the passphrase at a carriage return --- libcperciva/util/readpass_file.c | 25 +++++++++++++++++++++++-- tests/08-passphrase-file.sh | 22 +++++++++++----------- 2 files changed, 34 insertions(+), 13 deletions(-) diff --git a/libcperciva/util/readpass_file.c b/libcperciva/util/readpass_file.c index 4379a61d..fa9f193c 100644 --- a/libcperciva/util/readpass_file.c +++ b/libcperciva/util/readpass_file.c @@ -52,8 +52,29 @@ readpass_file(char ** passwd, const char * filename) goto err1; } - /* Truncate at any newline character. */ - passbuf[strcspn(passbuf, "\r\n")] = '\0'; + /* + * Strip a trailing "\n" or a trailing "\r\n" explicitly, then reject the + * file if any "\r" or "\n" remains. This matches the contract in + * readpass.h: only a single trailing newline ("\n") or CRLF ("\r\n") is + * permitted at the end of the file; any other occurrences of CR or LF are + * an error. + */ + { + size_t len = strlen(passbuf); + + /* Strip trailing '\n', and an optional preceding '\r'. */ + if (len > 0 && passbuf[len - 1] == '\n') { + passbuf[--len] = '\0'; + if (len > 0 && passbuf[len - 1] == '\r') + passbuf[--len] = '\0'; + } + + /* If any CR or LF remains, the file contains embedded newlines. */ + if (strchr(passbuf, '\r') != NULL || strchr(passbuf, '\n') != NULL) { + warn0("Invalid passphrase file: %s", filename); + goto err1; + } + } /* Copy the password out. */ if ((*passwd = strdup(passbuf)) == NULL) { diff --git a/tests/08-passphrase-file.sh b/tests/08-passphrase-file.sh index 1d1de088..ea743cfe 100644 --- a/tests/08-passphrase-file.sh +++ b/tests/08-passphrase-file.sh @@ -13,12 +13,12 @@ decrypted_no_file_log="${s_basename}-decrypt-no-file.log" scenario_cmd() { # Create the passphrase file. - echo "${password}" > "${passphrase_file}" + printf "%s\n" "${password}" > "${passphrase_file}" # Decrypt a reference file using --passphrase file:FILENAME. setup_check "scrypt dec file" - ${c_valgrind_cmd} "${bindir}/scrypt" \ - dec --passphrase file:"${passphrase_file}" \ + ${c_valgrind_cmd} "${bindir}/scrypt" \ + dec --passphrase file:"${passphrase_file}" \ "${encrypted_reference_file}" "${decrypted_reference_file}" echo $? > "${c_exitfile}" @@ -30,15 +30,15 @@ scenario_cmd() { # Attempt to decrypt the reference file with a non-existent file. # We want this command to fail with 1. setup_check "scrypt dec file none" - ${c_valgrind_cmd} "${bindir}/scrypt" \ - dec --passphrase file:THIS_FILE_DOES_NOT_EXIST \ - "${encrypted_reference_file}" "${decrypted_reference_file}" \ + ${c_valgrind_cmd} "${bindir}/scrypt" \ + dec --passphrase file:THIS_FILE_DOES_NOT_EXIST \ + "${encrypted_reference_file}" "${decrypted_reference_file}" \ 2> "${decrypted_no_file_log}" expected_exitcode 1 $? > "${c_exitfile}" # We should have received an error message. setup_check "scrypt dec file none error" - grep -q "scrypt: fopen(THIS_FILE_DOES_NOT_EXIST)" \ + grep -q "scrypt: fopen(THIS_FILE_DOES_NOT_EXIST)" \ "${decrypted_no_file_log}" echo "$?" > "${c_exitfile}" @@ -50,10 +50,10 @@ scenario_cmd() { # Attempt to decrypt the reference file with an incorrect passphrase. # We want this command to fail with 1. setup_check "scrypt dec file bad" - echo "bad-pass" > "${bad_passphrase_file}" - ${c_valgrind_cmd} "${bindir}/scrypt" \ - dec --passphrase file:"${bad_passphrase_file}" \ - "${encrypted_reference_file}" "${decrypted_reference_file}" \ + printf "bad-pass\n" > "${bad_passphrase_file}" + ${c_valgrind_cmd} "${bindir}/scrypt" \ + dec --passphrase file:"${bad_passphrase_file}" \ + "${encrypted_reference_file}" "${decrypted_reference_file}" \ 2> "${decrypted_badpass_log}" expected_exitcode 1 $? > "${c_exitfile}"