Skip to content

Fix GH-21734: WHATWG URL parser accepts overlong UTF-8 and invalid continuation bytes#35

Closed
iliaal wants to merge 1 commit intomasterfrom
fix/gh-21734-lexbor-utf8-validation
Closed

Fix GH-21734: WHATWG URL parser accepts overlong UTF-8 and invalid continuation bytes#35
iliaal wants to merge 1 commit intomasterfrom
fix/gh-21734-lexbor-utf8-validation

Conversation

@iliaal
Copy link
Copy Markdown
Owner

@iliaal iliaal commented Apr 12, 2026

Fixes php#21734.

lxb_encoding_decode_valid_utf_8_single() had no UTF-8 validation: no continuation byte range checks, no overlong sequence rejection, no surrogate rejection. It was written to assume the caller already verified the input. The URL parser calls it on untrusted user input at 7 sites, and the IDNA code calls it on percent-decoded hostname bytes at 2 more.

An attacker feeding overlong ASCII characters into a hostname could get them through IDNA processing as their target codepoints, producing valid domains from byte sequences that look nothing like the canonical form. %C1%A5%C1%B6%C1%A9%C1%AC.com resolved to evil.com. Chrome, Firefox, and Safari reject overlong sequences at the UTF-8 decode step.

Added the missing validation to decode_valid_utf_8_single:

  • 2-byte: reject lead bytes < 0xC2 (overlong), validate continuation byte range
  • 3-byte: validate continuations, reject 0xE0 + < 0xA0 (overlong), reject 0xED + > 0x9F (surrogates)
  • 4-byte: reject lead > 0xF4, validate continuations, reject 0xF0 + < 0x90 (overlong), reject 0xF4 + > 0x8F (> U+10FFFF)

On error, the decoder advances by 1 byte (not the full sequence length) so the next byte gets its own decode attempt, matching browser behavior.

… continuation bytes

lxb_encoding_decode_valid_utf_8_single() skipped all UTF-8 validation
(continuation byte range, overlong sequences, surrogates), trusting
the caller to pass valid input. The URL parser calls it on untrusted
user input at 7 sites, and the IDNA code calls it on percent-decoded
hostname bytes at 2 more.

Overlong ASCII characters in hostnames passed through IDNA processing
as their target codepoints, producing valid domains from byte sequences
that look nothing like the canonical form (e.g., %C1%A5%C1%B6... →
"evil.com"). Chrome, Firefox, and Safari reject these at the UTF-8
decode step.

Add the missing validation to decode_valid_utf_8_single:
- 2-byte: reject lead bytes < 0xC2 (overlong), validate continuation
- 3-byte: validate continuations, reject 0xE0 + < 0xA0 (overlong),
  reject 0xED + > 0x9F (surrogates)
- 4-byte: reject lead > 0xF4, validate continuations, reject
  0xF0 + < 0x90 (overlong), reject 0xF4 + > 0x8F (> U+10FFFF)

On error, advance by 1 byte (not the full sequence length) so the
next byte gets its own decode attempt, matching browser behavior.

Closes phpGH-21734
@iliaal
Copy link
Copy Markdown
Owner Author

iliaal commented Apr 12, 2026

Submitted upstream as php#21735

@iliaal iliaal closed this Apr 12, 2026
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.

ext/uri: WHATWG URL parser accepts overlong UTF-8 and invalid continuation bytes in hostnames

1 participant