fix(hash): bound Argon2 time + PBKDF2 iterations against an untrusted-hash CPU-DoS#67
Merged
Conversation
…-hash CPU-DoS
A resource-exhaustion DoS: the Argon2 and PBKDF2 verify paths take their cost
parameters from the hash being verified (reachable over MCP and via
hash_crack_dictionary), but the iteration/time cost had no upper bound.
- Argon2: parseArgon2PHC capped memory (OOM) and parallelism is uint8-bounded,
but t= was unbounded. A crafted "$argon2id$v=19$m=65536,t=4294967295,p=1$…"
hash drives argon2.IDKey through ~4 billion passes — an unbounded CPU hang
the memory cap does not catch. Compute had the same hole via uint32(intOr):
time=-1 wraps to ~4 billion and slips past the lower-bound-only check.
- PBKDF2 (Django/Werkzeug, internal/webpass): parseIter checked n>=1 but had
no ceiling, so "pbkdf2_sha256$999999999999$…" spins ~10^12 HMACs on verify.
Sibling of the Argon2 bug; internal/dcc2 and internal/phpass already cap at
2^24.
Fixes, all enforced BEFORE the expensive derivation runs:
- argon2MaxTime = 1024 (hugely generous vs the RFC 9106 t=1..3) checked in
parseArgon2PHC (verify/crack) and compute. Compute now validates the signed
ints first, then narrows to uint32 — so a negative value is rejected with a
clear message instead of wrapping to a huge cost.
- maxPBKDF2Iterations = 1<<24 (~16.7M, far above any real Django/Werkzeug
config) checked in parseIter (verify) and Compute, matching the dcc2/phpass
convention.
Tests reject the hostile params in ~0ms (proving no derivation runs) while
legitimate params still compute. govulncheck clean; no new go.mod deps.
gofmt / go vet / task lint (0 issues) / task test:full (race) all green.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
A resource-exhaustion DoS: the Argon2 and PBKDF2 verify paths take their cost parameters from the hash being verified (reachable over MCP and via
hash_crack_dictionary), but the iteration/time cost had no upper bound.parseArgon2PHCcapped memory (OOM) and parallelism isuint8-bounded, butt=was unbounded. A crafted$argon2id$v=19$m=65536,t=4294967295,p=1$…hash drivesargon2.IDKeythrough ~4 billion passes — an unbounded CPU hang the memory cap doesn't catch. Compute had the same hole viauint32(intOr(...)):time=-1wraps to ~4 billion and slips past the lower-bound-only check.parseItercheckedn>=1but had no ceiling, sopbkdf2_sha256$999999999999$…spins ~10¹² HMACs on verify. Sibling of the Argon2 bug —internal/dcc2andinternal/phpassalready cap at 2²⁴.Fixes (all enforced before the expensive derivation)
argon2MaxTime = 1024(hugely generous vs RFC 9106's t=1..3) checked inparseArgon2PHC(verify/crack) and compute. Compute now validates the signed ints first, then narrows touint32— so a negative value is rejected cleanly instead of wrapping to a huge cost.maxPBKDF2Iterations = 1<<24(~16.7M, far above any real config) inparseIter+Compute, matching the dcc2/phpass convention.Tests
Reject the hostile params in ~0ms (proving no derivation runs) while legitimate params still compute.
govulncheckclean; no newgo.moddeps.Gates
gofmt·go vet ./...·task lint0 issues ·task test:full(race) ·task vuln— all green.