From 91a9e246ae06e55b26e0dd631146562e9cd83508 Mon Sep 17 00:00:00 2001 From: Weilin Du Date: Tue, 3 Nov 2026 21:01:40 +0800 Subject: [PATCH 1/2] ext/standard: Decode quoted-printable input after NUL bytes --- NEWS | 1 + ext/standard/quot_print.c | 16 ++++---- .../quoted_printable_decode_null_bytes.phpt | 37 +++++++++++++++++++ 3 files changed, 47 insertions(+), 7 deletions(-) create mode 100644 ext/standard/tests/strings/quoted_printable_decode_null_bytes.phpt diff --git a/NEWS b/NEWS index 67c44a4420e9..959677409407 100644 --- a/NEWS +++ b/NEWS @@ -119,6 +119,7 @@ PHP NEWS sockets. (David Carlier) - Standard: + . Fixed quoted_printable_decode() truncating input at NUL bytes. (Weilin Du) . Fixed bug #60110 (fclose(), file_put_contents(), copy() do not return false properly). (Jakub Zelenka, Ilija Tovilo) . Fixed three Windows-only proc_open() defects: an uninitialized diff --git a/ext/standard/quot_print.c b/ext/standard/quot_print.c index e9d8fafb9987..2c478842b80a 100644 --- a/ext/standard/quot_print.c +++ b/ext/standard/quot_print.c @@ -194,22 +194,24 @@ PHP_FUNCTION(quoted_printable_decode) char *str_in; zend_string *str_out; size_t i = 0, j = 0, k; + size_t len; ZEND_PARSE_PARAMETERS_START(1, 1) Z_PARAM_STR(arg1) ZEND_PARSE_PARAMETERS_END(); - if (ZSTR_LEN(arg1) == 0) { + len = ZSTR_LEN(arg1); + if (len == 0) { /* shortcut */ RETURN_EMPTY_STRING(); } str_in = ZSTR_VAL(arg1); - str_out = zend_string_alloc(ZSTR_LEN(arg1), 0); - while (str_in[i]) { + str_out = zend_string_alloc(len, 0); + while (i < len) { switch (str_in[i]) { case '=': - if (str_in[i + 1] && str_in[i + 2] && + if (len - i > 2 && isxdigit((unsigned char)str_in[i + 1]) && isxdigit((unsigned char)str_in[i + 2])) { @@ -218,15 +220,15 @@ PHP_FUNCTION(quoted_printable_decode) i += 3; } else /* check for soft line break according to RFC 2045*/ { k = 1; - while (str_in[i + k] && ((str_in[i + k] == 32) || (str_in[i + k] == 9))) { + while (k < len - i && ((str_in[i + k] == 32) || (str_in[i + k] == 9))) { /* Possibly, skip spaces/tabs at the end of line */ k++; } - if (!str_in[i + k]) { + if (k == len - i) { /* End of line reached */ i += k; } - else if ((str_in[i + k] == 13) && (str_in[i + k + 1] == 10)) { + else if ((str_in[i + k] == 13) && k + 1 < len - i && (str_in[i + k + 1] == 10)) { /* CRLF */ i += k + 2; } diff --git a/ext/standard/tests/strings/quoted_printable_decode_null_bytes.phpt b/ext/standard/tests/strings/quoted_printable_decode_null_bytes.phpt new file mode 100644 index 000000000000..0f5724bb987d --- /dev/null +++ b/ext/standard/tests/strings/quoted_printable_decode_null_bytes.phpt @@ -0,0 +1,37 @@ +--TEST-- +quoted_printable_decode() processes input after NUL bytes +--FILE-- + +--EXPECT-- +bool(true) +61626300646566 +410042 +410042 +413d0042 +413d200042 +410042 +413d300042 +41 +41 From 445282f151133c50c1c3044940d14ca9d95e956b Mon Sep 17 00:00:00 2001 From: Weilin Du Date: Tue, 3 Nov 2026 21:02:09 +0800 Subject: [PATCH 2/2] ext/standard: Reject NUL bytes in version_compare() arguments --- NEWS | 2 ++ .../version_compare_null_bytes.phpt | 26 +++++++++++++++++++ ext/standard/versioning.c | 4 +-- 3 files changed, 30 insertions(+), 2 deletions(-) create mode 100644 ext/standard/tests/versioning/version_compare_null_bytes.phpt diff --git a/NEWS b/NEWS index 959677409407..61fba5432083 100644 --- a/NEWS +++ b/NEWS @@ -120,6 +120,8 @@ PHP NEWS - Standard: . Fixed quoted_printable_decode() truncating input at NUL bytes. (Weilin Du) + . Fixed version_compare() silently truncating version strings at NUL bytes by + rejecting such input. (Weilin Du) . Fixed bug #60110 (fclose(), file_put_contents(), copy() do not return false properly). (Jakub Zelenka, Ilija Tovilo) . Fixed three Windows-only proc_open() defects: an uninitialized diff --git a/ext/standard/tests/versioning/version_compare_null_bytes.phpt b/ext/standard/tests/versioning/version_compare_null_bytes.phpt new file mode 100644 index 000000000000..3a110f7cace1 --- /dev/null +++ b/ext/standard/tests/versioning/version_compare_null_bytes.phpt @@ -0,0 +1,26 @@ +--TEST-- +version_compare() rejects NUL bytes in version strings +--FILE-- +getMessage(), "\n"; + } +} +var_dump(version_compare('1.0', '1.0')); +var_dump(version_compare('1.0', '1.0', 'eq')); +?> +--EXPECT-- +ValueError: version_compare(): Argument #1 ($version1) must not contain any null bytes +ValueError: version_compare(): Argument #2 ($version2) must not contain any null bytes +ValueError: version_compare(): Argument #1 ($version1) must not contain any null bytes +ValueError: version_compare(): Argument #2 ($version2) must not contain any null bytes +int(0) +bool(true) diff --git a/ext/standard/versioning.c b/ext/standard/versioning.c index dc7ca71af469..f9ae95ddab41 100644 --- a/ext/standard/versioning.c +++ b/ext/standard/versioning.c @@ -215,8 +215,8 @@ PHP_FUNCTION(version_compare) int compare; ZEND_PARSE_PARAMETERS_START(2, 3) - Z_PARAM_STRING(v1, v1_len) - Z_PARAM_STRING(v2, v2_len) + Z_PARAM_PATH(v1, v1_len) + Z_PARAM_PATH(v2, v2_len) Z_PARAM_OPTIONAL Z_PARAM_STR_OR_NULL(op) ZEND_PARSE_PARAMETERS_END();