From 5f4d63d05b3a3717f1579c393db058f3af352ed4 Mon Sep 17 00:00:00 2001 From: RHEL Packaging Agent Date: Wed, 5 Aug 2026 13:18:25 +0000 Subject: [PATCH] Fix CVE-2026-8177: out-of-bounds UTF-8 heap read in perl-XML-LibXML Backport upstream commit 059abf5f to fix an out-of-bounds heap read in domParseChar() (CVE-2026-8177). The vulnerable function is removed and replaced with libxml2's xmlValidateName(), which correctly handles all UTF-8 edge cases including truncated sequences and invalid continuation bytes. Includes 65 regression tests covering the affected DOM entry points. CVE: CVE-2026-8177 Upstream patches: - https://github.com/cpan-authors/XML-LibXML/commit/059abf5f9336e2213794b5b545c707394cca3ac7.patch Resolves: RHEL-186531 This commit was backported by Ymir, a Red Hat Enterprise Linux software maintenance AI agent. Assisted-by: Ymir --- perl-XML-LibXML-2.0210-CVE-2026-8177.patch | 321 +++++++++++++++++++++ perl-XML-LibXML.spec | 9 +- 2 files changed, 329 insertions(+), 1 deletion(-) create mode 100644 perl-XML-LibXML-2.0210-CVE-2026-8177.patch diff --git a/perl-XML-LibXML-2.0210-CVE-2026-8177.patch b/perl-XML-LibXML-2.0210-CVE-2026-8177.patch new file mode 100644 index 0000000..42a5349 --- /dev/null +++ b/perl-XML-LibXML-2.0210-CVE-2026-8177.patch @@ -0,0 +1,321 @@ +From e8c598132ea411f2e5553b952708602bdfd0862b Mon Sep 17 00:00:00 2001 +From: Toddr Bot +Date: Tue, 19 May 2026 19:32:08 +0000 +Subject: [PATCH] fix: replace domParseChar with xmlValidateName to prevent OOB + UTF-8 read + +domParseChar() read continuation bytes for multi-byte UTF-8 sequences without +validating they exist or have required 10xxxxxx form. Truncated sequences like +"a\xF0" caused heap reads past NUL terminator. Replace with libxml2's +xmlValidateName(), which correctly handles all UTF-8 edge cases. + +Adds 65 regression tests covering truncation points, invalid continuations, and +5 affected DOM entry points (createElement, createAttribute, setNodeName, etc). + +Fixes https://github.com/cpan-authors/XML-LibXML/issues/146 +--- + LibXML.xs | 33 +------------ + MANIFEST | 1 + + dom.c | 88 --------------------------------- + dom.h | 32 ------------ + t/48_security_oob_utf8_gh146.t | 90 ++++++++++++++++++++++++++++++++++ + 5 files changed, 93 insertions(+), 151 deletions(-) + create mode 100644 t/48_security_oob_utf8_gh146.t + +diff --git a/LibXML.xs b/LibXML.xs +index 8709421..349f6be 100644 +--- a/LibXML.xs ++++ b/LibXML.xs +@@ -998,40 +998,11 @@ LibXML_cleanup_parser() { + int + LibXML_test_node_name( xmlChar * name ) + { +- xmlChar * cur = name; +- int tc = 0; +- int len = 0; +- +- if ( cur == NULL || *cur == 0 ) { +- /* warn("name is empty" ); */ +- return(0); +- } +- +- tc = domParseChar( cur, &len ); +- +- if ( !( IS_LETTER( tc ) || (tc == '_') || (tc == ':')) ) { +- /* warn( "is not a letter\n" ); */ ++ if ( name == NULL || *name == 0 ) { + return(0); + } + +- tc = 0; +- cur += len; +- +- while (*cur != 0 ) { +- tc = domParseChar( cur, &len ); +- +- if (!(IS_LETTER(tc) || IS_DIGIT(tc) || (tc == '_') || +- (tc == '-') || (tc == ':') || (tc == '.') || +- IS_COMBINING(tc) || IS_EXTENDER(tc)) ) { +- /* warn( "is not a letter\n" ); */ +- return(0); +- } +- tc = 0; +- cur += len; +- } +- +- /* warn("name is ok"); */ +- return(1); ++ return xmlValidateName( name, 0 ) == 0; + } + + /* Assumes that the node has a proxy. */ +diff --git a/MANIFEST b/MANIFEST +index 4f9bb6e..c855c19 100644 +--- a/MANIFEST ++++ b/MANIFEST +@@ -169,6 +169,7 @@ t/48_replaceNode_DTD_nodes_rT_80521.t + t/48_rt123379_setNamespace.t + t/48_rt55000.t + t/48_rt93429_recover_2_in_html_parsing.t ++t/48_security_oob_utf8_gh146.t + t/48importing_nodes_IDs_rt_69520.t + t/49_load_html.t + t/49callbacks_returning_undef.t +diff --git a/dom.c b/dom.c +index 10eb33d..0a818b2 100644 +--- a/dom.c ++++ b/dom.c +@@ -235,94 +235,6 @@ domReconcileNs(xmlNodePtr tree) + xmlFreeNsList(unused); + } + +-/** +- * NAME domParseChar +- * TYPE function +- * SYNOPSIS +- * int utf8char = domParseChar( curchar, &len ); +- * +- * The current char value, if using UTF-8 this may actually span +- * multiple bytes in the given string. This function parses an utf8 +- * character from a string into a UTF8 character (an integer). It uses +- * a slightly modified version of libxml2's character parser. libxml2 +- * itself does not provide any function to parse characters dircetly +- * from a string and test if they are valid utf8 characters. +- * +- * XML::LibXML uses this function rather than perls native UTF8 +- * support for two reasons: +- * 1) perls UTF8 handling functions often lead to encoding errors, +- * which partly comes, that they are badly documented. +- * 2) not all perl versions XML::LibXML intends to run with have native +- * UTF8 support. +- * +- * domParseChar() allows to use the very same code with all versions +- * of perl :) +- * +- * Returns the current char value and its length +- * +- * NOTE: If the character passed to this function is not a UTF +- * character, the return value will be 0 and the length of the +- * character is -1! +- */ +-int +-domParseChar( xmlChar *cur, int *len ) +-{ +- unsigned char c; +- unsigned int val; +- +- /* +- * We are supposed to handle UTF8, check it's valid +- * From rfc2044: encoding of the Unicode values on UTF-8: +- * +- * UCS-4 range (hex.) UTF-8 octet sequence (binary) +- * 0000 0000-0000 007F 0xxxxxxx +- * 0000 0080-0000 07FF 110xxxxx 10xxxxxx +- * 0000 0800-0000 FFFF 1110xxxx 10xxxxxx 10xxxxxx +- * +- * Check for the 0x110000 limit too +- */ +- +- if ( cur == NULL || *cur == 0 ) { +- *len = 0; +- return(0); +- } +- +- c = *cur; +- if ( c & 0x80 ) { +- if ((c & 0xe0) == 0xe0) { +- if ((c & 0xf0) == 0xf0) { +- /* 4-byte code */ +- *len = 4; +- val = (cur[0] & 0x7) << 18; +- val |= (cur[1] & 0x3f) << 12; +- val |= (cur[2] & 0x3f) << 6; +- val |= cur[3] & 0x3f; +- } else { +- /* 3-byte code */ +- *len = 3; +- val = (cur[0] & 0xf) << 12; +- val |= (cur[1] & 0x3f) << 6; +- val |= cur[2] & 0x3f; +- } +- } else { +- /* 2-byte code */ +- *len = 2; +- val = (cur[0] & 0x1f) << 6; +- val |= cur[1] & 0x3f; +- } +- if ( !IS_CHAR(val) ) { +- *len = -1; +- return(0); +- } +- return(val); +- } +- else { +- /* 1-byte code */ +- *len = 1; +- return((int)c); +- } +-} +- + /** + * Name: domReadWellBalancedString + * Synopsis: xmlNodePtr domReadWellBalancedString( xmlDocPtr doc, xmlChar *string ) +diff --git a/dom.h b/dom.h +index 29cf2b5..cf0f474 100644 +--- a/dom.h ++++ b/dom.h +@@ -54,38 +54,6 @@ extern "C" { + void + domReconcileNs(xmlNodePtr tree); + +-/** +- * NAME domParseChar +- * TYPE function +- * SYNOPSIS +- * int utf8char = domParseChar( curchar, &len ); +- * +- * The current char value, if using UTF-8 this may actually span +- * multiple bytes in the given string. This function parses an utf8 +- * character from a string into a UTF8 character (an integer). It uses +- * a slightly modified version of libxml2's character parser. libxml2 +- * itself does not provide any function to parse characters dircetly +- * from a string and test if they are valid utf8 characters. +- * +- * XML::LibXML uses this function rather than perls native UTF8 +- * support for two reasons: +- * 1) perls UTF8 handling functions often lead to encoding errors, +- * which partly comes, that they are badly documented. +- * 2) not all perl versions XML::LibXML intends to run with have native +- * UTF8 support. +- * +- * domParseChar() allows to use the very same code with all versions +- * of perl :) +- * +- * Returns the current char value and its length +- * +- * NOTE: If the character passed to this function is not a UTF +- * character, the return value will be 0 and the length of the +- * character is -1! +- */ +-int +-domParseChar( xmlChar *characters, int *len ); +- + xmlNodePtr + domReadWellBalancedString( xmlDocPtr doc, xmlChar* string, int repair ); + +diff --git a/t/48_security_oob_utf8_gh146.t b/t/48_security_oob_utf8_gh146.t +new file mode 100644 +index 0000000..74fb6d4 +--- /dev/null ++++ b/t/48_security_oob_utf8_gh146.t +@@ -0,0 +1,90 @@ ++# Security regression test for GitHub issue #146: ++# Out-of-bounds heap read via hand-rolled UTF-8 walker on truncated sequences. ++# ++# The original domParseChar() read continuation bytes for multi-byte UTF-8 ++# sequences without verifying they exist or are valid. A truncated sequence ++# (e.g., "a\xF0") caused reads past the NUL terminator into uninitialized ++# heap memory. This affects all DOM methods that validate node names via ++# LibXML_test_node_name(): createElement, createAttribute, setNodeName, ++# createElementNS, createAttributeNS, etc. ++# ++# Impact: denial of service (crash on unmapped memory) and potential ++# information disclosure (reading adjacent heap allocations). ++# ++# Fixed by replacing the hand-rolled UTF-8 walker (domParseChar) with ++# libxml2's own xmlValidateName(), which correctly handles all UTF-8 ++# edge cases. ++# ++# NOTE: This test verifies that malformed UTF-8 does not crash the process ++# (the actual security issue). Whether a given sequence is rejected depends ++# on the linked libxml2 version — older 2.9.x builds may accept some ++# sequences that newer versions reject. ++ ++use strict; ++use warnings; ++ ++use Test::More; ++use XML::LibXML; ++ ++# Truncated UTF-8 sequences that previously caused OOB heap reads. ++# Each entry: [ bytes, description ] ++my @truncated_sequences = ( ++ [ "a\xC0", "truncated 2-byte (leader only)" ], ++ [ "a\xC2", "truncated 2-byte (valid leader, missing continuation)" ], ++ [ "a\xE0", "truncated 3-byte (leader only)" ], ++ [ "a\xE0\x80", "truncated 3-byte (leader + 1 continuation)" ], ++ [ "a\xF0", "truncated 4-byte (leader only)" ], ++ [ "a\xF0\x80", "truncated 4-byte (leader + 1 continuation)" ], ++ [ "a\xF0\x80\x80", "truncated 4-byte (leader + 2 continuations)" ], ++); ++ ++# Invalid continuation bytes — the leader is valid but the continuations ++# are not 10xxxxxx. ++my @invalid_continuations = ( ++ [ "a\xC2\x41", "2-byte with ASCII continuation" ], ++ [ "a\xE0\x41\x80", "3-byte with ASCII in first continuation" ], ++ [ "a\xE0\x80\x41", "3-byte with ASCII in second continuation" ], ++ [ "a\xF0\x41\x80\x80", "4-byte with ASCII in first continuation" ], ++ [ "a\xF0\x80\x41\x80", "4-byte with ASCII in second continuation" ], ++ [ "a\xF0\x80\x80\x41", "4-byte with ASCII in third continuation" ], ++); ++ ++my @all_bad = (@truncated_sequences, @invalid_continuations); ++ ++my @methods = qw( createElement setNodeName createElementNS ++ createAttribute createAttributeNS ); ++ ++# TEST:$bad_count=13 ++# TEST:$method_count=5 ++plan tests => scalar(@all_bad) * scalar(@methods); ++ ++my $doc = XML::LibXML::Document->new(); ++my $nsURI = "http://example.com/ns"; ++ ++for my $case (@all_bad) { ++ my ($bytes, $desc) = @$case; ++ ++ for my $method (@methods) { ++ eval { ++ if ($method eq 'createElement') { ++ $doc->createElement($bytes); ++ } ++ elsif ($method eq 'setNodeName') { ++ my $node = $doc->createElement("tmp"); ++ $node->setNodeName($bytes); ++ } ++ elsif ($method eq 'createElementNS') { ++ $doc->createElementNS($nsURI, $bytes); ++ } ++ elsif ($method eq 'createAttribute') { ++ $doc->createAttribute($bytes, "value"); ++ } ++ elsif ($method eq 'createAttributeNS') { ++ $doc->createAttributeNS($nsURI, $bytes, "value"); ++ } ++ }; ++ ++ # TEST*$bad_count*$method_count ++ pass("$method survives $desc without crashing"); ++ } ++} diff --git a/perl-XML-LibXML.spec b/perl-XML-LibXML.spec index b92940a..991d1bf 100644 --- a/perl-XML-LibXML.spec +++ b/perl-XML-LibXML.spec @@ -8,7 +8,7 @@ Name: perl-XML-LibXML # it might not be needed anymore # this module is maintained, the other is not Version: 2.0210 -Release: 4%{?dist} +Release: 5%{?dist} Epoch: 1 Summary: Perl interface to the libxml2 library License: (GPL-1.0-or-later OR Artistic-1.0-Perl) AND MIT @@ -19,6 +19,9 @@ Source0: https://cpan.metacpan.org/authors/id/S/SH/SHLOMIF/XML-LibXML-%{v Patch0: XML-LibXML-2.0202-Parse-an-ampersand-entity-in-SAX-interface.patch # To reduce dependencies replace Alien::Libxml2 with pkg-config Patch1: XML-LibXML-2.0208-Use-pkgconfig-instead-of-Alien-Libxml2.patch +# https://github.com/cpan-authors/XML-LibXML/issues/146 +# https://github.com/cpan-authors/XML-LibXML/commit/059abf5f9336e2213794b5b545c707394cca3ac7 +Patch2: perl-XML-LibXML-2.0210-CVE-2026-8177.patch BuildRequires: coreutils BuildRequires: findutils BuildRequires: glibc-common @@ -189,6 +192,10 @@ fi %{_libexecdir}/%{name} %changelog +* Wed Aug 05 2026 RHEL Packaging Agent - 1:2.0210-5 +- Fix out-of-bounds UTF-8 read in domParseChar() (CVE-2026-8177) + Resolves: RHEL-186531 + * Tue Oct 29 2024 Troy Dawson - 1:2.0210-4 - Bump release for October 2024 mass rebuild: Resolves: RHEL-64018