new file mode 100644
@@ -0,0 +1,225 @@
+From 4889caf31a4c5a6b3c051f0031bf2dbd78f2c287 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 invalid UTF backwards-scan reads; see GHSA-9qww-pwc4-77qq
+ for details
+
+CVE: CVE-2026-89160
+Upstream-Status: Backport [https://github.com/PCRE2Project/pcre2/commit/4889caf31a4c5a6b3c051f0031bf2dbd78f2c287]
+Signed-off-by: Peter Marko <peter.marko@siemens.com>
+---
+ src/pcre2_extuni.c | 10 +++++-----
+ src/pcre2_match.c | 10 +++++-----
+ testdata/testinput10 | 10 ++++++++++
+ testdata/testinput12 | 10 ++++++++++
+ testdata/testoutput10 | 12 ++++++++++++
+ testdata/testoutput12-16 | 12 ++++++++++++
+ testdata/testoutput12-32 | 12 ++++++++++++
+ 7 files changed, 66 insertions(+), 10 deletions(-)
+
+diff --git a/src/pcre2_extuni.c b/src/pcre2_extuni.c
+index 1b7f04b4..fea098a7 100644
+--- a/src/pcre2_extuni.c
++++ b/src/pcre2_extuni.c
+@@ -54,12 +54,12 @@ support, because some compilers do not like functionless source files. */
+
+ #ifndef SUPPORT_UNICODE
+ PCRE2_SPTR
+-PRIV(extuni)(uint32_t c, PCRE2_SPTR eptr, PCRE2_SPTR start_subject,
++PRIV(extuni)(uint32_t c, PCRE2_SPTR eptr, PCRE2_SPTR check_subject,
+ PCRE2_SPTR end_subject, BOOL utf, int *xcount)
+ {
+ (void)c;
+ (void)eptr;
+-(void)start_subject;
++(void)check_subject;
+ (void)end_subject;
+ (void)utf;
+ (void)xcount;
+@@ -80,7 +80,7 @@ same behaviour.
+ Arguments:
+ c the first character
+ eptr pointer to next character
+- start_subject pointer to start of subject
++ check_subject pointer to start of validated subject
+ end_subject pointer to end of subject
+ utf TRUE if in UTF mode
+ xcount pointer to count of additional characters,
+@@ -90,7 +90,7 @@ Returns: pointer after the end of the sequence
+ */
+
+ PCRE2_SPTR
+-PRIV(extuni)(uint32_t c, PCRE2_SPTR eptr, PCRE2_SPTR start_subject,
++PRIV(extuni)(uint32_t c, PCRE2_SPTR eptr, PCRE2_SPTR check_subject,
+ PCRE2_SPTR end_subject, BOOL utf, int *xcount)
+ {
+ BOOL was_ep_ZWJ = FALSE;
+@@ -121,7 +121,7 @@ while (eptr < end_subject)
+
+ /* bptr is pointing to the left-hand character */
+
+- while (bptr > start_subject)
++ while (bptr > check_subject)
+ {
+ bptr--;
+ if (utf)
+diff --git a/src/pcre2_match.c b/src/pcre2_match.c
+index a5a8421f..966576e1 100644
+--- a/src/pcre2_match.c
++++ b/src/pcre2_match.c
+@@ -2893,7 +2893,7 @@ fprintf(stderr, "++ %2ld op=%3d %s\n", Fecode - mb->start_code, *Fecode,
+ else
+ {
+ GETCHARINCTEST(fc, Feptr);
+- Feptr = PRIV(extuni)(fc, Feptr, mb->start_subject, mb->end_subject, utf,
++ Feptr = PRIV(extuni)(fc, Feptr, mb->check_subject, mb->end_subject, utf,
+ NULL);
+ }
+ CHECK_PARTIAL();
+@@ -3244,7 +3244,7 @@ fprintf(stderr, "++ %2ld op=%3d %s\n", Fecode - mb->start_code, *Fecode,
+ else
+ {
+ GETCHARINCTEST(fc, Feptr);
+- Feptr = PRIV(extuni)(fc, Feptr, mb->start_subject,
++ Feptr = PRIV(extuni)(fc, Feptr, mb->check_subject,
+ mb->end_subject, utf, NULL);
+ }
+ CHECK_PARTIAL();
+@@ -4069,7 +4069,7 @@ fprintf(stderr, "++ %2ld op=%3d %s\n", Fecode - mb->start_code, *Fecode,
+ else
+ {
+ GETCHARINCTEST(fc, Feptr);
+- Feptr = PRIV(extuni)(fc, Feptr, mb->start_subject, mb->end_subject,
++ Feptr = PRIV(extuni)(fc, Feptr, mb->check_subject, mb->end_subject,
+ utf, NULL);
+ }
+ CHECK_PARTIAL();
+@@ -4658,7 +4658,7 @@ fprintf(stderr, "++ %2ld op=%3d %s\n", Fecode - mb->start_code, *Fecode,
+ else
+ {
+ GETCHARINCTEST(fc, Feptr);
+- Feptr = PRIV(extuni)(fc, Feptr, mb->start_subject, mb->end_subject,
++ Feptr = PRIV(extuni)(fc, Feptr, mb->check_subject, mb->end_subject,
+ utf, NULL);
+ }
+ CHECK_PARTIAL();
+@@ -6233,7 +6233,7 @@ fprintf(stderr, "++ %2ld op=%3d %s\n", Fecode - mb->start_code, *Fecode,
+ {
+ for (i = 0; i < Lmax; i++)
+ {
+- if (Feptr == mb->start_subject)
++ if (Feptr <= mb->check_subject)
+ {
+ if (i < Lmin) RRETURN(MATCH_NOMATCH);
+ Lmax = i;
+diff --git a/testdata/testinput10 b/testdata/testinput10
+index d9e6ba8c..bfa9dad8 100644
+--- a/testdata/testinput10
++++ b/testdata/testinput10
+@@ -585,6 +585,16 @@
+ AAA\x80BXYZ
+ AAA\x80BBXYZ
+
++# Backward scans must not enter the invalid prefix before check_subject.
++
++/(?<=a{1,2})X/utf,match_invalid_utf
++\= Expect no match
++ \x80X
++
++/\X\X/utf,match_invalid_utf
++\= Expect no match
++ \x80\x{1f1e6}\x{1f1e7}
++
+ # -------------------------------------
+
+ /(*UTF)(?=\x{123})/I
+diff --git a/testdata/testinput12 b/testdata/testinput12
+index 01cc76a4..c4a89a26 100644
+--- a/testdata/testinput12
++++ b/testdata/testinput12
+@@ -498,6 +498,16 @@
+ /(..)(*scs:(1)ab$)/match_invalid_utf
+ ab\x{df00}cde
+
++# Backward scans must not enter the invalid prefix before check_subject.
++
++/(?<=a{1,2})X/utf,match_invalid_utf
++\= Expect no match
++ \x{dc00}X
++
++/\X\X/utf,match_invalid_utf
++\= Expect no match
++ \x{dc00}\x{1f1e6}\x{1f1e7}
++
+ # ----------------------------------------------------
+
+ /(*UTF)(?=\x{123})/I
+diff --git a/testdata/testoutput10 b/testdata/testoutput10
+index 393ac207..9e124e2b 100644
+--- a/testdata/testoutput10
++++ b/testdata/testoutput10
+@@ -1779,6 +1779,18 @@ No match
+ AAA\x80BBXYZ
+ No match
+
++# Backward scans must not enter the invalid prefix before check_subject.
++
++/(?<=a{1,2})X/utf,match_invalid_utf
++\= Expect no match
++ \x80X
++No match
++
++/\X\X/utf,match_invalid_utf
++\= Expect no match
++ \x80\x{1f1e6}\x{1f1e7}
++No match
++
+ # -------------------------------------
+
+ /(*UTF)(?=\x{123})/I
+diff --git a/testdata/testoutput12-16 b/testdata/testoutput12-16
+index d235c11f..b0676a19 100644
+--- a/testdata/testoutput12-16
++++ b/testdata/testoutput12-16
+@@ -1659,6 +1659,18 @@ No match
+ 0: ab
+ 1: ab
+
++# Backward scans must not enter the invalid prefix before check_subject.
++
++/(?<=a{1,2})X/utf,match_invalid_utf
++\= Expect no match
++ \x{dc00}X
++No match
++
++/\X\X/utf,match_invalid_utf
++\= Expect no match
++ \x{dc00}\x{1f1e6}\x{1f1e7}
++No match
++
+ # ----------------------------------------------------
+
+ /(*UTF)(?=\x{123})/I
+diff --git a/testdata/testoutput12-32 b/testdata/testoutput12-32
+index 725cb274..a97051a6 100644
+--- a/testdata/testoutput12-32
++++ b/testdata/testoutput12-32
+@@ -1658,6 +1658,18 @@ No match
+ 0: ab
+ 1: ab
+
++# Backward scans must not enter the invalid prefix before check_subject.
++
++/(?<=a{1,2})X/utf,match_invalid_utf
++\= Expect no match
++ \x{dc00}X
++No match
++
++/\X\X/utf,match_invalid_utf
++\= Expect no match
++ \x{dc00}\x{1f1e6}\x{1f1e7}
++No match
++
+ # ----------------------------------------------------
+
+ /(*UTF)(?=\x{123})/I
@@ -18,6 +18,7 @@ SRC_URI = "${GITHUB_BASE_URI}/download/pcre2-${PV}/pcre2-${PV}.tar.bz2 \
file://CVE-2026-89161.patch \
file://CVE-2026-89156.patch \
file://CVE-2026-89157.patch \
+ file://CVE-2026-89160.patch \
"
GITHUB_BASE_URI = "https://github.com/PCRE2Project/pcre2/releases"