Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,20 @@ PHP NEWS
. Added Pdo\Pgsql::ATTR_CHUNK_SIZE to fetch a result set in chunks of the
given number of rows. (KentarouTakeda)

- PGSQL:
. Fixed the pg_insert(), pg_update() and pg_delete() flag error messages,
which did not name the set of flags actually accepted. (lacatoire)

- Phar:
. Fixed bug GH-23418 (Use-after-free when looking up mounted directories).
(Weilin Du)
. Fixed bug GH-23477 (Memory leak on duplicate native Phar manifest entries).
(Weilin Du)

- Standard:
. Fixed an out-of-bounds read when following a redirect response with an
empty Location header. (iliaal)
. Fixed read buffer compaction in php_stream_filter_flush(). (crystarm)


27 Aug 2026, PHP 8.6.0beta2
Expand Down
14 changes: 8 additions & 6 deletions ext/pgsql/pgsql.c
Original file line number Diff line number Diff line change
Expand Up @@ -5740,8 +5740,9 @@ PHP_FUNCTION(pg_insert)
}

if (option & ~(PGSQL_CONV_OPTS|PGSQL_DML_NO_CONV|PGSQL_DML_EXEC|PGSQL_DML_ASYNC|PGSQL_DML_STRING|PGSQL_DML_ESCAPE)) {
zend_argument_value_error(4, "must be a valid bit mask of PGSQL_CONV_FORCE_NULL, PGSQL_DML_NO_CONV, "
"PGSQL_DML_ESCAPE, PGSQL_DML_EXEC, PGSQL_DML_ASYNC, and PGSQL_DML_STRING");
zend_argument_value_error(4, "must be a valid bit mask of PGSQL_CONV_IGNORE_DEFAULT, PGSQL_CONV_FORCE_NULL, "
"PGSQL_CONV_IGNORE_NOT_NULL, PGSQL_DML_NO_CONV, PGSQL_DML_ESCAPE, PGSQL_DML_EXEC, PGSQL_DML_ASYNC, "
"and PGSQL_DML_STRING");
RETURN_THROWS();
}

Expand Down Expand Up @@ -5972,8 +5973,9 @@ PHP_FUNCTION(pg_update)
}

if (option & ~(PGSQL_CONV_OPTS|PGSQL_DML_NO_CONV|PGSQL_DML_EXEC|PGSQL_DML_STRING|PGSQL_DML_ESCAPE)) {
zend_argument_value_error(5, "must be a valid bit mask of PGSQL_CONV_FORCE_NULL, PGSQL_DML_NO_CONV, "
"PGSQL_DML_ESCAPE, PGSQL_DML_EXEC, PGSQL_DML_ASYNC, and PGSQL_DML_STRING");
zend_argument_value_error(5, "must be a valid bit mask of PGSQL_CONV_IGNORE_DEFAULT, PGSQL_CONV_FORCE_NULL, "
"PGSQL_CONV_IGNORE_NOT_NULL, PGSQL_DML_NO_CONV, PGSQL_DML_ESCAPE, PGSQL_DML_EXEC, "
"and PGSQL_DML_STRING");
RETURN_THROWS();
}

Expand Down Expand Up @@ -6004,7 +6006,7 @@ PHP_PGSQL_API zend_result php_pgsql_delete(PGconn *pg_link, const zend_string *t
ZEND_ASSERT(pg_link != NULL);
ZEND_ASSERT(table != NULL);
ZEND_ASSERT(Z_TYPE_P(ids_array) == IS_ARRAY);
ZEND_ASSERT(!(opt & ~(PGSQL_CONV_FORCE_NULL|PGSQL_DML_EXEC|PGSQL_DML_STRING|PGSQL_DML_ESCAPE)));
ZEND_ASSERT(!(opt & ~(PGSQL_CONV_FORCE_NULL|PGSQL_DML_NO_CONV|PGSQL_DML_EXEC|PGSQL_DML_STRING|PGSQL_DML_ESCAPE)));

if (zend_hash_num_elements(Z_ARRVAL_P(ids_array)) == 0) {
return FAILURE;
Expand Down Expand Up @@ -6074,7 +6076,7 @@ PHP_FUNCTION(pg_delete)

if (option & ~(PGSQL_CONV_FORCE_NULL|PGSQL_DML_NO_CONV|PGSQL_DML_EXEC|PGSQL_DML_STRING|PGSQL_DML_ESCAPE)) {
zend_argument_value_error(4, "must be a valid bit mask of PGSQL_CONV_FORCE_NULL, PGSQL_DML_NO_CONV, "
"PGSQL_DML_ESCAPE, PGSQL_DML_EXEC, PGSQL_DML_ASYNC, and PGSQL_DML_STRING");
"PGSQL_DML_ESCAPE, PGSQL_DML_EXEC, and PGSQL_DML_STRING");
RETURN_THROWS();
}

Expand Down
64 changes: 64 additions & 0 deletions ext/pgsql/tests/pg_dml_option_flags.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
--TEST--
pg_insert()/pg_update()/pg_delete(): the flags refused are the flags the message names
--EXTENSIONS--
pgsql
--SKIPIF--
<?php include("inc/skipif.inc"); ?>
--FILE--
<?php

include('inc/config.inc');
$table_name = 'table_pg_dml_option_flags';

$conn = pg_connect($conn_str);
pg_query($conn, "CREATE TABLE {$table_name} (id INT, id2 INT)");

/* PGSQL_DML_ASYNC is not part of the accepted mask of these two */
try {
pg_update($conn, $table_name, ['id2' => 2], ['id' => 1], PGSQL_DML_ASYNC | PGSQL_DML_STRING);
} catch (ValueError $e) {
echo $e->getMessage(), "\n";
}

try {
pg_delete($conn, $table_name, ['id' => 1], PGSQL_DML_ASYNC | PGSQL_DML_STRING);
} catch (ValueError $e) {
echo $e->getMessage(), "\n";
}

/* 1 << 13 is not one of the flags at all */
try {
pg_insert($conn, $table_name, ['id' => 1, 'id2' => 1], 1 << 13);
} catch (ValueError $e) {
echo $e->getMessage(), "\n";
}

/* but PGSQL_DML_ASYNC is accepted by pg_insert() and pg_select() */
var_dump(is_string(pg_insert($conn, $table_name, ['id' => 1, 'id2' => 1], PGSQL_DML_ASYNC | PGSQL_DML_STRING)));
var_dump(is_string(pg_select($conn, $table_name, ['id' => 1], PGSQL_DML_ASYNC | PGSQL_DML_STRING)));

/* every PGSQL_CONV_* flag the messages name is genuinely accepted */
var_dump(is_string(pg_insert($conn, $table_name, ['id' => 1, 'id2' => 1], PGSQL_CONV_IGNORE_DEFAULT | PGSQL_DML_STRING)));
var_dump(is_string(pg_update($conn, $table_name, ['id2' => 2], ['id' => 1], PGSQL_CONV_IGNORE_NOT_NULL | PGSQL_DML_STRING)));

/* PGSQL_DML_NO_CONV is accepted by pg_delete() and reaches its helper */
var_dump(is_string(pg_delete($conn, $table_name, ['id' => 1], PGSQL_DML_NO_CONV | PGSQL_DML_STRING)));

?>
--CLEAN--
<?php
include('inc/config.inc');
$table_name = 'table_pg_dml_option_flags';

$conn = pg_connect($conn_str);
pg_query($conn, "DROP TABLE IF EXISTS {$table_name}");
?>
--EXPECT--
pg_update(): Argument #5 ($flags) must be a valid bit mask of PGSQL_CONV_IGNORE_DEFAULT, PGSQL_CONV_FORCE_NULL, PGSQL_CONV_IGNORE_NOT_NULL, PGSQL_DML_NO_CONV, PGSQL_DML_ESCAPE, PGSQL_DML_EXEC, and PGSQL_DML_STRING
pg_delete(): Argument #4 ($flags) must be a valid bit mask of PGSQL_CONV_FORCE_NULL, PGSQL_DML_NO_CONV, PGSQL_DML_ESCAPE, PGSQL_DML_EXEC, and PGSQL_DML_STRING
pg_insert(): Argument #4 ($flags) must be a valid bit mask of PGSQL_CONV_IGNORE_DEFAULT, PGSQL_CONV_FORCE_NULL, PGSQL_CONV_IGNORE_NOT_NULL, PGSQL_DML_NO_CONV, PGSQL_DML_ESCAPE, PGSQL_DML_EXEC, PGSQL_DML_ASYNC, and PGSQL_DML_STRING
bool(true)
bool(true)
bool(true)
bool(true)
bool(true)
5 changes: 4 additions & 1 deletion ext/phar/phar.c
Original file line number Diff line number Diff line change
Expand Up @@ -1227,7 +1227,10 @@ static zend_result phar_parse_pharfile(php_stream *fp, const char *fname, size_t
} else {
str = entry.filename;
}
zend_hash_add_mem(&mydata->manifest, str, (void*)&entry, sizeof(phar_entry_info));
if (!zend_hash_add_mem(&mydata->manifest, str, (void*)&entry, sizeof(phar_entry_info))) {
phar_metadata_tracker_free(&entry.metadata_tracker, entry.is_persistent);
zend_string_free(entry.filename);
}
if (mydata->is_persistent) {
zend_string_release(str);
}
Expand Down
39 changes: 39 additions & 0 deletions ext/phar/tests/gh23477.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
--TEST--
GH-23477 (Memory leak on duplicate native Phar manifest entry)
--EXTENSIONS--
phar
--INI--
phar.require_hash=0
--FILE--
<?php
$stub = "<?php __HALT_COMPILER(); ?>\r\n";

function u32($value) {
return pack('V', $value);
}

function entry($name, $data, $metadata) {
$header = u32(strlen($name)) . $name
. u32(strlen($data)) . u32(0) . u32(strlen($data))
. u32(crc32($data)) . u32(0)
. u32(strlen($metadata)) . $metadata;
return [$header, $data];
}

$first = entry('a.txt', 'hello', 'i:1;');
$second = entry('a.txt', 'world', 'i:2;');
$manifest = u32(2) . "\x11\x00" . u32(0) . u32(0) . u32(0)
. $first[0] . $second[0];

file_put_contents(__DIR__ . '/gh23477.phar',
$stub . u32(strlen($manifest)) . $manifest . $first[1] . $second[1]);

$phar = new Phar(__DIR__ . '/gh23477.phar');
echo iterator_count($phar), "\n";
?>
--CLEAN--
<?php
@unlink(__DIR__ . '/gh23477.phar');
?>
--EXPECT--
1
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
--TEST--
stream_filter_remove() compacts unread data before appending flushed data
--FILE--
<?php
class ClosingSuffixFilter extends php_user_filter
{
public function filter($in, $out, &$consumed, $closing): int
{
while ($bucket = stream_bucket_make_writeable($in)) {
$consumed += $bucket->datalen;
stream_bucket_append($out, $bucket);
}
if ($closing) {
stream_bucket_append($out, stream_bucket_new($this->stream, 'END'));
}
return PSFS_PASS_ON;
}
}
stream_filter_register('closing-suffix', ClosingSuffixFilter::class);
$stream = fopen('php://memory', 'w+');
fwrite($stream, 'abcdef');
rewind($stream);
$filter = stream_filter_append($stream, 'closing-suffix', STREAM_FILTER_READ);
var_dump(fread($stream, 2));
var_dump(stream_filter_remove($filter));
var_dump(stream_get_contents($stream));
?>
--EXPECT--
string(2) "ab"
bool(true)
string(7) "cdefEND"
4 changes: 2 additions & 2 deletions main/streams/filter.c
Original file line number Diff line number Diff line change
Expand Up @@ -506,9 +506,9 @@ PHPAPI zend_result php_stream_filter_flush(php_stream_filter *filter, bool finis
/* Dump any newly flushed data to the read buffer */
if (stream->readpos > 0) {
/* Back the buffer up */
memcpy(stream->readbuf, stream->readbuf + stream->readpos, stream->writepos - stream->readpos);
stream->readpos = 0;
memmove(stream->readbuf, stream->readbuf + stream->readpos, stream->writepos - stream->readpos);
stream->writepos -= stream->readpos;
stream->readpos = 0;
}
if (flushed_size > (stream->readbuflen - stream->writepos)) {
/* Grow the buffer */
Expand Down