Commit e9ee4c0b9f5 for php.net

commit e9ee4c0b9f5b6f6028187ab87236f2f5fee6fc65
Merge: 250f138329e d88895b103f
Author: Ilia Alshanetsky <ilia@ilia.ws>
Date:   Fri Jul 24 11:30:37 2026 -0400

    Merge branch 'PHP-8.5'

    * PHP-8.5:
      Don't expose a freed stream resource to user filters

diff --cc NEWS
index b2f7d0220a8,ca863b4f381..244ea8ea94f
--- a/NEWS
+++ b/NEWS
@@@ -62,25 -28,18 +62,27 @@@ PH
    . Fixed various memory related issues in ext/sockets. (David Carlier)

  - Streams:
 +  . Added a new IO copy API used by php_stream_copy_to_stream_ex() that
 +    leverages platform primitives (sendfile, splice, copy_file_range,
 +    TransmitFile) for faster stream copying. (Jakub Zelenka, David Carlier)
 +  . Fixed bug GH-22841 (php_stream_copy_to_stream_ex() drops progress
 +    notifications when using the copy fast path). (David Carlier)
+   . Fixed bug GH-15836 (Use-after-free when a user stream filter accesses
+     $this->stream during the close flush). (iliaal)

 -30 Jul 2026, PHP 8.5.9
 +16 Jul 2026, PHP 8.6.0alpha2

  - Core:
 -  . Fixed bug GH-22290 (AST pretty printing does not correctly handle strings
 -    containing NUL). (iliaal)
 -  . Fixed bug GH-22206 (missing return in global register detection).
 -    (P3p111n0)
 +  . Sync Boost.Context assembly with 1.91.0. (kn1g78)
 +  . Fixed bug GH-22387 (AST pretty-printing drops meaningful parentheses around
 +    RHS of instanceof). (timwolla)
 +  . Fixed bug GH-15672 and GH-15911 (Stack overflow when an internal function
 +    recurses through zend_call_function, such as a self-attached SPL
 +    iterator). (iliaal)
    . Lock unmodified readonly properties for modification after clone-with.
      (NickSdot)
 +  . abort() instead of exit() on hard OOM. (realFlowControl)
 +  . perf: ZTS: move AG and SCNG into native __thread storage. (henderkes)

  - Calendar:
    . Fixed bug GH-22602 (gregoriantojd() and juliantojd() integer overflow with
diff --cc ext/standard/user_filters.c
index 986bbbd5f4d,0c27130ee02..126b434713a
--- a/ext/standard/user_filters.c
+++ b/ext/standard/user_filters.c
@@@ -132,36 -123,6 +132,40 @@@ static void userfilter_dtor(php_stream_
  	zval_ptr_dtor(obj);
  }

 +static zend_result userfilter_assign_stream(php_stream *stream, zval *obj,
 +		zend_string **stream_name_p, uint32_t orig_no_fclose)
 +{
 +	/* Give the userfilter class a hook back to the stream */
 +	const zend_class_entry *old_scope = EG(fake_scope);
 +	EG(fake_scope) = Z_OBJCE_P(obj);
 +
 +	zend_string *stream_name = ZSTR_INIT_LITERAL("stream", false);
 +	bool stream_property_exists = Z_OBJ_HT_P(obj)->has_property(Z_OBJ_P(obj), stream_name, ZEND_PROPERTY_EXISTS, NULL);
 +	if (stream_property_exists) {
 +		zval stream_zval;
- 		php_stream_to_zval(stream, &stream_zval);
++		if (EXPECTED(stream->res && stream->res->type >= 0)) {
++			php_stream_to_zval(stream, &stream_zval);
++		} else {
++			ZVAL_NULL(&stream_zval);
++		}
 +		zend_update_property_ex(Z_OBJCE_P(obj), Z_OBJ_P(obj), stream_name, &stream_zval);
 +		/* If property update threw an exception, skip filter execution */
 +		if (EG(exception)) {
 +			EG(fake_scope) = old_scope;
 +			zend_string_release(stream_name);
 +			stream->flags &= ~PHP_STREAM_FLAG_NO_FCLOSE;
 +			stream->flags |= orig_no_fclose;
 +			return FAILURE;
 +		}
 +		*stream_name_p = stream_name;
 +	} else {
 +		zend_string_release(stream_name);
 +	}
 +	EG(fake_scope) = old_scope;
 +
 +	return SUCCESS;
 +}
 +
  static php_stream_filter_status_t userfilter_filter(
  			php_stream *stream,
  			php_stream_filter *thisfilter,