Commit 0b7f50ad2e4 for php.net
commit 0b7f50ad2e48161c3d4e8b3ddd6d547ea72ed691
Author: Ilia Alshanetsky <ilia@ilia.ws>
Date: Sun Aug 9 19:44:21 2026 -0400
Balance stream error ops on file_put_contents LOCK_EX URL reject (#23186)
file_put_contents begins a stream error operation then returns early
when LOCK_EX is used with a non-file URL, without ending the operation.
After ~1000 such failures the depth limit is exhausted and later stream
ops fail. Call php_stream_error_operation_end before that RETURN_FALSE.
Closes GH-23186
diff --git a/NEWS b/NEWS
index 39d5a948e26..b091ea27614 100644
--- a/NEWS
+++ b/NEWS
@@ -2,6 +2,10 @@ PHP NEWS
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
?? ??? ????, PHP 8.6.0beta1
+- Streams:
+ . Fixed file_put_contents() LOCK_EX early return leaking stream error
+ operation depth. (iliaal)
+
- Core:
. Changed run-tests.php to run test subprocesses without a shell where
possible. (NickSdot)
diff --git a/ext/standard/file.c b/ext/standard/file.c
index fd6f578e4ef..d6a8b9f1d0e 100644
--- a/ext/standard/file.c
+++ b/ext/standard/file.c
@@ -474,6 +474,7 @@ PHP_FUNCTION(file_put_contents)
if (php_memnstr(filename, "://", sizeof("://") - 1, filename + filename_len)) {
if (strncasecmp(filename, "file://", sizeof("file://") - 1)) {
php_error_docref(NULL, E_WARNING, "Exclusive locks may only be set for regular files");
+ php_stream_error_operation_end(context);
RETURN_FALSE;
}
}
diff --git a/ext/standard/tests/file/file_put_contents_lock_ex_url.phpt b/ext/standard/tests/file/file_put_contents_lock_ex_url.phpt
new file mode 100644
index 00000000000..6bd346f39f4
--- /dev/null
+++ b/ext/standard/tests/file/file_put_contents_lock_ex_url.phpt
@@ -0,0 +1,20 @@
+--TEST--
+file_put_contents LOCK_EX on non-file URL balances stream error operation
+--INI--
+allow_url_fopen=1
+--FILE--
+<?php
+for ($i = 0; $i < 1005; $i++) {
+ @file_put_contents('http://127.0.0.1/x', 'data', LOCK_EX);
+}
+set_error_handler(function (int $errno, string $errstr): bool {
+ if (str_contains($errstr, 'Stream error operation depth exceeded')) {
+ echo "depth_exceeded\n";
+ }
+ return true;
+});
+file_put_contents('http://127.0.0.1/x', 'data', LOCK_EX);
+echo "done\n";
+?>
+--EXPECT--
+done