Commit 1e8a51d545d for php

commit 1e8a51d545d92084e1d354d81030e30cc8a3e3a0
Author: Alexandre Daubois <alex.daubois@gmail.com>
Date:   Tue Sep 29 11:59:15 2026 +0200

    Backport PCRE2 security fixes to bundled 10.40

    Apply the upstream backports listed for PCRE2 10.40 in SUPPORT-LIFECYCLE.md through 10.49 (GHSA-2p8c-ff85-vh9x, GHSA-q8g2-wprr-34m9, GHSA-9qww-pwc4-77qq, GHSA-fmgr-6ggq-9859, GHSA-3r4p-g7gg-ppmf, GHSA-r9hj-j2rw-4q3m), adapted to 10.40 where the patched code differs. Disable the legacy ARM64 NEON SIMD scanning for GHSA-43gg-c3xx-7w7g, as the upstream fix is a rewrite with no small backport.

    Closes GH-24011.

diff --git a/NEWS b/NEWS
index 91bd9e2da7c..94580e7c7fe 100644
--- a/NEWS
+++ b/NEWS
@@ -21,6 +21,12 @@ PHP                                                                        NEWS
     php_openssl_matches_wildcard_name() on crafted server certificate wildcard
     CN). (CVE-2026-91767) (Jakub Zelenka)

+- PCRE:
+  . Applied security fixes to bundled PCRE2 10.40 and disabled its legacy
+    ARM64 SIMD optimization (GHSA-2p8c-ff85-vh9x, GHSA-q8g2-wprr-34m9,
+    GHSA-9qww-pwc4-77qq, GHSA-fmgr-6ggq-9859, GHSA-3r4p-g7gg-ppmf,
+    GHSA-r9hj-j2rw-4q3m, and GHSA-43gg-c3xx-7w7g). (alexandre-daubois)
+
 - Phar:
   . Fixed GHSA-j3wh-g957-2m85 (Integer overflow in phar_tar_number() allowing
     TAR archive entry injection). (CVE-2026-6103) (Jakub Zelenka)
diff --git a/ext/pcre/pcre2lib/pcre2_compile.c b/ext/pcre/pcre2lib/pcre2_compile.c
index de259c9c405..ec68d3850c6 100644
--- a/ext/pcre/pcre2lib/pcre2_compile.c
+++ b/ext/pcre/pcre2lib/pcre2_compile.c
@@ -5521,7 +5521,8 @@ for (;; pptr++)

     if (meta < META_ASTERISK || meta > META_MINMAX_QUERY)
       {
-      if (OFLOW_MAX - *lengthptr < (PCRE2_SIZE)(code - orig_code))
+      if (*lengthptr > OFLOW_MAX ||
+          OFLOW_MAX - *lengthptr < (PCRE2_SIZE)(code - orig_code))
         {
         *errorcodeptr = ERR20;   /* Integer overflow */
         return 0;
@@ -8305,7 +8306,8 @@ for (;;)
     *reqcuflagsptr = reqcuflags;
     if (lengthptr != NULL)
       {
-      if (OFLOW_MAX - *lengthptr < length)
+      if (*lengthptr > MAX_PATTERN_SIZE ||
+          MAX_PATTERN_SIZE - *lengthptr < length)
         {
         *errorcodeptr = ERR20;
         return 0;
@@ -8328,6 +8330,19 @@ for (;;)
     {
     code = *codeptr + 1 + LINK_SIZE + skipunits;
     length += 1 + LINK_SIZE;
+
+    /* Move the accumulated length into *lengthptr, providing the next call to
+    compile_branch with as much space in &length and &code as the first did. */
+
+    if (*lengthptr > MAX_PATTERN_SIZE ||
+        MAX_PATTERN_SIZE - *lengthptr < length)
+      {
+      *errorcodeptr = ERR20;
+      cb->erroroffset = 0;
+      return 0;
+      }
+    *lengthptr += length;
+    length = 0;
     }
   else
     {
@@ -10243,9 +10258,29 @@ the compiled pattern and names table. Integer overflow should no longer be
 possible because nowadays we limit the maximum value of cb.names_found and
 cb.name_entry_size. */

-re_blocksize = sizeof(pcre2_real_code) +
-  CU2BYTES(length +
+re_blocksize = CU2BYTES(
   (PCRE2_SIZE)cb.names_found * (PCRE2_SIZE)cb.name_entry_size);
+
+if (length > BYTES2CU(PCRE2_SIZE_MAX - re_blocksize))
+  {
+  /* Given the current value of 2^30 for MAX_PATTERN_SIZE, this block is only
+  reachable when both PCRE2_CODE_UNIT_WIDTH >= 16 and sizeof(size_t) is
+  32 bits. */
+  errorcode = ERR20;
+  cb.erroroffset = 0;
+  goto HAD_CB_ERROR;
+  }
+
+re_blocksize += CU2BYTES(length);
+
+if (sizeof(pcre2_real_code) > PCRE2_SIZE_MAX - re_blocksize)
+  {
+  errorcode = ERR20;
+  cb.erroroffset = 0;
+  goto HAD_CB_ERROR;
+  }
+
+re_blocksize += sizeof(pcre2_real_code);
 re = (pcre2_real_code *)
   ccontext->memctl.malloc(re_blocksize, ccontext->memctl.memory_data);
 if (re == NULL)
diff --git a/ext/pcre/pcre2lib/pcre2_convert.c b/ext/pcre/pcre2lib/pcre2_convert.c
index d45b6fee974..f336be4586a 100644
--- a/ext/pcre/pcre2lib/pcre2_convert.c
+++ b/ext/pcre/pcre2lib/pcre2_convert.c
@@ -1143,9 +1143,12 @@ for (i = 0; i < 2; i++)
   /* Allocate memory for the buffer, with hidden space for an allocator at
   the start. The next time round the loop runs the conversion for real. */

-  allocated = PRIV(memctl_malloc)(sizeof(pcre2_memctl) +
-    (*bufflenptr + 1)*PCRE2_CODE_UNIT_WIDTH, (pcre2_memctl *)ccontext);
-  if (allocated == NULL) return PCRE2_ERROR_NOMEMORY;
+  if (*bufflenptr > ((PCRE2_SIZE_MAX - sizeof(pcre2_memctl)) /
+        CU2BYTES(1)) - 1 ||
+      (allocated = PRIV(memctl_malloc)(sizeof(pcre2_memctl) +
+        CU2BYTES(*bufflenptr + 1),
+        (pcre2_memctl *)ccontext)) == NULL)
+    return PCRE2_ERROR_NOMEMORY;
   *buffptr = (PCRE2_UCHAR *)(((char *)allocated) + sizeof(pcre2_memctl));

   use_buffer = *buffptr;
diff --git a/ext/pcre/pcre2lib/pcre2_dfa_match.c b/ext/pcre/pcre2lib/pcre2_dfa_match.c
index d29130f2d0c..c52bd6805cc 100644
--- a/ext/pcre/pcre2lib/pcre2_dfa_match.c
+++ b/ext/pcre/pcre2lib/pcre2_dfa_match.c
@@ -397,8 +397,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)
@@ -414,9 +414,17 @@ more_workspace(RWS_anchor **rwsptr, unsigned int ovecsize, dfa_match_block *mb)
 {
 RWS_anchor *rws = *rwsptr;
 RWS_anchor *new;
+uint32_t requested;
+
+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;
   }

@@ -426,14 +434,30 @@ overflow. */

 else
   {
-  uint32_t newsize = (rws->size >= UINT32_MAX/2)? UINT32_MAX/2 : 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 (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 < RWS_RSIZE + ovecsize + RWS_ANCHOR_SIZE)
+  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;
diff --git a/ext/pcre/pcre2lib/pcre2_extuni.c b/ext/pcre/pcre2lib/pcre2_extuni.c
index b23946b0d16..b0e73350e72 100644
--- a/ext/pcre/pcre2lib/pcre2_extuni.c
+++ b/ext/pcre/pcre2lib/pcre2_extuni.c
@@ -57,12 +57,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;
@@ -79,7 +79,7 @@ return NULL;
 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,
@@ -89,7 +89,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)
 {
 int lgb = UCD_GRAPHBREAK(c);
@@ -113,7 +113,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/ext/pcre/pcre2lib/pcre2_internal.h b/ext/pcre/pcre2lib/pcre2_internal.h
index fe7a0e005a0..21b673e56df 100644
--- a/ext/pcre/pcre2lib/pcre2_internal.h
+++ b/ext/pcre/pcre2lib/pcre2_internal.h
@@ -1991,6 +1991,7 @@ is available. */
 #define _pcre2_is_newline            PCRE2_SUFFIX(_pcre2_is_newline_)
 #define _pcre2_jit_free_rodata       PCRE2_SUFFIX(_pcre2_jit_free_rodata_)
 #define _pcre2_jit_free              PCRE2_SUFFIX(_pcre2_jit_free_)
+#define _pcre2_jit_check_exec        PCRE2_SUFFIX(_pcre2_jit_check_exec_)
 #define _pcre2_jit_get_size          PCRE2_SUFFIX(_pcre2_jit_get_size_)
 #define _pcre2_jit_get_target        PCRE2_SUFFIX(_pcre2_jit_get_target_)
 #define _pcre2_memctl_malloc         PCRE2_SUFFIX(_pcre2_memctl_malloc_)
@@ -2018,6 +2019,7 @@ extern BOOL         _pcre2_is_newline(PCRE2_SPTR, uint32_t, PCRE2_SPTR,
                       uint32_t *, BOOL);
 extern void         _pcre2_jit_free_rodata(void *, void *);
 extern void         _pcre2_jit_free(void *, pcre2_memctl *);
+extern BOOL         _pcre2_jit_check_exec(void *, uint32_t);
 extern size_t       _pcre2_jit_get_size(void *);
 const char *        _pcre2_jit_get_target(void);
 extern void *       _pcre2_memctl_malloc(size_t, pcre2_memctl *);
diff --git a/ext/pcre/pcre2lib/pcre2_jit_compile.c b/ext/pcre/pcre2lib/pcre2_jit_compile.c
index 79b23d0f656..5de96673ad5 100644
--- a/ext/pcre/pcre2lib/pcre2_jit_compile.c
+++ b/ext/pcre/pcre2lib/pcre2_jit_compile.c
@@ -97,7 +97,7 @@ Fast, but limited size. */

 /* Growth rate for stack allocated by the OS. Should be the multiply
 of page size. */
-#define STACK_GROWTH_RATE 8192
+#define STACK_GROWTH_RATE (sljit_sw)8192

 /* Enable to check that the allocation could destroy temporaries. */
 #if defined SLJIT_DEBUG && SLJIT_DEBUG
@@ -438,6 +438,8 @@ typedef struct compiler_common {
   BOOL local_quit_available;
   /* Currently in a positive assertion. */
   BOOL in_positive_assertion;
+  /* More than STACK_GROWTH_RATE / 2 stack memory is allocated. */
+  BOOL large_stack_allocation;
   /* Newline control. */
   int nltype;
   sljit_u32 nlmax;
@@ -3144,6 +3146,8 @@ static SLJIT_INLINE void allocate_stack(compiler_common *common, int size)
 DEFINE_COMPILER;

 SLJIT_ASSERT(size > 0);
+if (size > (STACK_GROWTH_RATE / ((sljit_s32)sizeof(sljit_sw) * 2)))
+  common->large_stack_allocation = TRUE;
 OP2(SLJIT_SUB, STACK_TOP, 0, STACK_TOP, 0, SLJIT_IMM, size * sizeof(sljit_sw));
 #ifdef DESTROY_REGISTERS
 OP1(SLJIT_MOV, TMP1, 0, SLJIT_IMM, 12345);
@@ -14193,7 +14197,20 @@ SLJIT_ASSERT(TMP1 == SLJIT_R0 && STR_PTR == SLJIT_R1);

 OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), LOCALS1, STR_PTR, 0);
 OP1(SLJIT_MOV, SLJIT_R0, 0, ARGUMENTS, 0);
-OP2(SLJIT_SUB, SLJIT_R1, 0, STACK_LIMIT, 0, SLJIT_IMM, STACK_GROWTH_RATE);
+if (common->large_stack_allocation)
+  {
+  SLJIT_COMPILE_ASSERT((STACK_GROWTH_RATE & (STACK_GROWTH_RATE - 1)) == 0, stack_growth_must_be_power_of_2);
+  // Negative difference. The positive difference would also use the same amount
+  // of operations, but the last subtraction emits several instructions on x86.
+  OP2(SLJIT_SUB, SLJIT_R1, 0, STACK_TOP, 0, STACK_LIMIT, 0);
+  // Minimum extra space after allocation.
+  OP2(SLJIT_SUB, SLJIT_R1, 0, SLJIT_R1, 0, SLJIT_IMM, (STACK_GROWTH_RATE / 2));
+  // Rounds down negative numbers.
+  OP2(SLJIT_AND, SLJIT_R1, 0, SLJIT_R1, 0, SLJIT_IMM, ~(STACK_GROWTH_RATE - 1));
+  OP2(SLJIT_ADD, SLJIT_R1, 0, SLJIT_R1, 0, STACK_LIMIT, 0);
+  }
+else
+  OP2(SLJIT_SUB, SLJIT_R1, 0, STACK_LIMIT, 0, SLJIT_IMM, STACK_GROWTH_RATE);
 OP1(SLJIT_MOV, SLJIT_R0, 0, SLJIT_MEM1(SLJIT_R0), SLJIT_OFFSETOF(jit_arguments, stack));
 OP1(SLJIT_MOV, STACK_LIMIT, 0, TMP2, 0);

diff --git a/ext/pcre/pcre2lib/pcre2_jit_match.c b/ext/pcre/pcre2lib/pcre2_jit_match.c
index 1ab3af073e6..55b68d7aad6 100644
--- a/ext/pcre/pcre2lib/pcre2_jit_match.c
+++ b/ext/pcre/pcre2lib/pcre2_jit_match.c
@@ -112,6 +112,7 @@ jit_arguments arguments;
 int rc;
 int index = 0;

+/* The same check is performed by jit_check_exec(). */
 if ((options & PCRE2_PARTIAL_HARD) != 0)
   index = 2;
 else if ((options & PCRE2_PARTIAL_SOFT) != 0)
@@ -120,6 +121,16 @@ else if ((options & PCRE2_PARTIAL_SOFT) != 0)
 if (functions == NULL || functions->executable_funcs[index] == NULL)
   return PCRE2_ERROR_JIT_BADOPTION;

+/* If the match data block was previously used with PCRE2_COPY_MATCHED_SUBJECT,
+free the memory that was obtained. */
+
+if ((match_data->flags & PCRE2_MD_COPIED_SUBJECT) != 0)
+  {
+  match_data->memctl.free((void *)match_data->subject,
+    match_data->memctl.memory_data);
+  match_data->flags &= ~PCRE2_MD_COPIED_SUBJECT;
+  }
+
 /* Sanity checks should be handled by pcre2_match. */
 arguments.str = subject + start_offset;
 arguments.begin = subject;
diff --git a/ext/pcre/pcre2lib/pcre2_jit_misc.c b/ext/pcre/pcre2lib/pcre2_jit_misc.c
index e57afad065b..6233689727c 100644
--- a/ext/pcre/pcre2lib/pcre2_jit_misc.c
+++ b/ext/pcre/pcre2lib/pcre2_jit_misc.c
@@ -212,6 +212,32 @@ return sljit_get_platform_name();
 }


+/*************************************************
+*          Checks function compilation           *
+*************************************************/
+
+BOOL
+PRIV(jit_check_exec)(void *executable_jit, uint32_t options)
+{
+#ifndef SUPPORT_JIT
+(void)executable_jit;
+(void)options;
+return FALSE;
+#else  /* SUPPORT_JIT */
+/* The same check is performed at the beginning of pcre2_jit_match(). */
+executable_functions *functions = (executable_functions *)executable_jit;
+int index = 0;
+
+if ((options & PCRE2_PARTIAL_HARD) != 0)
+  index = 2;
+else if ((options & PCRE2_PARTIAL_SOFT) != 0)
+  index = 1;
+
+return functions->executable_funcs[index] != NULL;
+#endif
+}
+
+
 /*************************************************
 *              Get size of JIT code              *
 *************************************************/
diff --git a/ext/pcre/pcre2lib/pcre2_jit_simd_inc.h b/ext/pcre/pcre2lib/pcre2_jit_simd_inc.h
index d99cfc5ce46..46083f8e4d4 100644
--- a/ext/pcre/pcre2lib/pcre2_jit_simd_inc.h
+++ b/ext/pcre/pcre2lib/pcre2_jit_simd_inc.h
@@ -766,7 +766,7 @@ if (common->match_end_ptr != 0)

 #endif /* SLJIT_CONFIG_X86 */

-#if (defined SLJIT_CONFIG_ARM_64 && SLJIT_CONFIG_ARM_64 && (defined __ARM_NEON || defined __ARM_NEON__))
+#if 0

 #include <arm_neon.h>

diff --git a/ext/pcre/pcre2lib/pcre2_match.c b/ext/pcre/pcre2lib/pcre2_match.c
index e603cb24992..e36ecd02585 100644
--- a/ext/pcre/pcre2lib/pcre2_match.c
+++ b/ext/pcre/pcre2lib/pcre2_match.c
@@ -2550,7 +2550,7 @@ fprintf(stderr, "++ op=%d\n", *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();
@@ -2903,7 +2903,7 @@ fprintf(stderr, "++ op=%d\n", *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();
@@ -3725,7 +3725,7 @@ fprintf(stderr, "++ op=%d\n", *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();
@@ -4306,7 +4306,7 @@ fprintf(stderr, "++ op=%d\n", *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();
@@ -6327,10 +6327,6 @@ PCRE2_SPTR req_cu_ptr;
 PCRE2_SPTR start_partial;
 PCRE2_SPTR match_partial;

-#ifdef SUPPORT_JIT
-BOOL use_jit;
-#endif
-
 /* This flag is needed even when Unicode is not supported for convenience
 (it is used by the IS_NEWLINE macro). */

@@ -6340,9 +6336,6 @@ BOOL utf = FALSE;
 BOOL ucp = FALSE;
 BOOL allow_invalid;
 uint32_t fragment_options = 0;
-#ifdef SUPPORT_JIT
-BOOL jit_checked_utf = FALSE;
-#endif
 #endif  /* SUPPORT_UNICODE */

 PCRE2_SIZE frame_size;
@@ -6411,15 +6404,6 @@ options |= (re->flags & FF) / ((FF & (~FF+1)) / (OO & (~OO+1)));
 #undef FF
 #undef OO

-/* If the pattern was successfully studied with JIT support, we will run the
-JIT executable instead of the rest of this function. Most options must be set
-at compile time for the JIT code to be usable. */
-
-#ifdef SUPPORT_JIT
-use_jit = (re->executable_jit != NULL &&
-          (options & ~PUBLIC_JIT_MATCH_OPTIONS) == 0);
-#endif
-
 /* Initialize UTF/UCP parameters. */

 #ifdef SUPPORT_UNICODE
@@ -6465,16 +6449,22 @@ match_data->startchar = 0;

 /* ============================= JIT matching ============================== */

-/* Prepare for JIT matching. Check a UTF string for validity unless no check is
-requested or invalid UTF can be handled. We check only the portion of the
-subject that might be be inspected during matching - from the offset minus the
-maximum lookbehind to the given length. This saves time when a small part of a
-large subject is being matched by the use of a starting offset. Note that the
-maximum lookbehind is a number of characters, not code units. */
+/* If the pattern was successfully studied with JIT support, we will run the
+JIT executable instead of the rest of this function. Most options must be set
+at compile time for the JIT code to be usable. */

 #ifdef SUPPORT_JIT
-if (use_jit)
+if (re->executable_jit != NULL &&
+    (options & ~PUBLIC_JIT_MATCH_OPTIONS) == 0 &&
+    PRIV(jit_check_exec)(re->executable_jit, options))
   {
+  /* Prepare for JIT matching. Check a UTF string for validity unless no check
+  is requested or invalid UTF can be handled. We check only the portion of the
+  subject that might be be inspected during matching - from the offset minus
+  the maximum lookbehind to the given length. This saves time when a small part
+  of a large subject is being matched by the use of a starting offset. Note that
+  the maximum lookbehind is a number of characters, not code units. */
+
 #ifdef SUPPORT_UNICODE
   if (utf && (options & PCRE2_NO_UTF_CHECK) == 0 && !allow_invalid)
     {
@@ -6534,16 +6524,12 @@ if (use_jit)
       match_data->startchar += start_match - subject;
       return match_data->rc;
       }
-    jit_checked_utf = TRUE;
     }
 #endif  /* SUPPORT_UNICODE */

-  /* If JIT returns BADOPTION, which means that the selected complete or
-  partial matching mode was not compiled, fall through to the interpreter. */
-
   rc = pcre2_jit_match(code, subject, length, start_offset, options,
     match_data, mcontext);
-  if (rc != PCRE2_ERROR_JIT_BADOPTION)
+
     {
     if (rc >= 0 && (options & PCRE2_COPY_MATCHED_SUBJECT) != 0)
       {
@@ -6568,12 +6554,8 @@ this. */

 mb->check_subject = subject;

-/* If a UTF subject string was not checked for validity in the JIT code above,
-check it here, and handle support for invalid UTF strings. The check above
-happens only when invalid UTF is not supported and PCRE2_NO_CHECK_UTF is unset.
-If we get here in those circumstances, it means the subject string is valid,
-but for some reason JIT matching was not successful. There is no need to check
-the subject again.
+/* Check the validity of UTF subject strings. The check happens only when
+PCRE2_NO_CHECK_UTF is unset.

 We check only the portion of the subject that might be be inspected during
 matching - from the offset minus the maximum lookbehind to the given length.
@@ -6585,11 +6567,7 @@ Note also that support for invalid UTF forces a check, overriding the setting
 of PCRE2_NO_CHECK_UTF. */

 #ifdef SUPPORT_UNICODE
-if (utf &&
-#ifdef SUPPORT_JIT
-    !jit_checked_utf &&
-#endif
-    ((options & PCRE2_NO_UTF_CHECK) == 0 || allow_invalid))
+if (utf && ((options & PCRE2_NO_UTF_CHECK) == 0 || allow_invalid))
   {
 #if PCRE2_CODE_UNIT_WIDTH != 32
   BOOL skipped_bad_start = FALSE;
diff --git a/ext/pcre/pcre2lib/pcre2_substring.c b/ext/pcre/pcre2lib/pcre2_substring.c
index ddf5774e150..a52f542e72b 100644
--- a/ext/pcre/pcre2lib/pcre2_substring.c
+++ b/ext/pcre/pcre2lib/pcre2_substring.c
@@ -214,9 +214,10 @@ PCRE2_SIZE size;
 PCRE2_UCHAR *yield;
 rc = pcre2_substring_length_bynumber(match_data, stringnumber, &size);
 if (rc < 0) return rc;
-yield = PRIV(memctl_malloc)(sizeof(pcre2_memctl) +
-  (size + 1)*PCRE2_CODE_UNIT_WIDTH, (pcre2_memctl *)match_data);
-if (yield == NULL) return PCRE2_ERROR_NOMEMORY;
+if (size > ((PCRE2_SIZE_MAX - sizeof(pcre2_memctl)) / CU2BYTES(1)) - 1 ||
+    (yield = PRIV(memctl_malloc)(sizeof(pcre2_memctl) +
+      CU2BYTES(size + 1), (pcre2_memctl *)match_data)) == NULL)
+  return PCRE2_ERROR_NOMEMORY;
 yield = (PCRE2_UCHAR *)(((char *)yield) + sizeof(pcre2_memctl));
 memcpy(yield, match_data->subject + match_data->ovector[stringnumber*2],
   CU2BYTES(size));