diff mbox series

[v3,scarthgap] python3: fix CVE-2026-7210

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

Commit Message

Amaury Couderc July 6, 2026, 1:26 p.m. UTC
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

Comments

Yoann Congal July 19, 2026, 9:48 p.m. UTC | #1
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,
Yoann Congal Aug. 25, 2026, 8:43 a.m. UTC | #2
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,
Amaury Couderc Aug. 25, 2026, 2:13 p.m. UTC | #3
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
Yoann Congal Aug. 25, 2026, 3:44 p.m. UTC | #4
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 mbox series

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
+
+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 = " \