Commit 60d8e92ed4a for php.net
commit 60d8e92ed4a0c1b310a81103f3b423713aece6c8
Author: Ilia Alshanetsky <ilia@ilia.ws>
Date: Thu Jun 25 13:35:08 2026 -0400
Guard uninitialized SplFileObject in fputcsv() and next()
fputcsv() and next() reached the file stream without the
CHECK_SPL_FILE_OBJECT_IS_INITIALIZED guard their siblings carry, so
invoking them via reflection on an object from
newInstanceWithoutConstructor() (constructor bypassed, stream NULL)
crashed instead of throwing. next() only touches the stream with the
READ_AHEAD flag, itself settable through the equally unguarded
setFlags(). Both now throw "Object not initialized".
Fixes GH-16217
Closes GH-22454
diff --git a/NEWS b/NEWS
index 7dd1aea4b88..39f2359152f 100644
--- a/NEWS
+++ b/NEWS
@@ -24,6 +24,8 @@ PHP NEWS
(jorgsowa)
. Ignore leading back-slash in class_parents(), class_implements(), and
class_uses(). (jorgsowa)
+ . Fixed bug GH-16217 (SplFileObject::fputcsv() on an uninitialized object
+ segfaults). (iliaal)
- Standard:
. Fixed bug GH-22360 (convert.base64-encode corruption on
diff --git a/ext/spl/spl_directory.c b/ext/spl/spl_directory.c
index f83ea9f84ac..eb3fb8caef6 100644
--- a/ext/spl/spl_directory.c
+++ b/ext/spl/spl_directory.c
@@ -2224,6 +2224,8 @@ PHP_METHOD(SplFileObject, next)
RETURN_THROWS();
}
+ CHECK_SPL_FILE_OBJECT_IS_INITIALIZED(intern);
+
spl_filesystem_file_free_line(intern);
if (SPL_HAS_FLAG(intern->flags, SPL_FILE_OBJECT_READ_AHEAD)) {
spl_filesystem_file_read_line(ZEND_THIS, intern, true);
@@ -2376,6 +2378,8 @@ PHP_METHOD(SplFileObject, fputcsv)
RETURN_THROWS();
}
+ CHECK_SPL_FILE_OBJECT_IS_INITIALIZED(intern);
+
if (delim) {
if (d_len != 1) {
zend_argument_value_error(2, "must be a single character");
diff --git a/ext/spl/tests/gh16217.phpt b/ext/spl/tests/gh16217.phpt
new file mode 100644
index 00000000000..71760389c8e
--- /dev/null
+++ b/ext/spl/tests/gh16217.phpt
@@ -0,0 +1,35 @@
+--TEST--
+GH-16217 (SplFileObject methods on an uninitialized object segfault)
+--FILE--
+<?php
+function uninitialized(): SplFileObject {
+ return (new ReflectionClass(SplFileObject::class))->newInstanceWithoutConstructor();
+}
+
+try {
+ (new ReflectionMethod(SplFileObject::class, "fputcsv"))->invoke(uninitialized(), []);
+} catch (Error $e) {
+ echo "fputcsv: ", $e->getMessage(), "\n";
+}
+
+try {
+ (new ReflectionMethod(SplFileObject::class, "next"))->invoke(uninitialized());
+} catch (Error $e) {
+ echo "next: ", $e->getMessage(), "\n";
+}
+
+$obj = uninitialized();
+(new ReflectionMethod(SplFileObject::class, "setFlags"))->invoke($obj, SplFileObject::READ_AHEAD);
+try {
+ (new ReflectionMethod(SplFileObject::class, "next"))->invoke($obj);
+} catch (Error $e) {
+ echo "next (READ_AHEAD): ", $e->getMessage(), "\n";
+}
+
+echo "Done\n";
+?>
+--EXPECT--
+fputcsv: Object not initialized
+next: Object not initialized
+next (READ_AHEAD): Object not initialized
+Done