Commit 1e4eb1ca98 for perl

commit 1e4eb1ca98a6d1277bad0c0deaf819d4dcdcf239
Author: Paul Evans <leonerd@leonerd.org.uk>
Date:   Fri Oct 23 14:38:46 2026 +0100

    Also permit SVt_PVLV in the SvOOK macros because substr() uses it (fixes #24933)

    The added test is, as far as I can tell, the only test we have of
    substr()-in-substr(), which is why this bug wasn't previously detected
    by core tests.

diff --git a/pod/perldelta.pod b/pod/perldelta.pod
index a55eee3d5c..3478513a26 100644
--- a/pod/perldelta.pod
+++ b/pod/perldelta.pod
@@ -377,6 +377,23 @@ multiple branches of an C<if>/C<elsif>/C<else> chain call the same function.

 L<B::Xref> is able to use line numbers stored within nulled COPs. [GH #7947]

+=item *
+
+Perl 5.44.0 introduced some internal changes that broke uses of C<substr> when
+the string argument it is operating on was itself a special lvalue scalar
+created by another C<substr> call. This would cause debugging assertions or
+invalid C<free()> crashes from code that had a structure such as the following
+examples:
+
+    my $ref = \substr($str, $offset);
+    substr($$ref, 0, 1, "replacement");
+
+Z<>
+
+    substr(substr($str, $offset), 0, 1, "replacement");
+
+This bug has now been fixed. [GH #24933]
+
 =back

 =head1 Known Problems
diff --git a/sv.h b/sv.h
index e49657ae3c..becc080e94 100644
--- a/sv.h
+++ b/sv.h
@@ -992,7 +992,7 @@ Set the size of the string buffer for the SV. See C<L</SvLEN>>.
                                                   SVp_IOK|SVp_NOK|SVf_IVisUV))

 #define assert_scalar_or_IO_(sv) \
-                                assert_((SvTYPE(sv) <= SVt_PVMG) || (SvTYPE(sv) == SVt_PVIO))
+                                assert_(SvTYPE(sv) <= SVt_PVMG || SvTYPE(sv) == SVt_PVLV || SvTYPE(sv) == SVt_PVIO)
 #define assert_not_ROK(sv)	assert_(!SvROK(sv) || !SvRV(sv))
 #define assert_not_glob(sv)	assert_(!isGV_with_GP(sv))

@@ -1118,6 +1118,7 @@ Returns the vstring magic, or NULL if none
  * up using it despite being an SVt_PVIO. So we accept those cases as well.
  */
 #define SvOOK(sv)		((SvTYPE(sv) <= SVt_PVMG ||         /* regular scalars */ \
+                                        SvTYPE(sv) == SVt_PVLV ||   /* PVLV is a regular scalar too */ \
                                         SvTYPE(sv) == SVt_PVHV ||   /* hashes */          \
                                         SvTYPE(sv) == SVt_PVIO) &&  /* IOs */             \
                                     ((SvFLAGS(sv) & (SVf_ROK|SVf_OOK)) == SVf_OOK))
diff --git a/t/op/substr.t b/t/op/substr.t
index c951fe27d6..8003d75aa6 100644
--- a/t/op/substr.t
+++ b/t/op/substr.t
@@ -22,7 +22,8 @@ $SIG{__WARN__} = sub {
      }
 };

-plan(400);
+# We can't use done_testing() at the end because of odd interactions with t/op/substr_thr.t
+plan(401);

 run_tests() unless caller;

@@ -918,4 +919,14 @@ fresh_perl_is('$0 = "/usr/bin/perl"; substr($0, 0, 0, $0)', '', {}, "(perl #1293
     is $bar, boz => 'first arg to 4-arg substr is loose lvalue context';
 }

+# [perl GH24933]
+{
+    # Under this bug, this would cause an assertion failure or double-free().
+    # The mere fact it doesn't crash is sufficient to test for absence of the
+    # bug.
+    my $s = "abcdef";
+    substr(substr($s, 3), 0, 1, "");
+    pass "substr(substr()) does not crash";
+}
+
 1;