Skip to content

argon2-wasm: fix uninitialized read for random salt - #325

Open
danleh wants to merge 1 commit into
WebKit:mainfrom
danleh:wasm-argon2-salt-fix
Open

danleh wants to merge 1 commit into
WebKit:mainfrom
danleh:wasm-argon2-salt-fix

Conversation

@danleh

@danleh danleh commented Oct 31, 2026

Copy link
Copy Markdown
Contributor

In workloads/argon2-wasm/benchmark.js (added in #47), Benchmark.randomSalt() allocates a 12-byte buffer with malloc and writes three random 32-bit words into Module.HEAPU32:

Module.HEAPU32[result.ptr + i] = Math.floor(Math.random() * (2 ** 32));

result.ptr is a byte offset in Wasm linear memory, while Module.HEAPU32 is a Uint32Array indexed by 4-byte words. Without dividing result.ptr by 4, the writes land at byte offset 4 * result.ptr instead of inside the allocated buffer, so argon2_hash() reads uninitialized heap memory as the salt. argon2_verify() still passes because argon2_hash() encodes whatever salt bytes it read into the output string, and argon2_verify() extracts the salt from that string.

Divide the byte offset by 4 ((result.ptr >> 2) + i) when indexing Module.HEAPU32.

Scores: Argon2id's work per hash (t=3, m=1 MiB, p=1) does not depend on the salt bytes, and scores do not change significantly (<shell> cli.js --test=argon2-wasm on a Linux x64 workstation, 16 interleaved rounds):

Shell main This PR Change 95% CI
d8 (V8 14.6.82) 128.38 130.97 +2.0% (10/16) −0.8% to +4.7%
jsc (JSC 306270) 81.09 81.50 +0.5% (9/16) −2.1% to +3.3%
SpiderMonkey 148.0b7 127.82 125.60 −1.7% (4/16) −4.3% to +0.9%

In workloads/argon2-wasm/benchmark.js (added in WebKit#47),
Benchmark.randomSalt() allocates a 12-byte buffer with malloc and
writes three random 32-bit words into Module.HEAPU32:

  Module.HEAPU32[result.ptr + i] = Math.floor(Math.random() * (2 ** 32));

result.ptr is a byte offset in Wasm linear memory, while Module.HEAPU32
is a Uint32Array indexed by 4-byte words. Without dividing result.ptr
by 4, the writes land at byte offset 4 * result.ptr instead of inside
the allocated buffer, so argon2_hash() reads uninitialized heap memory
as the salt. argon2_verify() still passes because argon2_hash() encodes
whatever salt bytes it read into the output string, and argon2_verify()
extracts the salt from that string.

Divide the byte offset by 4 ((result.ptr >> 2) + i) when indexing
Module.HEAPU32. Argon2id's work per hash (t=3, m=1 MiB, p=1) does not
depend on the salt bytes, and scores do not change significantly.
@danleh
danleh requested review from eqrion and kmiller68 October 31, 2026 02:15
@netlify

netlify Bot commented Oct 31, 2026 •

Copy link
Copy Markdown

✅ Deploy Preview for webkit-jetstream-preview ready!

Name Link
🔨 Latest commit cc2a761
🔍 Latest deploy log https://app.netlify.com/projects/webkit-jetstream-preview/deploys/6ae54f26a13f9e76755f6739
😎 Deploy Preview https://deploy-preview-325--webkit-jetstream-preview.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

⌂ Proxy