Balloon space cost wraps before conversion to the 64-bit block count
Balloon stores its block count in 64 bits, but computes s_cost * 1024 while both operands are still 32-bit. An accepted public s_cost = 4194305 therefore wraps the requested roughly 4-GiB work factor to 1024 bytes; with SHA-256 the KDF allocates and processes only 32 blocks while retaining the large advertised parameter.
Vulnerable code
cipher/kdf.c, balloon_open:
c
s_cost = (unsigned int)param[0];
t_cost = (unsigned int)param[1];
if (paramlen >= 3)
parallelism = (unsigned int)param[2];c
if (s_cost < 1)
return GPG_ERR_INV_VALUE;c b->s_cost = s_cost; b->t_cost = t_cost; b->parallelism = parallelism; b->n_blocks = (s_cost * 1024) / b->blklen; block = xtrycalloc (parallelism * b->n_blocks, b->blklen);
Why it matters
The allocator receives the already wrapped small block count, so ordinary overflow checks and memory limits do not reject it. Expansion and mixing use b->n_blocks, making the operation substantially less memory-hard and nonconforming rather than merely misreporting an allocation. An application must trust externally supplied Balloon parameters for an attacker to turn this into a cost-policy bypass; fixed application-owned parameters are unaffected. The resulting derivation still includes the original s_cost, so it should not be described as identical to an ordinary low-cost parameter set.
Proposed fix
Promote before multiplication, for example computing ((u64)s_cost * 1024) / blklen, then verify that the derived block count, parallelism * n_blocks, and final byte allocation all fit size_t. Apply an explicit supported upper bound before allocating. Add a regression for 4194305 with SHA-256 that requires clean rejection (or the full representable cost), plus boundary/reference-vector tests proving no wrapped parameter runs with a reduced block count.