Skip to content

scryptenc: free the expanded AES key when a write fails - #430

Open
woahwhattheheck wants to merge 2 commits into
Tarsnap:masterfrom
woahwhattheheck:scrypt-free-key-on-write-error
Open

scryptenc: free the expanded AES key when a write fails#430
woahwhattheheck wants to merge 2 commits into
Tarsnap:masterfrom
woahwhattheheck:scrypt-free-key-on-write-error

Conversation

@woahwhattheheck

@woahwhattheheck woahwhattheheck commented Sep 6, 2026

Copy link
Copy Markdown

Disclosure, per this repository's AGENTS.md: I am an LLM (Claude), submitting on behalf of the account owner. I am available to discuss this change and to revise it in response to review feedback.

Fixes #429.

scryptenc_file() and scryptdec_file_copy() free the crypto_aesctr stream
when a write to the output file fails partway through, but not the
struct crypto_aes_key * it was built from:

		if (fwrite(buf, 1, readlen, outfile) < readlen) {
			crypto_aesctr_free(AES);
			rc = SCRYPT_EWRFILE;
			goto err1;
		}

err1 in scryptenc_file() only zeroes dk, and err0 in
scryptdec_file_copy() only returns, so key_enc_exp never reaches
crypto_aes_key_free(). The crypto_aesctr_init() failure path a few lines
above each of these already frees it; this just makes the write-error path
match. The _buf variants have no fwrite() and are unaffected.

The change

Two lines, one in each function. After it, every exit from the scope of a
crypto_aes_key_expand() in this file frees the result.

Test

tests/13-write-error.sh is new. The write-error path in these two loops has
no coverage today, which is why make test USE_VALGRIND=1 does not catch this.
The test builds an input larger than stdio's buffer — so the failure lands
inside the loop rather than at the final flush — and points the output at
/dev/full, checking exit status 1 and the Error writing file message for
both enc and dec. It also confirms that encrypting the same large input to
a normal file still succeeds.

/dev/full is not portable, so the test does that part only if
[ -c /dev/full ] and [ -w /dev/full ]; elsewhere it runs the plain
large-file encryption check and returns. Explicit parameters plus -f keep
the key derivation cheap so the test does not measure CPU speed on every run.

Note on the test number

Numbered 13 so it does not collide with tests/11-info.sh from #426 or
tests/12-same-file.sh from #428. The ordering works whichever of the three
lands first; the only overlap between them is the adjacent line each adds to
EXTRA_DIST in Makefile.am. Happy to renumber.

scryptenc_file() and scryptdec_file_copy() both free the crypto_aesctr
stream when a write to the output file fails partway through the data, but
not the struct crypto_aes_key it was built from.  err1 in scryptenc_file()
only zeroes dk, and err0 in scryptdec_file_copy() only returns, so
key_enc_exp never reaches crypto_aes_key_free().

The crypto_aesctr_init() failure path a few lines above each of these
already frees it; make the write-error path match.  scryptenc_buf() and
scryptdec_buf() have no fwrite() and are unaffected.

Add tests/13-write-error.sh, which drives both loops into a failing write
via /dev/full -- these paths had no coverage, which is why "make test
USE_VALGRIND=1" did not notice.  The test uses an input larger than stdio's
buffer so the failure lands inside the loop rather than at the final flush,
and skips the /dev/full part on platforms which do not have it.
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.
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] The expanded AES key is leaked when a write to the output file fails

2 participants