new file mode 100644
@@ -0,0 +1,158 @@
+From c932e70451eafef922ebef364ac25042f0031135 Mon Sep 17 00:00:00 2001
+From: Nicholas Wilson <nicholas@nicholaswilson.me.uk>
+Date: Thu, 27 Aug 2026 16:52:16 +0100
+Subject: [PATCH] Fix DFA workspace overflows; see GHSA-3r4p-g7gg-ppmf for
+ details
+
+CVE: CVE-2026-86145
+Upstream-Status: Backport [https://github.com/PCRE2Project/pcre2/commit/c932e70451eafef922ebef364ac25042f0031135]
+Signed-off-by: Peter Marko <peter.marko@siemens.com>
+---
+ src/pcre2_dfa_match.c | 44 ++++++++++++++++++++++++++++++++++++-------
+ testdata/testinput6 | 7 +++++++
+ testdata/testoutput6 | 8 ++++++++
+ 3 files changed, 52 insertions(+), 7 deletions(-)
+
+diff --git a/src/pcre2_dfa_match.c b/src/pcre2_dfa_match.c
+index 314e9775..8e9512c4 100644
+--- a/src/pcre2_dfa_match.c
++++ b/src/pcre2_dfa_match.c
+@@ -405,8 +405,8 @@ return (mb->callout)(cb, mb->callout_data);
+
+ /* This function is called when internal_dfa_match() is about to be called
+ recursively and there is insufficient working space left in the current
+-workspace block. If there's an existing next block, use it; otherwise get a new
+-block unless the heap limit is reached.
++workspace block. If there's a sufficiently large next block, use it; get a new
++block unless the heap limit is (or has been) reached.
+
+ Arguments:
+ rwsptr pointer to block pointer (updated)
+@@ -422,9 +422,18 @@ more_workspace(RWS_anchor **rwsptr, unsigned int ovecsize, dfa_match_block *mb)
+ {
+ RWS_anchor *rws = *rwsptr;
+ RWS_anchor *new;
++uint32_t requested;
++
++PCRE2_ASSERT(ovecsize <= UINT32_MAX - RWS_RSIZE - RWS_ANCHOR_SIZE);
++requested = RWS_RSIZE + ovecsize + RWS_ANCHOR_SIZE;
+
+ if (rws->next != NULL)
+ {
++ /* Although the initial block is large, and subsequent ones try to double, the
++ heap limit may cause the last one to be smaller; in this case, we have already
++ hit the heap limit and allocating a larger block will not be possible. */
++ if (rws->next->size < requested)
++ return PCRE2_ERROR_HEAPLIMIT;
+ new = rws->next;
+ }
+
+@@ -434,14 +443,30 @@ overflow. */
+
+ else
+ {
+- uint32_t newsize = (rws->size >= UINT32_MAX/(sizeof(int)*2))? UINT32_MAX/sizeof(int) : rws->size * 2;
++ uint32_t newsize = (rws->size >= (UINT32_MAX/sizeof(int))/2)?
++ UINT32_MAX/sizeof(int) : rws->size * 2;
+ uint32_t newsizeK = newsize/(1024/sizeof(int));
+
+- if (newsizeK + mb->heap_used > mb->heap_limit)
+- newsizeK = (uint32_t)(mb->heap_limit - mb->heap_used);
+- newsize = newsizeK*(1024/sizeof(int));
++ /* Clamp the allocation to the remaining heap allowance with care for overflows */
+
+- if (newsize < RWS_RSIZE + ovecsize + RWS_ANCHOR_SIZE)
++ if (mb->heap_used >= mb->heap_limit)
++ {
++ newsize = 0;
++ newsizeK = 0;
++ }
++ else
++ {
++ PCRE2_SIZE availableK = mb->heap_limit - mb->heap_used;
++ /* newsize always capped at UINT32_MAX/sizeof(int), so newsizeK also capped;
++ and - if availableK is smaller - then multiplication to form newsize is safe */
++ if (newsizeK > availableK)
++ {
++ newsize = (uint32_t)(availableK*(1024/sizeof(int)));
++ newsizeK = availableK;
++ }
++ }
++
++ if (newsize < requested)
+ return PCRE2_ERROR_HEAPLIMIT;
+ new = mb->memctl.malloc(newsize*sizeof(int), mb->memctl.memory_data);
+ if (new == NULL) return PCRE2_ERROR_NOMEMORY;
+@@ -2801,6 +2826,7 @@ for (;;)
+
+ local_offsets = (PCRE2_SIZE *)(RWS + rws->size - rws->free);
+ local_workspace = ((int *)local_offsets) + RWS_OVEC_OSIZE;
++ PCRE2_ASSERT(rws->free >= RWS_RSIZE + RWS_OVEC_OSIZE);
+ rws->free -= RWS_RSIZE + RWS_OVEC_OSIZE;
+
+ while (*endasscode == OP_ALT) endasscode += GET(endasscode, 1);
+@@ -2900,6 +2926,7 @@ for (;;)
+
+ local_offsets = (PCRE2_SIZE *)(RWS + rws->size - rws->free);
+ local_workspace = ((int *)local_offsets) + RWS_OVEC_OSIZE;
++ PCRE2_ASSERT(rws->free >= RWS_RSIZE + RWS_OVEC_OSIZE);
+ rws->free -= RWS_RSIZE + RWS_OVEC_OSIZE;
+
+ while (*endasscode == OP_ALT) endasscode += GET(endasscode, 1);
+@@ -2951,6 +2978,7 @@ for (;;)
+
+ local_offsets = (PCRE2_SIZE *)(RWS + rws->size - rws->free);
+ local_workspace = ((int *)local_offsets) + RWS_OVEC_RSIZE;
++ PCRE2_ASSERT(rws->free >= RWS_RSIZE + RWS_OVEC_RSIZE);
+ rws->free -= RWS_RSIZE + RWS_OVEC_RSIZE;
+
+ /* Check for repeating a recursion without advancing the subject
+@@ -3050,6 +3078,7 @@ for (;;)
+
+ local_offsets = (PCRE2_SIZE *)(RWS + rws->size - rws->free);
+ local_workspace = ((int *)local_offsets) + RWS_OVEC_OSIZE;
++ PCRE2_ASSERT(rws->free >= RWS_RSIZE + RWS_OVEC_OSIZE);
+ rws->free -= RWS_RSIZE + RWS_OVEC_OSIZE;
+
+ if (codevalue == OP_BRAPOSZERO)
+@@ -3149,6 +3178,7 @@ for (;;)
+
+ local_offsets = (PCRE2_SIZE *)(RWS + rws->size - rws->free);
+ local_workspace = ((int *)local_offsets) + RWS_OVEC_OSIZE;
++ PCRE2_ASSERT(rws->free >= RWS_RSIZE + RWS_OVEC_OSIZE);
+ rws->free -= RWS_RSIZE + RWS_OVEC_OSIZE;
+
+ rc = internal_dfa_match(
+diff --git a/testdata/testinput6 b/testdata/testinput6
+index f6f5cbf4..197f6f76 100644
+--- a/testdata/testinput6
++++ b/testdata/testinput6
+@@ -5263,4 +5263,11 @@
+ abc\=replace=xyz
+ abc\=replace=xyz,substitute_matched
+
++# --------------
++
++# Test workspace resizing and workspace re-use
++
++/(*LIMIT_HEAP=4)(?=(?=(?=(?=(?=(?=(?=(?=a))(?R)))))))./
++ a\=dfa
++
+ # End of testinput6
+diff --git a/testdata/testoutput6 b/testdata/testoutput6
+index 8ecf0040..4316c8a6 100644
+--- a/testdata/testoutput6
++++ b/testdata/testoutput6
+@@ -8237,4 +8237,12 @@ Failed: error -42: pattern contains an item that is not supported for DFA matchi
+ abc\=replace=xyz,substitute_matched
+ Failed: error -41: function is not supported for DFA matching
+
++# --------------
++
++# Test workspace resizing and workspace re-use
++
++/(*LIMIT_HEAP=4)(?=(?=(?=(?=(?=(?=(?=(?=a))(?R)))))))./
++ a\=dfa
++Failed: error -63: heap limit exceeded
++
+ # End of testinput6
@@ -20,6 +20,7 @@ SRC_URI = "${GITHUB_BASE_URI}/download/pcre2-${PV}/pcre2-${PV}.tar.bz2 \
file://CVE-2026-89157.patch \
file://CVE-2026-89160.patch \
file://CVE-2026-89158.patch \
+ file://CVE-2026-86145.patch \
"
GITHUB_BASE_URI = "https://github.com/PCRE2Project/pcre2/releases"