From 70512977e19f9767af3fc68466f3518bff0ca89c Mon Sep 17 00:00:00 2001 From: tokenjunkielabs Date: Sun, 6 Sep 2026 11:22:04 +0000 Subject: [PATCH 1/3] main: refuse to write the output into the input file "scrypt enc secret.txt secret.txt" opens secret.txt for reading, reads a passphrase, then opens the same path with fopen(..., "wb"), which truncates it. scryptenc_file() then reads from the now-empty file, gets EOF on the first fread(), writes a valid 128-byte scrypt file containing an encryption of no data, and returns SCRYPT_OK; scrypt exits 0. The file the user asked to encrypt is gone, with no error and nothing to indicate that anything went wrong. "scrypt dec f f" destroys the ciphertext the same way, and both are also reachable with the input on stdin, e.g. "scrypt enc - f < f". Compare the open input file and the output path with fstat() and stat() before opening the output, and exit with an error if they are the same regular file. Devices are excluded: opening /dev/null for writing discards nothing. The check runs before the passphrase is read, so the user is not asked to type a passphrase for a command which cannot succeed. Add tests/12-same-file.sh, and document the restriction in scrypt.1. --- Makefile.am | 3 ++- main.c | 47 +++++++++++++++++++++++++++++++- scrypt.1 | 12 ++++++++- tests/12-same-file.sh | 63 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 122 insertions(+), 3 deletions(-) create mode 100644 tests/12-same-file.sh diff --git a/Makefile.am b/Makefile.am index f3212b4a..0073ff7b 100644 --- a/Makefile.am +++ b/Makefile.am @@ -309,6 +309,7 @@ EXTRA_DIST= \ tests/08-passphrase-file.sh \ tests/09-explicit-params.sh \ tests/10-check-params.sh \ + tests/12-same-file.sh \ tests/shared_test_functions.sh \ tests/shared_valgrind_functions.sh \ tests/test_scrypt.sh \ @@ -335,4 +336,4 @@ tests_valgrind_potential_memleaks_SOURCES= tests/valgrind/potential-memleaks.c # we can't only build "scrypt tests/verify-strings/test_scrypt" because that # won't build the BUILT_SOURCES. test: all - $(top_srcdir)/tests/test_scrypt.sh . + $(top_srcdir)/tests/test_scrypt.sh . \ No newline at end of file diff --git a/main.c b/main.c index 2b05e252..b883d261 100644 --- a/main.c +++ b/main.c @@ -25,6 +25,9 @@ */ #include "platform.h" +#include +#include + #include #include #include @@ -96,6 +99,37 @@ scrypt_mode_info(const char * infilename) return (-1); } +/** + * same_file(infile, outfilename): + * Return non-zero if the already-open ${infile} and the path ${outfilename} + * refer to the same regular file. If we cannot tell -- most importantly if + * ${outfilename} does not exist yet -- return zero. + */ +static int +same_file(FILE * infile, const char * outfilename) +{ + struct stat sb_in; + struct stat sb_out; + + /* If we can't stat either file, assume that they're different. */ + if (fstat(fileno(infile), &sb_in)) + return (0); + if (stat(outfilename, &sb_out)) + return (0); + + /* + * Only a regular file can be destroyed this way; opening a character + * device such as /dev/null for writing does not discard the data we + * are about to read. + */ + if (!S_ISREG(sb_in.st_mode) || !S_ISREG(sb_out.st_mode)) + return (0); + + /* The same device and inode is the same file. */ + return ((sb_in.st_dev == sb_out.st_dev) && + (sb_in.st_ino == sb_out.st_ino)); +} + /** * scrypt_mode_enc_dec(params, passphrase_entry, passphrase_arg, dec, verbose, * force_resources, infilename, outfilename): @@ -129,6 +163,17 @@ scrypt_mode_enc_dec(struct scryptenc_params params, infile = stdin; } + /* + * Refuse to write the output into the file we are reading. Opening + * it for writing truncates it, so we would destroy the data we were + * asked to encrypt or decrypt and then "succeed" in writing an + * encryption of nothing. + */ + if ((outfilename != NULL) && same_file(infile, outfilename)) { + warn0("Input and output files are the same: %s", outfilename); + goto err1; + } + /* Get the password. */ if (passphrase_entry_readpass(&passwd, passphrase_entry, passphrase_arg, "Please enter passphrase", @@ -397,4 +442,4 @@ main(int argc, char * argv[]) err0: /* Failure! */ exit(1); -} +} \ No newline at end of file diff --git a/scrypt.1 b/scrypt.1 index b81bb365..85de433c 100644 --- a/scrypt.1 +++ b/scrypt.1 @@ -63,6 +63,16 @@ if specified, or the standard output otherwise. The user will be prompted to enter the passphrase used at encryption time to generate the derived encryption key. .Pp +.Ar outfile +cannot be the same file as +.Ar infile : +.Nm +truncates +.Ar outfile +before it reads +.Ar infile , +so it exits with an error instead of destroying the input. +.Pp .Nm Cm info provides information about the encryption parameters used for .Ar infile . @@ -248,4 +258,4 @@ The key derivation function was invented in March 2009 by Colin Percival in order to allow key files from the .Nm Tarsnap -backup system to be passphrase protected. +backup system to be passphrase protected. \ No newline at end of file diff --git a/tests/12-same-file.sh b/tests/12-same-file.sh new file mode 100644 index 00000000..a42e8e3f --- /dev/null +++ b/tests/12-same-file.sh @@ -0,0 +1,63 @@ +#!/bin/sh + +### Constants +c_valgrind_min=1 +reference_file="${scriptdir}/verify-strings/test_scrypt.good" +encrypted_reference_file="${scriptdir}/verify-strings/test_scrypt_good.enc" +plaintext_target="${s_basename}-plaintext.txt" +encrypted_target="${s_basename}-encrypted.enc" +normal_output="${s_basename}-normal.enc" +stderr_enc="${s_basename}-enc.stderr" +stderr_dec="${s_basename}-dec.stderr" + +scenario_cmd() { + # "scrypt enc" must not write into the file it is reading; opening + # the output file truncates it. + cp "${reference_file}" "${plaintext_target}" + + setup_check "scrypt enc same file" + ( + echo "${password}" | ${c_valgrind_cmd} "${bindir}/scrypt" \ + enc --passphrase dev:stdin-once \ + "${plaintext_target}" "${plaintext_target}" \ + 2> "${stderr_enc}" + expected_exitcode 1 $? > "${c_exitfile}" + ) + + setup_check "scrypt enc same file error" + grep -q "scrypt: Input and output files are the same" \ + "${stderr_enc}" + echo $? > "${c_exitfile}" + + setup_check "scrypt enc same file leaves input alone" + cmp -s "${plaintext_target}" "${reference_file}" + echo $? > "${c_exitfile}" + + # The same applies to "scrypt dec". + cp "${encrypted_reference_file}" "${encrypted_target}" + + setup_check "scrypt dec same file" + ( + echo "${password}" | ${c_valgrind_cmd} "${bindir}/scrypt" \ + dec --passphrase dev:stdin-once \ + "${encrypted_target}" "${encrypted_target}" \ + 2> "${stderr_dec}" + expected_exitcode 1 $? > "${c_exitfile}" + ) + + setup_check "scrypt dec same file error" + grep -q "scrypt: Input and output files are the same" \ + "${stderr_dec}" + echo $? > "${c_exitfile}" + + setup_check "scrypt dec same file leaves input alone" + cmp -s "${encrypted_target}" "${encrypted_reference_file}" + echo $? > "${c_exitfile}" + + # Writing to a different file must still work. + setup_check "scrypt enc different file" + echo "${password}" | ${c_valgrind_cmd} "${bindir}/scrypt" \ + enc --passphrase dev:stdin-once \ + "${plaintext_target}" "${normal_output}" + echo $? > "${c_exitfile}" +} \ No newline at end of file From 30ac68db8070705e07e5cd376fa155a9443be8ba Mon Sep 17 00:00:00 2001 From: tokenjunkielabs Date: Sun, 6 Sep 2026 12:20:34 +0000 Subject: [PATCH 2/3] Restore the trailing newline on the files this branch touches The previous commit was uploaded through the GitHub API by a helper which dropped the final newline of every text file it sent. Rewrite those files with their trailing newline intact; no other change. --- Makefile.am | 2 +- main.c | 2 +- scrypt.1 | 2 +- tests/12-same-file.sh | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Makefile.am b/Makefile.am index 0073ff7b..88c64b30 100644 --- a/Makefile.am +++ b/Makefile.am @@ -336,4 +336,4 @@ tests_valgrind_potential_memleaks_SOURCES= tests/valgrind/potential-memleaks.c # we can't only build "scrypt tests/verify-strings/test_scrypt" because that # won't build the BUILT_SOURCES. test: all - $(top_srcdir)/tests/test_scrypt.sh . \ No newline at end of file + $(top_srcdir)/tests/test_scrypt.sh . diff --git a/main.c b/main.c index b883d261..150d7fca 100644 --- a/main.c +++ b/main.c @@ -442,4 +442,4 @@ main(int argc, char * argv[]) err0: /* Failure! */ exit(1); -} \ No newline at end of file +} diff --git a/scrypt.1 b/scrypt.1 index 85de433c..8b22ed4a 100644 --- a/scrypt.1 +++ b/scrypt.1 @@ -258,4 +258,4 @@ The key derivation function was invented in March 2009 by Colin Percival in order to allow key files from the .Nm Tarsnap -backup system to be passphrase protected. \ No newline at end of file +backup system to be passphrase protected. diff --git a/tests/12-same-file.sh b/tests/12-same-file.sh index a42e8e3f..fded421c 100644 --- a/tests/12-same-file.sh +++ b/tests/12-same-file.sh @@ -60,4 +60,4 @@ scenario_cmd() { enc --passphrase dev:stdin-once \ "${plaintext_target}" "${normal_output}" echo $? > "${c_exitfile}" -} \ No newline at end of file +} From 0ccde263804d7587163bfe3f2bde97ae37be80ce Mon Sep 17 00:00:00 2001 From: woahwhattheheck Date: Tue, 8 Sep 2026 09:23:49 -0400 Subject: [PATCH 3/3] Extend same-file input preservation coverage (#4) Add the authored static-alias and independent-file regression coverage to the existing test scenario. Preserve the application implementation and existing upstream submission. --- tests/12-same-file.sh | 90 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) diff --git a/tests/12-same-file.sh b/tests/12-same-file.sh index fded421c..d23a5658 100644 --- a/tests/12-same-file.sh +++ b/tests/12-same-file.sh @@ -60,4 +60,94 @@ scenario_cmd() { enc --passphrase dev:stdin-once \ "${plaintext_target}" "${normal_output}" echo $? > "${c_exitfile}" + + # Extended static-alias coverage. This block belongs inside scenario_cmd. + alias_password="${s_basename}-alias-password.txt" + printf '%s\n' "${password}" > "${alias_password}" + for alias_mode in enc dec; do + alias_params="" + alias_reference="${encrypted_reference_file}" + if [ "${alias_mode}" = enc ]; then + alias_params="--logN 10 -r 1 -p 1" + alias_reference="${reference_file}" + fi + for alias_kind in hardlink output-symlink input-symlink stdin dotdot; do + alias_input="${s_basename}-${alias_mode}-${alias_kind}-input" + alias_link="${s_basename}-${alias_mode}-${alias_kind}-link" + alias_stderr="${s_basename}-${alias_mode}-${alias_kind}.stderr" + cp "${alias_reference}" "${alias_input}" + alias_source="${alias_input}" + alias_dest="${alias_link}" + case "${alias_kind}" in + hardlink) + ln "${alias_input}" "${alias_link}" + ;; + output-symlink) + ln -s "${alias_input}" "${alias_link}" + ;; + input-symlink) + ln -s "${alias_input}" "${alias_link}" + alias_source="${alias_link}" + alias_dest="${alias_input}" + ;; + stdin) + alias_source="-" + alias_dest="${alias_input}" + ;; + dotdot) + alias_dir="${s_basename}-${alias_mode}-${alias_kind}-dir" + mkdir "${alias_dir}" + alias_dest="${alias_dir}/../$(basename "${alias_input}")" + ;; + esac + + setup_check "scrypt ${alias_mode} ${alias_kind} rejects alias" + ${c_valgrind_cmd} "${bindir}/scrypt" "${alias_mode}" \ + ${alias_params} --passphrase file:"${alias_password}" \ + "${alias_source}" "${alias_dest}" \ + < "${alias_input}" 2> "${alias_stderr}" + expected_exitcode 1 $? > "${c_exitfile}" + + setup_check "scrypt ${alias_mode} ${alias_kind} error" + grep -q "scrypt: Input and output files are the same" \ + "${alias_stderr}" + echo $? > "${c_exitfile}" + + setup_check "scrypt ${alias_mode} ${alias_kind} preserves input" + cmp -s "${alias_input}" "${alias_reference}" + echo $? > "${c_exitfile}" + done + done + + # A character device is not a destructive same-regular-file alias. + setup_check "scrypt enc permits same null device" + ${c_valgrind_cmd} "${bindir}/scrypt" enc --logN 10 -r 1 -p 1 \ + --passphrase file:"${alias_password}" /dev/null /dev/null + echo $? > "${c_exitfile}" + + # Equal contents are not the same inode; preserve and roundtrip the input. + independent_input="${s_basename}-independent-input" + independent_output="${s_basename}-independent-output" + independent_plain="${s_basename}-independent-plain" + cp "${reference_file}" "${independent_input}" + cp "${reference_file}" "${independent_output}" + setup_check "scrypt enc permits identical contents in independent files" + ${c_valgrind_cmd} "${bindir}/scrypt" enc --logN 10 -r 1 -p 1 \ + --passphrase file:"${alias_password}" \ + "${independent_input}" "${independent_output}" + echo $? > "${c_exitfile}" + + setup_check "scrypt enc independent files preserves input" + cmp -s "${independent_input}" "${reference_file}" + echo $? > "${c_exitfile}" + + setup_check "scrypt dec independent output roundtrip" + ${c_valgrind_cmd} "${bindir}/scrypt" dec \ + --passphrase file:"${alias_password}" \ + "${independent_output}" "${independent_plain}" + echo $? > "${c_exitfile}" + + setup_check "scrypt dec independent output matches input" + cmp -s "${independent_plain}" "${reference_file}" + echo $? > "${c_exitfile}" }