From d18b498913f2cb2f1f48bb2a59b6b0f196a8a23d Mon Sep 17 00:00:00 2001 From: Alexandre Daubois Date: Fri, 11 Sep 2026 10:51:39 +0200 Subject: [PATCH] lexbor: Merge upstream WHATWG URL and memory safety fixes --- NEWS | 7 ++ .../modern/css_selectors/lexbor_contains.phpt | 16 +++ .../lexbor/css/selectors/pseudo_state.c | 5 +- ext/lexbor/lexbor/encoding/decode.c | 37 ++++++- ext/lexbor/lexbor/unicode/idna.c | 28 ++++-- ext/lexbor/lexbor/url/url.c | 19 ++-- ...nd-column-information-for-use-in-PHP.patch | 2 +- ...d-added-nodes-for-options-use-in-PHP.patch | 2 +- ...and-data-structure-to-be-able-to-gen.patch | 2 +- ...ve-unused-upper-case-tag-static-data.patch | 2 +- ...nk-size-of-static-binary-search-tree.patch | 2 +- ...0006-Patch-out-unused-CSS-style-code.patch | 2 +- ...07-URL-fixed-setters-for-empty-hosts.patch | 2 +- ...ialized-memory-in-the-path-buffer-gr.patch | 2 +- ...URL-containing-empty-host-and-userin.patch | 2 +- ...Percent-encode-the-caret-in-the-path.patch | 2 +- ...ointer-in-URLSearchParams-for-delimi.patch | 29 ++++++ ...uffer-overflow-in-lexbor-contains-pa.patch | 35 +++++++ ...uffer-overflows-in-malformed-decode-.patch | 97 +++++++++++++++++++ ...-Fix-parsing-of-usernames-containing.patch | 38 ++++++++ ...initialized-memory-in-IDNA-buffer-gr.patch | 81 ++++++++++++++++ .../parsing/host_success_multibyte_long.phpt | 21 ++++ .../parsing/username_success_at_sign.phpt | 18 ++++ 23 files changed, 415 insertions(+), 36 deletions(-) create mode 100644 ext/dom/tests/modern/css_selectors/lexbor_contains.phpt create mode 100644 ext/lexbor/patches/0011-URL-fixed-tail-pointer-in-URLSearchParams-for-delimi.patch create mode 100644 ext/lexbor/patches/0012-CSS-fixed-heap-buffer-overflow-in-lexbor-contains-pa.patch create mode 100644 ext/lexbor/patches/0013-Encoding-fixed-buffer-overflows-in-malformed-decode-.patch create mode 100644 ext/lexbor/patches/0014-URL-Fix-parsing-of-usernames-containing.patch create mode 100644 ext/lexbor/patches/0015-Unicode-fixed-uninitialized-memory-in-IDNA-buffer-gr.patch create mode 100644 ext/uri/tests/whatwg/parsing/host_success_multibyte_long.phpt create mode 100644 ext/uri/tests/whatwg/parsing/username_success_at_sign.phpt diff --git a/NEWS b/NEWS index 37f27f3572c3..115e6e3cc4f1 100644 --- a/NEWS +++ b/NEWS @@ -11,6 +11,13 @@ PHP NEWS registrations are freed while still reachable from the cycle collector. (Ilia Alshanetsky) +- Lexbor: + . Merge patches 859f100, 8a14bc0, f67ce4b, a36e09a and b0f7412, fixing a heap + buffer overflow in :lexbor-contains() parsing, buffer overflows in malformed + decode replay, uninitialized memory in IDNA buffer growth, dropped usernames + containing an at sign and the URLSearchParams tail pointer. + (alexandre-daubois) + 24 Sep 2026, PHP 8.5.11 diff --git a/ext/dom/tests/modern/css_selectors/lexbor_contains.phpt b/ext/dom/tests/modern/css_selectors/lexbor_contains.phpt new file mode 100644 index 000000000000..795a8f110e37 --- /dev/null +++ b/ext/dom/tests/modern/css_selectors/lexbor_contains.phpt @@ -0,0 +1,16 @@ +--TEST-- +CSS Selectors - Pseudo classes: :lexbor-contains() with an argument longer than its string header +--EXTENSIONS-- +dom +--FILE-- +needle

', LIBXML_NOERROR); + +var_dump($dom->querySelectorAll(':lexbor-contains("' . str_repeat('needle', 1024) . '")')->length); +var_dump($dom->querySelectorAll(':lexbor-contains("needle")')->length); + +?> +--EXPECT-- +int(0) +int(0) diff --git a/ext/lexbor/lexbor/css/selectors/pseudo_state.c b/ext/lexbor/lexbor/css/selectors/pseudo_state.c index 263ca52f35e4..2321ddf01bbe 100644 --- a/ext/lexbor/lexbor/css/selectors/pseudo_state.c +++ b/ext/lexbor/lexbor/css/selectors/pseudo_state.c @@ -227,13 +227,12 @@ lxb_css_selectors_state_pseudo_class_function_lexbor_contains(lxb_css_parser_t * contains->insensitive = false; str = &contains->str; - str->data = lexbor_mraw_alloc(parser->memory->mraw, - sizeof(lexbor_str_t)); + str->data = lexbor_mraw_alloc(parser->memory->mraw, length + 1); if (str->data == NULL) { return lxb_css_parser_memory_fail(parser); } - memcpy(str->data, data, length + 1); + memcpy(str->data, data, length); str->length = length; str->data[length] = '\0'; diff --git a/ext/lexbor/lexbor/encoding/decode.c b/ext/lexbor/lexbor/encoding/decode.c index 3e48971e3657..05c4b9bde771 100644 --- a/ext/lexbor/lexbor/encoding/decode.c +++ b/ext/lexbor/lexbor/encoding/decode.c @@ -912,6 +912,13 @@ lxb_encoding_decode_iso_2022_jp(lxb_encoding_decode_t *ctx, } LXB_ENCODING_DECODE_ERROR_END(); + if (ctx->buffer_used >= ctx->buffer_length) { + iso->prepand = iso->lead; + iso->lead = 0x00; + + return LXB_STATUS_SMALL_BUFFER; + } + byte = iso->lead; iso->lead = 0x00; @@ -1279,6 +1286,12 @@ lxb_encoding_decode_utf_16(lxb_encoding_decode_t *ctx, bool is_be, } LXB_ENCODING_DECODE_ERROR_END(); + if (ctx->buffer_used >= ctx->buffer_length) { + ctx->u.lead = lead + 0x01; + + return LXB_STATUS_SMALL_BUFFER; + } + goto lead_state; } @@ -1723,6 +1736,13 @@ lxb_encoding_decode_gb18030(lxb_encoding_decode_t *ctx, } LXB_ENCODING_DECODE_ERROR_END(); + if (ctx->buffer_used >= ctx->buffer_length) { + ctx->prepend = true; + ctx->u.gb18030.first = second; + + return LXB_STATUS_SMALL_BUFFER; + } + first = second; goto prepend_first; @@ -1756,11 +1776,8 @@ lxb_encoding_decode_gb18030(lxb_encoding_decode_t *ctx, } LXB_ENCODING_DECODE_ERROR_END(); - LXB_ENCODING_DECODE_APPEND_WO_CHECK(ctx, second); - - if (ctx->buffer_used == ctx->buffer_length) { + if (ctx->buffer_used >= ctx->buffer_length) { ctx->prepend = true; - ctx->have_error = true; /* First is a fake for trigger */ ctx->u.gb18030.first = 0x01; @@ -1770,6 +1787,18 @@ lxb_encoding_decode_gb18030(lxb_encoding_decode_t *ctx, return LXB_STATUS_SMALL_BUFFER; } + LXB_ENCODING_DECODE_APPEND_WO_CHECK(ctx, second); + + if (ctx->buffer_used >= ctx->buffer_length) { + ctx->prepend = true; + + ctx->u.gb18030.first = third; + ctx->u.gb18030.second = 0x00; + ctx->u.gb18030.third = 0x00; + + return LXB_STATUS_SMALL_BUFFER; + } + first = third; goto prepend_first; diff --git a/ext/lexbor/lexbor/unicode/idna.c b/ext/lexbor/lexbor/unicode/idna.c index 754f6b2026de..b31ae3338b92 100644 --- a/ext/lexbor/lexbor/unicode/idna.c +++ b/ext/lexbor/lexbor/unicode/idna.c @@ -117,12 +117,14 @@ lxb_unicode_idna_realloc(lxb_codepoint_t *buf, const lxb_codepoint_t *buffer, lxb_codepoint_t *tmp; nlen = ((*buf_end - buf) * 4) + len; - + if (buf == buffer) { tmp = lexbor_malloc(nlen * sizeof(lxb_codepoint_t)); if (tmp == NULL) { return NULL; } + + memcpy(tmp, buf, (*buf_p - buf) * sizeof(lxb_codepoint_t)); } else { tmp = lexbor_realloc(buf, nlen * sizeof(lxb_codepoint_t)); @@ -458,13 +460,17 @@ lxb_unicode_idna_ascii_puny_cb(const lxb_char_t *data, size_t length, void *ctx, if (asc->buf == asc->buffer) { tmp = lexbor_malloc(nlen); + if (tmp == NULL) { + return LXB_STATUS_ERROR_MEMORY_ALLOCATION; + } + + memcpy(tmp, asc->buf, asc->p - asc->buf); } else { tmp = lexbor_realloc(asc->buf, nlen); - } - - if (tmp == NULL) { - return LXB_STATUS_ERROR_MEMORY_ALLOCATION; + if (tmp == NULL) { + return LXB_STATUS_ERROR_MEMORY_ALLOCATION; + } } asc->p = tmp + (asc->p - asc->buf); @@ -711,13 +717,17 @@ lxb_unicode_idna_to_unicode_cb(const lxb_codepoint_t *part, size_t len, if (asc->buf == asc->buffer) { tmp = lexbor_malloc(nlen); + if (tmp == NULL) { + return LXB_STATUS_ERROR_MEMORY_ALLOCATION; + } + + memcpy(tmp, asc->buf, asc->p - asc->buf); } else { tmp = lexbor_realloc(asc->buf, nlen); - } - - if (tmp == NULL) { - return LXB_STATUS_ERROR_MEMORY_ALLOCATION; + if (tmp == NULL) { + return LXB_STATUS_ERROR_MEMORY_ALLOCATION; + } } asc->p = tmp + (asc->p - asc->buf); diff --git a/ext/lexbor/lexbor/url/url.c b/ext/lexbor/lexbor/url/url.c index de19239936a1..654e2e6970b7 100644 --- a/ext/lexbor/lexbor/url/url.c +++ b/ext/lexbor/lexbor/url/url.c @@ -1753,16 +1753,13 @@ lxb_url_parse_basic_h(lxb_url_parser_t *parser, lxb_url_t *url, break; } - if (pswd == NULL || !at_sign) { - tmp = (pswd != NULL) ? pswd - 1 : p; - - if (tmp > begin) { - status = lxb_url_percent_encode_after_utf_8(begin, tmp, - &url->username, url->mraw, - LXB_URL_MAP_USERINFO, false); - if (status != LXB_STATUS_OK) { - lxb_url_parse_return(orig_data, buf, status); - } + tmp = (pswd != NULL) ? pswd - 1 : p; + if (tmp > begin) { + status = lxb_url_percent_encode_after_utf_8(begin, tmp, + &url->username, url->mraw, + LXB_URL_MAP_USERINFO, false); + if (status != LXB_STATUS_OK) { + lxb_url_parse_return(orig_data, buf, status); } } @@ -5106,6 +5103,8 @@ lxb_url_search_params_parse(lxb_url_search_params_t *search_params, return status; } + last = entry; + lexbor_str_init(&entry->value, mraw, 0); if (entry->value.data == NULL) { return LXB_STATUS_ERROR_MEMORY_ALLOCATION; diff --git a/ext/lexbor/patches/0001-Expose-line-and-column-information-for-use-in-PHP.patch b/ext/lexbor/patches/0001-Expose-line-and-column-information-for-use-in-PHP.patch index 533598837822..6bc4929e9b07 100644 --- a/ext/lexbor/patches/0001-Expose-line-and-column-information-for-use-in-PHP.patch +++ b/ext/lexbor/patches/0001-Expose-line-and-column-information-for-use-in-PHP.patch @@ -1,7 +1,7 @@ From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Sat, 26 Aug 2023 15:08:59 +0200 -Subject: [PATCH 01/10] Expose line and column information for use in PHP +Subject: [PATCH 01/15] Expose line and column information for use in PHP --- source/lexbor/dom/interfaces/node.h | 2 ++ diff --git a/ext/lexbor/patches/0002-Track-implied-added-nodes-for-options-use-in-PHP.patch b/ext/lexbor/patches/0002-Track-implied-added-nodes-for-options-use-in-PHP.patch index 8814d5955354..29bc4b12adce 100644 --- a/ext/lexbor/patches/0002-Track-implied-added-nodes-for-options-use-in-PHP.patch +++ b/ext/lexbor/patches/0002-Track-implied-added-nodes-for-options-use-in-PHP.patch @@ -1,7 +1,7 @@ From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Mon, 14 Aug 2023 20:18:51 +0200 -Subject: [PATCH 02/10] Track implied added nodes for options use in PHP +Subject: [PATCH 02/15] Track implied added nodes for options use in PHP --- source/lexbor/html/tree.h | 3 +++ diff --git a/ext/lexbor/patches/0003-Patch-utilities-and-data-structure-to-be-able-to-gen.patch b/ext/lexbor/patches/0003-Patch-utilities-and-data-structure-to-be-able-to-gen.patch index aa4802320491..286fd2e16fd7 100644 --- a/ext/lexbor/patches/0003-Patch-utilities-and-data-structure-to-be-able-to-gen.patch +++ b/ext/lexbor/patches/0003-Patch-utilities-and-data-structure-to-be-able-to-gen.patch @@ -1,7 +1,7 @@ From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Thu, 24 Aug 2023 22:57:48 +0200 -Subject: [PATCH 03/10] Patch utilities and data structure to be able to +Subject: [PATCH 03/15] Patch utilities and data structure to be able to generate smaller lookup tables Changed the generation script to check if everything fits in 32-bits. diff --git a/ext/lexbor/patches/0004-Remove-unused-upper-case-tag-static-data.patch b/ext/lexbor/patches/0004-Remove-unused-upper-case-tag-static-data.patch index 1a28b21ccdc5..34b58217aa6f 100644 --- a/ext/lexbor/patches/0004-Remove-unused-upper-case-tag-static-data.patch +++ b/ext/lexbor/patches/0004-Remove-unused-upper-case-tag-static-data.patch @@ -1,7 +1,7 @@ From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Wed, 29 Nov 2023 21:26:47 +0100 -Subject: [PATCH 04/10] Remove unused upper case tag static data +Subject: [PATCH 04/15] Remove unused upper case tag static data --- source/lexbor/tag/res.h | 2 ++ diff --git a/ext/lexbor/patches/0005-Shrink-size-of-static-binary-search-tree.patch b/ext/lexbor/patches/0005-Shrink-size-of-static-binary-search-tree.patch index a1dda1fcd112..0c88f6031720 100644 --- a/ext/lexbor/patches/0005-Shrink-size-of-static-binary-search-tree.patch +++ b/ext/lexbor/patches/0005-Shrink-size-of-static-binary-search-tree.patch @@ -1,7 +1,7 @@ From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Wed, 29 Nov 2023 21:29:31 +0100 -Subject: [PATCH 05/10] Shrink size of static binary search tree +Subject: [PATCH 05/15] Shrink size of static binary search tree This also makes it more efficient on the data cache. --- diff --git a/ext/lexbor/patches/0006-Patch-out-unused-CSS-style-code.patch b/ext/lexbor/patches/0006-Patch-out-unused-CSS-style-code.patch index 57f1e0e92fcb..3e31f7925889 100644 --- a/ext/lexbor/patches/0006-Patch-out-unused-CSS-style-code.patch +++ b/ext/lexbor/patches/0006-Patch-out-unused-CSS-style-code.patch @@ -1,7 +1,7 @@ From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Sun, 7 Jan 2024 21:59:28 +0100 -Subject: [PATCH 06/10] Patch out unused CSS style code +Subject: [PATCH 06/15] Patch out unused CSS style code --- source/lexbor/css/rule.h | 2 ++ diff --git a/ext/lexbor/patches/0007-URL-fixed-setters-for-empty-hosts.patch b/ext/lexbor/patches/0007-URL-fixed-setters-for-empty-hosts.patch index b55f5aac5894..309d3fdad75b 100644 --- a/ext/lexbor/patches/0007-URL-fixed-setters-for-empty-hosts.patch +++ b/ext/lexbor/patches/0007-URL-fixed-setters-for-empty-hosts.patch @@ -1,7 +1,7 @@ From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: Alexander Borisov Date: Fri, 26 Jun 2026 18:55:56 +0300 -Subject: [PATCH 07/10] URL: fixed setters for empty hosts. +Subject: [PATCH 07/15] URL: fixed setters for empty hosts. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit diff --git a/ext/lexbor/patches/0008-URL-fixed-uninitialized-memory-in-the-path-buffer-gr.patch b/ext/lexbor/patches/0008-URL-fixed-uninitialized-memory-in-the-path-buffer-gr.patch index d967c8ca4eb9..5d5eb2301f70 100644 --- a/ext/lexbor/patches/0008-URL-fixed-uninitialized-memory-in-the-path-buffer-gr.patch +++ b/ext/lexbor/patches/0008-URL-fixed-uninitialized-memory-in-the-path-buffer-gr.patch @@ -1,7 +1,7 @@ From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: Alexander Borisov Date: Fri, 5 Jun 2026 22:13:32 +0300 -Subject: [PATCH 08/10] URL: fixed uninitialized memory in the path buffer +Subject: [PATCH 08/15] URL: fixed uninitialized memory in the path buffer growth. When a path was long enough to outgrow the on-stack buffer, the first diff --git a/ext/lexbor/patches/0009-Fix-parsing-for-URL-containing-empty-host-and-userin.patch b/ext/lexbor/patches/0009-Fix-parsing-for-URL-containing-empty-host-and-userin.patch index 1e7aa4d1087d..23a2466a7b4a 100644 --- a/ext/lexbor/patches/0009-Fix-parsing-for-URL-containing-empty-host-and-userin.patch +++ b/ext/lexbor/patches/0009-Fix-parsing-for-URL-containing-empty-host-and-userin.patch @@ -1,7 +1,7 @@ From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1t=C3=A9=20Kocsis?= Date: Thu, 9 Jul 2026 21:51:05 +0200 -Subject: [PATCH 09/10] Fix parsing for URL containing empty host and userinfo +Subject: [PATCH 09/15] Fix parsing for URL containing empty host and userinfo The returned error code (LXB_URL_ERROR_TYPE_INVALID_CREDENTIALS) apparently contradicts the specification: diff --git a/ext/lexbor/patches/0010-Percent-encode-the-caret-in-the-path.patch b/ext/lexbor/patches/0010-Percent-encode-the-caret-in-the-path.patch index 04d91bda68a9..764903f69676 100644 --- a/ext/lexbor/patches/0010-Percent-encode-the-caret-in-the-path.patch +++ b/ext/lexbor/patches/0010-Percent-encode-the-caret-in-the-path.patch @@ -1,7 +1,7 @@ From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1t=C3=A9=20Kocsis?= Date: Fri, 10 Jul 2026 22:31:16 +0200 -Subject: [PATCH 10/10] Percent-encode the caret in the path +Subject: [PATCH 10/15] Percent-encode the caret in the path The caret (^) is part of the path percent-encode set: diff --git a/ext/lexbor/patches/0011-URL-fixed-tail-pointer-in-URLSearchParams-for-delimi.patch b/ext/lexbor/patches/0011-URL-fixed-tail-pointer-in-URLSearchParams-for-delimi.patch new file mode 100644 index 000000000000..ab11f88d126e --- /dev/null +++ b/ext/lexbor/patches/0011-URL-fixed-tail-pointer-in-URLSearchParams-for-delimi.patch @@ -0,0 +1,29 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Alexander Borisov +Date: Fri, 5 Jun 2026 21:46:26 +0300 +Subject: [PATCH 11/15] URL: fixed tail pointer in URLSearchParams for + delimiter-free query. + +When a query had a single token without '=' or '&' (e.g. "?abc"), the +internal tail pointer wasn't updated, so a later append() could lose the +added parameter (and write through a stale pointer). Fixed by keeping the +tail pointer in sync. + +Per report from Xiansheng Cao (@HMF2021) +--- + source/lexbor/url/url.c | 2 ++ + 1 file changed, 2 insertions(+) + +diff --git a/source/lexbor/url/url.c b/source/lexbor/url/url.c +index de19239..fcae2d6 100644 +--- a/source/lexbor/url/url.c ++++ b/source/lexbor/url/url.c +@@ -5106,6 +5106,8 @@ lxb_url_search_params_parse(lxb_url_search_params_t *search_params, + return status; + } + ++ last = entry; ++ + lexbor_str_init(&entry->value, mraw, 0); + if (entry->value.data == NULL) { + return LXB_STATUS_ERROR_MEMORY_ALLOCATION; diff --git a/ext/lexbor/patches/0012-CSS-fixed-heap-buffer-overflow-in-lexbor-contains-pa.patch b/ext/lexbor/patches/0012-CSS-fixed-heap-buffer-overflow-in-lexbor-contains-pa.patch new file mode 100644 index 000000000000..43b798227519 --- /dev/null +++ b/ext/lexbor/patches/0012-CSS-fixed-heap-buffer-overflow-in-lexbor-contains-pa.patch @@ -0,0 +1,35 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Alexander Borisov +Date: Fri, 5 Jun 2026 22:34:23 +0300 +Subject: [PATCH 12/15] CSS: fixed heap buffer overflow in :lexbor-contains() + parsing. + +The contains string buffer was allocated by the size of the string +structure instead of the content length, so any value longer than +that overflowed the buffer. + +Per report from Xiansheng Cao (@HMF2021) +--- + source/lexbor/css/selectors/pseudo_state.c | 5 ++--- + 1 file changed, 2 insertions(+), 3 deletions(-) + +diff --git a/source/lexbor/css/selectors/pseudo_state.c b/source/lexbor/css/selectors/pseudo_state.c +index 263ca52..2321ddf 100644 +--- a/source/lexbor/css/selectors/pseudo_state.c ++++ b/source/lexbor/css/selectors/pseudo_state.c +@@ -227,13 +227,12 @@ again: + contains->insensitive = false; + str = &contains->str; + +- str->data = lexbor_mraw_alloc(parser->memory->mraw, +- sizeof(lexbor_str_t)); ++ str->data = lexbor_mraw_alloc(parser->memory->mraw, length + 1); + if (str->data == NULL) { + return lxb_css_parser_memory_fail(parser); + } + +- memcpy(str->data, data, length + 1); ++ memcpy(str->data, data, length); + + str->length = length; + str->data[length] = '\0'; diff --git a/ext/lexbor/patches/0013-Encoding-fixed-buffer-overflows-in-malformed-decode-.patch b/ext/lexbor/patches/0013-Encoding-fixed-buffer-overflows-in-malformed-decode-.patch new file mode 100644 index 000000000000..3bc0e44bb528 --- /dev/null +++ b/ext/lexbor/patches/0013-Encoding-fixed-buffer-overflows-in-malformed-decode-.patch @@ -0,0 +1,97 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Alexander Borisov +Date: Wed, 10 Jun 2026 19:50:10 +0300 +Subject: [PATCH 13/15] Encoding: fixed buffer overflows in malformed decode + replay. + +Fixed out-of-bounds writes in buffering decoders when replacement output +fills the caller-provided codepoint buffer and decoder replay continues in +the same call. + +Affected decoders: +- GB18030 malformed third/fourth byte replay. +- ISO-2022-JP malformed escape replay. +- UTF-16BE/LE invalid surrogate replay. + +Per report from @hurric9-droid on GitHub. +--- + source/lexbor/encoding/decode.c | 37 +++++++++++++++++++++++++++++---- + 1 file changed, 33 insertions(+), 4 deletions(-) + +diff --git a/source/lexbor/encoding/decode.c b/source/lexbor/encoding/decode.c +index 3e48971..05c4b9b 100644 +--- a/source/lexbor/encoding/decode.c ++++ b/source/lexbor/encoding/decode.c +@@ -912,6 +912,13 @@ lxb_encoding_decode_iso_2022_jp(lxb_encoding_decode_t *ctx, + } + LXB_ENCODING_DECODE_ERROR_END(); + ++ if (ctx->buffer_used >= ctx->buffer_length) { ++ iso->prepand = iso->lead; ++ iso->lead = 0x00; ++ ++ return LXB_STATUS_SMALL_BUFFER; ++ } ++ + byte = iso->lead; + iso->lead = 0x00; + +@@ -1279,6 +1286,12 @@ lxb_encoding_decode_utf_16(lxb_encoding_decode_t *ctx, bool is_be, + } + LXB_ENCODING_DECODE_ERROR_END(); + ++ if (ctx->buffer_used >= ctx->buffer_length) { ++ ctx->u.lead = lead + 0x01; ++ ++ return LXB_STATUS_SMALL_BUFFER; ++ } ++ + goto lead_state; + } + +@@ -1723,6 +1736,13 @@ lxb_encoding_decode_gb18030(lxb_encoding_decode_t *ctx, + } + LXB_ENCODING_DECODE_ERROR_END(); + ++ if (ctx->buffer_used >= ctx->buffer_length) { ++ ctx->prepend = true; ++ ctx->u.gb18030.first = second; ++ ++ return LXB_STATUS_SMALL_BUFFER; ++ } ++ + first = second; + + goto prepend_first; +@@ -1756,11 +1776,8 @@ lxb_encoding_decode_gb18030(lxb_encoding_decode_t *ctx, + } + LXB_ENCODING_DECODE_ERROR_END(); + +- LXB_ENCODING_DECODE_APPEND_WO_CHECK(ctx, second); +- +- if (ctx->buffer_used == ctx->buffer_length) { ++ if (ctx->buffer_used >= ctx->buffer_length) { + ctx->prepend = true; +- ctx->have_error = true; + + /* First is a fake for trigger */ + ctx->u.gb18030.first = 0x01; +@@ -1770,6 +1787,18 @@ lxb_encoding_decode_gb18030(lxb_encoding_decode_t *ctx, + return LXB_STATUS_SMALL_BUFFER; + } + ++ LXB_ENCODING_DECODE_APPEND_WO_CHECK(ctx, second); ++ ++ if (ctx->buffer_used >= ctx->buffer_length) { ++ ctx->prepend = true; ++ ++ ctx->u.gb18030.first = third; ++ ctx->u.gb18030.second = 0x00; ++ ctx->u.gb18030.third = 0x00; ++ ++ return LXB_STATUS_SMALL_BUFFER; ++ } ++ + first = third; + + goto prepend_first; diff --git a/ext/lexbor/patches/0014-URL-Fix-parsing-of-usernames-containing.patch b/ext/lexbor/patches/0014-URL-Fix-parsing-of-usernames-containing.patch new file mode 100644 index 000000000000..dce9ec511e8f --- /dev/null +++ b/ext/lexbor/patches/0014-URL-Fix-parsing-of-usernames-containing.patch @@ -0,0 +1,38 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Tim=20D=C3=BCsterhus?= +Date: Fri, 31 Jul 2026 20:56:18 +0200 +Subject: [PATCH 14/15] URL: Fix parsing of usernames containing `@` + +Fixes lexbor/lexbor#399. +--- + source/lexbor/url/url.c | 17 +++++++---------- + 1 file changed, 7 insertions(+), 10 deletions(-) + +diff --git a/source/lexbor/url/url.c b/source/lexbor/url/url.c +index fcae2d6..654e2e6 100644 +--- a/source/lexbor/url/url.c ++++ b/source/lexbor/url/url.c +@@ -1753,16 +1753,13 @@ again: + break; + } + +- if (pswd == NULL || !at_sign) { +- tmp = (pswd != NULL) ? pswd - 1 : p; +- +- if (tmp > begin) { +- status = lxb_url_percent_encode_after_utf_8(begin, tmp, +- &url->username, url->mraw, +- LXB_URL_MAP_USERINFO, false); +- if (status != LXB_STATUS_OK) { +- lxb_url_parse_return(orig_data, buf, status); +- } ++ tmp = (pswd != NULL) ? pswd - 1 : p; ++ if (tmp > begin) { ++ status = lxb_url_percent_encode_after_utf_8(begin, tmp, ++ &url->username, url->mraw, ++ LXB_URL_MAP_USERINFO, false); ++ if (status != LXB_STATUS_OK) { ++ lxb_url_parse_return(orig_data, buf, status); + } + } + diff --git a/ext/lexbor/patches/0015-Unicode-fixed-uninitialized-memory-in-IDNA-buffer-gr.patch b/ext/lexbor/patches/0015-Unicode-fixed-uninitialized-memory-in-IDNA-buffer-gr.patch new file mode 100644 index 000000000000..7d1ef98a49b7 --- /dev/null +++ b/ext/lexbor/patches/0015-Unicode-fixed-uninitialized-memory-in-IDNA-buffer-gr.patch @@ -0,0 +1,81 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Alexander Borisov +Date: Mon, 7 Sep 2026 22:11:39 +0300 +Subject: [PATCH 15/15] Unicode: fixed uninitialized memory in IDNA buffer + growth. + +When an IDNA buffer outgrew the stack allocation, the move to the heap +did not copy the existing contents. The converted domain could therefore +contain uninitialized heap data. + +Fixed copying of the codepoint, ASCII and UTF-8 buffers. + +Per report from Muhammad Daffa (@daffainfo). +--- + source/lexbor/unicode/idna.c | 28 +++++++++++++++++++--------- + 1 file changed, 19 insertions(+), 9 deletions(-) + +diff --git a/source/lexbor/unicode/idna.c b/source/lexbor/unicode/idna.c +index 754f6b2..b31ae33 100644 +--- a/source/lexbor/unicode/idna.c ++++ b/source/lexbor/unicode/idna.c +@@ -117,12 +117,14 @@ lxb_unicode_idna_realloc(lxb_codepoint_t *buf, const lxb_codepoint_t *buffer, + lxb_codepoint_t *tmp; + + nlen = ((*buf_end - buf) * 4) + len; +- ++ + if (buf == buffer) { + tmp = lexbor_malloc(nlen * sizeof(lxb_codepoint_t)); + if (tmp == NULL) { + return NULL; + } ++ ++ memcpy(tmp, buf, (*buf_p - buf) * sizeof(lxb_codepoint_t)); + } + else { + tmp = lexbor_realloc(buf, nlen * sizeof(lxb_codepoint_t)); +@@ -458,13 +460,17 @@ lxb_unicode_idna_ascii_puny_cb(const lxb_char_t *data, size_t length, void *ctx, + + if (asc->buf == asc->buffer) { + tmp = lexbor_malloc(nlen); ++ if (tmp == NULL) { ++ return LXB_STATUS_ERROR_MEMORY_ALLOCATION; ++ } ++ ++ memcpy(tmp, asc->buf, asc->p - asc->buf); + } + else { + tmp = lexbor_realloc(asc->buf, nlen); +- } +- +- if (tmp == NULL) { +- return LXB_STATUS_ERROR_MEMORY_ALLOCATION; ++ if (tmp == NULL) { ++ return LXB_STATUS_ERROR_MEMORY_ALLOCATION; ++ } + } + + asc->p = tmp + (asc->p - asc->buf); +@@ -711,13 +717,17 @@ lxb_unicode_idna_to_unicode_cb(const lxb_codepoint_t *part, size_t len, + + if (asc->buf == asc->buffer) { + tmp = lexbor_malloc(nlen); ++ if (tmp == NULL) { ++ return LXB_STATUS_ERROR_MEMORY_ALLOCATION; ++ } ++ ++ memcpy(tmp, asc->buf, asc->p - asc->buf); + } + else { + tmp = lexbor_realloc(asc->buf, nlen); +- } +- +- if (tmp == NULL) { +- return LXB_STATUS_ERROR_MEMORY_ALLOCATION; ++ if (tmp == NULL) { ++ return LXB_STATUS_ERROR_MEMORY_ALLOCATION; ++ } + } + + asc->p = tmp + (asc->p - asc->buf); diff --git a/ext/uri/tests/whatwg/parsing/host_success_multibyte_long.phpt b/ext/uri/tests/whatwg/parsing/host_success_multibyte_long.phpt new file mode 100644 index 000000000000..109cb39de1f0 --- /dev/null +++ b/ext/uri/tests/whatwg/parsing/host_success_multibyte_long.phpt @@ -0,0 +1,21 @@ +--TEST-- +Test Uri\WhatWg\Url parsing - host - IDN host longer than the IDNA on-stack buffer +--FILE-- +getAsciiHost(); + +var_dump(strlen($host)); +var_dump(substr($host, 0, 12)); +var_dump(substr($host, -6)); +var_dump(substr_count($host, "a")); +var_dump($url->getUnicodeHost() === str_repeat("é", 5000) . ".com"); + +?> +--EXPECT-- +int(5010) +string(12) "xn--9caaaaaa" +string(6) "aa.com" +int(5000) +bool(true) diff --git a/ext/uri/tests/whatwg/parsing/username_success_at_sign.phpt b/ext/uri/tests/whatwg/parsing/username_success_at_sign.phpt new file mode 100644 index 000000000000..910d984858f3 --- /dev/null +++ b/ext/uri/tests/whatwg/parsing/username_success_at_sign.phpt @@ -0,0 +1,18 @@ +--TEST-- +Test Uri\WhatWg\Url parsing - username - at sign in the username and the password +--FILE-- +getUsername()); +var_dump($url->getPassword()); +var_dump($url->getAsciiHost()); +var_dump($url->toAsciiString()); + +?> +--EXPECT-- +string(11) "user%40name" +string(11) "pass%40word" +string(9) "localhost" +string(41) "http://user%40name:pass%40word@localhost/"