diff --git a/NEWS b/NEWS index 91e7169f98fd..308ebe4d8f2e 100644 --- a/NEWS +++ b/NEWS @@ -233,6 +233,8 @@ PHP NEWS AF_INET* family only. (David Carlier) . Fixed GH-20532 (socket_addrinfo_lookup gives the error code with a new optional parameter). (David Carlier) + . Added AF_PACKET support completion for socket_sendto()/socket_recvfrom(). + (David Carlier) - Sodium: . Added support for libsodium 1.0.21 IPcrypt and XOF APIs. (jedisct1) diff --git a/UPGRADING.INTERNALS b/UPGRADING.INTERNALS index a467df3b6be5..a8eb6b805413 100644 --- a/UPGRADING.INTERNALS +++ b/UPGRADING.INTERNALS @@ -36,6 +36,8 @@ PHP 8.6 INTERNALS UPGRADE NOTES . The zval_dtor() alias of zval_ptr_dtor_nogc() has been removed. Call zval_ptr_dtor_nogc() directly instead. . The internal zend_copy_parameters_array() function is no longer exposed. + . The internal zend_hash_minmax() function is no longer exposed. Scan the + HashTable directly and use zend_compare() for value comparisons instead. . The zend_make_callable() function has been removed, if a callable zval needs to be obtained use the zend_get_callable_zval_from_fcc() function instead. If this was used to store a callable, then an FCC should be diff --git a/Zend/zend_hash.c b/Zend/zend_hash.c index 2913af9c2b5f..acc342bc267d 100644 --- a/Zend/zend_hash.c +++ b/Zend/zend_hash.c @@ -3238,73 +3238,6 @@ ZEND_API int zend_hash_compare(HashTable *ht1, const HashTable *ht2, compare_fun } -ZEND_API zval* ZEND_FASTCALL zend_hash_minmax(const HashTable *ht, compare_func_t compar, uint32_t flag) -{ - uint32_t idx; - zval *res; - - IS_CONSISTENT(ht); - - if (ht->nNumOfElements == 0 ) { - return NULL; - } - - if (HT_IS_PACKED(ht)) { - zval *zv; - - idx = 0; - while (1) { - if (idx == ht->nNumUsed) { - return NULL; - } - if (Z_TYPE(ht->arPacked[idx]) != IS_UNDEF) break; - idx++; - } - res = ht->arPacked + idx; - for (; idx < ht->nNumUsed; idx++) { - zv = ht->arPacked + idx; - if (UNEXPECTED(Z_TYPE_P(zv) == IS_UNDEF)) continue; - - if (flag) { - if (compar(res, zv) < 0) { /* max */ - res = zv; - } - } else { - if (compar(res, zv) > 0) { /* min */ - res = zv; - } - } - } - } else { - Bucket *p; - - idx = 0; - while (1) { - if (idx == ht->nNumUsed) { - return NULL; - } - if (Z_TYPE(ht->arData[idx].val) != IS_UNDEF) break; - idx++; - } - res = &ht->arData[idx].val; - for (; idx < ht->nNumUsed; idx++) { - p = ht->arData + idx; - if (UNEXPECTED(Z_TYPE(p->val) == IS_UNDEF)) continue; - - if (flag) { - if (compar(res, &p->val) < 0) { /* max */ - res = &p->val; - } - } else { - if (compar(res, &p->val) > 0) { /* min */ - res = &p->val; - } - } - } - } - return res; -} - ZEND_API bool ZEND_FASTCALL _zend_handle_numeric_str_ex(const char *key, size_t length, zend_ulong *idx) { const char *tmp = key; diff --git a/Zend/zend_hash.h b/Zend/zend_hash.h index 6c0d4a241a1b..1181bee29fae 100644 --- a/Zend/zend_hash.h +++ b/Zend/zend_hash.h @@ -303,7 +303,6 @@ typedef int (*bucket_compare_func_t)(Bucket *a, Bucket *b); ZEND_API int zend_hash_compare(HashTable *ht1, const HashTable *ht2, compare_func_t compar, bool ordered); ZEND_API void ZEND_FASTCALL zend_hash_sort_ex(HashTable *ht, sort_func_t sort_func, bucket_compare_func_t compare_func, bool renumber); ZEND_API void ZEND_FASTCALL zend_array_sort_ex(HashTable *ht, sort_func_t sort_func, bucket_compare_func_t compare_func, bool renumber); -ZEND_API zval* ZEND_FASTCALL zend_hash_minmax(const HashTable *ht, compare_func_t compar, uint32_t flag); static zend_always_inline void ZEND_FASTCALL zend_hash_sort(HashTable *ht, bucket_compare_func_t compare_func, bool renumber) { zend_hash_sort_ex(ht, zend_sort, compare_func, renumber); diff --git a/ext/hash/hash.c b/ext/hash/hash.c index 3ba42c8ff85d..e2af98c81ddf 100644 --- a/ext/hash/hash.c +++ b/ext/hash/hash.c @@ -386,7 +386,7 @@ static void php_hash_do_hash( } php_stream_close(stream); if (n < 0) { - efree(context); + php_hash_free_context(ops, context); RETURN_FALSE; } } else { @@ -395,7 +395,7 @@ static void php_hash_do_hash( digest = zend_string_alloc(ops->digest_size, 0); ops->hash_final((unsigned char *) ZSTR_VAL(digest), context); - efree(context); + php_hash_free_context(ops, context); if (raw_output) { ZSTR_VAL(digest)[ops->digest_size] = 0; @@ -534,7 +534,7 @@ static void php_hash_do_hash_hmac( } php_stream_close(stream); if (n < 0) { - efree(context); + php_hash_free_context(ops, context); efree(K); zend_string_efree(digest); RETURN_FALSE; @@ -552,7 +552,7 @@ static void php_hash_do_hash_hmac( /* Zero the key */ ZEND_SECURE_ZERO(K, ops->block_size); efree(K); - efree(context); + php_hash_free_context(ops, context); if (raw_output) { ZSTR_VAL(digest)[ops->digest_size] = 0; @@ -813,7 +813,7 @@ PHP_FUNCTION(hash_final) ZSTR_VAL(digest)[digest_len] = 0; /* Invalidate the object from further use */ - efree(hash->context); + php_hash_free_context(hash->ops, hash->context); hash->context = NULL; if (raw_output) { @@ -967,7 +967,7 @@ PHP_FUNCTION(hash_hkdf) ZEND_SECURE_ZERO(digest, ops->digest_size); ZEND_SECURE_ZERO(prk, ops->digest_size); efree(K); - efree(context); + php_hash_free_context(ops, context); efree(prk); efree(digest); ZSTR_VAL(returnval)[length] = 0; @@ -1083,7 +1083,7 @@ PHP_FUNCTION(hash_pbkdf2) efree(K1); efree(K2); efree(computed_salt); - efree(context); + php_hash_free_context(ops, context); efree(digest); efree(temp); @@ -1348,7 +1348,7 @@ PHP_FUNCTION(mhash_keygen_s2k) RETVAL_STRINGL(key, bytes); ZEND_SECURE_ZERO(key, bytes); efree(digest); - efree(context); + php_hash_free_context(ops, context); efree(key); } } @@ -1378,7 +1378,7 @@ static void php_hashcontext_dtor(zend_object *obj) { php_hashcontext_object *hash = php_hashcontext_from_object(obj); if (hash->context) { - efree(hash->context); + php_hash_free_context(hash->ops, hash->context); hash->context = NULL; } @@ -1414,7 +1414,7 @@ static zend_object *php_hashcontext_clone(zend_object *zobj) { newobj->ops->hash_init(newobj->context, NULL); if (SUCCESS != newobj->ops->hash_copy(newobj->ops, oldobj->context, newobj->context)) { - efree(newobj->context); + php_hash_free_context(newobj->ops, newobj->context); newobj->context = NULL; return znew; } diff --git a/ext/hash/hash_adler32.c b/ext/hash/hash_adler32.c index 2a40b3318cd5..1f5ae3756a0a 100644 --- a/ext/hash/hash_adler32.c +++ b/ext/hash/hash_adler32.c @@ -68,5 +68,6 @@ const php_hash_ops php_hash_adler32_ops = { 4, /* what to say here? */ 4, sizeof(PHP_ADLER32_CTX), + 0, 0 }; diff --git a/ext/hash/hash_crc32.c b/ext/hash/hash_crc32.c index 795cfcdf05b2..407150a1cdd3 100644 --- a/ext/hash/hash_crc32.c +++ b/ext/hash/hash_crc32.c @@ -100,6 +100,7 @@ const php_hash_ops php_hash_crc32_ops = { 4, /* what to say here? */ 4, sizeof(PHP_CRC32_CTX), + 0, 0 }; @@ -115,6 +116,7 @@ const php_hash_ops php_hash_crc32b_ops = { 4, /* what to say here? */ 4, sizeof(PHP_CRC32_CTX), + 0, 0 }; @@ -130,5 +132,6 @@ const php_hash_ops php_hash_crc32c_ops = { 4, /* what to say here? */ 4, sizeof(PHP_CRC32_CTX), + 0, 0 }; diff --git a/ext/hash/hash_fnv.c b/ext/hash/hash_fnv.c index 58101c4f2e6c..7859e5908870 100644 --- a/ext/hash/hash_fnv.c +++ b/ext/hash/hash_fnv.c @@ -30,6 +30,7 @@ const php_hash_ops php_hash_fnv132_ops = { 4, 4, sizeof(PHP_FNV132_CTX), + 0, 0 }; @@ -45,6 +46,7 @@ const php_hash_ops php_hash_fnv1a32_ops = { 4, 4, sizeof(PHP_FNV132_CTX), + 0, 0 }; @@ -60,6 +62,7 @@ const php_hash_ops php_hash_fnv164_ops = { 8, 4, sizeof(PHP_FNV164_CTX), + 0, 0 }; @@ -75,6 +78,7 @@ const php_hash_ops php_hash_fnv1a64_ops = { 8, 4, sizeof(PHP_FNV164_CTX), + 0, 0 }; diff --git a/ext/hash/hash_gost.c b/ext/hash/hash_gost.c index ad87754970b9..91edbd50834f 100644 --- a/ext/hash/hash_gost.c +++ b/ext/hash/hash_gost.c @@ -327,7 +327,8 @@ const php_hash_ops php_hash_gost_ops = { 32, 32, sizeof(PHP_GOST_CTX), - 1 + 1, + 0 }; const php_hash_ops php_hash_gost_crypto_ops = { @@ -342,5 +343,6 @@ const php_hash_ops php_hash_gost_crypto_ops = { 32, 32, sizeof(PHP_GOST_CTX), - 1 + 1, + 0 }; diff --git a/ext/hash/hash_haval.c b/ext/hash/hash_haval.c index 2adafdb189e2..b326142cc7ef 100644 --- a/ext/hash/hash_haval.c +++ b/ext/hash/hash_haval.c @@ -250,7 +250,7 @@ const php_hash_ops php_hash_##p##haval##b##_ops = { \ php_hash_serialize, \ php_hash_unserialize, \ PHP_HAVAL_SPEC, \ - ((b) / 8), 128, sizeof(PHP_HAVAL_CTX), 1 }; \ + ((b) / 8), 128, sizeof(PHP_HAVAL_CTX), 1, 0 }; \ PHP_HASH_API void PHP_##p##HAVAL##b##Init(PHP_HAVAL_CTX *context, ZEND_ATTRIBUTE_UNUSED HashTable *args) \ { int i; context->count[0] = context->count[1] = 0; \ for(i = 0; i < 8; i++) context->state[i] = D0[i]; \ diff --git a/ext/hash/hash_joaat.c b/ext/hash/hash_joaat.c index 0d7b64092f47..0550af18dce7 100644 --- a/ext/hash/hash_joaat.c +++ b/ext/hash/hash_joaat.c @@ -31,6 +31,7 @@ const php_hash_ops php_hash_joaat_ops = { 4, 4, sizeof(PHP_JOAAT_CTX), + 0, 0 }; diff --git a/ext/hash/hash_md.c b/ext/hash/hash_md.c index 0bd48a9c823c..44d43e16777f 100644 --- a/ext/hash/hash_md.c +++ b/ext/hash/hash_md.c @@ -27,7 +27,8 @@ const php_hash_ops php_hash_md5_ops = { 16, 64, sizeof(PHP_MD5_CTX), - 1 + 1, + 0 }; const php_hash_ops php_hash_md4_ops = { @@ -42,7 +43,8 @@ const php_hash_ops php_hash_md4_ops = { 16, 64, sizeof(PHP_MD4_CTX), - 1 + 1, + 0 }; static hash_spec_result php_md2_unserialize(php_hashcontext_object *hash, zend_long magic, const zval *zv); @@ -59,7 +61,8 @@ const php_hash_ops php_hash_md2_ops = { 16, 16, sizeof(PHP_MD2_CTX), - 1 + 1, + 0 }; /* MD common stuff */ diff --git a/ext/hash/hash_murmur.c b/ext/hash/hash_murmur.c index cd5d5f4be520..1e63262c20ed 100644 --- a/ext/hash/hash_murmur.c +++ b/ext/hash/hash_murmur.c @@ -31,6 +31,7 @@ const php_hash_ops php_hash_murmur3a_ops = { 4, 4, sizeof(PHP_MURMUR3A_CTX), + 0, 0 }; @@ -93,6 +94,7 @@ const php_hash_ops php_hash_murmur3c_ops = { 16, 4, sizeof(PHP_MURMUR3C_CTX), + 0, 0 }; @@ -172,6 +174,7 @@ const php_hash_ops php_hash_murmur3f_ops = { 16, 8, sizeof(PHP_MURMUR3F_CTX), + 0, 0 }; diff --git a/ext/hash/hash_ripemd.c b/ext/hash/hash_ripemd.c index 1b76f58ff098..c6396ba2a60a 100644 --- a/ext/hash/hash_ripemd.c +++ b/ext/hash/hash_ripemd.c @@ -31,7 +31,8 @@ const php_hash_ops php_hash_ripemd128_ops = { 16, 64, sizeof(PHP_RIPEMD128_CTX), - 1 + 1, + 0 }; const php_hash_ops php_hash_ripemd160_ops = { @@ -46,7 +47,8 @@ const php_hash_ops php_hash_ripemd160_ops = { 20, 64, sizeof(PHP_RIPEMD160_CTX), - 1 + 1, + 0 }; const php_hash_ops php_hash_ripemd256_ops = { @@ -61,7 +63,8 @@ const php_hash_ops php_hash_ripemd256_ops = { 32, 64, sizeof(PHP_RIPEMD256_CTX), - 1 + 1, + 0 }; const php_hash_ops php_hash_ripemd320_ops = { @@ -76,7 +79,8 @@ const php_hash_ops php_hash_ripemd320_ops = { 40, 64, sizeof(PHP_RIPEMD320_CTX), - 1 + 1, + 0 }; /* {{{ PHP_RIPEMD128Init diff --git a/ext/hash/hash_sha.c b/ext/hash/hash_sha.c index fd4d8ce5a36a..3a7729e31fc0 100644 --- a/ext/hash/hash_sha.c +++ b/ext/hash/hash_sha.c @@ -73,7 +73,8 @@ const php_hash_ops php_hash_sha1_ops = { 20, 64, sizeof(PHP_SHA1_CTX), - 1 + 1, + 0 }; /* sha224/sha256 */ @@ -90,7 +91,8 @@ const php_hash_ops php_hash_sha256_ops = { 32, 64, sizeof(PHP_SHA256_CTX), - 1 + 1, + 0 }; const php_hash_ops php_hash_sha224_ops = { @@ -105,7 +107,8 @@ const php_hash_ops php_hash_sha224_ops = { 28, 64, sizeof(PHP_SHA224_CTX), - 1 + 1, + 0 }; #define ROTR32(b,x) ((x >> b) | (x << (32 - b))) @@ -622,7 +625,8 @@ const php_hash_ops php_hash_sha384_ops = { 48, 128, sizeof(PHP_SHA384_CTX), - 1 + 1, + 0 }; /* {{{ PHP_SHA512InitArgs @@ -801,7 +805,8 @@ const php_hash_ops php_hash_sha512_ops = { 64, 128, sizeof(PHP_SHA512_CTX), - 1 + 1, + 0 }; const php_hash_ops php_hash_sha512_256_ops = { @@ -816,7 +821,8 @@ const php_hash_ops php_hash_sha512_256_ops = { 32, 128, sizeof(PHP_SHA512_CTX), - 1 + 1, + 0 }; const php_hash_ops php_hash_sha512_224_ops = { @@ -831,5 +837,6 @@ const php_hash_ops php_hash_sha512_224_ops = { 28, 128, sizeof(PHP_SHA512_CTX), - 1 + 1, + 0 }; diff --git a/ext/hash/hash_sha3.c b/ext/hash/hash_sha3.c index d82840e81de6..3f5d966820cd 100644 --- a/ext/hash/hash_sha3.c +++ b/ext/hash/hash_sha3.c @@ -249,7 +249,8 @@ const php_hash_ops php_hash_sha3_##bits##_ops = { \ bits >> 3, \ (1600 - (2 * bits)) >> 3, \ sizeof(PHP_SHA3_##bits##_CTX), \ - 1 \ + 1, \ + 0 \ } #else @@ -337,7 +338,8 @@ const php_hash_ops php_hash_sha3_##bits##_ops = { \ bits >> 3, \ (1600 - (2 * bits)) >> 3, \ sizeof(PHP_SHA3_CTX), \ - 1 \ + 1, \ + 0 \ } #endif diff --git a/ext/hash/hash_snefru.c b/ext/hash/hash_snefru.c index 5f08eb088229..3fe16b4398a3 100644 --- a/ext/hash/hash_snefru.c +++ b/ext/hash/hash_snefru.c @@ -212,5 +212,6 @@ const php_hash_ops php_hash_snefru_ops = { 32, 32, sizeof(PHP_SNEFRU_CTX), - 1 + 1, + 0 }; diff --git a/ext/hash/hash_tiger.c b/ext/hash/hash_tiger.c index 1153711d85d4..f9b4dc737581 100644 --- a/ext/hash/hash_tiger.c +++ b/ext/hash/hash_tiger.c @@ -263,7 +263,8 @@ static hash_spec_result php_tiger_unserialize(php_hashcontext_object *hash, zend b/8, \ 64, \ sizeof(PHP_TIGER_CTX), \ - 1 \ + 1, \ + 0 \ } PHP_HASH_TIGER_OPS(3, 128); diff --git a/ext/hash/hash_whirlpool.c b/ext/hash/hash_whirlpool.c index 2a5b220d3a18..9d895efa06e7 100644 --- a/ext/hash/hash_whirlpool.c +++ b/ext/hash/hash_whirlpool.c @@ -455,5 +455,6 @@ const php_hash_ops php_hash_whirlpool_ops = { 64, 64, sizeof(PHP_WHIRLPOOL_CTX), - 1 + 1, + 0 }; diff --git a/ext/hash/hash_xxhash.c b/ext/hash/hash_xxhash.c index b6177905e691..e0f400fc517e 100644 --- a/ext/hash/hash_xxhash.c +++ b/ext/hash/hash_xxhash.c @@ -32,6 +32,7 @@ const php_hash_ops php_hash_xxh32_ops = { 4, 4, sizeof(PHP_XXH32_CTX), + 0, 0 }; @@ -99,6 +100,7 @@ const php_hash_ops php_hash_xxh64_ops = { 8, 8, sizeof(PHP_XXH64_CTX), + 0, 0 }; @@ -150,7 +152,8 @@ const php_hash_ops php_hash_xxh3_64_ops = { 8, 8, sizeof(PHP_XXH3_64_CTX), - 0 + 0, + 64 }; typedef XXH_errorcode (*xxh3_reset_with_secret_func_t)(XXH3_state_t*, const void*, size_t); @@ -255,7 +258,8 @@ const php_hash_ops php_hash_xxh3_128_ops = { 16, 8, sizeof(PHP_XXH3_128_CTX), - 0 + 0, + 64 }; PHP_HASH_API void PHP_XXH3_128_Init(PHP_XXH3_128_CTX *ctx, HashTable *args) diff --git a/ext/hash/php_hash.h b/ext/hash/php_hash.h index 9eac5da78c8d..f89bee8ab010 100644 --- a/ext/hash/php_hash.h +++ b/ext/hash/php_hash.h @@ -58,6 +58,7 @@ typedef struct _php_hash_ops { size_t block_size; size_t context_size; unsigned is_crypto: 1; + size_t context_align; } php_hash_ops; struct _php_hashcontext_object { @@ -161,9 +162,26 @@ PHP_HASH_API hash_spec_result php_hash_unserialize_spec(php_hashcontext_object * static inline void *php_hash_alloc_context(const php_hash_ops *ops) { /* Zero out context memory so serialization doesn't expose internals */ + if (ops->context_align > 0) { + size_t align = ops->context_align; + char *base = ecalloc(1, ops->context_size + align); + size_t offset = align - ((uintptr_t)base & (align - 1)); + char *ptr = base + offset; + ptr[-1] = (char)offset; + return ptr; + } return ecalloc(1, ops->context_size); } +static inline void php_hash_free_context(const php_hash_ops *ops, void *ctx) { + if (ops->context_align > 0) { + unsigned char offset = ((unsigned char *)ctx)[-1]; + efree((char *)ctx - offset); + return; + } + efree(ctx); +} + static inline void php_hash_bin2hex(char *out, const unsigned char *in, size_t in_len) { static const char hexits[17] = "0123456789abcdef"; diff --git a/ext/intl/tests/intlchar_reset_error.phpt b/ext/intl/tests/intlchar_reset_error.phpt new file mode 100644 index 000000000000..45f6d1df2961 --- /dev/null +++ b/ext/intl/tests/intlchar_reset_error.phpt @@ -0,0 +1,19 @@ +--TEST-- +IntlChar methods reset intl error on success +--EXTENSIONS-- +intl +--FILE-- + +--EXPECT-- +bool(false) +bool(true) +bool(true) +bool(true) +bool(true) diff --git a/ext/intl/uchar/uchar.cpp b/ext/intl/uchar/uchar.cpp index 83d5ab15d341..8dc6a58a8287 100644 --- a/ext/intl/uchar/uchar.cpp +++ b/ext/intl/uchar/uchar.cpp @@ -70,6 +70,8 @@ IC_METHOD(chr) { char buffer[5]; int buffer_len = 0; + intl_error_reset(NULL); + if (parse_code_point_param(INTERNAL_FUNCTION_PARAM_PASSTHRU, &cp) == FAILURE) { RETURN_NULL(); } @@ -89,6 +91,8 @@ IC_METHOD(chr) { IC_METHOD(ord) { UChar32 cp; + intl_error_reset(NULL); + if (parse_code_point_param(INTERNAL_FUNCTION_PARAM_PASSTHRU, &cp) == FAILURE) { RETURN_NULL(); } @@ -104,6 +108,8 @@ IC_METHOD(hasBinaryProperty) { zend_string *string_codepoint; zend_long int_codepoint = 0; + intl_error_reset(NULL); + ZEND_PARSE_PARAMETERS_START(2, 2) Z_PARAM_STR_OR_LONG(string_codepoint, int_codepoint) Z_PARAM_LONG(prop) @@ -124,6 +130,8 @@ IC_METHOD(getIntPropertyValue) { zend_string *string_codepoint; zend_long int_codepoint = 0; + intl_error_reset(NULL); + ZEND_PARSE_PARAMETERS_START(2, 2) Z_PARAM_STR_OR_LONG(string_codepoint, int_codepoint) Z_PARAM_LONG(prop) @@ -141,6 +149,8 @@ IC_METHOD(getIntPropertyValue) { IC_METHOD(getIntPropertyMinValue) { zend_long prop; + intl_error_reset(NULL); + ZEND_PARSE_PARAMETERS_START(1, 1) Z_PARAM_LONG(prop) ZEND_PARSE_PARAMETERS_END(); @@ -153,6 +163,8 @@ IC_METHOD(getIntPropertyMinValue) { IC_METHOD(getIntPropertyMaxValue) { zend_long prop; + intl_error_reset(NULL); + ZEND_PARSE_PARAMETERS_START(1, 1) Z_PARAM_LONG(prop) ZEND_PARSE_PARAMETERS_END(); @@ -165,6 +177,8 @@ IC_METHOD(getIntPropertyMaxValue) { IC_METHOD(getNumericValue) { UChar32 cp; + intl_error_reset(NULL); + if (parse_code_point_param(INTERNAL_FUNCTION_PARAM_PASSTHRU, &cp) == FAILURE) { RETURN_NULL(); } @@ -207,6 +221,8 @@ static UBool enumCharType_callback(enumCharType_data *context, IC_METHOD(enumCharTypes) { enumCharType_data context; + intl_error_reset(NULL); + ZEND_PARSE_PARAMETERS_START(1, 1) Z_PARAM_FUNC(context.fci, context.fci_cache) ZEND_PARSE_PARAMETERS_END(); @@ -218,6 +234,8 @@ IC_METHOD(enumCharTypes) { IC_METHOD(getBlockCode) { UChar32 cp; + intl_error_reset(NULL); + if (parse_code_point_param(INTERNAL_FUNCTION_PARAM_PASSTHRU, &cp) == FAILURE) { RETURN_NULL(); } @@ -236,6 +254,8 @@ IC_METHOD(charName) { zend_string *buffer = NULL; int32_t buffer_len; + intl_error_reset(NULL); + ZEND_PARSE_PARAMETERS_START(1, 2) Z_PARAM_STR_OR_LONG(string_codepoint, int_codepoint) Z_PARAM_OPTIONAL @@ -266,6 +286,8 @@ IC_METHOD(charFromName) { UChar32 ret; UErrorCode error = U_ZERO_ERROR; + intl_error_reset(NULL); + ZEND_PARSE_PARAMETERS_START(1, 2) Z_PARAM_STRING(name, name_len) Z_PARAM_OPTIONAL @@ -317,6 +339,7 @@ IC_METHOD(enumCharNames) { zend_long nameChoice = U_UNICODE_CHAR_NAME; UErrorCode error = U_ZERO_ERROR; + intl_error_reset(NULL); ZEND_PARSE_PARAMETERS_START(3, 4) Z_PARAM_STR_OR_LONG(string_start, int_start) @@ -342,6 +365,8 @@ IC_METHOD(getPropertyName) { zend_long nameChoice = U_LONG_PROPERTY_NAME; const char *ret; + intl_error_reset(NULL); + ZEND_PARSE_PARAMETERS_START(1, 2) Z_PARAM_LONG(property) Z_PARAM_OPTIONAL @@ -364,6 +389,8 @@ IC_METHOD(getPropertyEnum) { char *alias; size_t alias_len; + intl_error_reset(NULL); + ZEND_PARSE_PARAMETERS_START(1, 1) Z_PARAM_STRING(alias, alias_len) ZEND_PARSE_PARAMETERS_END(); @@ -377,6 +404,8 @@ IC_METHOD(getPropertyValueName) { zend_long property, value, nameChoice = U_LONG_PROPERTY_NAME; const char *ret; + intl_error_reset(NULL); + ZEND_PARSE_PARAMETERS_START(2, 3) Z_PARAM_LONG(property) Z_PARAM_LONG(value) @@ -401,6 +430,8 @@ IC_METHOD(getPropertyValueEnum) { char *name; size_t name_len; + intl_error_reset(NULL); + ZEND_PARSE_PARAMETERS_START(2, 2) Z_PARAM_LONG(property) Z_PARAM_STRING(name, name_len) @@ -417,6 +448,8 @@ IC_METHOD(foldCase) { zend_string *string_codepoint; zend_long int_codepoint = 0; + intl_error_reset(NULL); + ZEND_PARSE_PARAMETERS_START(1, 2) Z_PARAM_STR_OR_LONG(string_codepoint, int_codepoint) Z_PARAM_OPTIONAL @@ -448,6 +481,8 @@ IC_METHOD(digit) { zend_string *string_codepoint; zend_long int_codepoint = 0; + intl_error_reset(NULL); + ZEND_PARSE_PARAMETERS_START(1, 2) Z_PARAM_STR_OR_LONG(string_codepoint, int_codepoint) Z_PARAM_OPTIONAL @@ -472,6 +507,8 @@ IC_METHOD(digit) { IC_METHOD(forDigit) { zend_long digit, radix = 10; + intl_error_reset(NULL); + ZEND_PARSE_PARAMETERS_START(1, 2) Z_PARAM_LONG(digit) Z_PARAM_OPTIONAL @@ -488,6 +525,8 @@ IC_METHOD(charAge) { UVersionInfo version; int i; + intl_error_reset(NULL); + if (parse_code_point_param(INTERNAL_FUNCTION_PARAM_PASSTHRU, &cp) == FAILURE) { RETURN_NULL(); } @@ -505,6 +544,8 @@ IC_METHOD(getUnicodeVersion) { UVersionInfo version; int i; + intl_error_reset(NULL); + ZEND_PARSE_PARAMETERS_NONE(); u_getUnicodeVersion(version); @@ -523,6 +564,8 @@ IC_METHOD(getFC_NFKC_Closure) { int32_t closure_len; UErrorCode error = U_ZERO_ERROR; + intl_error_reset(NULL); + if (parse_code_point_param(INTERNAL_FUNCTION_PARAM_PASSTHRU, &cp) == FAILURE) { RETURN_NULL(); } @@ -551,6 +594,7 @@ IC_METHOD(getFC_NFKC_Closure) { #define IC_BOOL_METHOD_CHAR(name) \ IC_METHOD(name) { \ UChar32 cp; \ + intl_error_reset(NULL); \ if (parse_code_point_param(INTERNAL_FUNCTION_PARAM_PASSTHRU, &cp) == FAILURE) { \ RETURN_NULL(); \ } \ @@ -591,6 +635,7 @@ IC_BOOL_METHOD_CHAR(isJavaIDPart) #define IC_INT_METHOD_CHAR(name) \ IC_METHOD(name) { \ UChar32 cp; \ + intl_error_reset(NULL); \ if (parse_code_point_param(INTERNAL_FUNCTION_PARAM_PASSTHRU, &cp) == FAILURE) { \ RETURN_NULL(); \ } \ @@ -611,6 +656,7 @@ IC_METHOD(name) { \ UChar32 cp, ret; \ zend_string *string_codepoint; \ zend_long int_codepoint = -1; \ + intl_error_reset(NULL); \ ZEND_PARSE_PARAMETERS_START(1, 1) \ Z_PARAM_STR_OR_LONG(string_codepoint, int_codepoint) \ ZEND_PARSE_PARAMETERS_END(); \ diff --git a/ext/json/json_encoder.c b/ext/json/json_encoder.c index 424315eca7ec..22af1c15a833 100644 --- a/ext/json/json_encoder.c +++ b/ext/json/json_encoder.c @@ -577,6 +577,11 @@ static zend_result php_json_encode_serializable_object(smart_str *buf, zend_obje ZEND_GUARD_PROTECT_RECURSION(guard, JSON); + /* jsonSerialize() may drop the last reference to the object, e.g. by + * nulling a reference that aliases the encoded array slot; keep it alive + * so the recursion guard and the identity check below stay valid. */ + GC_ADDREF(obj); + zend_function *json_serialize_method = zend_hash_str_find_ptr(&ce->function_table, ZEND_STRL("jsonserialize")); ZEND_ASSERT(json_serialize_method != NULL && "This should be guaranteed prior to calling this function"); zend_call_known_function(json_serialize_method, obj, ce, &retval, 0, NULL, NULL); @@ -586,6 +591,7 @@ static zend_result php_json_encode_serializable_object(smart_str *buf, zend_obje smart_str_appendl(buf, "null", 4); } ZEND_GUARD_UNPROTECT_RECURSION(guard, JSON); + OBJ_RELEASE(obj); return FAILURE; } @@ -600,6 +606,7 @@ static zend_result php_json_encode_serializable_object(smart_str *buf, zend_obje } zval_ptr_dtor(&retval); + OBJ_RELEASE(obj); return return_code; } diff --git a/ext/json/tests/gh21024.phpt b/ext/json/tests/gh21024.phpt new file mode 100644 index 000000000000..612c577b2d88 --- /dev/null +++ b/ext/json/tests/gh21024.phpt @@ -0,0 +1,21 @@ +--TEST-- +GH-21024 (UAF in json_encode() when jsonSerialize() frees the object) +--EXTENSIONS-- +json +--FILE-- + 1]; + } +} +$arr = [new Bar]; +$ref = &$arr[0]; +var_dump(json_encode($arr)); +echo "survived\n"; +?> +--EXPECT-- +string(9) "[{"k":1}]" +survived diff --git a/ext/sockets/sockets.c b/ext/sockets/sockets.c index 7b9b903585b8..dbf7f4e6ad4b 100644 --- a/ext/sockets/sockets.c +++ b/ext/sockets/sockets.c @@ -1492,7 +1492,7 @@ PHP_FUNCTION(socket_send) /* {{{ Receives data from a socket, connected or not */ PHP_FUNCTION(socket_recvfrom) { - zval *arg1, *arg2, *arg5, *arg6 = NULL; + zval *zsocket, *zdata, *zaddr, *zport = NULL; php_socket *php_sock; struct sockaddr_un s_un; struct sockaddr_in sin; @@ -1500,36 +1500,69 @@ PHP_FUNCTION(socket_recvfrom) struct sockaddr_in6 sin6; #endif #ifdef AF_PACKET - //struct sockaddr_ll sll; + struct sockaddr_ll sll; #endif char addrbuf[INET6_ADDRSTRLEN]; socklen_t slen; int retval; - zend_long arg3, arg4; + zend_long length, flags; const char *address; zend_string *recv_buf; ZEND_PARSE_PARAMETERS_START(5, 6) - Z_PARAM_OBJECT_OF_CLASS(arg1, socket_ce) - Z_PARAM_ZVAL(arg2) - Z_PARAM_LONG(arg3) - Z_PARAM_LONG(arg4) - Z_PARAM_ZVAL(arg5) + Z_PARAM_OBJECT_OF_CLASS(zsocket, socket_ce) + Z_PARAM_ZVAL(zdata) + Z_PARAM_LONG(length) + Z_PARAM_LONG(flags) + Z_PARAM_ZVAL(zaddr) Z_PARAM_OPTIONAL - Z_PARAM_ZVAL(arg6) + Z_PARAM_ZVAL(zport) ZEND_PARSE_PARAMETERS_END(); - php_sock = Z_SOCKET_P(arg1); + php_sock = Z_SOCKET_P(zsocket); ENSURE_SOCKET_VALID(php_sock); +#ifdef AF_PACKET + /* On packet sockets, restrict flags to a finite safe subset. In + * particular MSG_TRUNC must be excluded: it makes recvfrom() report the + * untruncated frame length, which can exceed the buffer. */ + if (php_sock->type == AF_PACKET) { + const zend_long allowed_flags = 0 +#ifdef MSG_OOB + | MSG_OOB +#endif +#ifdef MSG_PEEK + | MSG_PEEK +#endif +#ifdef MSG_WAITALL + | MSG_WAITALL +#endif +#ifdef MSG_DONTWAIT + | MSG_DONTWAIT +#endif +#ifdef MSG_ERRQUEUE + | MSG_ERRQUEUE +#endif +#ifdef MSG_CMSG_CLOEXEC + | MSG_CMSG_CLOEXEC +#endif + ; + + if (flags & ~allowed_flags) { + zend_argument_value_error(4, "must be a combination of MSG_OOB, MSG_PEEK, MSG_WAITALL, MSG_DONTWAIT, MSG_ERRQUEUE, and MSG_CMSG_CLOEXEC for AF_PACKET sockets"); + RETURN_THROWS(); + } + } +#endif + /* overflow check */ /* Shouldthrow ? */ - if (arg3 <= 0 || arg3 > ZEND_LONG_MAX - 1) { + if (length <= 0 || length > ZEND_LONG_MAX - 1) { RETURN_FALSE; } - recv_buf = zend_string_alloc(arg3 + 1, 0); + recv_buf = zend_string_alloc(length + 1, 0); switch (php_sock->type) { case AF_UNIX: @@ -1537,18 +1570,18 @@ PHP_FUNCTION(socket_recvfrom) memset(&s_un, 0, slen); s_un.sun_family = AF_UNIX; - retval = recvfrom(php_sock->bsd_socket, ZSTR_VAL(recv_buf), arg3, arg4, (struct sockaddr *)&s_un, (socklen_t *)&slen); + retval = recvfrom(php_sock->bsd_socket, ZSTR_VAL(recv_buf), length, flags, (struct sockaddr *)&s_un, (socklen_t *)&slen); if (retval < 0) { PHP_SOCKET_ERROR(php_sock, "Unable to recvfrom", errno); zend_string_efree(recv_buf); RETURN_FALSE; } - ZSTR_LEN(recv_buf) = retval; + ZSTR_LEN(recv_buf) = MIN((size_t)retval, (size_t)length); ZSTR_VAL(recv_buf)[ZSTR_LEN(recv_buf)] = '\0'; - ZEND_TRY_ASSIGN_REF_NEW_STR(arg2, recv_buf); - ZEND_TRY_ASSIGN_REF_STRING(arg5, s_un.sun_path); + ZEND_TRY_ASSIGN_REF_NEW_STR(zdata, recv_buf); + ZEND_TRY_ASSIGN_REF_STRING(zaddr, s_un.sun_path); break; case AF_INET: @@ -1556,7 +1589,7 @@ PHP_FUNCTION(socket_recvfrom) memset(&sin, 0, slen); sin.sin_family = AF_INET; - if (arg6 == NULL) { + if (zport == NULL) { zend_string_efree(recv_buf); zend_throw_exception( zend_ce_argument_count_error, @@ -1565,21 +1598,21 @@ PHP_FUNCTION(socket_recvfrom) RETURN_THROWS(); } - retval = recvfrom(php_sock->bsd_socket, ZSTR_VAL(recv_buf), arg3, arg4, (struct sockaddr *)&sin, (socklen_t *)&slen); + retval = recvfrom(php_sock->bsd_socket, ZSTR_VAL(recv_buf), length, flags, (struct sockaddr *)&sin, (socklen_t *)&slen); if (retval < 0) { PHP_SOCKET_ERROR(php_sock, "Unable to recvfrom", errno); zend_string_efree(recv_buf); RETURN_FALSE; } - ZSTR_LEN(recv_buf) = retval; + ZSTR_LEN(recv_buf) = MIN((size_t)retval, (size_t)length); ZSTR_VAL(recv_buf)[ZSTR_LEN(recv_buf)] = '\0'; address = inet_ntop(AF_INET, &sin.sin_addr, addrbuf, sizeof(addrbuf)); - ZEND_TRY_ASSIGN_REF_NEW_STR(arg2, recv_buf); - ZEND_TRY_ASSIGN_REF_STRING(arg5, address ? address : "0.0.0.0"); - ZEND_TRY_ASSIGN_REF_LONG(arg6, ntohs(sin.sin_port)); + ZEND_TRY_ASSIGN_REF_NEW_STR(zdata, recv_buf); + ZEND_TRY_ASSIGN_REF_STRING(zaddr, address ? address : "0.0.0.0"); + ZEND_TRY_ASSIGN_REF_LONG(zport, ntohs(sin.sin_port)); break; #ifdef HAVE_IPV6 case AF_INET6: @@ -1587,7 +1620,7 @@ PHP_FUNCTION(socket_recvfrom) memset(&sin6, 0, slen); sin6.sin6_family = AF_INET6; - if (arg6 == NULL) { + if (zport == NULL) { zend_string_efree(recv_buf); zend_throw_exception( zend_ce_argument_count_error, @@ -1596,41 +1629,38 @@ PHP_FUNCTION(socket_recvfrom) RETURN_THROWS(); } - retval = recvfrom(php_sock->bsd_socket, ZSTR_VAL(recv_buf), arg3, arg4, (struct sockaddr *)&sin6, (socklen_t *)&slen); + retval = recvfrom(php_sock->bsd_socket, ZSTR_VAL(recv_buf), length, flags, (struct sockaddr *)&sin6, (socklen_t *)&slen); if (retval < 0) { PHP_SOCKET_ERROR(php_sock, "unable to recvfrom", errno); zend_string_efree(recv_buf); RETURN_FALSE; } - ZSTR_LEN(recv_buf) = retval; + ZSTR_LEN(recv_buf) = MIN((size_t)retval, (size_t)length); ZSTR_VAL(recv_buf)[ZSTR_LEN(recv_buf)] = '\0'; inet_ntop(AF_INET6, &sin6.sin6_addr, addrbuf, sizeof(addrbuf)); - ZEND_TRY_ASSIGN_REF_NEW_STR(arg2, recv_buf); - ZEND_TRY_ASSIGN_REF_STRING(arg5, addrbuf[0] ? addrbuf : "::"); - ZEND_TRY_ASSIGN_REF_LONG(arg6, ntohs(sin6.sin6_port)); + ZEND_TRY_ASSIGN_REF_NEW_STR(zdata, recv_buf); + ZEND_TRY_ASSIGN_REF_STRING(zaddr, addrbuf[0] ? addrbuf : "::"); + ZEND_TRY_ASSIGN_REF_LONG(zport, ntohs(sin6.sin6_port)); break; #endif #ifdef AF_PACKET - /* - case AF_PACKET: - // TODO expose and use proper ethernet frame type instead i.e. src mac, dst mac and payload to userland - // ditto for socket_sendto - slen = sizeof(sll); - memset(&sll, 0, sizeof(sll)); - sll.sll_family = AF_PACKET; + case AF_PACKET: { char ifrname[IFNAMSIZ]; - retval = recvfrom(php_sock->bsd_socket, ZSTR_VAL(recv_buf), arg3, arg4, (struct sockaddr *)&sll, (socklen_t *)&slen); + slen = sizeof(sll); + memset(&sll, 0, slen); + + retval = recvfrom(php_sock->bsd_socket, ZSTR_VAL(recv_buf), length, flags, (struct sockaddr *)&sll, (socklen_t *)&slen); if (retval < 0) { PHP_SOCKET_ERROR(php_sock, "unable to recvfrom", errno); zend_string_efree(recv_buf); RETURN_FALSE; } - ZSTR_LEN(recv_buf) = retval; + ZSTR_LEN(recv_buf) = MIN((size_t)retval, (size_t)length); ZSTR_VAL(recv_buf)[ZSTR_LEN(recv_buf)] = '\0'; if (UNEXPECTED(!if_indextoname(sll.sll_ifindex, ifrname))) { @@ -1639,15 +1669,17 @@ PHP_FUNCTION(socket_recvfrom) RETURN_FALSE; } - ZEND_TRY_ASSIGN_REF_NEW_STR(arg2, recv_buf); - ZEND_TRY_ASSIGN_REF_STRING(arg5, ifrname); - ZEND_TRY_ASSIGN_REF_LONG(arg6, sll.sll_ifindex); + ZEND_TRY_ASSIGN_REF_NEW_STR(zdata, recv_buf); + ZEND_TRY_ASSIGN_REF_STRING(zaddr, ifrname); + + if (zport) { + ZEND_TRY_ASSIGN_REF_LONG(zport, sll.sll_ifindex); + } break; - */ + } #endif default: - zend_string_efree(recv_buf); - zend_argument_value_error(1, "must be one of AF_UNIX, AF_INET, or AF_INET6"); + zend_argument_value_error(1, "must be one of AF_UNIX, AF_PACKET, AF_INET, or AF_INET6"); RETURN_THROWS(); } @@ -1658,7 +1690,7 @@ PHP_FUNCTION(socket_recvfrom) /* {{{ Sends a message to a socket, whether it is connected or not */ PHP_FUNCTION(socket_sendto) { - zval *arg1; + zval *zsocket; php_socket *php_sock; struct sockaddr_un s_un; struct sockaddr_in sin; @@ -1666,7 +1698,7 @@ PHP_FUNCTION(socket_sendto) struct sockaddr_in6 sin6; #endif #ifdef AF_PACKET - //struct sockaddr_ll sll; + struct sockaddr_ll sll; #endif int retval; size_t buf_len; @@ -1676,7 +1708,7 @@ PHP_FUNCTION(socket_sendto) zend_string *addr; ZEND_PARSE_PARAMETERS_START(5, 6) - Z_PARAM_OBJECT_OF_CLASS(arg1, socket_ce) + Z_PARAM_OBJECT_OF_CLASS(zsocket, socket_ce) Z_PARAM_STRING(buf, buf_len) Z_PARAM_LONG(len) Z_PARAM_LONG(flags) @@ -1685,14 +1717,19 @@ PHP_FUNCTION(socket_sendto) Z_PARAM_LONG_OR_NULL(port, port_is_null) ZEND_PARSE_PARAMETERS_END(); - php_sock = Z_SOCKET_P(arg1); + php_sock = Z_SOCKET_P(zsocket); ENSURE_SOCKET_VALID(php_sock); - if (port < 0 || port > USHRT_MAX) { - zend_argument_value_error(6, "must be between 0 and %u", USHRT_MAX); - RETURN_THROWS(); +#ifdef AF_PACKET + if (php_sock->type != AF_PACKET) { +#endif + if (port < 0 || port > USHRT_MAX) { + zend_argument_value_error(6, "must be between 0 and %u", USHRT_MAX); + RETURN_THROWS(); + } +#ifdef AF_PACKET } - +#endif if (len < 0) { zend_argument_value_error(3, "must be greater than or equal to 0"); @@ -1749,7 +1786,6 @@ PHP_FUNCTION(socket_sendto) break; #endif #ifdef AF_PACKET - /* case AF_PACKET: if (port_is_null) { zend_argument_value_error(6, "cannot be null when the socket type is AF_PACKET"); @@ -1758,14 +1794,13 @@ PHP_FUNCTION(socket_sendto) memset(&sll, 0, sizeof(sll)); sll.sll_family = AF_PACKET; - sll.sll_ifindex = port; + sll.sll_ifindex = (int)port; - retval = sendto(php_sock->bsd_socket, buf, ((size_t)len > buf_len) ? buf_len : (size_t)len, flags, (struct sockaddr *) &sin, sizeof(sin)); + retval = sendto(php_sock->bsd_socket, buf, ((size_t)len > buf_len) ? buf_len : (size_t)len, flags, (struct sockaddr *)&sll, sizeof(sll)); break; - */ #endif default: - zend_argument_value_error(1, "must be one of AF_UNIX, AF_INET, or AF_INET6"); + zend_argument_value_error(1, "must be one of AF_UNIX, AF_PACKET, AF_INET, or AF_INET6"); RETURN_THROWS(); } diff --git a/ext/sockets/tests/socket_recvfrom_afpacket_no_port.phpt b/ext/sockets/tests/socket_recvfrom_afpacket_no_port.phpt new file mode 100644 index 000000000000..a66398c3a0e1 --- /dev/null +++ b/ext/sockets/tests/socket_recvfrom_afpacket_no_port.phpt @@ -0,0 +1,44 @@ +--TEST-- +AF_PACKET socket_recvfrom() without optional port argument +--EXTENSIONS-- +sockets +posix +--SKIPIF-- + +--FILE-- += 60); +var_dump($addr === 'lo'); + +socket_close($s_send); +socket_close($s_recv); +?> +--EXPECT-- +bool(true) +bool(true) diff --git a/ext/sockets/tests/socket_sendto_recvfrom_afpacket.phpt b/ext/sockets/tests/socket_sendto_recvfrom_afpacket.phpt new file mode 100644 index 000000000000..f2857671b94d --- /dev/null +++ b/ext/sockets/tests/socket_sendto_recvfrom_afpacket.phpt @@ -0,0 +1,126 @@ +--TEST-- +Test if socket_recvfrom() receives raw data sent by socket_sendto() via AF_PACKET +--EXTENSIONS-- +sockets +posix +--SKIPIF-- + +--FILE-- += 60); + +$buf = recv_matching($s_recv, $header, 65536, $addr); +var_dump($buf !== false && strlen($buf) >= 60); +var_dump(is_string($buf)); +var_dump($addr === 'lo'); +var_dump(is_string($buf) && str_contains($buf, "ETH_P_ALL test")); + +socket_close($s_send); +socket_close($s_recv); + +echo "--- ETH_P_LOOP send and receive ---\n"; +$s_send = socket_create(AF_PACKET, SOCK_RAW, ETH_P_ALL); +$s_recv = socket_create(AF_PACKET, SOCK_RAW, ETH_P_ALL); +socket_bind($s_send, 'lo'); +socket_bind($s_recv, 'lo'); + +$header = $dst_mac . $src_mac . pack("n", ETH_P_LOOP); +$frame = build_frame($dst_mac, $src_mac, ETH_P_LOOP, "loopback payload"); +$sent = socket_sendto($s_send, $frame, strlen($frame), 0, "lo", 1); +var_dump($sent >= 60); + +$buf = recv_matching($s_recv, $header, 65536, $addr); +var_dump($buf !== false && strlen($buf) >= 60); +// Verify ETH_P_LOOP ethertype at offset 12-13. +var_dump(is_string($buf) && unpack("n", $buf, 12)[1] === ETH_P_LOOP); +var_dump(is_string($buf) && str_contains($buf, "loopback payload")); + +socket_close($s_send); +socket_close($s_recv); + +echo "--- Large payload ---\n"; +$s_send = socket_create(AF_PACKET, SOCK_RAW, ETH_P_ALL); +$s_recv = socket_create(AF_PACKET, SOCK_RAW, ETH_P_ALL); +socket_bind($s_send, 'lo'); +socket_bind($s_recv, 'lo'); + +$header = $dst_mac . $src_mac . pack("n", 0x9000); +$payload = random_bytes(1024); +$frame = build_frame($dst_mac, $src_mac, 0x9000, $payload); +$sent = socket_sendto($s_send, $frame, strlen($frame), 0, "lo", 1); +var_dump($sent >= strlen($frame)); + +$buf = recv_matching($s_recv, $header, 65536, $addr, $port); +var_dump($buf !== false && strlen($buf) >= strlen($frame)); +var_dump(is_int($port)); +// Verify the payload is intact in the raw buffer. +var_dump(is_string($buf) && str_contains($buf, $payload)); + +socket_close($s_send); +socket_close($s_recv); +?> +--EXPECT-- +--- ETH_P_ALL send and receive --- +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +--- ETH_P_LOOP send and receive --- +bool(true) +bool(true) +bool(true) +bool(true) +--- Large payload --- +bool(true) +bool(true) +bool(true) +bool(true) diff --git a/ext/sockets/tests/socket_sendto_recvfrom_afpacket_errors.phpt b/ext/sockets/tests/socket_sendto_recvfrom_afpacket_errors.phpt new file mode 100644 index 000000000000..775a1381506c --- /dev/null +++ b/ext/sockets/tests/socket_sendto_recvfrom_afpacket_errors.phpt @@ -0,0 +1,69 @@ +--TEST-- +AF_PACKET socket_sendto() and socket_recvfrom() error cases +--EXTENSIONS-- +sockets +posix +--SKIPIF-- + +--FILE-- +getMessage(), PHP_EOL; +} +socket_close($s); + +echo "--- sendto with invalid interface name ---\n"; +$s = socket_create(AF_PACKET, SOCK_RAW, ETH_P_ALL); +socket_bind($s, 'lo'); + +$ret = @socket_sendto($s, str_repeat("\x00", 60), 60, 0, "lo", 999999); +var_dump($ret === false); +socket_close($s); + +echo "--- recvfrom on non-blocking socket with no data ---\n"; +$s = socket_create(AF_PACKET, SOCK_RAW, ETH_P_ALL); +socket_bind($s, 'lo'); +socket_set_nonblock($s); + +$ret = @socket_recvfrom($s, $buf, 65536, 0, $addr); +var_dump($ret === false); +socket_close($s); + +echo "--- recvfrom with MSG_TRUNC is rejected ---\n"; +$s = socket_create(AF_PACKET, SOCK_RAW, ETH_P_ALL); +socket_bind($s, 'lo'); + +try { + socket_recvfrom($s, $buf, 65536, MSG_TRUNC, $addr); +} catch (ValueError $e) { + echo $e->getMessage(), PHP_EOL; +} +socket_close($s); + +?> +--EXPECT-- +--- sendto without port (ifindex) --- +socket_sendto(): Argument #6 ($port) cannot be null when the socket type is AF_PACKET +--- sendto with invalid interface name --- +bool(true) +--- recvfrom on non-blocking socket with no data --- +bool(true) +--- recvfrom with MSG_TRUNC is rejected --- +socket_recvfrom(): Argument #4 ($flags) must be a combination of MSG_OOB, MSG_PEEK, MSG_WAITALL, MSG_DONTWAIT, MSG_ERRQUEUE, and MSG_CMSG_CLOEXEC for AF_PACKET sockets diff --git a/ext/sockets/tests/socket_sendto_recvfrom_afpacket_malformed.phpt b/ext/sockets/tests/socket_sendto_recvfrom_afpacket_malformed.phpt new file mode 100644 index 000000000000..68af685cf0bf --- /dev/null +++ b/ext/sockets/tests/socket_sendto_recvfrom_afpacket_malformed.phpt @@ -0,0 +1,225 @@ +--TEST-- +AF_PACKET socket_sendto/socket_recvfrom with malformed and edge-case frames +--EXTENSIONS-- +sockets +posix +--SKIPIF-- + +--FILE-- += 60); +// The raw buffer is just padding after the header. +var_dump(is_string($buf) && strlen($buf) >= 60); + +socket_close($s_send); +socket_close($s_recv); + +echo "--- Bogus ethertype ---\n"; +$s_send = socket_create(AF_PACKET, SOCK_RAW, ETH_P_ALL); +$s_recv = socket_create(AF_PACKET, SOCK_RAW, ETH_P_ALL); +socket_bind($s_send, 'lo'); +socket_bind($s_recv, 'lo'); + +// Use a made-up ethertype (0xBEEF). Kernel delivers it fine on loopback. +$header = $dst_mac . $src_mac . pack("n", 0xBEEF); +$frame = str_pad($header . "bogus", 60, "\x00"); +$sent = socket_sendto($s_send, $frame, strlen($frame), 0, "lo", 1); +var_dump($sent === 60); + +$buf = recv_matching($s_recv, $header, 65536, $addr); +var_dump($buf !== false && strlen($buf) >= 60); +// Ethertype bytes should be in the raw buffer at offset 12-13. +var_dump(is_string($buf) && unpack("n", $buf, 12)[1] === 0xBEEF); +var_dump(is_string($buf) && str_contains($buf, "bogus")); + +socket_close($s_send); +socket_close($s_recv); + +echo "--- Garbage payload with custom ethertype ---\n"; +$s_send = socket_create(AF_PACKET, SOCK_RAW, ETH_P_ALL); +$s_recv = socket_create(AF_PACKET, SOCK_RAW, ETH_P_ALL); +socket_bind($s_send, 'lo'); +socket_bind($s_recv, 'lo'); + +// Use a non-standard ethertype (0x88B5, reserved for local experimental use) +// with garbage payload. Avoids kernel IP/IPv6 stack interception. +$header = $dst_mac . $src_mac . pack("n", 0x88B5); +$frame = str_pad($header . "\xDE\xAD\xBE\xEF", 60, "\x00"); +$sent = socket_sendto($s_send, $frame, strlen($frame), 0, "lo", 1); +var_dump($sent === 60); + +$buf = recv_matching($s_recv, $header, 65536, $addr); +var_dump($buf !== false && strlen($buf) >= 60); +// Raw buffer is delivered as-is — PHP doesn't parse, so no crash. +var_dump(is_string($buf) && str_contains($buf, "\xDE\xAD\xBE\xEF")); + +socket_close($s_send); +socket_close($s_recv); + +echo "--- Another garbage payload with experimental ethertype ---\n"; +$s_send = socket_create(AF_PACKET, SOCK_RAW, ETH_P_ALL); +$s_recv = socket_create(AF_PACKET, SOCK_RAW, ETH_P_ALL); +socket_bind($s_send, 'lo'); +socket_bind($s_recv, 'lo'); + +// Use 0x88B6, another local experimental ethertype. +$header = $dst_mac . $src_mac . pack("n", 0x88B6); +$frame = str_pad($header . "\xCA\xFE", 60, "\x00"); +$sent = socket_sendto($s_send, $frame, strlen($frame), 0, "lo", 1); +var_dump($sent === 60); + +$buf = recv_matching($s_recv, $header, 65536, $addr); +var_dump($buf !== false && strlen($buf) >= 60); +// Delivered raw — no parsing, no crash. +var_dump(is_string($buf) && str_contains($buf, "\xCA\xFE")); + +socket_close($s_send); +socket_close($s_recv); + +echo "--- Invalid ethertype 0x0000 ---\n"; +$s_send = socket_create(AF_PACKET, SOCK_RAW, ETH_P_ALL); +$s_recv = socket_create(AF_PACKET, SOCK_RAW, ETH_P_ALL); +socket_bind($s_send, 'lo'); +socket_bind($s_recv, 'lo'); + +$header = $dst_mac . $src_mac . pack("n", 0x0000); +$frame = str_pad($header . "zerotype", 60, "\x00"); +$sent = socket_sendto($s_send, $frame, strlen($frame), 0, "lo", 1); +var_dump($sent === 60); + +$buf = recv_matching($s_recv, $header, 65536, $addr); +var_dump($buf !== false && strlen($buf) >= 60); +var_dump(is_string($buf) && unpack("n", $buf, 12)[1] === 0x0000); +var_dump(is_string($buf) && str_contains($buf, "zerotype")); + +socket_close($s_send); +socket_close($s_recv); + +echo "--- Invalid ethertype 0xFFFF ---\n"; +$s_send = socket_create(AF_PACKET, SOCK_RAW, ETH_P_ALL); +$s_recv = socket_create(AF_PACKET, SOCK_RAW, ETH_P_ALL); +socket_bind($s_send, 'lo'); +socket_bind($s_recv, 'lo'); + +$header = $dst_mac . $src_mac . pack("n", 0xFFFF); +$frame = str_pad($header . "maxtype", 60, "\x00"); +$sent = socket_sendto($s_send, $frame, strlen($frame), 0, "lo", 1); +var_dump($sent === 60); + +$buf = recv_matching($s_recv, $header, 65536, $addr); +var_dump($buf !== false && strlen($buf) >= 60); +var_dump(is_string($buf) && unpack("n", $buf, 12)[1] === 0xFFFF); +var_dump(is_string($buf) && str_contains($buf, "maxtype")); + +socket_close($s_send); +socket_close($s_recv); + +echo "--- Small receive buffer (truncation) ---\n"; +$s_send = socket_create(AF_PACKET, SOCK_RAW, ETH_P_ALL); +$s_recv = socket_create(AF_PACKET, SOCK_RAW, ETH_P_ALL); +socket_bind($s_send, 'lo'); +socket_bind($s_recv, 'lo'); + +$header = $dst_mac . $src_mac . pack("n", 0x9000); +$payload = str_repeat("X", 200); +$frame = str_pad($header . $payload, 214, "\x00"); +$sent = socket_sendto($s_send, $frame, strlen($frame), 0, "lo", 1); +var_dump($sent === 214); + +// Request only 30 bytes — less than the frame. Kernel truncates. +$buf = recv_matching($s_recv, $header, 30, $addr); +var_dump($buf !== false && strlen($buf) === 30); +var_dump(is_string($buf) && strlen($buf) === 30); + +socket_close($s_send); +socket_close($s_recv); +?> +--EXPECT-- +--- Undersized frame (below 14-byte ethernet header) --- +bool(true) +--- Zero-length payload (header only, padded to 60) --- +bool(true) +bool(true) +bool(true) +--- Bogus ethertype --- +bool(true) +bool(true) +bool(true) +bool(true) +--- Garbage payload with custom ethertype --- +bool(true) +bool(true) +bool(true) +--- Another garbage payload with experimental ethertype --- +bool(true) +bool(true) +bool(true) +--- Invalid ethertype 0x0000 --- +bool(true) +bool(true) +bool(true) +bool(true) +--- Invalid ethertype 0xFFFF --- +bool(true) +bool(true) +bool(true) +bool(true) +--- Small receive buffer (truncation) --- +bool(true) +bool(true) +bool(true) diff --git a/ext/standard/array.c b/ext/standard/array.c index 3b17ca3f7942..a233e6c4dcb5 100644 --- a/ext/standard/array.c +++ b/ext/standard/array.c @@ -1089,9 +1089,23 @@ PHP_FUNCTION(key) } /* }}} */ -static int php_data_compare(const void *f, const void *s) /* {{{ */ +static zval *php_array_data_minmax(HashTable *array, bool max) /* {{{ */ { - return zend_compare((zval*)f, (zval*)s); + zval *entry, *result = NULL; + + ZEND_HASH_FOREACH_VAL(array, entry) { + if (!result) { + result = entry; + continue; + } + + int cmp = zend_compare(result, entry); + if (max ? cmp < 0 : cmp > 0) { + result = entry; + } + } ZEND_HASH_FOREACH_END(); + + return result; } /* }}} */ @@ -1114,7 +1128,7 @@ PHP_FUNCTION(min) zend_wrong_parameter_type_error(1, Z_EXPECTED_ARRAY, &args[0]); RETURN_THROWS(); } else { - zval *result = zend_hash_minmax(Z_ARRVAL(args[0]), php_data_compare, 0); + zval *result = php_array_data_minmax(Z_ARRVAL(args[0]), false); if (result) { RETURN_COPY_DEREF(result); } else { @@ -1242,7 +1256,7 @@ PHP_FUNCTION(max) zend_wrong_parameter_type_error(1, Z_EXPECTED_ARRAY, &args[0]); RETURN_THROWS(); } else { - zval *result = zend_hash_minmax(Z_ARRVAL(args[0]), php_data_compare, 1); + zval *result = php_array_data_minmax(Z_ARRVAL(args[0]), true); if (result) { RETURN_COPY_DEREF(result); } else {