| Message ID | 20260706132712.51931-1-amaury.couderc@est.tech |
|---|---|
| State | Superseded, archived |
| Delegated to: | Yoann Congal |
| Headers | show |
| Series | [v3,scarthgap] python3: fix CVE-2026-7210 | expand |
Hello, On Mon Jul 6, 2026 at 3:26 PM CEST, Amaury Couderc via lists.openembedded.org wrote: > From: Amaury Couderc <amaury.couderc@est.tech> > > CVE-2026-7210 is a hash-flooding denial-of-service vulnerability in > Python's XML parsing modules (xml.parsers.expat, xml.etree.ElementTree). > An attacker can craft XML input that forces O(n²) hash collisions in > libexpat's internal name dictionary, causing excessive CPU consumption. > > The previous mitigation seeded libexpat's hash function with only 4 > bytes of entropy, which is insufficient against a determined attacker. > This patch upgrades to XML_SetHashSalt16Bytes (libexpat >= 2.8.0), > providing a full 16-byte secret. Older expat versions fall back > gracefully to the legacy XML_SetHashSalt via a runtime NULL check. > > Backport patch to fix CVE-2026-7210. > https://nvd.nist.gov/vuln/detail/CVE-2026-7210 > > Upstream fix: > https://github.com/python/cpython/commit/24b8f12544468e4cedf5bfbe25442fcd495391e4 > > Tested with ptest: > Before: PASSED: 40019, FAILED: 0, SKIPPED: 1882 > After: PASSED: 40020, FAILED: 0, SKIPPED: 1882 > > CVE: CVE-2026-7210 > > Signed-off-by: Amaury Couderc <amaury.couderc@est.tech> > --- > > NOTE: CVE-2026-7210 full mitigation requires the companion > expat CVE-2026-41080 patch (raises expat to >= 2.8.0 API) > > .../python/python3/CVE-2026-7210.patch | 122 ++++++++++++++++++ > .../python/python3_3.12.13.bb | 1 + > 2 files changed, 123 insertions(+) > create mode 100644 meta/recipes-devtools/python/python3/CVE-2026-7210.patch > > diff --git a/meta/recipes-devtools/python/python3/CVE-2026-7210.patch b/meta/recipes-devtools/python/python3/CVE-2026-7210.patch > new file mode 100644 > index 0000000000..8acf174a0a > --- /dev/null > +++ b/meta/recipes-devtools/python/python3/CVE-2026-7210.patch > @@ -0,0 +1,122 @@ > +From 2ed6138dea0bc94c726f879501e4525712e885d1 Mon Sep 17 00:00:00 2001 > +From: Stan Ulbrych <stan@python.org> > +Date: Sun, 10 May 2026 18:36:26 +0100 > +Subject: [PATCH] gh-149018: Use `XML_SetHashSalt16Bytes` in > + `pyexpat`/`_elementtree` when possible (#149023) > + > + > +CVE: CVE-2026-7210 > +Upstream-Status: Backport [https://github.com/python/cpython/commit/24b8f12544468e4cedf5bfbe25442fcd495391e4] > + > +Signed-off-by: Amaury Couderc <amaury.couderc@est.tech> > +--- > + Include/pyexpat.h | 3 +++ > + Include/pyhash.h | 8 +++++--- > + .../2026-04-26-19-30-45.gh-issue-149018.a9SqWb.rst | 3 +++ > + Modules/_elementtree.c | 8 ++++++-- > + Modules/pyexpat.c | 10 +++++++++- > + 5 files changed, 26 insertions(+), 6 deletions(-) > + create mode 100644 Misc/NEWS.d/next/Security/2026-04-26-19-30-45.gh-issue-149018.a9SqWb.rst The patched files have slighty changed Include/internal/pycore_pyhash.h -> Include/pyhash.h This time, I could check that nothing important changed but next time I'd like a like a little change log about what changed between upstream and the imported patch. > [...] > +diff --git a/Modules/pyexpat.c b/Modules/pyexpat.c > +index 79492ca5c4f..388473d3855 100644 > +--- a/Modules/pyexpat.c > ++++ b/Modules/pyexpat.c > +@@ -1388,7 +1388,10 @@ newxmlparseobject(pyexpat_state *state, const char *encoding, > + Py_DECREF(self); > + return NULL; > + } > +-#if XML_COMBINED_VERSION >= 20100 > ++#if XML_COMBINED_VERSION >= 20800 > ++ /* This feature was added upstream in libexpat 2.8.0. */ > ++ XML_SetHashSalt16Bytes(self->itself, _Py_HashSecret.expat.hashsalt16); > ++#elif XML_COMBINED_VERSION >= 20100 > + /* This feature was added upstream in libexpat 2.1.0. */ > + XML_SetHashSalt(self->itself, > + (unsigned long)_Py_HashSecret.expat.hashsalt); > +@@ -2257,6 +2260,11 @@ pyexpat_exec(PyObject *mod) > + #else > + capi->SetHashSalt = NULL; > + #endif > ++#if XML_COMBINED_VERSION >= 20800 > ++ capi->SetHashSalt16Bytes = XML_SetHashSalt16Bytes; > ++#else > ++ capi->SetHashSalt16Bytes = NULL; > ++#endif > + #if XML_COMBINED_VERSION >= 20600 > + capi->SetReparseDeferralEnabled = XML_SetReparseDeferralEnabled; > + #else We did not upgrade expat but patched in the fixing API XML_SetHashSalt16Bytes. Can you check we can activate the fixed code path with those "#if XML_COMBINED_VERSION >= 20800"? I will hold the patch in the meantime, I really want to avoid a false negative in CVE data. Regards,
On Sun Jul 19, 2026 at 11:48 PM CEST, Yoann Congal wrote: > Hello, > > On Mon Jul 6, 2026 at 3:26 PM CEST, Amaury Couderc via lists.openembedded.org wrote: >> From: Amaury Couderc <amaury.couderc@est.tech> >> >> CVE-2026-7210 is a hash-flooding denial-of-service vulnerability in >> Python's XML parsing modules (xml.parsers.expat, xml.etree.ElementTree). >> An attacker can craft XML input that forces O(n²) hash collisions in >> libexpat's internal name dictionary, causing excessive CPU consumption. >> >> The previous mitigation seeded libexpat's hash function with only 4 >> bytes of entropy, which is insufficient against a determined attacker. >> This patch upgrades to XML_SetHashSalt16Bytes (libexpat >= 2.8.0), >> providing a full 16-byte secret. Older expat versions fall back >> gracefully to the legacy XML_SetHashSalt via a runtime NULL check. >> >> Backport patch to fix CVE-2026-7210. >> https://nvd.nist.gov/vuln/detail/CVE-2026-7210 >> >> Upstream fix: >> https://github.com/python/cpython/commit/24b8f12544468e4cedf5bfbe25442fcd495391e4 >> >> Tested with ptest: >> Before: PASSED: 40019, FAILED: 0, SKIPPED: 1882 >> After: PASSED: 40020, FAILED: 0, SKIPPED: 1882 >> >> CVE: CVE-2026-7210 >> >> Signed-off-by: Amaury Couderc <amaury.couderc@est.tech> >> --- >> >> NOTE: CVE-2026-7210 full mitigation requires the companion >> expat CVE-2026-41080 patch (raises expat to >= 2.8.0 API) >> >> .../python/python3/CVE-2026-7210.patch | 122 ++++++++++++++++++ >> .../python/python3_3.12.13.bb | 1 + >> 2 files changed, 123 insertions(+) >> create mode 100644 meta/recipes-devtools/python/python3/CVE-2026-7210.patch >> >> diff --git a/meta/recipes-devtools/python/python3/CVE-2026-7210.patch b/meta/recipes-devtools/python/python3/CVE-2026-7210.patch >> new file mode 100644 >> index 0000000000..8acf174a0a >> --- /dev/null >> +++ b/meta/recipes-devtools/python/python3/CVE-2026-7210.patch >> @@ -0,0 +1,122 @@ >> +From 2ed6138dea0bc94c726f879501e4525712e885d1 Mon Sep 17 00:00:00 2001 >> +From: Stan Ulbrych <stan@python.org> >> +Date: Sun, 10 May 2026 18:36:26 +0100 >> +Subject: [PATCH] gh-149018: Use `XML_SetHashSalt16Bytes` in >> + `pyexpat`/`_elementtree` when possible (#149023) >> + >> + >> +CVE: CVE-2026-7210 >> +Upstream-Status: Backport [https://github.com/python/cpython/commit/24b8f12544468e4cedf5bfbe25442fcd495391e4] >> + >> +Signed-off-by: Amaury Couderc <amaury.couderc@est.tech> >> +--- >> + Include/pyexpat.h | 3 +++ >> + Include/pyhash.h | 8 +++++--- >> + .../2026-04-26-19-30-45.gh-issue-149018.a9SqWb.rst | 3 +++ >> + Modules/_elementtree.c | 8 ++++++-- >> + Modules/pyexpat.c | 10 +++++++++- >> + 5 files changed, 26 insertions(+), 6 deletions(-) >> + create mode 100644 Misc/NEWS.d/next/Security/2026-04-26-19-30-45.gh-issue-149018.a9SqWb.rst > > The patched files have slighty changed > Include/internal/pycore_pyhash.h -> Include/pyhash.h > This time, I could check that nothing important changed but next time > I'd like a like a little change log about what changed between upstream > and the imported patch. > >> [...] >> +diff --git a/Modules/pyexpat.c b/Modules/pyexpat.c >> +index 79492ca5c4f..388473d3855 100644 >> +--- a/Modules/pyexpat.c >> ++++ b/Modules/pyexpat.c >> +@@ -1388,7 +1388,10 @@ newxmlparseobject(pyexpat_state *state, const char *encoding, >> + Py_DECREF(self); >> + return NULL; >> + } >> +-#if XML_COMBINED_VERSION >= 20100 >> ++#if XML_COMBINED_VERSION >= 20800 >> ++ /* This feature was added upstream in libexpat 2.8.0. */ >> ++ XML_SetHashSalt16Bytes(self->itself, _Py_HashSecret.expat.hashsalt16); >> ++#elif XML_COMBINED_VERSION >= 20100 >> + /* This feature was added upstream in libexpat 2.1.0. */ >> + XML_SetHashSalt(self->itself, >> + (unsigned long)_Py_HashSecret.expat.hashsalt); >> +@@ -2257,6 +2260,11 @@ pyexpat_exec(PyObject *mod) >> + #else >> + capi->SetHashSalt = NULL; >> + #endif >> ++#if XML_COMBINED_VERSION >= 20800 >> ++ capi->SetHashSalt16Bytes = XML_SetHashSalt16Bytes; >> ++#else >> ++ capi->SetHashSalt16Bytes = NULL; >> ++#endif >> + #if XML_COMBINED_VERSION >= 20600 >> + capi->SetReparseDeferralEnabled = XML_SetReparseDeferralEnabled; >> + #else > > We did not upgrade expat but patched in the fixing API > XML_SetHashSalt16Bytes. Can you check we can activate the fixed code > path with those "#if XML_COMBINED_VERSION >= 20800"? > > I will hold the patch in the meantime, I really want to avoid a false > negative in CVE data. Hello, Gentle ping for the above Regards,
Hi Yoann, Thanks for the reminder, I believe I sent a v4 of this patch here https://lists.openembedded.org/g/openembedded-core/message/242445 Later on this patch has been merged in scarthgap the 18th of august: https://github.com/openembedded/openembedded-core/blob/scarthgap/meta/recipes-devtools/python/python3/CVE-2026-7210.patch Here are the changes to v4: [yocto: Use weak symbol detection for XML_SetHashSalt16Bytes instead of XML_COMBINED_VERSION >= 20800, since our backported expat 2.6.4 provides the function but does not bump the version macros.] Let me know if you need further input from my side , Kind Regards, Amaury
On Tue Aug 25, 2026 at 4:13 PM CEST, Amaury Couderc wrote: > Hi Yoann, > > Thanks for the reminder, I believe I sent a v4 of this patch here > https://lists.openembedded.org/g/openembedded-core/message/242445 > > Later on this patch has been merged in scarthgap the 18th of august: > https://github.com/openembedded/openembedded-core/blob/scarthgap/meta/recipes-devtools/python/python3/CVE-2026-7210.patch > > Here are the changes to v4: > > [yocto: Use weak symbol detection for XML_SetHashSalt16Bytes instead of > XML_COMBINED_VERSION >= 20800, since our backported expat 2.6.4 provides > the function but does not bump the version macros.] > > Let me know if you need further input from my side , > > Kind Regards, > > Amaury Nope, thanks! The patchwork data needed an update, its done now. Regards,
diff --git a/meta/recipes-devtools/python/python3/CVE-2026-7210.patch b/meta/recipes-devtools/python/python3/CVE-2026-7210.patch new file mode 100644 index 0000000000..8acf174a0a --- /dev/null +++ b/meta/recipes-devtools/python/python3/CVE-2026-7210.patch @@ -0,0 +1,122 @@ +From 2ed6138dea0bc94c726f879501e4525712e885d1 Mon Sep 17 00:00:00 2001 +From: Stan Ulbrych <stan@python.org> +Date: Sun, 10 May 2026 18:36:26 +0100 +Subject: [PATCH] gh-149018: Use `XML_SetHashSalt16Bytes` in + `pyexpat`/`_elementtree` when possible (#149023) + + +CVE: CVE-2026-7210 +Upstream-Status: Backport [https://github.com/python/cpython/commit/24b8f12544468e4cedf5bfbe25442fcd495391e4] + +Signed-off-by: Amaury Couderc <amaury.couderc@est.tech> +--- + Include/pyexpat.h | 3 +++ + Include/pyhash.h | 8 +++++--- + .../2026-04-26-19-30-45.gh-issue-149018.a9SqWb.rst | 3 +++ + Modules/_elementtree.c | 8 ++++++-- + Modules/pyexpat.c | 10 +++++++++- + 5 files changed, 26 insertions(+), 6 deletions(-) + create mode 100644 Misc/NEWS.d/next/Security/2026-04-26-19-30-45.gh-issue-149018.a9SqWb.rst + +diff --git a/Include/pyexpat.h b/Include/pyexpat.h +index 04548b7684a..d28d6828975 100644 +--- a/Include/pyexpat.h ++++ b/Include/pyexpat.h +@@ -57,6 +57,9 @@ struct PyExpat_CAPI + XML_Parser parser, unsigned long long activationThresholdBytes); + XML_Bool (*SetAllocTrackerMaximumAmplification)( + XML_Parser parser, float maxAmplificationFactor); ++ /* might be NULL for expat < 2.8.0 */ ++ XML_Bool (*SetHashSalt16Bytes)( ++ XML_Parser parser, const uint8_t entropy[16]); + /* always add new stuff to the end! */ + }; + +diff --git a/Include/pyhash.h b/Include/pyhash.h +index 182d223fab1..ec359bd2f35 100644 +--- a/Include/pyhash.h ++++ b/Include/pyhash.h +@@ -39,14 +39,14 @@ PyAPI_FUNC(Py_hash_t) _Py_HashBytes(const void*, Py_ssize_t); + * pppppppp ssssssss ........ fnv -- two Py_hash_t + * k0k0k0k0 k1k1k1k1 ........ siphash -- two uint64_t + * ........ ........ ssssssss djbx33a -- 16 bytes padding + one Py_hash_t +- * ........ ........ eeeeeeee pyexpat XML hash salt ++ * eeeeeeee eeeeeeee eeeeeeee pyexpat XML hash salt + * + * memory layout on 32 bit systems + * cccccccc cccccccc cccccccc uc + * ppppssss ........ ........ fnv -- two Py_hash_t + * k0k0k0k0 k1k1k1k1 ........ siphash -- two uint64_t (*) + * ........ ........ ssss.... djbx33a -- 16 bytes padding + one Py_hash_t +- * ........ ........ eeee.... pyexpat XML hash salt ++ * eeeeeeee eeeeeeee eeee.... pyexpat XML hash salt + * + * (*) The siphash member may not be available on 32 bit platforms without + * an unsigned int64 data type. +@@ -71,7 +71,9 @@ typedef union { + Py_hash_t suffix; + } djbx33a; + struct { +- unsigned char padding[16]; ++ /* 16 bytes for XML_SetHashSalt16Bytes */ ++ uint8_t hashsalt16[16]; ++ /* 4/8 bytes for legacy XML_SetHashSalt */ + Py_hash_t hashsalt; + } expat; + } _Py_HashSecret_t; +diff --git a/Misc/NEWS.d/next/Security/2026-04-26-19-30-45.gh-issue-149018.a9SqWb.rst b/Misc/NEWS.d/next/Security/2026-04-26-19-30-45.gh-issue-149018.a9SqWb.rst +new file mode 100644 +index 00000000000..d1b5b368684 +--- /dev/null ++++ b/Misc/NEWS.d/next/Security/2026-04-26-19-30-45.gh-issue-149018.a9SqWb.rst +@@ -0,0 +1,3 @@ ++Improved protection against XML hash-flooding attacks in ++:mod:`xml.parsers.expat` and :mod:`xml.etree.ElementTree` when Python is ++compiled with libExpat 2.8.0 or later. +diff --git a/Modules/_elementtree.c b/Modules/_elementtree.c +index 56d1508af13..941376613b0 100644 +--- a/Modules/_elementtree.c ++++ b/Modules/_elementtree.c +@@ -3657,8 +3657,12 @@ _elementtree_XMLParser___init___impl(XMLParserObject *self, PyObject *target, + PyErr_NoMemory(); + return -1; + } +- /* expat < 2.1.0 has no XML_SetHashSalt() */ +- if (EXPAT(st, SetHashSalt) != NULL) { ++ // Prefer 16-byte entropy, only expat >= 2.8.0. See gh-149018 ++ if (EXPAT(st, SetHashSalt16Bytes) != NULL) { ++ EXPAT(st, SetHashSalt16Bytes)(self->parser, ++ _Py_HashSecret.expat.hashsalt16); ++ } ++ else if (EXPAT(st, SetHashSalt) != NULL) { + EXPAT(st, SetHashSalt)(self->parser, + (unsigned long)_Py_HashSecret.expat.hashsalt); + } +diff --git a/Modules/pyexpat.c b/Modules/pyexpat.c +index 79492ca5c4f..388473d3855 100644 +--- a/Modules/pyexpat.c ++++ b/Modules/pyexpat.c +@@ -1388,7 +1388,10 @@ newxmlparseobject(pyexpat_state *state, const char *encoding, + Py_DECREF(self); + return NULL; + } +-#if XML_COMBINED_VERSION >= 20100 ++#if XML_COMBINED_VERSION >= 20800 ++ /* This feature was added upstream in libexpat 2.8.0. */ ++ XML_SetHashSalt16Bytes(self->itself, _Py_HashSecret.expat.hashsalt16); ++#elif XML_COMBINED_VERSION >= 20100 + /* This feature was added upstream in libexpat 2.1.0. */ + XML_SetHashSalt(self->itself, + (unsigned long)_Py_HashSecret.expat.hashsalt); +@@ -2257,6 +2260,11 @@ pyexpat_exec(PyObject *mod) + #else + capi->SetHashSalt = NULL; + #endif ++#if XML_COMBINED_VERSION >= 20800 ++ capi->SetHashSalt16Bytes = XML_SetHashSalt16Bytes; ++#else ++ capi->SetHashSalt16Bytes = NULL; ++#endif + #if XML_COMBINED_VERSION >= 20600 + capi->SetReparseDeferralEnabled = XML_SetReparseDeferralEnabled; + #else diff --git a/meta/recipes-devtools/python/python3_3.12.13.bb b/meta/recipes-devtools/python/python3_3.12.13.bb index bf0e1702d5..1c0de11a93 100644 --- a/meta/recipes-devtools/python/python3_3.12.13.bb +++ b/meta/recipes-devtools/python/python3_3.12.13.bb @@ -43,6 +43,7 @@ SRC_URI = "http://www.python.org/ftp/python/${PV}/Python-${PV}.tar.xz \ file://CVE-2026-6019_p1.patch \ file://CVE-2026-6019_p2.patch \ file://CVE-2025-13462.patch \ + file://CVE-2026-7210.patch \ " SRC_URI:append:class-native = " \