From fea6742e39e14e5a9e9f1895676cd88ff24a3dad Mon Sep 17 00:00:00 2001 From: lamentxu <1372449351@qq.com> Date: Sat, 11 Apr 2026 18:41:52 +0800 Subject: [PATCH 1/3] add zend-apis --- Zend/zend_string.c | 32 ++++++++++++++++++++++++++++++++ Zend/zend_string.h | 3 +++ 2 files changed, 35 insertions(+) diff --git a/Zend/zend_string.c b/Zend/zend_string.c index 348f37999efd..68dc42fc466a 100644 --- a/Zend/zend_string.c +++ b/Zend/zend_string.c @@ -52,6 +52,18 @@ ZEND_API zend_string *zend_empty_string = NULL; ZEND_API zend_string *zend_one_char_string[256]; ZEND_API zend_string **zend_known_strings = NULL; +/* this is read-only, so it's ok */ +ZEND_SET_ALIGNED(16, static const char zend_hexconvtab_lower[]) = "0123456789abcdef"; +ZEND_SET_ALIGNED(16, static const char zend_hexconvtab_upper[]) = "0123456789ABCDEF"; + +static zend_always_inline void zend_bin2hex_impl(char *out, const unsigned char *in, size_t in_len, const char *hexconvtab) +{ + for (size_t i = 0; i < in_len; i++) { + out[i * 2] = hexconvtab[in[i] >> 4]; + out[i * 2 + 1] = hexconvtab[in[i] & 0x0f]; + } +} + ZEND_API zend_ulong ZEND_FASTCALL zend_string_hash_func(zend_string *str) { return ZSTR_H(str) = zend_hash_func(ZSTR_VAL(str), ZSTR_LEN(str)); @@ -62,6 +74,26 @@ ZEND_API zend_ulong ZEND_FASTCALL zend_hash_func(const char *str, size_t len) return zend_inline_hash_func(str, len); } +ZEND_API void ZEND_FASTCALL zend_bin2hex(char *out, const unsigned char *in, size_t in_len) +{ + zend_bin2hex_impl(out, in, in_len, zend_hexconvtab_lower); +} + +ZEND_API void ZEND_FASTCALL zend_bin2hex_upper(char *out, const unsigned char *in, size_t in_len) +{ + zend_bin2hex_impl(out, in, in_len, zend_hexconvtab_upper); +} + +ZEND_API zend_string *zend_bin2hex_str(const unsigned char *in, size_t in_len) +{ + zend_string *result = zend_string_safe_alloc(in_len, 2 * sizeof(char), 0, 0); + + zend_bin2hex(ZSTR_VAL(result), in, in_len); + ZSTR_VAL(result)[in_len * 2] = '\0'; + + return result; +} + static void _str_dtor(zval *zv) { zend_string *str = Z_STR_P(zv); diff --git a/Zend/zend_string.h b/Zend/zend_string.h index 69c2edd13b3d..ced322edae1e 100644 --- a/Zend/zend_string.h +++ b/Zend/zend_string.h @@ -38,6 +38,9 @@ ZEND_API extern zend_string_init_existing_interned_func_t zend_string_init_exist ZEND_API zend_ulong ZEND_FASTCALL zend_string_hash_func(zend_string *str); ZEND_API zend_ulong ZEND_FASTCALL zend_hash_func(const char *str, size_t len); ZEND_API zend_string* ZEND_FASTCALL zend_interned_string_find_permanent(zend_string *str); +ZEND_API void ZEND_FASTCALL zend_bin2hex(char *out, const unsigned char *in, size_t in_len); +ZEND_API void ZEND_FASTCALL zend_bin2hex_upper(char *out, const unsigned char *in, size_t in_len); +ZEND_API zend_string *zend_bin2hex_str(const unsigned char *in, size_t in_len); ZEND_API zend_string *zend_string_concat2( const char *str1, size_t str1_len, From e24a9d65c646df8c9eba19a5794625c2e1a31709 Mon Sep 17 00:00:00 2001 From: lamentxu <1372449351@qq.com> Date: Mon, 13 Apr 2026 18:20:39 +0800 Subject: [PATCH 2/3] initial patch --- ext/standard/tests/url/gh21738.phpt | 27 +++++++++++++++++++++++++++ ext/standard/url.c | 8 ++++---- 2 files changed, 31 insertions(+), 4 deletions(-) create mode 100644 ext/standard/tests/url/gh21738.phpt diff --git a/ext/standard/tests/url/gh21738.phpt b/ext/standard/tests/url/gh21738.phpt new file mode 100644 index 000000000000..cb86566015c0 --- /dev/null +++ b/ext/standard/tests/url/gh21738.phpt @@ -0,0 +1,27 @@ +--TEST-- +GH-21738 (Avoid UB when urldecode() inspects non-ASCII bytes) +--FILE-- + +--EXPECT-- +string(6) "258041" +string(6) "258041" +string(6) "254180" +string(6) "254180" +string(6) "25ff41" +string(6) "25ff41" +string(6) "2546ff" +string(6) "2546ff" diff --git a/ext/standard/url.c b/ext/standard/url.c index 4ddf7f80c64f..a82130d5aac4 100644 --- a/ext/standard/url.c +++ b/ext/standard/url.c @@ -591,8 +591,8 @@ PHPAPI size_t php_url_decode_ex(char *dest, const char *src, size_t src_len) if (*data == '+') { *dest = ' '; } - else if (*data == '%' && src_len >= 2 && isxdigit((int) *(data + 1)) - && isxdigit((int) *(data + 2))) { + else if (*data == '%' && src_len >= 2 && isxdigit((unsigned char) *(data + 1)) + && isxdigit((unsigned char) *(data + 2))) { *dest = (char) php_htoi(data + 1); data += 2; src_len -= 2; @@ -664,8 +664,8 @@ PHPAPI size_t php_raw_url_decode_ex(char *dest, const char *src, size_t src_len) const char *data = src; while (src_len--) { - if (*data == '%' && src_len >= 2 && isxdigit((int) *(data + 1)) - && isxdigit((int) *(data + 2))) { + if (*data == '%' && src_len >= 2 && isxdigit((unsigned char) *(data + 1)) + && isxdigit((unsigned char) *(data + 2))) { *dest = (char) php_htoi(data + 1); data += 2; src_len -= 2; From 03205f18745892d158a150c959d50306fa26d455 Mon Sep 17 00:00:00 2001 From: lamentxu <1372449351@qq.com> Date: Mon, 13 Apr 2026 18:22:03 +0800 Subject: [PATCH 3/3] Revert "initial patch" This reverts commit e24a9d65c646df8c9eba19a5794625c2e1a31709. --- ext/standard/tests/url/gh21738.phpt | 27 --------------------------- ext/standard/url.c | 8 ++++---- 2 files changed, 4 insertions(+), 31 deletions(-) delete mode 100644 ext/standard/tests/url/gh21738.phpt diff --git a/ext/standard/tests/url/gh21738.phpt b/ext/standard/tests/url/gh21738.phpt deleted file mode 100644 index cb86566015c0..000000000000 --- a/ext/standard/tests/url/gh21738.phpt +++ /dev/null @@ -1,27 +0,0 @@ ---TEST-- -GH-21738 (Avoid UB when urldecode() inspects non-ASCII bytes) ---FILE-- - ---EXPECT-- -string(6) "258041" -string(6) "258041" -string(6) "254180" -string(6) "254180" -string(6) "25ff41" -string(6) "25ff41" -string(6) "2546ff" -string(6) "2546ff" diff --git a/ext/standard/url.c b/ext/standard/url.c index a82130d5aac4..4ddf7f80c64f 100644 --- a/ext/standard/url.c +++ b/ext/standard/url.c @@ -591,8 +591,8 @@ PHPAPI size_t php_url_decode_ex(char *dest, const char *src, size_t src_len) if (*data == '+') { *dest = ' '; } - else if (*data == '%' && src_len >= 2 && isxdigit((unsigned char) *(data + 1)) - && isxdigit((unsigned char) *(data + 2))) { + else if (*data == '%' && src_len >= 2 && isxdigit((int) *(data + 1)) + && isxdigit((int) *(data + 2))) { *dest = (char) php_htoi(data + 1); data += 2; src_len -= 2; @@ -664,8 +664,8 @@ PHPAPI size_t php_raw_url_decode_ex(char *dest, const char *src, size_t src_len) const char *data = src; while (src_len--) { - if (*data == '%' && src_len >= 2 && isxdigit((unsigned char) *(data + 1)) - && isxdigit((unsigned char) *(data + 2))) { + if (*data == '%' && src_len >= 2 && isxdigit((int) *(data + 1)) + && isxdigit((int) *(data + 2))) { *dest = (char) php_htoi(data + 1); data += 2; src_len -= 2;