Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -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 \
Expand Down
45 changes: 45 additions & 0 deletions main.c
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@
*/
#include "platform.h"

#include <sys/types.h>
#include <sys/stat.h>

#include <errno.h>
#include <math.h>
#include <stdint.h>
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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",
Expand Down
10 changes: 10 additions & 0 deletions scrypt.1
Original file line number Diff line number Diff line change
Expand Up @@ -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 .
Expand Down
153 changes: 153 additions & 0 deletions tests/12-same-file.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
#!/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}"

# 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}"
}