Skip to content

Stop Base64url.parse from leaking its reverse map across urlSafe calls - #540

Open
afonsojanu wants to merge 1 commit into
brix:developfrom
afonsojanu:fix/base64url-parse-reversemap-leak
Open

Stop Base64url.parse from leaking its reverse map across urlSafe calls#540
afonsojanu wants to merge 1 commit into
brix:developfrom
afonsojanu:fix/base64url-parse-reversemap-leak

Conversation

@afonsojanu

Copy link
Copy Markdown

Fixes #531.

parse() memoizes its reverse-lookup table directly on the shared Base64url object the first time it runs, but that table only has entries for whichever map (safe or non-safe) built it. Once cached, every later call reused the same table regardless of the urlSafe argument, so a non-safe parse() followed by a safe one (or vice versa) silently decoded any character unique to the other map into garbage instead of throwing or failing loudly.

I confirmed this with a small standalone repro before touching anything: encoding a byte whose base64url form uses -, calling parse('AAAA', false) first to prime the cache with the non-safe table, then parsing the --containing string back. Unpatched, it decodes to the wrong bytes (re-encoding gives Aw instead of the original -w). With the fix applied, it round-trips correctly.

The fix keeps two separate cached tables (_safeReverseMap and _reverseMap) instead of one, mirroring how stringify() already keeps _safe_map and _map apart, so nothing about the memoization behavior changes other than making it correct per variant.

There wasn't an existing test file for Base64url at all (only enc-base64-test.js for the non-url variant), so I added test/enc-base64url-test.js in the same YUI Test style, covering stringify/parse for both map variants plus the specific cross-contamination scenario from the issue in both directions.

On verification: this repo's npm test only runs jsonlint + jshint (no automated functional test runner is wired up here, and there's no test/index.html in this checkout to drive the YUI tests in a browser), so I couldn't run the added test file through the project's own harness. I did verify the fix and the added test's assertions manually against both the unpatched and patched source with a small Node script requiring src/core.js and src/enc-base64url.js directly, and ran jshint against the changed source file with the repo's own .jshintrc, which passes clean.

parse() memoized its reverse-lookup table on the shared Base64url
object the first time it ran, but the table only holds entries for
whichever map (safe or non-safe) built it. A later call with a
different urlSafe value kept reusing that stale table instead of
building its own, so decoding a url-safe string right after a
non-safe call (or the other way around) silently produced the wrong
bytes for any character that only exists in one of the two maps.

Cached the reverse map per variant instead (_safeReverseMap and
_reverseMap) so each urlSafe value gets its own table, same as
stringify() already keeps _safe_map and _map separate.

Added enc-base64url-test.js, which didn't exist yet, covering both
directions of the original bug plus the existing stringify/parse
round trip for both map variants.
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.

CryptoJS.enc.Base64url.parse bug

1 participant