From 84a2e48050f6c6dc2f7eac61c861ff9ad2da0506 Mon Sep 17 00:00:00 2001 From: Ilija Tovilo Date: Mon, 3 Jul 2023 14:59:13 +0200 Subject: [PATCH 1/7] Fix use-of-uninitialized-value with ??= on assert Normally, PHP evaluates all expressions in offsets (property or array), as well as the right hand side of assignments before actually fetching the offsets. This is well explained in this blog post. https://www.npopov.com/2017/04/14/PHP-7-Virtual-machine.html#writes-and-memory-safety For ??= we have a bit of a problem in that the rhs must only be evaluated if the lhs is null or undefined. Thus, we have to first compile the lhs with BP_VAR_IS, conditionally run the rhs and then re-fetch the lhs with BP_VAR_W to to make sure the offsets are valid if they have been invalidated. However, we don't want to just re-evaluate the entire lhs because it may contain side-effects, as in $array[$x++] ??= 42;. In this case, we don't want to re-evaluate $x++ because it would result in writing to a different offset than was previously tested. The same goes for function calls, like $array[foo()] ??= 42;, where the second call to foo() might result in a different value. PHP behaves correctly in these cases. This is implemented by memoizing sub-expressions in the lhs of ??= and reusing them when compiling the lhs for the second time. This is done for any expression that isn't a variable, i.e. anything that can (potentially) be written to. Unfortunately, this also means that function calls are considered writable due to their return-by-reference semantics, and will thus not be memoized. The expression foo()['bar'] ??= 42; will invoke foo() twice. Even worse, foo(bar()) ??= 42; will call both foo() and bar() twice, but foo(bar() + 1) ??= 42; will only call foo() twice. This is likely not by design, and was just overlooked in the implementation. The RFC does not specify how function calls in the lhs of the coalesce assignment behaves. This should probably be improved in the future. Now, the problem this commit actually fixes is that ??= may memoize expressions inside assert() function calls that may not actually execute. This is not only an issue when using the VAR in the second expression (which would usually also be skipped) but also when freeing the VAR. For this reason, it is not safe to memoize assert() sub-expressions. There are two possible solutions: 1. Don't memoize any sub-expressions of assert(), meaning they will execute twice. 2. Throw a compile error. Option 2 is not quite simple, because we can't disallow all memoization inside assert(), as that would break assertions like assert($array[foo()] ??= 'bar');. Code like this is highly unlikely (and dubious) but possible. In this case, we would need to make sure that a memoized value could not be used across the assert boundary it was created in. The complexity for this is not worthwhile. So we opt for option 1 and disable memoization immediately inside assert(). Fixes GH-11580 Closes GH-11581 --- NEWS | 1 + Zend/tests/gh11580.phpt | 13 +++++++++++++ Zend/zend_compile.c | 6 ++++++ 3 files changed, 20 insertions(+) create mode 100644 Zend/tests/gh11580.phpt diff --git a/NEWS b/NEWS index ab0e1f210dd2e..292ab3b4d166a 100644 --- a/NEWS +++ b/NEWS @@ -11,6 +11,7 @@ PHP NEWS - Core: . Fixed oss-fuzz #60011 (Mis-compilation of by-reference nullsafe operator). (ilutov) + . Fixed use-of-uninitialized-value with ??= on assert. (ilutov) - Date: . Fixed bug GH-11368 (Date modify returns invalid datetime). (Derick) diff --git a/Zend/tests/gh11580.phpt b/Zend/tests/gh11580.phpt new file mode 100644 index 0000000000000..194c8b5a1aa0e --- /dev/null +++ b/Zend/tests/gh11580.phpt @@ -0,0 +1,13 @@ +--TEST-- +GH-11580: assert() with ??= operator can lead to use-of-uninitialized-value +--INI-- +zend.assertions=0 +--FILE-- + +--EXPECTF-- +Fatal error: Uncaught Error: Undefined constant "y" in %s:%d +Stack trace: +#0 {main} + thrown in %s on line %d diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 59e4d369595ae..d470385cc98e0 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -4083,6 +4083,10 @@ static void zend_compile_assert(znode *result, zend_ast_list *args, zend_string zend_op *opline; uint32_t check_op_number = get_next_op_number(); + /* Assert expression may not be memoized and reused as it may not actually be evaluated. */ + int orig_memoize_mode = CG(memoize_mode); + CG(memoize_mode) = ZEND_MEMOIZE_NONE; + zend_emit_op(NULL, ZEND_ASSERT_CHECK, NULL, NULL); if (fbc && fbc_is_finalized(fbc)) { @@ -4116,6 +4120,8 @@ static void zend_compile_assert(znode *result, zend_ast_list *args, zend_string opline = &CG(active_op_array)->opcodes[check_op_number]; opline->op2.opline_num = get_next_op_number(); SET_NODE(opline->result, result); + + CG(memoize_mode) = orig_memoize_mode; } else { if (!fbc) { zend_string_release_ex(name, 0); From 849fdcae7db9ffdab28ca7d6b32f4942e1dbe3f7 Mon Sep 17 00:00:00 2001 From: Ilija Tovilo Date: Fri, 26 May 2023 12:08:28 +0200 Subject: [PATCH 2/7] Implement flaky test section This re-uses the already used for automatic retesting. That's certainly better than XFAIL. Closes GH-11325 --- ext/standard/tests/hrtime/hrtime.phpt | 2 +- run-tests.php | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/ext/standard/tests/hrtime/hrtime.phpt b/ext/standard/tests/hrtime/hrtime.phpt index 795b78c1dcb37..614056c7d3364 100644 --- a/ext/standard/tests/hrtime/hrtime.phpt +++ b/ext/standard/tests/hrtime/hrtime.phpt @@ -1,6 +1,6 @@ --TEST-- Test hrtime() aligns with microtime() ---XFAIL-- +--FLAKY-- This test frequently fails in CI --FILE-- hasSection('FLAKY'); } /** @@ -3861,6 +3862,7 @@ class TestFile 'INI', 'ENV', 'EXTENSIONS', 'SKIPIF', 'XFAIL', 'XLEAK', 'CLEAN', 'CREDITS', 'DESCRIPTION', 'CONFLICTS', 'WHITESPACE_SENSITIVE', + 'FLAKY', ]; /** From 0dadd6616a491418871fb0b41590a73b128aa212 Mon Sep 17 00:00:00 2001 From: Eno <895183594@qq.com> Date: Sat, 13 May 2023 01:15:24 +0800 Subject: [PATCH 3/7] Improve openssl ext to generate EC keys with custom EC parameters This change extends supported parameter when generating EC keys. Specifically following parameters are now supported: p, a, b, order, generator, seed, cofactory, g_x, g_y, x, y and d. Those parameters can be passed to ec field in openssl_pkey_new options. It also fixes some issues openssl_pkey_get_details related to SM2 support. Closes GH-9991 --- NEWS | 3 + ext/openssl/openssl.c | 400 ++++++++++++++++------- ext/openssl/tests/ecc.phpt | 8 +- ext/openssl/tests/ecc_custom_params.phpt | 113 +++++++ ext/openssl/tests/ecc_sm2.phpt | 92 ++++++ 5 files changed, 495 insertions(+), 121 deletions(-) create mode 100644 ext/openssl/tests/ecc_custom_params.phpt create mode 100644 ext/openssl/tests/ecc_sm2.phpt diff --git a/NEWS b/NEWS index 8f6738078ab83..0e0a6652d4f58 100644 --- a/NEWS +++ b/NEWS @@ -10,6 +10,9 @@ PHP NEWS . Fixed line number of JMP instruction over else block. (ilutov) . Fixed use-of-uninitialized-value with ??= on assert. (ilutov) +- OpenSSL + . Added support for additional EC parameters in openssl_pkey_new. (Eno-CN) + 06 Jul 2023, PHP 8.3.0alpha3 - Core: diff --git a/ext/openssl/openssl.c b/ext/openssl/openssl.c index 4009a5b84df1f..33f51bfa4de9b 100644 --- a/ext/openssl/openssl.c +++ b/ext/openssl/openssl.c @@ -97,6 +97,14 @@ #if !defined(OPENSSL_NO_EC) && defined(EVP_PKEY_EC) #define HAVE_EVP_PKEY_EC 1 + +/* the OPENSSL_EC_EXPLICIT_CURVE value was added + * in OpenSSL 1.1.0; previous versions should + * use 0 instead. + */ +#ifndef OPENSSL_EC_EXPLICIT_CURVE +#define OPENSSL_EC_EXPLICIT_CURVE 0x000 +#endif #endif ZEND_DECLARE_MODULE_GLOBALS(openssl) @@ -4264,158 +4272,290 @@ static EVP_PKEY *php_openssl_pkey_init_dh(zval *data, bool *is_private) #ifdef HAVE_EVP_PKEY_EC #if PHP_OPENSSL_API_VERSION < 0x30000 static bool php_openssl_pkey_init_legacy_ec(EC_KEY *eckey, zval *data, bool *is_private) { + BIGNUM *p = NULL, *a = NULL, *b = NULL, *order = NULL, *g_x = NULL, *g_y = NULL , *cofactor = NULL; + BIGNUM *x = NULL, *y = NULL, *d = NULL; + EC_POINT *point_g = NULL; + EC_POINT *point_q = NULL; EC_GROUP *group = NULL; - EC_POINT *pnt = NULL; - BIGNUM *d = NULL; - zval *bn; - zval *x; - zval *y; + BN_CTX *bctx = BN_CTX_new(); *is_private = false; - if ((bn = zend_hash_str_find(Z_ARRVAL_P(data), "curve_name", sizeof("curve_name") - 1)) != NULL && - Z_TYPE_P(bn) == IS_STRING) { - int nid = OBJ_sn2nid(Z_STRVAL_P(bn)); - if (nid != NID_undef) { - group = EC_GROUP_new_by_curve_name(nid); - if (!group) { - php_openssl_store_errors(); + zval *curve_name_zv = zend_hash_str_find(Z_ARRVAL_P(data), "curve_name", sizeof("curve_name") - 1); + if (curve_name_zv && Z_TYPE_P(curve_name_zv) == IS_STRING && Z_STRLEN_P(curve_name_zv) > 0) { + int nid = OBJ_sn2nid(Z_STRVAL_P(curve_name_zv)); + if (nid == NID_undef) { + php_error_docref(NULL, E_WARNING, "Unknown elliptic curve (short) name %s", Z_STRVAL_P(curve_name_zv)); + goto clean_exit; + } + + if (!(group = EC_GROUP_new_by_curve_name(nid))) { + goto clean_exit; + } + EC_GROUP_set_asn1_flag(group, OPENSSL_EC_NAMED_CURVE); + } else { + OPENSSL_PKEY_SET_BN(data, p); + OPENSSL_PKEY_SET_BN(data, a); + OPENSSL_PKEY_SET_BN(data, b); + OPENSSL_PKEY_SET_BN(data, order); + + if (!(p && a && b && order)) { + if (!p && !a && !b && !order) { + php_error_docref(NULL, E_WARNING, "Missing params: curve_name"); + } else { + php_error_docref( + NULL, E_WARNING, "Missing params: curve_name or p, a, b, order"); + } + goto clean_exit; + } + + if (!(group = EC_GROUP_new_curve_GFp(p, a, b, bctx))) { + goto clean_exit; + } + + if (!(point_g = EC_POINT_new(group))) { + goto clean_exit; + } + + zval *generator_zv = zend_hash_str_find(Z_ARRVAL_P(data), "generator", sizeof("generator") - 1); + if (generator_zv && Z_TYPE_P(generator_zv) == IS_STRING && Z_STRLEN_P(generator_zv) > 0) { + if (!(EC_POINT_oct2point(group, point_g, (unsigned char *)Z_STRVAL_P(generator_zv), Z_STRLEN_P(generator_zv), bctx))) { goto clean_exit; } - EC_GROUP_set_asn1_flag(group, OPENSSL_EC_NAMED_CURVE); - EC_GROUP_set_point_conversion_form(group, POINT_CONVERSION_UNCOMPRESSED); - if (!EC_KEY_set_group(eckey, group)) { - php_openssl_store_errors(); + } else { + OPENSSL_PKEY_SET_BN(data, g_x); + OPENSSL_PKEY_SET_BN(data, g_y); + + if (!g_x || !g_y) { + php_error_docref( + NULL, E_WARNING, "Missing params: generator or g_x and g_y"); + goto clean_exit; + } + + if (!EC_POINT_set_affine_coordinates_GFp(group, point_g, g_x, g_y, bctx)) { goto clean_exit; } } + + zval *seed_zv = zend_hash_str_find(Z_ARRVAL_P(data), "seed", sizeof("seed") - 1); + if (seed_zv && Z_TYPE_P(seed_zv) == IS_STRING && Z_STRLEN_P(seed_zv) > 0) { + if (!EC_GROUP_set_seed(group, (unsigned char *)Z_STRVAL_P(seed_zv), Z_STRLEN_P(seed_zv))) { + goto clean_exit; + } + } + + /* + * OpenSSL uses 0 cofactor as a marker for "unknown cofactor". + * So accept cofactor == NULL or cofactor >= 0. + * Internally, the lib will check the cofactor value. + */ + OPENSSL_PKEY_SET_BN(data, cofactor); + if (!EC_GROUP_set_generator(group, point_g, order, cofactor)) { + goto clean_exit; + } + EC_GROUP_set_asn1_flag(group, OPENSSL_EC_EXPLICIT_CURVE); } - if (group == NULL) { - php_error_docref(NULL, E_WARNING, "Unknown curve name"); + EC_GROUP_set_point_conversion_form(group, POINT_CONVERSION_UNCOMPRESSED); + + if (!EC_KEY_set_group(eckey, group)) { goto clean_exit; } - // The public key 'pnt' can be calculated from 'd' or is defined by 'x' and 'y' - if ((bn = zend_hash_str_find(Z_ARRVAL_P(data), "d", sizeof("d") - 1)) != NULL && - Z_TYPE_P(bn) == IS_STRING) { + OPENSSL_PKEY_SET_BN(data, d); + OPENSSL_PKEY_SET_BN(data, x); + OPENSSL_PKEY_SET_BN(data, y); + + if (d) { *is_private = true; - d = BN_bin2bn((unsigned char*) Z_STRVAL_P(bn), Z_STRLEN_P(bn), NULL); if (!EC_KEY_set_private_key(eckey, d)) { - php_openssl_store_errors(); - goto clean_exit; - } - // Calculate the public key by multiplying the Point Q with the public key - // P = d * Q - pnt = EC_POINT_new(group); - if (!pnt || !EC_POINT_mul(group, pnt, d, NULL, NULL, NULL)) { - php_openssl_store_errors(); goto clean_exit; } - BN_free(d); - } else if ((x = zend_hash_str_find(Z_ARRVAL_P(data), "x", sizeof("x") - 1)) != NULL && - Z_TYPE_P(x) == IS_STRING && - (y = zend_hash_str_find(Z_ARRVAL_P(data), "y", sizeof("y") - 1)) != NULL && - Z_TYPE_P(y) == IS_STRING) { - pnt = EC_POINT_new(group); - if (pnt == NULL) { - php_openssl_store_errors(); + point_q = EC_POINT_new(group); + if (!point_q || !EC_POINT_mul(group, point_q, d, NULL, NULL, bctx)) { goto clean_exit; } - if (!EC_POINT_set_affine_coordinates_GFp( - group, pnt, BN_bin2bn((unsigned char*) Z_STRVAL_P(x), Z_STRLEN_P(x), NULL), - BN_bin2bn((unsigned char*) Z_STRVAL_P(y), Z_STRLEN_P(y), NULL), NULL)) { - php_openssl_store_errors(); + } else if (x && y) { + /* OpenSSL does not allow setting EC_PUB_X/EC_PUB_Y, so convert to encoded format. */ + point_q = EC_POINT_new(group); + if (!point_q || !EC_POINT_set_affine_coordinates_GFp(group, point_q, x, y, bctx)) { goto clean_exit; } } - if (pnt != NULL) { - if (!EC_KEY_set_public_key(eckey, pnt)) { - php_openssl_store_errors(); + if (point_q != NULL) { + if (!EC_KEY_set_public_key(eckey, point_q)) { goto clean_exit; } - EC_POINT_free(pnt); - pnt = NULL; } if (!EC_KEY_check_key(eckey)) { *is_private = true; PHP_OPENSSL_RAND_ADD_TIME(); EC_KEY_generate_key(eckey); - php_openssl_store_errors(); - } - if (EC_KEY_check_key(eckey)) { - EC_GROUP_free(group); - return true; - } else { - php_openssl_store_errors(); } clean_exit: - BN_free(d); - EC_POINT_free(pnt); + php_openssl_store_errors(); + BN_CTX_free(bctx); EC_GROUP_free(group); - return false; + EC_POINT_free(point_g); + EC_POINT_free(point_q); + BN_free(p); + BN_free(a); + BN_free(b); + BN_free(order); + BN_free(g_x); + BN_free(g_y); + BN_free(cofactor); + BN_free(d); + BN_free(x); + BN_free(y); + return EC_KEY_check_key(eckey); } #endif static EVP_PKEY *php_openssl_pkey_init_ec(zval *data, bool *is_private) { #if PHP_OPENSSL_API_VERSION >= 0x30000 - BIGNUM *d = NULL, *x = NULL, *y = NULL; + int nid = NID_undef; + BIGNUM *p = NULL, *a = NULL, *b = NULL, *order = NULL, *g_x = NULL, *g_y = NULL, *cofactor = NULL; + BIGNUM *x = NULL, *y = NULL, *d = NULL; + EC_POINT *point_g = NULL; + EC_POINT *point_q = NULL; + unsigned char *point_g_buf = NULL; + unsigned char *point_q_buf = NULL; EC_GROUP *group = NULL; - EC_POINT *pnt = NULL; - unsigned char *pnt_oct = NULL; EVP_PKEY *param_key = NULL, *pkey = NULL; EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_EC, NULL); + BN_CTX *bctx = BN_CTX_new(); OSSL_PARAM *params = NULL; OSSL_PARAM_BLD *bld = OSSL_PARAM_BLD_new(); - zval *curve_name_zv = zend_hash_str_find(Z_ARRVAL_P(data), "curve_name", sizeof("curve_name") - 1); - - OPENSSL_PKEY_SET_BN(data, d); - OPENSSL_PKEY_SET_BN(data, x); - OPENSSL_PKEY_SET_BN(data, y); *is_private = false; - if (!ctx || !bld || !curve_name_zv || Z_TYPE_P(curve_name_zv) != IS_STRING) { - goto cleanup; - } + zval *curve_name_zv = zend_hash_str_find(Z_ARRVAL_P(data), "curve_name", sizeof("curve_name") - 1); + if (curve_name_zv && Z_TYPE_P(curve_name_zv) == IS_STRING && Z_STRLEN_P(curve_name_zv) > 0) { + nid = OBJ_sn2nid(Z_STRVAL_P(curve_name_zv)); + if (nid == NID_undef) { + php_error_docref(NULL, E_WARNING, "Unknown elliptic curve (short) name %s", Z_STRVAL_P(curve_name_zv)); + goto cleanup; + } - int nid = OBJ_sn2nid(Z_STRVAL_P(curve_name_zv)); - group = EC_GROUP_new_by_curve_name(nid); - if (!group) { - php_error_docref(NULL, E_WARNING, "Unknown curve name"); - goto cleanup; - } + if (!(group = EC_GROUP_new_by_curve_name(nid))) { + goto cleanup; + } - OSSL_PARAM_BLD_push_utf8_string( - bld, OSSL_PKEY_PARAM_GROUP_NAME, Z_STRVAL_P(curve_name_zv), Z_STRLEN_P(curve_name_zv)); + if (!OSSL_PARAM_BLD_push_utf8_string(bld, OSSL_PKEY_PARAM_GROUP_NAME, Z_STRVAL_P(curve_name_zv), Z_STRLEN_P(curve_name_zv))) { + goto cleanup; + } + } else { + OPENSSL_PKEY_SET_BN(data, p); + OPENSSL_PKEY_SET_BN(data, a); + OPENSSL_PKEY_SET_BN(data, b); + OPENSSL_PKEY_SET_BN(data, order); + + if (!(p && a && b && order)) { + if (!p && !a && !b && !order) { + php_error_docref(NULL, E_WARNING, "Missing params: curve_name"); + } else { + php_error_docref(NULL, E_WARNING, "Missing params: curve_name or p, a, b, order"); + } + goto cleanup; + } - if (d) { - OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_PRIV_KEY, d); + if (!OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_EC_P, p) || + !OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_EC_A, a) || + !OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_EC_B, b) || + !OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_EC_ORDER, order) || + !OSSL_PARAM_BLD_push_utf8_string(bld, OSSL_PKEY_PARAM_EC_FIELD_TYPE, SN_X9_62_prime_field, 0)) { + goto cleanup; + } - pnt = EC_POINT_new(group); - if (!pnt || !EC_POINT_mul(group, pnt, d, NULL, NULL, NULL)) { + if (!(group = EC_GROUP_new_curve_GFp(p, a, b, bctx))) { goto cleanup; } - } else if (x && y) { - /* OpenSSL does not allow setting EC_PUB_X/EC_PUB_Y, so convert to encoded format. */ - pnt = EC_POINT_new(group); - if (!pnt || !EC_POINT_set_affine_coordinates(group, pnt, x, y, NULL)) { + + if (!(point_g = EC_POINT_new(group))) { goto cleanup; } - } - if (pnt) { - size_t pnt_oct_len = - EC_POINT_point2buf(group, pnt, POINT_CONVERSION_COMPRESSED, &pnt_oct, NULL); - if (!pnt_oct_len) { + zval *generator_zv = zend_hash_str_find(Z_ARRVAL_P(data), "generator", sizeof("generator") - 1); + if (generator_zv && Z_TYPE_P(generator_zv) == IS_STRING && Z_STRLEN_P(generator_zv) > 0) { + if (!EC_POINT_oct2point(group, point_g, (unsigned char *)Z_STRVAL_P(generator_zv), Z_STRLEN_P(generator_zv), bctx) || + !OSSL_PARAM_BLD_push_octet_string(bld, OSSL_PKEY_PARAM_EC_GENERATOR, Z_STRVAL_P(generator_zv), Z_STRLEN_P(generator_zv))) { + goto cleanup; + } + } else { + OPENSSL_PKEY_SET_BN(data, g_x); + OPENSSL_PKEY_SET_BN(data, g_y); + + if (!g_x || !g_y) { + php_error_docref( + NULL, E_WARNING, "Missing params: generator or g_x and g_y"); + goto cleanup; + } + + if (!EC_POINT_set_affine_coordinates(group, point_g, g_x, g_y, bctx)) { + goto cleanup; + } + + size_t point_g_buf_len = + EC_POINT_point2buf(group, point_g, POINT_CONVERSION_COMPRESSED, &point_g_buf, bctx); + if (!point_g_buf_len) { + goto cleanup; + } + + if (!OSSL_PARAM_BLD_push_octet_string(bld, OSSL_PKEY_PARAM_EC_GENERATOR, point_g_buf, point_g_buf_len)) { + goto cleanup; + } + } + + zval *seed_zv = zend_hash_str_find(Z_ARRVAL_P(data), "seed", sizeof("seed") - 1); + if (seed_zv && Z_TYPE_P(seed_zv) == IS_STRING && Z_STRLEN_P(seed_zv) > 0) { + if (!EC_GROUP_set_seed(group, (unsigned char *)Z_STRVAL_P(seed_zv), Z_STRLEN_P(seed_zv)) || + !OSSL_PARAM_BLD_push_octet_string(bld, OSSL_PKEY_PARAM_EC_SEED, Z_STRVAL_P(seed_zv), Z_STRLEN_P(seed_zv))) { + goto cleanup; + } + } + + OPENSSL_PKEY_SET_BN(data, cofactor); + if (!OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_EC_COFACTOR, cofactor) || + !EC_GROUP_set_generator(group, point_g, order, cofactor)) { goto cleanup; } - OSSL_PARAM_BLD_push_octet_string(bld, OSSL_PKEY_PARAM_PUB_KEY, pnt_oct, pnt_oct_len); + nid = EC_GROUP_check_named_curve(group, 0, bctx); + } + + /* custom params not supported with SM2, SKIP */ + if (nid != NID_sm2) { + OPENSSL_PKEY_SET_BN(data, d); + OPENSSL_PKEY_SET_BN(data, x); + OPENSSL_PKEY_SET_BN(data, y); + + if (d) { + point_q = EC_POINT_new(group); + if (!point_q || !EC_POINT_mul(group, point_q, d, NULL, NULL, bctx) || + !OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_PRIV_KEY, d)) { + goto cleanup; + } + } else if (x && y) { + /* OpenSSL does not allow setting EC_PUB_X/EC_PUB_Y, so convert to encoded format. */ + point_q = EC_POINT_new(group); + if (!point_q || !EC_POINT_set_affine_coordinates(group, point_q, x, y, bctx)) { + goto cleanup; + } + } + + if (point_q) { + size_t point_q_buf_len = + EC_POINT_point2buf(group, point_q, POINT_CONVERSION_COMPRESSED, &point_q_buf, bctx); + if (!point_q_buf_len || + !OSSL_PARAM_BLD_push_octet_string(bld, OSSL_PKEY_PARAM_PUB_KEY, point_q_buf, point_q_buf_len)) { + goto cleanup; + } + } } params = OSSL_PARAM_BLD_to_param(bld); @@ -4423,21 +4563,25 @@ static EVP_PKEY *php_openssl_pkey_init_ec(zval *data, bool *is_private) { goto cleanup; } - if (EVP_PKEY_fromdata_init(ctx) <= 0 || + if (d || (x && y)) { + if (EVP_PKEY_fromdata_init(ctx) <= 0 || EVP_PKEY_fromdata(ctx, ¶m_key, EVP_PKEY_KEYPAIR, params) <= 0) { - goto cleanup; + goto cleanup; + } + EVP_PKEY_CTX_free(ctx); + ctx = EVP_PKEY_CTX_new(param_key, NULL); } - - EVP_PKEY_CTX_free(ctx); - ctx = EVP_PKEY_CTX_new(param_key, NULL); - if (EVP_PKEY_check(ctx)) { + + if (EVP_PKEY_check(ctx) || EVP_PKEY_public_check_quick(ctx)) { *is_private = d != NULL; EVP_PKEY_up_ref(param_key); pkey = param_key; } else { *is_private = true; PHP_OPENSSL_RAND_ADD_TIME(); - if (EVP_PKEY_keygen_init(ctx) <= 0 || EVP_PKEY_keygen(ctx, &pkey) <= 0) { + if (EVP_PKEY_keygen_init(ctx) != 1 || + EVP_PKEY_CTX_set_params(ctx, params) != 1 || + EVP_PKEY_generate(ctx, &pkey) != 1) { goto cleanup; } } @@ -4446,11 +4590,21 @@ static EVP_PKEY *php_openssl_pkey_init_ec(zval *data, bool *is_private) { php_openssl_store_errors(); EVP_PKEY_free(param_key); EVP_PKEY_CTX_free(ctx); + BN_CTX_free(bctx); OSSL_PARAM_free(params); OSSL_PARAM_BLD_free(bld); - EC_POINT_free(pnt); EC_GROUP_free(group); - OPENSSL_free(pnt_oct); + EC_POINT_free(point_g); + EC_POINT_free(point_q); + OPENSSL_free(point_g_buf); + OPENSSL_free(point_q_buf); + BN_free(p); + BN_free(a); + BN_free(b); + BN_free(order); + BN_free(g_x); + BN_free(g_y); + BN_free(cofactor); BN_free(d); BN_free(x); BN_free(y); @@ -4796,7 +4950,21 @@ PHP_FUNCTION(openssl_pkey_get_details) */ #if PHP_OPENSSL_API_VERSION >= 0x30000 zval ary; - switch (EVP_PKEY_base_id(pkey)) { + int base_id = 0; + + if (EVP_PKEY_id(pkey) != EVP_PKEY_KEYMGMT) { + base_id = EVP_PKEY_base_id(pkey); + } else { + const char *type_name = EVP_PKEY_get0_type_name(pkey); + if (type_name) { + int nid = OBJ_txt2nid(type_name); + if (nid != NID_undef) { + base_id = EVP_PKEY_type(nid); + } + } + } + + switch (base_id) { case EVP_PKEY_RSA: ktype = OPENSSL_KEYTYPE_RSA; array_init(&ary); @@ -4859,7 +5027,9 @@ PHP_FUNCTION(openssl_pkey_get_details) break; } #endif - EMPTY_SWITCH_DEFAULT_CASE(); + default: + ktype = -1; + break; } #else switch (EVP_PKEY_base_id(pkey)) { @@ -4955,24 +5125,22 @@ PHP_FUNCTION(openssl_pkey_get_details) ec_group = EC_KEY_get0_group(ec_key); - // Curve nid (numerical identifier) used for ASN1 mapping - nid = EC_GROUP_get_curve_name(ec_group); - if (nid == NID_undef) { - break; - } array_init(&ec); - // Short object name - crv_sn = (char*) OBJ_nid2sn(nid); - if (crv_sn != NULL) { - add_assoc_string(&ec, "curve_name", crv_sn); - } + /** Curve nid (numerical identifier) used for ASN1 mapping */ + nid = EC_GROUP_get_curve_name(ec_group); + if (nid != NID_undef) { + crv_sn = (char*) OBJ_nid2sn(nid); + if (crv_sn != NULL) { + add_assoc_string(&ec, "curve_name", crv_sn); + } - obj = OBJ_nid2obj(nid); - if (obj != NULL) { - int oir_len = OBJ_obj2txt(oir_buf, sizeof(oir_buf), obj, 1); - add_assoc_stringl(&ec, "curve_oid", (char*) oir_buf, oir_len); - ASN1_OBJECT_free(obj); + obj = OBJ_nid2obj(nid); + if (obj != NULL) { + int oir_len = OBJ_obj2txt(oir_buf, sizeof(oir_buf), obj, 1); + add_assoc_stringl(&ec, "curve_oid", (char*) oir_buf, oir_len); + ASN1_OBJECT_free(obj); + } } pub = EC_KEY_get0_public_key(ec_key); diff --git a/ext/openssl/tests/ecc.phpt b/ext/openssl/tests/ecc.phpt index a881d59c9a2be..c5a6f2899898c 100644 --- a/ext/openssl/tests/ecc.phpt +++ b/ext/openssl/tests/ecc.phpt @@ -74,7 +74,8 @@ var_dump($csr); $pubkey1 = openssl_pkey_get_details(openssl_csr_get_public_key($csr)); var_dump(isset($pubkey1["ec"]["priv_key"])); unset($d1["ec"]["priv_key"]); -var_dump(array_diff($d1["ec"], $pubkey1["ec"])); +$diff = array_diff($d1["ec"], $pubkey1["ec"]); +var_dump(isset($diff["d"]) && is_string($diff["d"]) && strlen($diff["d"]) > 0); $x509 = openssl_csr_sign($csr, null, $key1, 365, $args); var_dump($x509); @@ -121,10 +122,7 @@ Testing openssl_csr_new with existing ecc key object(OpenSSLCertificateSigningRequest)#%d (0) { } bool(false) -array(1) { - ["d"]=> - string(%d) "%a" -} +bool(true) object(OpenSSLCertificate)#%d (0) { } Testing openssl_x509_check_private_key diff --git a/ext/openssl/tests/ecc_custom_params.phpt b/ext/openssl/tests/ecc_custom_params.phpt new file mode 100644 index 0000000000000..0c63af1450ba6 --- /dev/null +++ b/ext/openssl/tests/ecc_custom_params.phpt @@ -0,0 +1,113 @@ +--TEST-- +openssl_*() with OPENSSL_KEYTYPE_EC for ec custom params +--EXTENSIONS-- +openssl +--SKIPIF-- + +--FILE-- + array( + 'curve_name' => $curve_name, + ) +)); + +var_dump($ec); +$details = openssl_pkey_get_details($ec); +$ec_details = $details['ec']; +var_dump($ec_details['curve_name'] === $curve_name); + +// EC - generate keypair from priv_key "d" with explicit parameters (OSCCA WAPIP192v1 Elliptic curve) +echo "Testing openssl_pkey_new with ec explicit parameters\n"; +$d = hex2bin('8D0AC65AAEA0D6B96254C65817D4A143A9E7A03876F1A37D'); +$x = hex2bin('98E07AAD50C31F9189EBE6B8B5C70E5DEE59D7A8BC344CC6'); +$y = hex2bin('6109D3D96E52D0867B9D05D72D07BE5876A3D973E0E96792'); +$p = hex2bin('BDB6F4FE3E8B1D9E0DA8C0D46F4C318CEFE4AFE3B6B8551F'); +$a = hex2bin('BB8E5E8FBC115E139FE6A814FE48AAA6F0ADA1AA5DF91985'); +$b = hex2bin('1854BEBDC31B21B7AEFC80AB0ECD10D5B1B3308E6DBF11C1'); +$g_x = hex2bin('4AD5F7048DE709AD51236DE65E4D4B482C836DC6E4106640'); +$g_y = hex2bin('02BB3A02D4AAADACAE24817A4CA3A1B014B5270432DB27D2'); +$order = hex2bin('BDB6F4FE3E8B1D9E0DA8C0D40FC962195DFAE76F56564677'); + +$ec = openssl_pkey_new(array( + 'ec'=> array( + 'p' => $p, + 'a' => $a, + 'b' => $b, + 'order' => $order, + 'g_x' => $g_x, + 'g_y' => $g_y, + 'd' => $d, + ) +)); + +$details = openssl_pkey_get_details($ec); +$ec_details = $details['ec']; +var_dump($ec_details['x'] === $x); +var_dump($ec_details['y'] === $y); +var_dump($ec_details['d'] === $d); + +echo "Testing openssl_pkey_new with ec missing params \n"; +// EC - invalid curve_name +$ec = openssl_pkey_new(array( + 'ec'=> array( + 'curve_name' => 'invalid_curve_name', + ) +)); +var_dump($ec); + +// EC - missing all params +$ec = openssl_pkey_new(array( + 'ec'=> array() +)); +var_dump($ec); + +// EC - missing "p" param +$ec = openssl_pkey_new(array( + 'ec'=> array( + 'a' => $a, + 'b' => $b, + 'order' => $order + ) +)); +var_dump($ec); + +// EC - missing "generator" or "g_x" and "g_y" param +$ec = openssl_pkey_new(array( + 'ec'=> array( + 'p' => $p, + 'a' => $a, + 'b' => $b, + 'order' => $order + ) +)); +var_dump($ec); +?> +--EXPECTF-- +Testing openssl_pkey_new with ec curve_name +object(OpenSSLAsymmetricKey)#%d (0) { +} +bool(true) +Testing openssl_pkey_new with ec explicit parameters +bool(true) +bool(true) +bool(true) +Testing openssl_pkey_new with ec missing params + +Warning: openssl_pkey_new(): Unknown elliptic curve (short) name invalid_curve_name in %s on line %d +bool(false) + +Warning: openssl_pkey_new(): Missing params: curve_name in %s on line %d +bool(false) + +Warning: openssl_pkey_new(): Missing params: curve_name or p, a, b, order in %s on line %d +bool(false) + +Warning: openssl_pkey_new(): Missing params: generator or g_x and g_y in %s on line %d +bool(false) diff --git a/ext/openssl/tests/ecc_sm2.phpt b/ext/openssl/tests/ecc_sm2.phpt new file mode 100644 index 0000000000000..11d4c22ae5b26 --- /dev/null +++ b/ext/openssl/tests/ecc_sm2.phpt @@ -0,0 +1,92 @@ +--TEST-- +openssl_*() with OPENSSL_KEYTYPE_EC for SM2 +--EXTENSIONS-- +openssl +--SKIPIF-- + +--FILE-- + array( + 'curve_name' => 'SM2', + ) +)); + +var_dump($ec); +$details = openssl_pkey_get_details($ec); +var_dump($details["bits"]); +var_dump(strlen($details["key"])); +var_dump($details["ec"]["curve_name"]); +var_dump($details["type"] == OPENSSL_KEYTYPE_EC); + +// EC - generate SM2 keypair with curve_name +echo "Testing openssl_pkey_get_public from SM2 pem pubkey\n"; +$public_key = openssl_pkey_get_public($details["key"]); +var_dump($public_key); +$details_public_key = openssl_pkey_get_details($public_key); +var_dump(strlen($details["key"])); +var_dump($details_public_key["ec"]["curve_name"]); +var_dump($details["type"] == OPENSSL_KEYTYPE_EC); +var_dump($details_public_key["ec"]["x"] === $details["ec"]["x"]); +var_dump($details_public_key["ec"]["y"] === $details["ec"]["y"]); + +// EC - generate keypair with explicit parameters (SM2 curve) +echo "Testing openssl_pkey_new with ec explicit parameters (SM2 curve)\n"; +$p = hex2bin('FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFF'); +$a = hex2bin('FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFC'); +$b = hex2bin('28E9FA9E9D9F5E344D5A9E4BCF6509A7F39789F515AB8F92DDBCBD414D940E93'); +$g_x = hex2bin('32C4AE2C1F1981195F9904466A39C9948FE30BBFF2660BE1715A4589334C74C7'); +$g_y = hex2bin('BC3736A2F4F6779C59BDCEE36B692153D0A9877CC62A474002DF32E52139F0A0'); +$order = hex2bin('FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFF7203DF6B21C6052B53BBF40939D54123'); + +$ec = openssl_pkey_new(array( + 'ec'=> array( + 'p' => $p, + 'a' => $a, + 'b' => $b, + 'order' => $order, + 'g_x' => $g_x, + 'g_y' => $g_y + ) +)); + +$details = openssl_pkey_get_details($ec); +var_dump($details['bits']); +var_dump(strlen($details['key'])); +var_dump($details['type'] == OPENSSL_KEYTYPE_EC); +$public_key = openssl_pkey_get_public($details["key"]); +$details_public_key = openssl_pkey_get_details($public_key); +var_dump(strlen($details["key"])); +var_dump($details["type"] == OPENSSL_KEYTYPE_EC); +var_dump($details_public_key["ec"]["x"] === $details["ec"]["x"]); +var_dump($details_public_key["ec"]["y"] === $details["ec"]["y"]); +?> +--EXPECTF-- +Testing openssl_pkey_new with ec curve_name SM2 +object(OpenSSLAsymmetricKey)#%d (0) { +} +int(256) +int(178) +string(3) "SM2" +bool(true) +Testing openssl_pkey_get_public from SM2 pem pubkey +object(OpenSSLAsymmetricKey)#%d (0) { +} +int(178) +string(3) "SM2" +bool(true) +bool(true) +bool(true) +Testing openssl_pkey_new with ec explicit parameters (SM2 curve) +int(256) +int(475) +bool(true) +int(475) +bool(true) +bool(true) +bool(true) From cd5aae6847a2fbcc99810729467c28948bb8e480 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Sun, 2 Jul 2023 14:34:28 +0200 Subject: [PATCH 4/7] Remove unused is_recursive entry This only takes up space and time. --- UPGRADING.INTERNALS | 1 + ext/spl/spl_directory.c | 2 -- ext/spl/spl_directory.h | 1 - 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/UPGRADING.INTERNALS b/UPGRADING.INTERNALS index 84ef7ac8bc7d4..dc8a1c99877fc 100644 --- a/UPGRADING.INTERNALS +++ b/UPGRADING.INTERNALS @@ -115,6 +115,7 @@ PHP 8.3 INTERNALS UPGRADE NOTES e. ext/spl - The PHPAPI spl_iterator_apply() function now returns zend_result instead of int. There are no functional changes. + - The field _spl_filesystem_object->is_recursive has been removed. f. ext/dom - A new function dom_get_doc_props_read_only() is added to gather the document diff --git a/ext/spl/spl_directory.c b/ext/spl/spl_directory.c index 378f707f44c3b..9a89201ab3a77 100644 --- a/ext/spl/spl_directory.c +++ b/ext/spl/spl_directory.c @@ -759,8 +759,6 @@ static void spl_filesystem_object_construct(INTERNAL_FUNCTION_PARAMETERS, zend_l } zend_restore_error_handling(&error_handling); - - intern->u.dir.is_recursive = instanceof_function(intern->std.ce, spl_ce_RecursiveDirectoryIterator) ? 1 : 0; } /* }}} */ diff --git a/ext/spl/spl_directory.h b/ext/spl/spl_directory.h index f94ac670d4fa0..d036013161cb1 100644 --- a/ext/spl/spl_directory.h +++ b/ext/spl/spl_directory.h @@ -64,7 +64,6 @@ struct _spl_filesystem_object { php_stream *dirp; zend_string *sub_path; int index; - int is_recursive; zend_function *func_rewind; zend_function *func_next; zend_function *func_valid; From efb94fb7bef6c166a34e43a47a288aad9f7c12a1 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Sun, 2 Jul 2023 14:34:59 +0200 Subject: [PATCH 5/7] Reserve less file space if possible in a directory entry On POSIX systems, we can use the maximum file length instead of maximum path length. This saves space and time for clearing the memory. --- main/php_streams.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/main/php_streams.h b/main/php_streams.h index 5acb94f3042d3..4465fccca11ea 100644 --- a/main/php_streams.h +++ b/main/php_streams.h @@ -107,7 +107,11 @@ typedef struct _php_stream_statbuf { } php_stream_statbuf; typedef struct _php_stream_dirent { +#ifdef NAME_MAX + char d_name[NAME_MAX + 1]; +#else char d_name[MAXPATHLEN]; +#endif } php_stream_dirent; /* operations on streams that are file-handles */ From 368767ff9ae7a3c01f2a09738d380029f4ad7370 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Sun, 2 Jul 2023 14:35:43 +0200 Subject: [PATCH 6/7] Cache d_type in directory entry --- UPGRADING.INTERNALS | 3 +++ Zend/zend_virtual_cwd.h | 10 ++++++++++ ext/spl/spl_directory.c | 5 +++++ main/php_streams.h | 1 + main/streams/plain_wrapper.c | 5 +++++ win32/readdir.c | 7 +++++++ win32/readdir.h | 6 ++++++ 7 files changed, 37 insertions(+) diff --git a/UPGRADING.INTERNALS b/UPGRADING.INTERNALS index dc8a1c99877fc..cdc8932df15d3 100644 --- a/UPGRADING.INTERNALS +++ b/UPGRADING.INTERNALS @@ -67,6 +67,9 @@ PHP 8.3 INTERNALS UPGRADE NOTES - zend_set_user_opcode_handler - zend_ssa_inference * Removed unused macros PHP_FNV1_32A_INIT and PHP_FNV1A_64_INIT. See GH-11114. +* _php_stream_dirent now has an extra d_type field that is used to store the + directory entry type. This can be used to avoid additional stat calls for + types when the type is already known. ======================== 2. Build system changes diff --git a/Zend/zend_virtual_cwd.h b/Zend/zend_virtual_cwd.h index f819389af33c7..15c3b984a9dc7 100644 --- a/Zend/zend_virtual_cwd.h +++ b/Zend/zend_virtual_cwd.h @@ -87,6 +87,16 @@ typedef unsigned short mode_t; #else #ifdef HAVE_DIRENT_H #include + +#ifndef DT_UNKNOWN +# define DT_UNKNOWN 0 +#endif +#ifndef DT_DIR +# define DT_DIR 4 +#endif +#ifndef DT_REG +# define DT_REG 8 +#endif #endif #define DEFAULT_SLASH '/' diff --git a/ext/spl/spl_directory.c b/ext/spl/spl_directory.c index 9a89201ab3a77..1b29dce787a08 100644 --- a/ext/spl/spl_directory.c +++ b/ext/spl/spl_directory.c @@ -1485,6 +1485,11 @@ PHP_METHOD(RecursiveDirectoryIterator, hasChildren) if (spl_filesystem_is_invalid_or_dot(intern->u.dir.entry.d_name)) { RETURN_FALSE; } else { + if (intern->u.dir.entry.d_type == DT_DIR) { + RETURN_TRUE; + } else if (intern->u.dir.entry.d_type == DT_REG) { + RETURN_FALSE; + } if (spl_filesystem_object_get_file_name(intern) == FAILURE) { RETURN_THROWS(); } diff --git a/main/php_streams.h b/main/php_streams.h index 4465fccca11ea..db7cc3d236995 100644 --- a/main/php_streams.h +++ b/main/php_streams.h @@ -112,6 +112,7 @@ typedef struct _php_stream_dirent { #else char d_name[MAXPATHLEN]; #endif + unsigned char d_type; } php_stream_dirent; /* operations on streams that are file-handles */ diff --git a/main/streams/plain_wrapper.c b/main/streams/plain_wrapper.c index 128c3410aa835..86c517132482e 100644 --- a/main/streams/plain_wrapper.c +++ b/main/streams/plain_wrapper.c @@ -1032,6 +1032,11 @@ static ssize_t php_plain_files_dirstream_read(php_stream *stream, char *buf, siz result = readdir(dir); if (result) { PHP_STRLCPY(ent->d_name, result->d_name, sizeof(ent->d_name), strlen(result->d_name)); +#ifdef _DIRENT_HAVE_D_TYPE + ent->d_type = result->d_type; +#else + ent->d_type = DT_UNKNOWN; +#endif return sizeof(php_stream_dirent); } return 0; diff --git a/win32/readdir.c b/win32/readdir.c index 2c3344381913c..89381e5834b62 100644 --- a/win32/readdir.c +++ b/win32/readdir.c @@ -120,6 +120,13 @@ struct dirent *readdir(DIR *dp) dp->dent.d_ino = 1; dp->dent.d_off = dp->offset; + if (dp->fileinfo.dwFileAttributes & (FILE_ATTRIBUTE_REPARSE_POINT | FILE_ATTRIBUTE_DEVICE)) { + dp->dent.d_type = DT_UNKNOWN; /* conservative */ + } else if (dp->fileinfo.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { + dp->dent.d_type = DT_DIR; + } else { + dp->dent.d_type = DT_REG; + } return &(dp->dent); }/*}}}*/ diff --git a/win32/readdir.h b/win32/readdir.h index cc8e1a9a2510c..4d5a7ce476f0e 100644 --- a/win32/readdir.h +++ b/win32/readdir.h @@ -15,11 +15,17 @@ extern "C" { #include "ioutil.h" +#define _DIRENT_HAVE_D_TYPE +#define DT_UNKNOWN 0 +#define DT_DIR 4 +#define DT_REG 8 + /* struct dirent - same as Unix */ struct dirent { long d_ino; /* inode (always 1 in WIN32) */ off_t d_off; /* offset to this dirent */ unsigned short d_reclen; /* length of d_name */ + unsigned char d_type; char d_name[1]; /* null terminated filename in the current encoding, glyph number <= 255 wchar_t's + \0 byte */ }; From f59d68df7b634934b2e3d465193c3b5986ba9846 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Mon, 3 Jul 2023 20:16:29 +0200 Subject: [PATCH 7/7] Explicitly initialize all dir entries --- ext/phar/dirstream.c | 1 + ext/standard/ftp_fopen_wrapper.c | 1 + main/streams/glob_wrapper.c | 1 + main/streams/userspace.c | 1 + 4 files changed, 4 insertions(+) diff --git a/ext/phar/dirstream.c b/ext/phar/dirstream.c index ede5397bb51d7..d7fda32de0422 100644 --- a/ext/phar/dirstream.c +++ b/ext/phar/dirstream.c @@ -108,6 +108,7 @@ static ssize_t phar_dir_read(php_stream *stream, char *buf, size_t count) /* {{{ memset(buf, 0, sizeof(php_stream_dirent)); memcpy(((php_stream_dirent *) buf)->d_name, ZSTR_VAL(str_key), to_read); ((php_stream_dirent *) buf)->d_name[to_read + 1] = '\0'; + ((php_stream_dirent *) buf)->d_type = DT_UNKNOWN; return sizeof(php_stream_dirent); } diff --git a/ext/standard/ftp_fopen_wrapper.c b/ext/standard/ftp_fopen_wrapper.c index c3b107be65527..1757921d3d47f 100644 --- a/ext/standard/ftp_fopen_wrapper.c +++ b/ext/standard/ftp_fopen_wrapper.c @@ -629,6 +629,7 @@ static ssize_t php_ftp_dirstream_read(php_stream *stream, char *buf, size_t coun memcpy(ent->d_name, ZSTR_VAL(basename), tmp_len); ent->d_name[tmp_len - 1] = '\0'; zend_string_release_ex(basename, 0); + ent->d_type = DT_UNKNOWN; /* Trim off trailing whitespace characters */ while (tmp_len > 0 && diff --git a/main/streams/glob_wrapper.c b/main/streams/glob_wrapper.c index 813e0bacdfd3f..dc18964d7a8ff 100644 --- a/main/streams/glob_wrapper.c +++ b/main/streams/glob_wrapper.c @@ -150,6 +150,7 @@ static ssize_t php_glob_stream_read(php_stream *stream, char *buf, size_t count) php_glob_stream_path_split(pglob, pglob->glob.gl_pathv[index], pglob->flags & GLOB_APPEND, &path); ++pglob->index; PHP_STRLCPY(ent->d_name, path, sizeof(ent->d_name), strlen(path)); + ent->d_type = DT_UNKNOWN; return sizeof(php_stream_dirent); } pglob->index = glob_result_count; diff --git a/main/streams/userspace.c b/main/streams/userspace.c index 165bd7da3ad98..c7e57ab3ba078 100644 --- a/main/streams/userspace.c +++ b/main/streams/userspace.c @@ -1320,6 +1320,7 @@ static ssize_t php_userstreamop_readdir(php_stream *stream, char *buf, size_t co if (call_result == SUCCESS && Z_TYPE(retval) != IS_FALSE && Z_TYPE(retval) != IS_TRUE) { convert_to_string(&retval); PHP_STRLCPY(ent->d_name, Z_STRVAL(retval), sizeof(ent->d_name), Z_STRLEN(retval)); + ent->d_type = DT_UNKNOWN; didread = sizeof(php_stream_dirent); } else if (call_result == FAILURE) {