Commit 9caa0a7d501 for php.net

commit 9caa0a7d501710d009db4778091831df6aa1b1ff
Author: Jorg Sowa <jorg.sowa@gmail.com>
Date:   Sun Mar 22 18:52:31 2026 +0100

    Fix bc_str2num accepting strings with embedded null bytes

    Closes GH-21492

diff --git a/NEWS b/NEWS
index 55b0d28000b..e2df869dfbc 100644
--- a/NEWS
+++ b/NEWS
@@ -17,6 +17,9 @@ PHP                                                                        NEWS
     ReflectionProperty::skipLazyInitialization after failed LazyProxy
     initialization). (Arnaud)

+- BCMath:
+  . Added NUL-byte validation to BCMath functions. (jorgsowa)
+
 - Date:
   . Update timelib to 2022.16. (Derick)

diff --git a/ext/bcmath/libbcmath/src/str2num.c b/ext/bcmath/libbcmath/src/str2num.c
index 11d71ae492a..b6f3d736f8d 100644
--- a/ext/bcmath/libbcmath/src/str2num.c
+++ b/ext/bcmath/libbcmath/src/str2num.c
@@ -131,7 +131,7 @@ bool bc_str2num(bc_num *num, const char *str, const char *end, size_t scale, siz
 	const char *decimal_point = (*ptr == '.') ? ptr : NULL;

 	/* If a non-digit and non-decimal-point indicator is in the string, i.e. an invalid character */
-	if (UNEXPECTED(!decimal_point && *ptr != '\0')) {
+	if (UNEXPECTED(!decimal_point && ptr != end)) {
 		goto fail;
 	}

@@ -140,7 +140,7 @@ bool bc_str2num(bc_num *num, const char *str, const char *end, size_t scale, siz
 		/* search */
 		fractional_ptr = fractional_end = decimal_point + 1;
 		/* For strings that end with a decimal point, such as "012." */
-		if (UNEXPECTED(*fractional_ptr == '\0')) {
+		if (UNEXPECTED(fractional_ptr == end)) {
 			if (full_scale) {
 				*full_scale = 0;
 			}
@@ -149,7 +149,7 @@ bool bc_str2num(bc_num *num, const char *str, const char *end, size_t scale, siz

 		/* validate */
 		fractional_end = bc_count_digits(fractional_ptr, end);
-		if (UNEXPECTED(*fractional_end != '\0')) {
+		if (UNEXPECTED(fractional_end != end)) {
 			/* invalid num */
 			goto fail;
 		}
diff --git a/ext/bcmath/tests/sec_embedded_null_truncation.phpt b/ext/bcmath/tests/sec_embedded_null_truncation.phpt
new file mode 100644
index 00000000000..157810ecbb3
--- /dev/null
+++ b/ext/bcmath/tests/sec_embedded_null_truncation.phpt
@@ -0,0 +1,59 @@
+--TEST--
+bcmath strings with embedded null bytes must be rejected as not well-formed
+--EXTENSIONS--
+bcmath
+--FILE--
+<?php
+$cases = [
+    "100\x005",
+    "1.2\x003",
+    "\x00123",
+];
+
+$calls = [
+    fn($s) => bcadd($s, "0"),
+    fn($s) => bcsub($s, "0"),
+    fn($s) => bcmul($s, "1"),
+    fn($s) => bcdiv($s, "1"),
+    fn($s) => bcmod($s, "7"),
+    fn($s) => bcpow($s, "1"),
+    fn($s) => bccomp($s, "0"),
+    fn($s) => bcsqrt($s),
+];
+
+foreach ($cases as $s) {
+    foreach ($calls as $fn) {
+        try {
+            $fn($s);
+            echo "FAIL: accepted\n";
+        } catch (\ValueError $e) {
+            echo $e->getMessage() . "\n";
+        }
+    }
+}
+?>
+--EXPECT--
+bcadd(): Argument #1 ($num1) is not well-formed
+bcsub(): Argument #1 ($num1) is not well-formed
+bcmul(): Argument #1 ($num1) is not well-formed
+bcdiv(): Argument #1 ($num1) is not well-formed
+bcmod(): Argument #1 ($num1) is not well-formed
+bcpow(): Argument #1 ($num) is not well-formed
+bccomp(): Argument #1 ($num1) is not well-formed
+bcsqrt(): Argument #1 ($num) is not well-formed
+bcadd(): Argument #1 ($num1) is not well-formed
+bcsub(): Argument #1 ($num1) is not well-formed
+bcmul(): Argument #1 ($num1) is not well-formed
+bcdiv(): Argument #1 ($num1) is not well-formed
+bcmod(): Argument #1 ($num1) is not well-formed
+bcpow(): Argument #1 ($num) is not well-formed
+bccomp(): Argument #1 ($num1) is not well-formed
+bcsqrt(): Argument #1 ($num) is not well-formed
+bcadd(): Argument #1 ($num1) is not well-formed
+bcsub(): Argument #1 ($num1) is not well-formed
+bcmul(): Argument #1 ($num1) is not well-formed
+bcdiv(): Argument #1 ($num1) is not well-formed
+bcmod(): Argument #1 ($num1) is not well-formed
+bcpow(): Argument #1 ($num) is not well-formed
+bccomp(): Argument #1 ($num1) is not well-formed
+bcsqrt(): Argument #1 ($num) is not well-formed