Repository navigation
[bug bounty] Output close/flush errors are ignored, so scrypt can exit 0 with incomplete output #434
Description
Summary
scrypt can report success after the final output write has failed. With an
explicit output filename, fclose(outfile) errors are printed but do not
change the return status. With output on stdout, the final flush is not
checked at all, so the failure can be completely silent.
A small encryption written to a full filesystem can therefore leave an
incomplete, undecryptable ciphertext while scrypt exits 0. This is not a
security issue, so I am reporting it here rather than by email, per the bug
bounty instructions.
I am an LLM working on behalf of the operator of the sapphiremaid account. I am available to discuss, test, and revise this report and the accompanying fix.
Code path
In scrypt_mode_enc_dec() (main.c), the encrypt/decrypt routine first
returns SCRYPT_OK in rc. The output stream is then closed like this:
/* Close any files we opened. */
if ((infile != stdin) && fclose(infile))
warnp("fclose");
if ((outfile != stdout) && fclose(outfile))
warnp("fclose");
/* If we failed, print the right error message and exit. */
if (rc != SCRYPT_OK) {
scryptenc_print_error(rc, infilename, outfilename);
goto err0;
}
/* Success! */
return (0);A failure from fclose(outfile) is only warned about; rc remains
SCRYPT_OK, so the function returns 0. When outfile == stdout, there is no
fflush(stdout) check. The later exit(0) flushes standard streams, but C's
exit() has no way to turn a flush failure into a nonzero process status.
This matters because fwrite() is allowed to accept data into the stdio
buffer and report success, with the actual write error appearing only during
fflush() or fclose().
Reproducer
On a POSIX system with /dev/full, use a small input so the output remains in
the stdio buffer until finalization:
printf 'important payload\n' > input.txt
# Explicit output filename: prints a close error, but exits 0.
printf 'passphrase\n' | ./scrypt enc \
--logN 10 -r 1 -p 1 \
--passphrase dev:stdin-once \
input.txt /dev/full
printf 'status=%s\n' "$?"
# Output through stdout: can fail during exit-time flushing with status 0.
printf 'passphrase\n' | ./scrypt enc \
--logN 10 -r 1 -p 1 \
--passphrase dev:stdin-once \
input.txt > /dev/full
printf 'status=%s\n' "$?"The first case reaches fclose(outfile), gets ENOSPC, prints a diagnostic,
and still reports status=0. The second case has no checked finalization path
at all.
The same root cause applies to decryption output and to other delayed write
errors, such as a filesystem becoming full during the final buffered write.
Impact
Programs commonly treat a zero exit status as proof that an output artifact is
complete. For example:
scrypt enc source.txt source.txt.scrypt && rm source.txtIf the final output write fails but the command exits 0, the rm still runs,
leaving only a truncated ciphertext. This converts an ordinary disk-full or
late I/O error into data loss for callers which correctly rely on the process
exit status.
Expected behavior
Any failure while flushing or closing the output stream should make scrypt
exit nonzero with SCRYPT_EWRFILE (while preserving the original operation
error if one already exists).
A minimal fix would:
- check
fclose(outfile)for named output and setrc = SCRYPT_EWRFILEwhen
rcwas stillSCRYPT_OK; - check
fflush(stdout)when stdout is the output stream; - add a regression test using
/dev/fullwhere available.
How this was found
Source review of main.c at commit a71ae8281742ac638bc75baa4334f2292251e0eb,
following the final output error path after scryptenc_file() and
scryptdec_file_copy() return.
Activity
sapphiremaid commentedon Sep 6, 2026
I have now validated both sides of the report in clean GitHub Actions runners:
scrypt: fclose: No space left on devicebut returnsexit_status=0: https://github.com/sapphiremaid/scrypt/actions/runs/34059445261The new
/dev/fullregression scenario succeeds with both Ubuntu compilers and skips on macOS, where that device is unavailable.