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
14 changes: 14 additions & 0 deletions NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,16 @@ PHP NEWS
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
?? ??? ????, PHP 8.6.0alpha3

- ODBC:
. Fixed bug GH-22668 (Heap buffer over-read when a column value exceeds the
driver-reported display size). (iliaal)

- PDO_ODBC:
. Fixed bug GH-22667 (Heap buffer over-read when a column value exceeds the
driver-reported display size). (iliaal)
. Fixed bug GH-22666 (Heap buffer overflow when an output parameter value is
longer than the declared maxlen). (iliaal)

- Reflection:
. Fixed bug GH-22681 (Reflection*::__toString() truncates on null bytes).
(DanielEScherzer)
Expand Down Expand Up @@ -336,6 +346,10 @@ PHP NEWS
. Mark Phar::buildFromIterator() base directory argument as a path.
(ndossche)

- phpdbg:
. Fixed GH-22480 (Use-after-free when re-watching an already-watched
variable). (iliaal)

- Posix:
. Added validity check to the flags argument for posix_access(). (arshidkv12)

Expand Down
35 changes: 25 additions & 10 deletions UPGRADING
Original file line number Diff line number Diff line change
Expand Up @@ -393,28 +393,36 @@ PHP 8.6 UPGRADE NOTES
6. New Functions
========================================

- Reflection:
. ReflectionConstant::inNamespace()
. ReflectionProperty::isReadable() and ReflectionProperty::isWritable()
RFC: https://wiki.php.net/rfc/isreadable-iswriteable
. ReflectionParameter::getDocComment().
RFC: https://wiki.php.net/rfc/parameter-doccomments

- Intl:
. grapheme_strrev() returns strrev for grapheme cluster unit.
. grapheme_strrev()
RFC: https://wiki.php.net/rfc/grapheme_strrev
. Locale::getDisplayKeyword() and Locale::getDisplayKeywordValue()
RFC: https://wiki.php.net/rfc/getdisplaykeyword_and_getdisplaykeywordvalue

- mysqli:
. Added mysqli::quote_string() and mysqli_quote_string().
RFC: https://wiki.php.net/rfc/mysqli_quote_string

- Reflection:
. ReflectionConstant::inNamespace()
. ReflectionProperty::isReadable() and ReflectionProperty::isWritable()
RFC: https://wiki.php.net/rfc/isreadable-iswriteable
. ReflectionParameter::getDocComment()
RFC: https://wiki.php.net/rfc/parameter-doccomments

- Standard:
. clamp() returns the given value if in range, else return the nearest
bound.
RFC: https://wiki.php.net/rfc/clamp_v2
. stream_last_errors() and stream_clear_errors().
. stream_last_errors() and stream_clear_errors()
RFC: https://wiki.php.net/rfc/stream_errors
. stream_socket_get_crypto_status().
. stream_socket_get_crypto_status()

- URI:
. Uri\Rfc3986\Uri::getUriType() and Uri\WhatWg\Url::isSpecialScheme()
RFC: https://wiki.php.net/rfc/uri_followup#uri_type_detection
. Uri\Rfc3986\Uri::getHostType() and Uri\WhatWg\Url::getHostType()
RFC: https://wiki.php.net/rfc/uri_followup#host_type_detection

- Zip:
. ZipArchive::openString()
Expand All @@ -424,6 +432,9 @@ PHP 8.6 UPGRADE NOTES
7. New Classes and Interfaces
========================================

- Intl:
. IntlNumberRangeFormatter

- OpenSSL:
. Openssl\OpensslException
. Openssl\Session
Expand Down Expand Up @@ -457,6 +468,10 @@ PHP 8.6 UPGRADE NOTES
. Io\Poll\InvalidHandleException
. StreamPollHandle

- URI:
. Uri\Rfc3986\UriBuilder
RFC: https://wiki.php.net/rfc/uri_followup#uri_building

========================================
8. Removed Extensions and SAPIs
========================================
Expand Down
14 changes: 12 additions & 2 deletions ext/odbc/php_odbc.c
Original file line number Diff line number Diff line change
Expand Up @@ -661,6 +661,7 @@ void odbc_bindcols(odbc_result *result)

for(i = 0; i < result->numcols; i++) {
bool char_extra_alloc = false;
result->values[i].value_max_len = 0;
colfieldid = SQL_COLUMN_DISPLAY_SIZE;

rc = SQLColAttribute(result->stmt, (SQLUSMALLINT)(i+1), SQL_DESC_NAME,
Expand Down Expand Up @@ -740,6 +741,7 @@ void odbc_bindcols(odbc_result *result)
displaysize *= 4;
}
result->values[i].value = (char *)emalloc(displaysize + 1);
result->values[i].value_max_len = displaysize;
rc = SQLBindCol(result->stmt, (SQLUSMALLINT)(i+1), SQL_C_CHAR, result->values[i].value,
displaysize + 1, &result->values[i].vallen);
break;
Expand Down Expand Up @@ -1458,7 +1460,11 @@ static void php_odbc_fetch(INTERNAL_FUNCTION_PARAMETERS, bool return_array, php_
ZVAL_FALSE(&tmp);
break;
}
ZVAL_STRINGL(&tmp, result->values[i].value, result->values[i].vallen);
SQLLEN str_len = result->values[i].vallen;
if (str_len > result->values[i].value_max_len) {
str_len = result->values[i].value_max_len;
}
ZVAL_STRINGL(&tmp, result->values[i].value, str_len);
break;
}

Expand Down Expand Up @@ -1671,7 +1677,11 @@ PHP_FUNCTION(odbc_result)
php_error_docref(NULL, E_WARNING, "Cannot get data of column #%d (driver cannot determine length)", field_ind + 1);
RETURN_FALSE;
} else {
RETURN_STRINGL(result->values[field_ind].value, result->values[field_ind].vallen);
SQLLEN str_len = result->values[field_ind].vallen;
if (str_len > result->values[field_ind].value_max_len) {
str_len = result->values[field_ind].value_max_len;
}
RETURN_STRINGL(result->values[field_ind].value, str_len);
}
break;
}
Expand Down
1 change: 1 addition & 0 deletions ext/odbc/php_odbc_includes.h
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ typedef struct odbc_result_value {
char name[256];
char *value;
SQLLEN vallen;
SQLLEN value_max_len;
SQLLEN coltype;
} odbc_result_value;

Expand Down
2 changes: 2 additions & 0 deletions ext/odbc/tests/config.inc
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
<?php

defined('ODBC_SQLITE_DSN') || define('ODBC_SQLITE_DSN', "Driver=SQLite3;Database=:memory:");

$dsn = getenv("ODBC_TEST_DSN");
$user = getenv("ODBC_TEST_USER");
$pass = getenv("ODBC_TEST_PASS");
Expand Down
33 changes: 33 additions & 0 deletions ext/odbc/tests/gh22668.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
--TEST--
GH-22668 (Heap buffer over-read when a column value exceeds the bound buffer)
--EXTENSIONS--
odbc
--SKIPIF--
<?php
include __DIR__ . "/config.inc";
$conn = @odbc_connect(ODBC_SQLITE_DSN, null, null);
if (!$conn) {
die("skip requires the SQLite3 ODBC driver");
}
?>
--FILE--
<?php
include __DIR__ . "/config.inc";
$conn = odbc_connect(ODBC_SQLITE_DSN, null, null);

// The SQLite3 driver reports a 255 byte display size for a computed column, so
// the bound buffer holds at most 255 bytes while the value is far longer. A
// conforming driver truncates into the buffer but reports the full length; the
// returned string must stay within the buffer, not over-read past it.
$result = odbc_exec($conn, "SELECT printf('%.*c', 4096, 'A') AS data");
$row = odbc_fetch_array($result);
$s = $row['data'];

echo "clamped to buffer: "; var_dump(strlen($s) < 4096);
echo "only value bytes: "; var_dump(strlen($s) === substr_count($s, 'A'));

odbc_close($conn);
?>
--EXPECT--
clamped to buffer: bool(true)
only value bytes: bool(true)
20 changes: 19 additions & 1 deletion ext/pdo_odbc/odbc_stmt.c
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,7 @@ static int odbc_stmt_param_hook(pdo_stmt_t *stmt, struct pdo_bound_param_data *p
param->driver_data = P;

P->len = 0; /* is re-populated each EXEC_PRE */
P->outbuflen = 0;
P->outbuf = NULL;

P->is_unicode = pdo_odbc_sqltype_is_unicode(S, sqltype);
Expand All @@ -380,6 +381,7 @@ static int odbc_stmt_param_hook(pdo_stmt_t *stmt, struct pdo_bound_param_data *p
P->len *= 2;
}
P->outbuf = emalloc(P->len + (P->is_unicode ? 2:1));
P->outbuflen = P->len;
}
}

Expand Down Expand Up @@ -477,10 +479,16 @@ static int odbc_stmt_param_hook(pdo_stmt_t *stmt, struct pdo_bound_param_data *p
case PDO_ODBC_CONV_FAIL:
case PDO_ODBC_CONV_NOT_REQUIRED:
P->len = Z_STRLEN_P(parameter);
if (P->len > P->outbuflen) {
P->len = P->outbuflen;
}
memcpy(P->outbuf, Z_STRVAL_P(parameter), P->len);
break;
case PDO_ODBC_CONV_OK:
P->len = ulen;
if (P->len > P->outbuflen) {
P->len = P->outbuflen;
}
memcpy(P->outbuf, S->convbuf, P->len);
break;
}
Expand All @@ -502,6 +510,9 @@ static int odbc_stmt_param_hook(pdo_stmt_t *stmt, struct pdo_bound_param_data *p
zval_ptr_dtor(parameter);

if (P->len >= 0) {
if (P->len > P->outbuflen) {
P->len = P->outbuflen;
}
ZVAL_STRINGL(parameter, P->outbuf, P->len);
switch (pdo_odbc_ucs22utf8(P->is_unicode, parameter)) {
case PDO_ODBC_CONV_FAIL:
Expand Down Expand Up @@ -761,7 +772,14 @@ static int odbc_stmt_get_col(pdo_stmt_t *stmt, int colno, zval *result, enum pdo
return 1;
} else if (C->fetched_len >= 0) {
/* it was stored perfectly */
ZVAL_STRINGL_FAST(result, C->data, C->fetched_len);
SQLLEN data_len = C->fetched_len;
if (!C->is_long) {
SQLLEN max_len = C->is_unicode ? (SQLLEN)C->datalen + 1 : (SQLLEN)C->datalen;
if (data_len > max_len) {
data_len = max_len;
}
}
ZVAL_STRINGL_FAST(result, C->data, data_len);
if (C->is_unicode) {
goto unicode_conv;
}
Expand Down
1 change: 1 addition & 0 deletions ext/pdo_odbc/php_pdo_odbc_int.h
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@ typedef struct {

typedef struct {
SQLLEN len;
SQLLEN outbuflen;
SQLSMALLINT paramtype;
char *outbuf;
unsigned is_unicode:1;
Expand Down
3 changes: 3 additions & 0 deletions ext/pdo_odbc/tests/config.inc
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<?php

const PDO_ODBC_SQLITE_DSN = "odbc:Driver=SQLite3;Database=:memory:";
30 changes: 30 additions & 0 deletions ext/pdo_odbc/tests/gh22666.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
--TEST--
GH-22666 (Heap buffer overflow when an output param value exceeds its maxlen)
--EXTENSIONS--
pdo_odbc
--SKIPIF--
<?php
require __DIR__ . '/config.inc';
try {
$pdo = new PDO(PDO_ODBC_SQLITE_DSN);
} catch (PDOException $e) {
die("skip requires the SQLite3 ODBC driver");
}
?>
--FILE--
<?php
require __DIR__ . '/config.inc';
$pdo = new PDO(PDO_ODBC_SQLITE_DSN);

// An INPUT_OUTPUT parameter declares a maxlen of 4, so the output buffer is 4
// bytes, but the runtime value is longer. The copy into that buffer must be
// bounded to the declared capacity, not overflow it.
$value = str_repeat('A', 64);
$stmt = $pdo->prepare('SELECT ?');
$stmt->bindParam(1, $value, PDO::PARAM_STR | PDO::PARAM_INPUT_OUTPUT, 4);
$stmt->execute();

echo "bounded to maxlen: "; var_dump($value);
?>
--EXPECT--
bounded to maxlen: string(4) "AAAA"
32 changes: 32 additions & 0 deletions ext/pdo_odbc/tests/gh22667.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
--TEST--
GH-22667 (Heap buffer over-read when a column value exceeds the bound buffer)
--EXTENSIONS--
pdo_odbc
--SKIPIF--
<?php
require __DIR__ . '/config.inc';
try {
$pdo = new PDO(PDO_ODBC_SQLITE_DSN);
} catch (PDOException $e) {
die("skip requires the SQLite3 ODBC driver");
}
?>
--FILE--
<?php
require __DIR__ . '/config.inc';
$pdo = new PDO(PDO_ODBC_SQLITE_DSN);

// The SQLite3 driver reports a 255 byte display size for a computed column, so
// the short-bound buffer holds at most 255 bytes while the value is far longer.
// A conforming driver truncates into the buffer but reports the full length; the
// returned string must stay within the buffer, not over-read past it.
$stmt = $pdo->query("SELECT printf('%.*c', 4096, 'A') AS data");
$row = $stmt->fetch(PDO::FETCH_ASSOC);
$s = $row['data'];

echo "clamped to buffer: "; var_dump(strlen($s) < 4096);
echo "only value bytes: "; var_dump(strlen($s) === substr_count($s, 'A'));
?>
--EXPECT--
clamped to buffer: bool(true)
only value bytes: bool(true)
22 changes: 21 additions & 1 deletion ext/sockets/tests/socket_recvfrom_afpacket_no_port.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,30 @@ $ethertype = pack("n", 0x9000);
$payload = "no port test";
$frame = str_pad($dst_mac . $src_mac . $ethertype . $payload, 60, "\x00");

// ETH_P_ALL sockets on loopback can observe unrelated localhost traffic from
// the parallel test runner. Read until we see our own frame, then validate the
// address returned by recvfrom() without the optional port argument.
function recv_matching(Socket $s, string $header, int $maxlen = 65536, ?string &$addr = null, &$bytes = null): string|false {
socket_set_nonblock($s);
$deadline = microtime(true) + 5.0;
while (microtime(true) < $deadline) {
$recvBytes = @socket_recvfrom($s, $buf, $maxlen, 0, $addr);
if ($recvBytes !== false && is_string($buf) && str_starts_with($buf, $header)) {
$bytes = $recvBytes;
return $buf;
}
if ($recvBytes === false) {
usleep(1000);
}
}
return false;
}

socket_sendto($s_send, $frame, strlen($frame), 0, "lo", 1);

// recvfrom without the optional 6th argument (port/ifindex).
$bytes = socket_recvfrom($s_recv, $buf, 65536, 0, $addr);
$bytes = 0;
$buf = recv_matching($s_recv, $dst_mac . $src_mac . $ethertype, 65536, $addr, $bytes);
var_dump($bytes >= 60);
var_dump($addr === 'lo');

Expand Down
6 changes: 4 additions & 2 deletions ext/standard/fsock.c
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
#include <stddef.h>
#include "php_network.h"
#include "file.h"
#include "streams/php_streams_int.h"

static size_t php_fsockopen_format_host_port(char **message, const char *prefix, size_t prefix_len,
const char *host, size_t host_len, zend_long port)
Expand Down Expand Up @@ -82,8 +83,9 @@ static void php_fsockopen_stream(INTERNAL_FUNCTION_PARAMETERS, int persistent)
}

if (persistent) {
php_fsockopen_format_host_port(&hashkey, "pfsockopen__", strlen("pfsockopen__"), host,
host_len, port);
zend_string *escaped = php_stream_escape_persistent_key(host, host_len);
spprintf(&hashkey, 0, "pfsockopen__%s:" ZEND_LONG_FMT, ZSTR_VAL(escaped), port);
zend_string_release_ex(escaped, false);
}

if (port > 0) {
Expand Down
5 changes: 4 additions & 1 deletion ext/standard/streamsfuncs.c
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
#include "streamsfuncs.h"
#include "php_network.h"
#include "php_string.h"
#include "streams/php_streams_int.h"
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
Expand Down Expand Up @@ -139,7 +140,9 @@ PHP_FUNCTION(stream_socket_client)
context = php_stream_context_from_zval(zcontext, flags & PHP_FILE_NO_DEFAULT_CONTEXT);

if (flags & PHP_STREAM_CLIENT_PERSISTENT) {
spprintf(&hashkey, 0, "stream_socket_client__%s", ZSTR_VAL(host));
zend_string *escaped = php_stream_escape_persistent_key(ZSTR_VAL(host), ZSTR_LEN(host));
spprintf(&hashkey, 0, "stream_socket_client__%s", ZSTR_VAL(escaped));
zend_string_release_ex(escaped, false);
}

/* prepare the timeout value for use */
Expand Down
Loading