From 47c9d32583821997bb6bf11e6293a4e2d0d590c2 Mon Sep 17 00:00:00 2001
From: Stan Ulbrych <stan@python.org>
Date: Tue, 9 Jun 2026 15:07:36 +0100
Subject: [PATCH] [3.13] gh-149018: Use `XML_SetHashSalt16Bytes` in
 `pyexpat`/`_elementtree` when possible (GH-149023) (cherry picked from commit
 eeea765cb9d8f1fc3d8918b272ac3c477983f27a)

Co-authored-by: Stan Ulbrych <stan@python.org>
(cherry picked from commit 24b8f12544468e4cedf5bfbe25442fcd495391e4)
---
 Include/pyexpat.h                                                        |    3 +++
 Include/pyhash.h                                                         |    7 ++++---
 Misc/NEWS.d/next/Security/2026-04-26-19-30-45.gh-issue-149018.a9SqWb.rst |    3 +++
 Modules/_elementtree.c                                                   |    8 ++++++--
 Modules/pyexpat.c                                                        |   10 +++++++++-
 5 files changed, 25 insertions(+), 6 deletions(-)
 create mode 100644 Misc/NEWS.d/next/Security/2026-04-26-19-30-45.gh-issue-149018.a9SqWb.rst

Index: Python-3.11.15/Include/pyexpat.h
===================================================================
--- Python-3.11.15.orig/Include/pyexpat.h	2026-03-03 01:52:57.000000000 +0100
+++ Python-3.11.15/Include/pyexpat.h	2026-07-27 22:40:48.414515632 +0200
@@ -57,6 +57,9 @@
         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! */
 };
 
Index: Python-3.11.15/Include/pyhash.h
===================================================================
--- Python-3.11.15.orig/Include/pyhash.h	2026-03-03 01:52:57.000000000 +0100
+++ Python-3.11.15/Include/pyhash.h	2026-07-27 22:40:48.415351362 +0200
@@ -35,7 +35,7 @@
 /* hash secret
  *
  * memory layout on 64 bit systems
- *   cccccccc cccccccc cccccccc  uc -- unsigned char[24]
+ *   cccccccc cccccccc cccccccc cccccccc cccccccc  uc -- unsigned char[40]
  *   pppppppp ssssssss ........  fnv -- two Py_hash_t
  *   k0k0k0k0 k1k1k1k1 ........  siphash -- two uint64_t
  *   ........ ........ ssssssss  djbx33a -- 16 bytes padding + one Py_hash_t
@@ -53,8 +53,8 @@
  */
 #ifndef Py_LIMITED_API
 typedef union {
-    /* ensure 24 bytes */
-    unsigned char uc[24];
+    /* ensure 40 bytes */
+    unsigned char uc[40];
     /* two Py_hash_t for FNV */
     struct {
         Py_hash_t prefix;
@@ -73,6 +73,7 @@
     struct {
         unsigned char padding[16];
         Py_hash_t hashsalt;
+        uint8_t hashsalt16[16];
     } expat;
 } _Py_HashSecret_t;
 PyAPI_DATA(_Py_HashSecret_t) _Py_HashSecret;
Index: Python-3.11.15/Misc/NEWS.d/next/Security/2026-04-26-19-30-45.gh-issue-149018.a9SqWb.rst
===================================================================
--- /dev/null	1970-01-01 00:00:00.000000000 +0000
+++ Python-3.11.15/Misc/NEWS.d/next/Security/2026-04-26-19-30-45.gh-issue-149018.a9SqWb.rst	2026-07-27 22:40:48.415533054 +0200
@@ -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.
Index: Python-3.11.15/Modules/_elementtree.c
===================================================================
--- Python-3.11.15.orig/Modules/_elementtree.c	2026-03-03 01:52:57.000000000 +0100
+++ Python-3.11.15/Modules/_elementtree.c	2026-07-27 22:40:48.416117877 +0200
@@ -3655,8 +3655,12 @@
         PyErr_NoMemory();
         return -1;
     }
-    /* expat < 2.1.0 has no XML_SetHashSalt() */
-    if (EXPAT(SetHashSalt) != NULL) {
+    // Prefer 16-byte entropy, only expat >= 2.8.0. See gh-149018
+    if (EXPAT(SetHashSalt16Bytes) != NULL) {
+        EXPAT(SetHashSalt16Bytes)(self->parser,
+                                      _Py_HashSecret.expat.hashsalt16);
+    }
+    else if (EXPAT(SetHashSalt) != NULL) {
         EXPAT(SetHashSalt)(self->parser,
                            (unsigned long)_Py_HashSecret.expat.hashsalt);
     }
Index: Python-3.11.15/Modules/pyexpat.c
===================================================================
--- Python-3.11.15.orig/Modules/pyexpat.c	2026-07-27 22:40:48.241525265 +0200
+++ Python-3.11.15/Modules/pyexpat.c	2026-07-27 22:40:48.416515654 +0200
@@ -1385,7 +1385,10 @@
         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);
@@ -2236,6 +2239,11 @@
 #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
