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