From e8c1e3a8b3dbf93befac5f8dd4f8e976ff65000e Mon Sep 17 00:00:00 2001 From: Michael Orlitzky Date: Mon, 7 Sep 2026 23:01:31 +0200 Subject: [PATCH 1/3] ext/session: allow session.save_path to be nonempty in this test commit 33b13ae5deae701d858f0d588c06e2a224efdc9e Author: Michael Orlitzky Date: Tue Sep 1 17:58:13 2026 -0400 ext/session/.../gh23043.phpt: session.save_path can be nonempty Allow session.save_path to be nonempty in this test, as the test itself does not empty it, and in automated setups it is common to force all "temporary stuff" to a dedicated location. Closes GH-23537 --- ext/session/tests/user_session_module/gh23043.phpt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ext/session/tests/user_session_module/gh23043.phpt b/ext/session/tests/user_session_module/gh23043.phpt index e3528884a79a..caf98ac93857 100644 --- a/ext/session/tests/user_session_module/gh23043.phpt +++ b/ext/session/tests/user_session_module/gh23043.phpt @@ -27,9 +27,9 @@ string(0) "" Warning: SessionHandler::write(): Session ID is too long or contains illegal characters. Only the A-Z, a-z, 0-9, "-", and "," characters are allowed in %s on line %d -Warning: session_write_close(): Failed to write session data using user defined save handler. (session.save_path: , handler: a::write) in %s on line %d +Warning: session_write_close(): Failed to write session data using user defined save handler. (session.save_path: %S, handler: a::write) in %s on line %d string(0) "" Warning: SessionHandler::write(): Session ID is too long or contains illegal characters. Only the A-Z, a-z, 0-9, "-", and "," characters are allowed in Unknown on line 0 -Warning: session_write_close(): Failed to write session data using user defined save handler. (session.save_path: , handler: a::write) in Unknown on line 0 +Warning: session_write_close(): Failed to write session data using user defined save handler. (session.save_path: %S, handler: a::write) in Unknown on line 0 From 797e3ea97599e3276bc71c1350ed056af435e4a4 Mon Sep 17 00:00:00 2001 From: Ilia Alshanetsky Date: Sun, 6 Sep 2026 11:00:21 -0400 Subject: [PATCH 2/3] soap: reject self-referential schema group and attributeGroup fix-up schema_content_model_fixup() and schema_attributegroup_fixup() followed a group ref back into schema_type_fixup() with nothing tracking the types already being fixed up, so a schema whose group references itself recursed until the C stack ran out. Track the in-progress types in the sdl context and raise a parse error when one is re-entered. Closes GH-23600 --- NEWS | 2 + ext/soap/php_schema.c | 15 +++++++ ext/soap/php_sdl.h | 1 + ext/soap/tests/schema-selfref-attrgroup.phpt | 44 +++++++++++++++++++ ext/soap/tests/schema-selfref-group.phpt | 46 ++++++++++++++++++++ 5 files changed, 108 insertions(+) create mode 100644 ext/soap/tests/schema-selfref-attrgroup.phpt create mode 100644 ext/soap/tests/schema-selfref-group.phpt diff --git a/NEWS b/NEWS index a9335d0840d2..6a6d61109709 100644 --- a/NEWS +++ b/NEWS @@ -107,6 +107,8 @@ PHP NEWS fails to initialize). (Lazizbek Ergashev) . Fixed WSDL cache corruption when a soap:header defines headerfaults. (Ilia Alshanetsky) + . Fixed stack overflow when parsing a WSDL with self-referential schema + groups or attributeGroups. (Ilia Alshanetsky) - Standard: . Fixed a segfault when a stream filter callback unsets StreamBucket::$data diff --git a/ext/soap/php_schema.c b/ext/soap/php_schema.c index a4911c659842..90d175a02535 100644 --- a/ext/soap/php_schema.c +++ b/ext/soap/php_schema.c @@ -2161,6 +2161,10 @@ static void schema_attributegroup_fixup(sdlCtx *ctx, sdlAttributePtr attr, HashT if (ctx->attributeGroups != NULL) { tmp = (sdlTypePtr)schema_find_by_ref(ctx->attributeGroups, attr->ref); if (tmp) { + if (zend_hash_index_find_ptr(&ctx->fixupInProgress, (zend_ulong)tmp) != NULL) { + soap_error1(E_ERROR, "Parsing Schema: recursive attributeGroup 'ref' attribute '%s'", attr->ref); + } + zend_hash_index_add_ptr(&ctx->fixupInProgress, (zend_ulong)tmp, tmp); if (tmp->attributes) { zend_hash_internal_pointer_reset(tmp->attributes); while ((tmp_attr = zend_hash_get_current_data_ptr(tmp->attributes)) != NULL) { @@ -2196,6 +2200,7 @@ static void schema_attributegroup_fixup(sdlCtx *ctx, sdlAttributePtr attr, HashT } } } + zend_hash_index_del(&ctx->fixupInProgress, (zend_ulong)tmp); } } efree(attr->ref); @@ -2210,6 +2215,9 @@ static void schema_content_model_fixup(sdlCtx *ctx, sdlContentModelPtr model) sdlTypePtr tmp; if (ctx->sdl->groups && (tmp = zend_hash_str_find_ptr(ctx->sdl->groups, model->u.group_ref, strlen(model->u.group_ref))) != NULL) { + if (zend_hash_index_find_ptr(&ctx->fixupInProgress, (zend_ulong)tmp) != NULL) { + soap_error1(E_ERROR, "Parsing Schema: recursive group 'ref' attribute '%s'", model->u.group_ref); + } schema_type_fixup(ctx, tmp); efree(model->u.group_ref); model->kind = XSD_CONTENT_GROUP; @@ -2253,6 +2261,8 @@ static void schema_type_fixup(sdlCtx *ctx, sdlTypePtr type) sdlTypePtr tmp; sdlAttributePtr attr; + zend_hash_index_add_ptr(&ctx->fixupInProgress, (zend_ulong)type, type); + if (type->ref != NULL) { if (ctx->sdl->elements != NULL) { tmp = (sdlTypePtr)schema_find_by_ref(ctx->sdl->elements, type->ref); @@ -2305,6 +2315,7 @@ static void schema_type_fixup(sdlCtx *ctx, sdlTypePtr type) } } } + zend_hash_index_del(&ctx->fixupInProgress, (zend_ulong)type); } void schema_pass2(sdlCtx *ctx) @@ -2313,6 +2324,8 @@ void schema_pass2(sdlCtx *ctx) sdlAttributePtr attr; sdlTypePtr type; + zend_hash_init(&ctx->fixupInProgress, 0, NULL, NULL, 0); + if (ctx->attributes) { ZEND_HASH_FOREACH_PTR(ctx->attributes, attr) { schema_attribute_fixup(ctx, attr); @@ -2346,6 +2359,8 @@ void schema_pass2(sdlCtx *ctx) zend_hash_destroy(ctx->attributeGroups); efree(ctx->attributeGroups); } + + zend_hash_destroy(&ctx->fixupInProgress); } void delete_model(zval *zv) diff --git a/ext/soap/php_sdl.h b/ext/soap/php_sdl.h index 3df4fbdca015..843b13141cc0 100644 --- a/ext/soap/php_sdl.h +++ b/ext/soap/php_sdl.h @@ -73,6 +73,7 @@ typedef struct sdlCtx { HashTable *attributes; /* array of sdlAttributePtr */ HashTable *attributeGroups; /* array of sdlTypesPtr */ + HashTable fixupInProgress; php_stream_context *context; zval old_header; } sdlCtx; diff --git a/ext/soap/tests/schema-selfref-attrgroup.phpt b/ext/soap/tests/schema-selfref-attrgroup.phpt new file mode 100644 index 000000000000..668826ea6c39 --- /dev/null +++ b/ext/soap/tests/schema-selfref-attrgroup.phpt @@ -0,0 +1,44 @@ +--TEST-- +SOAP XML Schema: self-referential attributeGroup fix-up recursion is rejected +--EXTENSIONS-- +soap +--FILE-- + + + + + + + + + + + + + + + + + + + +'; +$file = __DIR__ . '/schema-selfref-attrgroup.wsdl'; +file_put_contents($file, $wsdl); +try { + $c = new SoapClient($file, ['exceptions' => true, 'cache_wsdl' => WSDL_CACHE_NONE]); + echo "parsed ok\n"; +} catch (Throwable $e) { + echo $e::class, ": ", substr($e->getMessage(), 0, 120), "\n"; +} +echo "done\n"; +?> +--CLEAN-- + +--EXPECTF-- +SoapFault: SOAP-ERROR: Parsing Schema: recursive attributeGroup 'ref' attribute '%s' +done diff --git a/ext/soap/tests/schema-selfref-group.phpt b/ext/soap/tests/schema-selfref-group.phpt new file mode 100644 index 000000000000..be9d7df524f9 --- /dev/null +++ b/ext/soap/tests/schema-selfref-group.phpt @@ -0,0 +1,46 @@ +--TEST-- +SOAP XML Schema: self-referential group fix-up recursion is rejected +--EXTENSIONS-- +soap +--FILE-- + + + + + + + + + + + + + + + + + + + + + +'; +$file = __DIR__ . '/schema-selfref-group.wsdl'; +file_put_contents($file, $wsdl); +try { + $c = new SoapClient($file, ['exceptions' => true, 'cache_wsdl' => WSDL_CACHE_NONE]); + echo "parsed ok\n"; +} catch (Throwable $e) { + echo $e::class, ": ", substr($e->getMessage(), 0, 120), "\n"; +} +echo "done\n"; +?> +--CLEAN-- + +--EXPECTF-- +SoapFault: SOAP-ERROR: Parsing Schema: recursive group 'ref' attribute '%s' +done From 0c2fc141fcf9edb13b57b34c3843ed75e24ddcf5 Mon Sep 17 00:00:00 2001 From: Ilia Alshanetsky Date: Sun, 6 Sep 2026 11:01:32 -0400 Subject: [PATCH 3/3] odbc: check SQLColAttribute return codes in the field info functions odbc_column_lengths() and odbc_field_type() ignored the SQLColAttribute return code and returned their output buffer regardless, so a driver that fails the call left odbc_field_len(), odbc_field_scale() and odbc_field_type() reporting uninitialized stack. Check the code, warn with the driver's own diagnostic, and return 0 or false instead. Closes GH-23601 --- NEWS | 5 +++++ ext/odbc/php_odbc.c | 36 ++++++++++++++++++++++++++++++++++-- 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/NEWS b/NEWS index 6a6d61109709..935583e87338 100644 --- a/NEWS +++ b/NEWS @@ -66,6 +66,11 @@ PHP NEWS replacement when a \k backref has no closing delimiter. (Ilia Alshanetsky) +- ODBC: + . Fixed odbc_field_len(), odbc_field_scale() and odbc_field_type() + returning uninitialized memory when SQLColAttribute fails. + (Ilia Alshanetsky) + - Opcache: . Fixed a crash when the huge page SHM remap discarded mappings outside the reserved address range. (Piotr Hałas) diff --git a/ext/odbc/php_odbc.c b/ext/odbc/php_odbc.c index 8b78308f9426..798245c4ca99 100644 --- a/ext/odbc/php_odbc.c +++ b/ext/odbc/php_odbc.c @@ -683,6 +683,7 @@ void odbc_bindcols(odbc_result *result) result->values[i].value_max_len = 0; colfieldid = SQL_COLUMN_DISPLAY_SIZE; + result->values[i].name[0] = '\0'; rc = PHP_ODBC_SQLCOLATTRIBUTE(result->stmt, (SQLUSMALLINT)(i+1), PHP_ODBC_SQL_DESC_NAME, result->values[i].name, sizeof(result->values[i].name), &colnamelen, 0); result->values[i].coltype = 0; @@ -809,10 +810,30 @@ void odbc_transact(INTERNAL_FUNCTION_PARAMETERS, int type) } /* }}} */ +static void odbc_colattribute_failed(odbc_result *result, zend_long pv_num) +{ +#if defined(ODBCVER) && (ODBCVER >= 0x0300) + SQLINTEGER diag_error; + SQLCHAR diag_state[6]; + SQLCHAR diag_text[128]; + + memset(diag_state, '\0', sizeof(diag_state)); + memset(diag_text, '\0', sizeof(diag_text)); + if (SQL_SUCCESS == SQLGetDiagRec(SQL_HANDLE_STMT, result->stmt, 1, diag_state, &diag_error, diag_text, sizeof(diag_text), NULL)) { + diag_state[sizeof(diag_state) - 1] = '\0'; + diag_text[sizeof(diag_text) - 1] = '\0'; + php_error_docref(NULL, E_WARNING, "SQLColAttribute failed for field #%d: [%s] %s", (int)pv_num, diag_state, diag_text); + return; + } +#endif + php_error_docref(NULL, E_WARNING, "SQLColAttribute failed for field #%d", (int)pv_num); +} + /* {{{ odbc_column_lengths */ void odbc_column_lengths(INTERNAL_FUNCTION_PARAMETERS, int type) { odbc_result *result; + RETCODE rc; #if defined(HAVE_SOLID) || defined(HAVE_SOLID_30) /* this seems to be necessary for Solid2.3 ( tested by * tammy@synchronis.com) and Solid 3.0 (tested by eric@terra.telemediair.nl) @@ -849,7 +870,11 @@ void odbc_column_lengths(INTERNAL_FUNCTION_PARAMETERS, int type) RETURN_FALSE; } - PHP_ODBC_SQLCOLATTRIBUTE(result->stmt, (SQLUSMALLINT)pv_num, (SQLUSMALLINT) (type?SQL_COLUMN_SCALE:SQL_COLUMN_PRECISION), NULL, 0, NULL, &len); + rc = PHP_ODBC_SQLCOLATTRIBUTE(result->stmt, (SQLUSMALLINT)pv_num, (SQLUSMALLINT)(type ? SQL_COLUMN_SCALE : SQL_COLUMN_PRECISION), NULL, 0, NULL, &len); + if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) { + odbc_colattribute_failed(result, pv_num); + len = 0; + } RETURN_LONG(len); } @@ -2597,6 +2622,7 @@ PHP_FUNCTION(odbc_field_type) odbc_result *result; char tmp[32]; SQLSMALLINT tmplen; + RETCODE rc; zval *pv_res; zend_long pv_num; @@ -2622,7 +2648,13 @@ PHP_FUNCTION(odbc_field_type) RETURN_FALSE; } - PHP_ODBC_SQLCOLATTRIBUTE(result->stmt, (SQLUSMALLINT)pv_num, SQL_COLUMN_TYPE_NAME, tmp, 31, &tmplen, NULL); + tmp[0] = '\0'; + rc = PHP_ODBC_SQLCOLATTRIBUTE(result->stmt, (SQLUSMALLINT)pv_num, SQL_COLUMN_TYPE_NAME, tmp, sizeof(tmp) - 1, &tmplen, NULL); + if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) { + odbc_colattribute_failed(result, pv_num); + RETURN_FALSE; + } + RETURN_STRING(tmp); } /* }}} */