Skip to content

scryptenc: validate the header parameters used by "scrypt info" - #426

Open
woahwhattheheck wants to merge 3 commits into
Tarsnap:masterfrom
woahwhattheheck:scrypt-info-header-validation
Open

scryptenc: validate the header parameters used by "scrypt info"#426
woahwhattheheck wants to merge 3 commits into
Tarsnap:masterfrom
woahwhattheheck:scrypt-info-header-validation

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 #425.

scryptdec_file_printparams() — the scrypt info path — reads logN from
header[7] and passes it to display_params(), which computes
(uint64_t)(1) << logN. scryptdec_file_load_header() validates only the
magic and the version byte, so logN can be any value in [0, 255] and
anything >= 64 is an out-of-range shift. Every other caller of
display_params() bounds logN first (checkparams() rejects
logN < 1 || logN > 63; pickparams() never exceeds 63), so info is the
only way in — and the only one reached with untrusted input.

Commit 1 — reject out-of-range parameters in scrypt info

  • Validate logN, r and p in scryptdec_file_printparams() using the same
    rules checkparams() uses, returning SCRYPT_EINVAL ("Input is not valid
    scrypt-encrypted block") otherwise.
  • assert() the logN bound in display_params(), so the precondition the
    shift relies on is stated where the shift is.
  • Compute 128 * r * N in 64-bit arithmetic. 128 * r is a uint32_t
    expression and wraps for r >= 2^25, which checkparams() permits — a
    header with r = 0x02000000 is currently reported as needing "0 B" of
    memory rather than 8.5 GB, and that one is reachable from scrypt dec -v
    as well, since display_params() runs before the limit checks. The product
    can still exceed UINT64_MAX, so it saturates rather than wrapping.
  • Compute 4 * N * r * p / opps in floating point; accumulated in uint64_t
    it overflows for logN = 63.
  • Add tests/11-info.shscrypt info had no test coverage at all — with a
    96-byte fixture that has a valid checksum and an out-of-range logN.

Commit 2 — verify the header checksum in scrypt info

scryptdec_setup() rejects a header whose SHA-256 checksum does not match;
scryptdec_file_printparams() never checked it, so scrypt info on a file
with a damaged header printed whatever N, r and p the damaged bytes
encoded and exited 0. This performs the same check.

This commit is separable from the first — the first fixes the undefined
behaviour on its own — so it can be dropped if you would rather info stay
purely descriptive. The test fixture's checksum is valid either way, so the
logN test still exercises the parameter check with this commit applied.

Notes

  • No behaviour change for well-formed files: scrypt info on
    tests/verify-strings/test_scrypt_good.enc still prints
    N = 262144; r = 8; p = 1; and exits 0.
  • Public API and file format are untouched; the only new failure mode is
    SCRYPT_EINVAL on a header that was already invalid.
  • The assert() matches the existing one in scryptenc_setup().

scryptdec_file_printparams() passed header[7] straight to
display_params(), which computes N as (uint64_t)(1) << logN.  Because
scryptdec_file_load_header() only checks the magic and the version byte,
logN can be anything from 0 to 255, and any value >= 64 makes that an
out-of-range shift (C99 6.5.7p3).  A 96-byte file beginning "scrypt\0"
followed by a 0xff byte is enough to reach it via "scrypt info".

Validate logN, r and p with the same rules checkparams() uses before
handing them to display_params(), and return SCRYPT_EINVAL otherwise.

Also fix two overflows in display_params() itself: "128 * r" has type
uint32_t and wraps for r >= 2^25 (so a header with r = 0x02000000 was
reported as needing "0 B" of memory), and 4 * N * r * p can exceed
UINT64_MAX.  Compute the first in 64-bit arithmetic with saturation and
the second in floating point, and assert the logN bound the shift needs.

Add tests/11-info.sh, which covers "scrypt info" for the first time.
scryptdec_setup() rejects a header whose SHA-256 checksum does not match,
but scryptdec_file_printparams() never checked it.  "scrypt info" on a
file with a corrupt header therefore printed whatever N, r and p the
damaged bytes happened to encode and exited 0, reporting parameters that
were never used to encrypt anything.

Perform the same check that scryptdec_setup() does, so that "info"
reports "Input is not valid scrypt-encrypted block" instead.
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] "scrypt info" shifts by an unvalidated logN byte from the file header

2 participants