Commit fa825cb8a05 for php.net
commit fa825cb8a05ba993c30074fd400f08472f9babdb
Merge: 2f0362e2b1a 5982cdace63
Author: Alexandre Daubois <alex.daubois@gmail.com>
Date: Mon Sep 14 11:00:36 2026 +0200
Merge branch 'PHP-8.4' into PHP-8.5
* PHP-8.4:
lexbor: Merge upstream memory safety fixes 8a14bc0 and f67ce4b
diff --cc NEWS
index 511a899e527,1176aba9c44..387caeeca43
--- a/NEWS
+++ b/NEWS
@@@ -17,9 -15,12 +17,14 @@@ PH
. Fixed cloning IntlDateFormatter and MessageFormatter losing PHP-side state
such as dateType, timeType, calendar and the message pattern.
(Ilia Alshanetsky)
+ . Fixed a crash when converting with a cloned UConverter that uses
+ toUCallback/fromUCallback. (Ilia Alshanetsky)
+ - Lexbor:
+ . Merge patches 8a14bc0 and f67ce4b, fixing a heap buffer overflow in
+ :lexbor-contains() parsing and buffer overflows in malformed decode
+ replay. (alexandre-daubois)
+
- MBString:
. Fixed bug GH-23106 (mb_strpos() reads past the end of a haystack ending in
a truncated UTF-8 sequence). (Lazizbek Ergashev)
diff --cc ext/lexbor/patches/0001-Expose-line-and-column-information-for-use-in-PHP.patch
index 53359883782,04136b29f58..9aec14cca5d
--- 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 +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 1/8] Expose line and column information for use in PHP
++Subject: [PATCH 01/12] Expose line and column information for use in PHP
---
source/lexbor/dom/interfaces/node.h | 2 ++
diff --cc ext/lexbor/patches/0002-Track-implied-added-nodes-for-options-use-in-PHP.patch
index 8814d595535,615655d7f2e..9f4da029446
--- 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 +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 2/8] Track implied added nodes for options use in PHP
++Subject: [PATCH 02/12] Track implied added nodes for options use in PHP
---
source/lexbor/html/tree.h | 3 +++
diff --cc ext/lexbor/patches/0003-Patch-utilities-and-data-structure-to-be-able-to-gen.patch
index aa480232049,73c5afa19e1..fe7068d9bda
--- 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,8 -1,8 +1,8 @@@
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 3/8] Patch utilities and data structure to be able to generate
- smaller lookup tables
++Subject: [PATCH 03/12] 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.
And change the actual field types to 32-bits. This decreases the hash
diff --cc ext/lexbor/patches/0004-Remove-unused-upper-case-tag-static-data.patch
index 1a28b21ccdc,cc0a65a7cd5..35482388531
--- 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 +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 4/8] Remove unused upper case tag static data
++Subject: [PATCH 04/12] Remove unused upper case tag static data
---
source/lexbor/tag/res.h | 2 ++
diff --cc ext/lexbor/patches/0005-Shrink-size-of-static-binary-search-tree.patch
index a1dda1fcd11,b84120bf6c8..8e9b9524104
--- 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 +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 5/8] Shrink size of static binary search tree
++Subject: [PATCH 05/12] Shrink size of static binary search tree
This also makes it more efficient on the data cache.
---
diff --cc ext/lexbor/patches/0006-Patch-out-unused-CSS-style-code.patch
index 57f1e0e92fc,196a5a8a62d..0f4e4cd8661
--- 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 +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 6/8] Patch out unused CSS style code
++Subject: [PATCH 06/12] Patch out unused CSS style code
---
source/lexbor/css/rule.h | 2 ++
diff --cc ext/lexbor/patches/0007-URL-fixed-setters-for-empty-hosts.patch
index b55f5aac589,00000000000..44f0f918458
mode 100644,000000..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,191 -1,0 +1,191 @@@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Alexander Borisov <lex.borisov@gmail.com>
+Date: Fri, 26 Jun 2026 18:55:56 +0300
- Subject: [PATCH 07/10] URL: fixed setters for empty hosts.
++Subject: [PATCH 07/12] URL: fixed setters for empty hosts.
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Empty non-special hosts were represented as empty opaque hosts, so
+lxb_url_cannot_have_user_pass_port() allowed username, password, and port
+setters to modify scheme://.
+
+For fixed this store empty opaque-host input as LXB_URL_HOST_TYPE_EMPTY.
+
+Per report from Máté Kocsis (@kocsismate).
+
+This relates to #387 issue on GitHub.
+---
+ source/lexbor/url/url.c | 35 ++++++++++++++++++---
+ test/files/lexbor/url/changes.ton | 52 +++++++++++++++++++++++++++++--
+ test/files/lexbor/url/url.ton | 8 ++++-
+ 3 files changed, 86 insertions(+), 9 deletions(-)
+
+diff --git a/source/lexbor/url/url.c b/source/lexbor/url/url.c
+index 5a11434..86bcf8f 100644
+--- a/source/lexbor/url/url.c
++++ b/source/lexbor/url/url.c
+@@ -1115,11 +1115,13 @@ lxb_url_host_copy(const lxb_url_host_t *src, lxb_url_host_t *dst,
+
+ dst->type = src->type;
+
+- if (src->type <= LXB_URL_HOST_TYPE_OPAQUE) {
+- if (src->type == LXB_URL_HOST_TYPE__UNDEF) {
+- return LXB_STATUS_OK;
+- }
++ if (src->type == LXB_URL_HOST_TYPE__UNDEF
++ || src->type == LXB_URL_HOST_TYPE_EMPTY)
++ {
++ return LXB_STATUS_OK;
++ }
+
++ if (src->type <= LXB_URL_HOST_TYPE_OPAQUE) {
+ return lxb_url_str_copy(&src->u.domain,
+ &dst->u.domain, dst_mraw);
+ }
+@@ -1152,6 +1154,24 @@ lxb_url_host_set_empty(lxb_url_host_t *host, lexbor_mraw_t *mraw)
+ host->type = LXB_URL_HOST_TYPE_EMPTY;
+ }
+
++lxb_inline bool
++lxb_url_host_is_empty(const lxb_url_host_t *host)
++{
++ if (host->type == LXB_URL_HOST_TYPE_EMPTY) {
++ return true;
++ }
++
++ if (host->type == LXB_URL_HOST_TYPE_DOMAIN) {
++ return host->u.domain.length == 0;
++ }
++
++ if (host->type == LXB_URL_HOST_TYPE_OPAQUE) {
++ return host->u.opaque.length == 0;
++ }
++
++ return false;
++}
++
+ static bool
+ lxb_url_host_eq(lxb_url_host_t *host, const lxb_char_t *data, size_t length)
+ {
+@@ -1251,7 +1271,7 @@ lxb_url_normalized_windows_drive_letter(const lxb_char_t *data,
+ static bool
+ lxb_url_cannot_have_user_pass_port(lxb_url_t *url)
+ {
+- return url->host.type == LXB_URL_HOST_TYPE_EMPTY
++ return lxb_url_host_is_empty(&url->host)
+ || url->host.type == LXB_URL_HOST_TYPE__UNDEF
+ || url->scheme.type == LXB_URL_SCHEMEL_TYPE_FILE;
+ }
+@@ -3978,6 +3998,11 @@ lxb_url_opaque_host_parse(lxb_url_parser_t *parser, const lxb_char_t *data,
+ lxb_status_t status;
+ const lxb_char_t *p;
+
++ if (data == end) {
++ lxb_url_host_set_empty(host, mraw);
++ return LXB_STATUS_OK;
++ }
++
+ p = data;
+
+ while (p < end) {
+diff --git a/test/files/lexbor/url/changes.ton b/test/files/lexbor/url/changes.ton
+index 07bc944..1a0b6e3 100644
+--- a/test/files/lexbor/url/changes.ton
++++ b/test/files/lexbor/url/changes.ton
+@@ -1,5 +1,5 @@
+ [
+- /* Test count: 1 */
++ /* Test count: 47 */
+ /* 1 */
+ {
+ "url": "https://user:pass@lexbor.com/docs/html/path?x=y&a=b#best-fragment",
+@@ -982,9 +982,53 @@
+ "failed": false
+ },
+ /* 45 */
++ {
++ "url": "scheme://",
++ "done": "scheme://",
++ "change": {
++ "href": null,
++ "protocol": null,
++ "username": "user",
++ "password": "pass",
++ "host": null,
++ "hostname": null,
++ "port": "433",
++ "pathname": null,
++ "search": null,
++ "hash": null
++ },
++ "scheme": "scheme",
++ "host": "",
++ "path": "",
++ "failed": false
++ },
++ /* 46 */
++ {
++ "url": "scheme://host",
++ "done": "scheme://host:433",
++ "change": {
++ "href": null,
++ "protocol": null,
++ "username": null,
++ "password": null,
++ "host": null,
++ "hostname": null,
++ "port": "433",
++ "pathname": null,
++ "search": null,
++ "hash": null
++ },
++ "scheme": "scheme",
++ "host": "host",
++ "port": 433,
++ "has_port": true,
++ "path": "",
++ "failed": false
++ },
++ /* 47 */
+ {
+ "url": "https://example.com:432",
+- "done": "https://example.com:432",
++ "done": "https://example.com:432/",
+ "change": {
+ "href": null,
+ "protocol": null,
+@@ -999,7 +1043,9 @@
+ },
+ "scheme": "https",
+ "host": "example.com",
+- "port": "432",
++ "port": 432,
++ "has_port": true,
++ "path": "/",
+ "failed": true
+ }
+ ]
+diff --git a/test/files/lexbor/url/url.ton b/test/files/lexbor/url/url.ton
+index 2baa4bc..85794c5 100644
+--- a/test/files/lexbor/url/url.ton
++++ b/test/files/lexbor/url/url.ton
+@@ -1,5 +1,5 @@
+ [
+- /* Test count: 7 */
++ /* Test count: 8 */
+ /* 1 */
+ {
+ "url": "https://user:pass@lexbor.com:450/docs/lexbor/?search=lxb_status_t#version",
+@@ -74,5 +74,11 @@
+ "path": "",
+ "failed": false,
+ "encoding": "utf-8"
++ },
++ /* 8 */
++ {
++ "url": "scheme://:433",
++ "failed": true,
++ "encoding": "utf-8"
+ }
+ ]
diff --cc ext/lexbor/patches/0008-URL-fixed-uninitialized-memory-in-the-path-buffer-gr.patch
index d967c8ca4eb,00000000000..df1f6a1c2ac
mode 100644,000000..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,37 -1,0 +1,37 @@@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Alexander Borisov <lex.borisov@gmail.com>
+Date: Fri, 5 Jun 2026 22:13:32 +0300
- Subject: [PATCH 08/10] URL: fixed uninitialized memory in the path buffer
++Subject: [PATCH 08/12] URL: fixed uninitialized memory in the path buffer
+ growth.
+
+When a path was long enough to outgrow the on-stack buffer, the first
+move to the heap didn't copy what was already written, so the start of
+the path could contain garbage. Also fixed the 'last' pointer after the
+move.
+
+Per report from Recep Asan (recep@asan.me)
+---
+ source/lexbor/url/url.c | 3 ++-
+ 1 file changed, 2 insertions(+), 1 deletion(-)
+
+diff --git a/source/lexbor/url/url.c b/source/lexbor/url/url.c
+index 86bcf8f..b6c0a1e 100644
+--- a/source/lexbor/url/url.c
++++ b/source/lexbor/url/url.c
+@@ -499,6 +499,7 @@ lxb_url_scheme_length = sizeof(lxb_url_scheme_res) / sizeof(lxb_url_scheme_data_
+ if (tmp == NULL) { \
+ return NULL; \
+ } \
++ memcpy(tmp, (sbuf_begin), offset); \
+ } \
+ else { \
+ tmp = lexbor_realloc((sbuf_begin), new_len); \
+@@ -509,7 +510,7 @@ lxb_url_scheme_length = sizeof(lxb_url_scheme_res) / sizeof(lxb_url_scheme_data_
+ } \
+ \
+ (sbuf) = tmp + offset; \
+- (last) = sbuf + lst; \
++ (last) = tmp + lst; \
+ (sbuf_begin) = tmp; \
+ (sbuf_end) = tmp + new_len; \
+ } \
diff --cc ext/lexbor/patches/0009-Fix-parsing-for-URL-containing-empty-host-and-userin.patch
index 1e7aa4d1087,00000000000..9abcd37fe3f
mode 100644,000000..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,48 -1,0 +1,48 @@@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?M=C3=A1t=C3=A9=20Kocsis?= <kocsismate@woohoolabs.com>
+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/12] Fix parsing for URL containing empty host and userinfo
+
+The returned error code (LXB_URL_ERROR_TYPE_INVALID_CREDENTIALS) apparently contradicts the specification:
+
+"If atSignSeen is true and buffer is the empty string, host-missing validation error, return failure."
+---
+ source/lexbor/url/url.c | 2 +-
+ test/files/lexbor/url/username_password.ton | 8 +++++++-
+ 2 files changed, 8 insertions(+), 2 deletions(-)
+
+diff --git a/source/lexbor/url/url.c b/source/lexbor/url/url.c
+index b6c0a1e..19ec238 100644
+--- a/source/lexbor/url/url.c
++++ b/source/lexbor/url/url.c
+@@ -1814,7 +1814,7 @@ again:
+ if (at_sign) {
+ if (begin == p || begin == p - 1) {
+ status = lxb_url_log_append(parser, p,
+- LXB_URL_ERROR_TYPE_INVALID_CREDENTIALS);
++ LXB_URL_ERROR_TYPE_HOST_MISSING);
+ if (status != LXB_STATUS_OK) {
+ lxb_url_parse_return(orig_data, buf, status);
+ }
+diff --git a/test/files/lexbor/url/username_password.ton b/test/files/lexbor/url/username_password.ton
+index 28a27fd..5a5e63e 100644
+--- a/test/files/lexbor/url/username_password.ton
++++ b/test/files/lexbor/url/username_password.ton
+@@ -1,5 +1,5 @@
+ [
+- /* Test count: 11 */
++ /* Test count: 12 */
+ /* 1 */
+ {
+ "url": "https://user:password@lexbor.com",
+@@ -124,4 +124,10 @@
+ "failed": false,
+ "encoding": "utf-8"
+ }
++ /* 12 */
++ {
++ "url": "https://user:pass@",
++ "failed": true,
++ "encoding": "utf-8"
++ }
+ ]
diff --cc ext/lexbor/patches/0010-Percent-encode-the-caret-in-the-path.patch
index 04d91bda68a,00000000000..48777d5de6d
mode 100644,000000..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,29 -1,0 +1,29 @@@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?M=C3=A1t=C3=A9=20Kocsis?= <kocsismate@woohoolabs.com>
+Date: Fri, 10 Jul 2026 22:31:16 +0200
- Subject: [PATCH 10/10] Percent-encode the caret in the path
++Subject: [PATCH 10/12] Percent-encode the caret in the path
+
+The caret (^) is part of the path percent-encode set:
+
+"The path percent-encode set is a percent-encode set consisting of the query percent-encode set and U+003F (?), U+005E (^), U+0060 (`), U+007B ({), and U+007D (})."
+
+Until now, this character wasn't percent-encoded in the path likely due to a copy-paste error. This is mistake is fixed by adding LXB_URL_MAP_PATH to the lxb_url_map entry for the caret.
+
+Originally reported at https://github.com/php/php-src/issues/22628
+---
+ source/lexbor/url/url.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/source/lexbor/url/url.c b/source/lexbor/url/url.c
+index 19ec238..de19239 100644
+--- a/source/lexbor/url/url.c
++++ b/source/lexbor/url/url.c
+@@ -159,7 +159,7 @@ static const uint8_t lxb_url_map[256] =
+ LXB_URL_MAP_USERINFO|LXB_URL_MAP_COMPONENT|LXB_URL_MAP_X_WWW_FORM, /* 0x5b ([) */
+ LXB_URL_MAP_USERINFO|LXB_URL_MAP_COMPONENT|LXB_URL_MAP_X_WWW_FORM, /* 0x5c (\) */
+ LXB_URL_MAP_USERINFO|LXB_URL_MAP_COMPONENT|LXB_URL_MAP_X_WWW_FORM, /* 0x5d (]) */
+- LXB_URL_MAP_USERINFO|LXB_URL_MAP_COMPONENT|LXB_URL_MAP_X_WWW_FORM, /* 0x5e (^) */
++ LXB_URL_MAP_USERINFO|LXB_URL_MAP_PATH|LXB_URL_MAP_COMPONENT|LXB_URL_MAP_X_WWW_FORM, /* 0x5e (^) */
+ LXB_URL_MAP_UNDEF, /* 0x5f (_) */
+ LXB_URL_MAP_PATH|LXB_URL_MAP_FRAGMENT|LXB_URL_MAP_USERINFO|LXB_URL_MAP_COMPONENT|LXB_URL_MAP_X_WWW_FORM, /* 0x60 (`) */
+ LXB_URL_MAP_UNDEF, /* 0x61 (a) */
diff --cc ext/lexbor/patches/0011-CSS-fixed-heap-buffer-overflow-in-lexbor-contains-pa.patch
index 00000000000,cc689714b6c..3928daa7eab
mode 000000,100644..100644
--- a/ext/lexbor/patches/0011-CSS-fixed-heap-buffer-overflow-in-lexbor-contains-pa.patch
+++ b/ext/lexbor/patches/0011-CSS-fixed-heap-buffer-overflow-in-lexbor-contains-pa.patch
@@@ -1,0 -1,35 +1,35 @@@
+ From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+ From: Alexander Borisov <lex.borisov@gmail.com>
+ Date: Fri, 5 Jun 2026 22:34:23 +0300
-Subject: [PATCH 7/8] CSS: fixed heap buffer overflow in :lexbor-contains()
++Subject: [PATCH 11/12] 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 --cc ext/lexbor/patches/0012-Encoding-fixed-buffer-overflows-in-malformed-decode-.patch
index 00000000000,352ebd95b2c..32f3aff4ee7
mode 000000,100644..100644
--- a/ext/lexbor/patches/0012-Encoding-fixed-buffer-overflows-in-malformed-decode-.patch
+++ b/ext/lexbor/patches/0012-Encoding-fixed-buffer-overflows-in-malformed-decode-.patch
@@@ -1,0 -1,97 +1,97 @@@
+ From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+ From: Alexander Borisov <lex.borisov@gmail.com>
+ Date: Wed, 10 Jun 2026 19:50:10 +0300
-Subject: [PATCH 8/8] Encoding: fixed buffer overflows in malformed decode
++Subject: [PATCH 12/12] 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;