Commit c135a869c14 for php.net

commit c135a869c1404a95d0ef0d36bfd3a0342cdc46aa
Author: Ilia Alshanetsky <ilia@ilia.ws>
Date:   Mon Aug 24 09:47:39 2026 -0400

    ext/pgsql: Restore binary-safe pg_lo_write()

    The conversion of the data parameter to Z_PARAM_PATH_STR() made
    pg_lo_write() reject any data containing a null byte, breaking writes of
    arbitrary binary payloads which were accepted before. Accept embedded
    null bytes again by parsing the data as a length-aware string. Sibling
    audit: the other Z_PARAM_PATH* conversions from the same change
    (pg_connect(), pg_lo_import(), table/statement identifiers) operate on
    paths or SQL text where null-byte rejection is intentional.

    Closes GH-23649

diff --git a/NEWS b/NEWS
index 60c8b43a325..9642ef64b09 100644
--- a/NEWS
+++ b/NEWS
@@ -22,6 +22,9 @@ PHP                                                                        NEWS
   . Fixed bug GH-23106 (mb_strpos() reads past the end of a haystack ending in
     a truncated UTF-8 sequence). (Lazizbek Ergashev)

+- PGSQL:
+  . Fixed pg_lo_write() rejecting data containing null bytes. (Ilia Alshanetsky)
+

 24 Sep 2026, PHP 8.5.11

diff --git a/ext/pgsql/pgsql.c b/ext/pgsql/pgsql.c
index 0106b074fd4..16e8589219b 100644
--- a/ext/pgsql/pgsql.c
+++ b/ext/pgsql/pgsql.c
@@ -2808,7 +2808,7 @@ PHP_FUNCTION(pg_lo_write)

 	ZEND_PARSE_PARAMETERS_START(2, 3)
 		Z_PARAM_OBJECT_OF_CLASS(pgsql_id, pgsql_lob_ce)
-		Z_PARAM_PATH_STR(str)
+		Z_PARAM_STR(str)
 		Z_PARAM_OPTIONAL
 		Z_PARAM_LONG_OR_NULL(z_len, z_len_is_null)
 	ZEND_PARSE_PARAMETERS_END();
diff --git a/ext/pgsql/tests/05large_object.phpt b/ext/pgsql/tests/05large_object.phpt
index 957f2dffa8e..a785b76a572 100644
--- a/ext/pgsql/tests/05large_object.phpt
+++ b/ext/pgsql/tests/05large_object.phpt
@@ -17,11 +17,6 @@
 if (!$oid) echo ("pg_lo_create() error\n");
 $handle = pg_lo_open ($db, $oid, "w");
 if (!$handle) echo ("pg_lo_open() error\n");
-try {
-	pg_lo_write ($handle, "large\0object data");
-} catch (\ValueError $e) {
-	echo $e->getMessage(), PHP_EOL;
-}
 pg_lo_write ($handle, "large object data");
 pg_lo_close ($handle);
 pg_exec ($db, "COMMIT");
@@ -110,7 +105,6 @@
 ?>
 --EXPECTF--
 create/write/close LO
-pg_lo_write(): Argument #2 ($data) must not contain any null bytes
 open/read/tell/seek/close LO
 string(5) "large"
 int(5)
diff --git a/ext/pgsql/tests/pg_lo_write_null_bytes.phpt b/ext/pgsql/tests/pg_lo_write_null_bytes.phpt
new file mode 100644
index 00000000000..44073692a18
--- /dev/null
+++ b/ext/pgsql/tests/pg_lo_write_null_bytes.phpt
@@ -0,0 +1,49 @@
+--TEST--
+pg_lo_write() must accept data containing null bytes
+--EXTENSIONS--
+pgsql
+--SKIPIF--
+<?php include("inc/skipif.inc"); ?>
+--FILE--
+<?php
+include('inc/config.inc');
+
+$db = pg_connect($conn_str);
+pg_exec($db, "BEGIN");
+$oid = pg_lo_create($db);
+$handle = pg_lo_open($db, $oid, "w");
+var_dump(pg_lo_write($handle, "bin\0ary\0data"));
+var_dump(pg_lo_write($handle, "ab\0cd", 4));
+var_dump(pg_lo_write($handle, "ab\0cd", 2));
+var_dump(pg_lo_write($handle, "", 0));
+try {
+    pg_lo_write($handle, "abc", -1);
+} catch (Throwable $e) {
+    echo $e::class, ": ", $e->getMessage(), "\n";
+}
+try {
+    pg_lo_write($handle, "abc", 4);
+} catch (Throwable $e) {
+    echo $e::class, ": ", $e->getMessage(), "\n";
+}
+pg_lo_close($handle);
+pg_exec($db, "ROLLBACK");
+
+pg_exec($db, "BEGIN");
+$oid = pg_lo_create($db);
+$handle = pg_lo_open($db, $oid, "w");
+pg_lo_write($handle, "x\0y");
+pg_lo_close($handle);
+$handle = pg_lo_open($db, $oid, "r");
+var_dump(bin2hex(pg_lo_read($handle)));
+pg_lo_close($handle);
+pg_exec($db, "ROLLBACK");
+?>
+--EXPECT--
+int(12)
+int(4)
+int(2)
+int(0)
+ValueError: pg_lo_write(): Argument #3 ($length) must be greater than or equal to 0
+ValueError: pg_lo_write(): Argument #3 ($length) must be less than or equal to the length of argument #2 ($buf)
+string(6) "780079"