Skip to content
Merged
2 changes: 1 addition & 1 deletion NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ PHP NEWS

- Sockets:
. Fixed socket_set_option() validation error messages for UDP_SEGMENT and
SO_LINGER options. (Weilin Du)
TCP_USER_TIMEOUT, and SO_LINGER options. (Weilin Du)

16 Jul 2026, PHP 8.6.0alpha2

Expand Down
2 changes: 1 addition & 1 deletion Zend/tests/display_error_function_args.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,6 @@ Warning: unlink('/'): %s in %s on line %d

Warning: password_hash(Object(SensitiveParameterValue), '2y', Array): The "salt" option has been ignored, since providing a custom salt is no longer supported in %s on line %d

Warning: unlink(/): %s in %s on line %d
Warning: unlink(): %s in %s on line %d

Warning: password_hash(): The "salt" option has been ignored, since providing a custom salt is no longer supported in %s on line %d
14 changes: 10 additions & 4 deletions ext/curl/interface.c
Original file line number Diff line number Diff line change
Expand Up @@ -522,9 +522,11 @@ PHP_MSHUTDOWN_FUNCTION(curl)
/* {{{ curl_write */
static size_t curl_write(char *data, size_t size, size_t nmemb, void *ctx)
{
ZEND_ASSERT(size == 1);

php_curl *ch = (php_curl *) ctx;
php_curl_write *write_handler = ch->handlers.write;
size_t length = size * nmemb;
size_t length = nmemb;

#if PHP_CURL_DEBUG
fprintf(stderr, "curl_write() called\n");
Expand Down Expand Up @@ -786,6 +788,8 @@ static int curl_ssh_hostkeyfunction(void *clientp, int keytype, const char *key,
/* {{{ curl_read */
static size_t curl_read(char *data, size_t size, size_t nmemb, void *ctx)
{
ZEND_ASSERT(size == 1);

php_curl *ch = (php_curl *)ctx;
php_curl_read *read_handler = ch->handlers.read;
size_t length = 0;
Expand All @@ -808,15 +812,15 @@ static size_t curl_read(char *data, size_t size, size_t nmemb, void *ctx)
} else {
ZVAL_NULL(&argv[1]);
}
ZVAL_LONG(&argv[2], (int)size * nmemb);
ZVAL_LONG(&argv[2], (zend_long) nmemb);

ch->in_callback = true;
zend_call_known_fcc(&read_handler->fcc, &retval, /* param_count */ 3, argv, /* named_params */ NULL);
ch->in_callback = false;
if (!Z_ISUNDEF(retval)) {
_php_curl_verify_handlers(ch, /* reporterror */ true);
if (Z_TYPE(retval) == IS_STRING) {
length = MIN(size * nmemb, Z_STRLEN(retval));
length = MIN(nmemb, Z_STRLEN(retval));
memcpy(data, Z_STRVAL(retval), length);
} else if (Z_TYPE(retval) == IS_LONG) {
length = Z_LVAL_P(&retval);
Expand Down Expand Up @@ -884,9 +888,11 @@ static int curl_seek(void *clientp, curl_off_t offset, int origin)
/* {{{ curl_write_header */
static size_t curl_write_header(char *data, size_t size, size_t nmemb, void *ctx)
{
ZEND_ASSERT(size == 1);

php_curl *ch = (php_curl *) ctx;
php_curl_write *write_handler = ch->handlers.write_header;
size_t length = size * nmemb;
size_t length = nmemb;

switch (write_handler->method) {
case PHP_CURL_STDOUT:
Expand Down
12 changes: 6 additions & 6 deletions ext/odbc/php_odbc.c
Original file line number Diff line number Diff line change
Expand Up @@ -1952,9 +1952,9 @@ bool odbc_sqlconnect(zval *zv, char *db, char *uid, char *pwd, int cur_opt, bool

/* Force UID and PWD to be set in the DSN */
if (use_uid_arg || use_pwd_arg) {
db_end--;
if ((unsigned char)*(db_end) == ';') {
*db_end = '\0';
size_t base_len = db_len;
if (base_len > 0 && db[base_len - 1] == ';') {
base_len--;
}

char *uid_quoted = NULL, *pwd_quoted = NULL;
Expand All @@ -1970,7 +1970,7 @@ bool odbc_sqlconnect(zval *zv, char *db, char *uid, char *pwd, int cur_opt, bool
}

if (!use_pwd_arg) {
spprintf(&ldb, 0, "%s;UID=%s;", db, uid_quoted);
spprintf(&ldb, 0, "%.*s;UID=%s;", (int) base_len, db, uid_quoted);
}
}

Expand All @@ -1985,12 +1985,12 @@ bool odbc_sqlconnect(zval *zv, char *db, char *uid, char *pwd, int cur_opt, bool
}

if (!use_uid_arg) {
spprintf(&ldb, 0, "%s;PWD=%s;", db, pwd_quoted);
spprintf(&ldb, 0, "%.*s;PWD=%s;", (int) base_len, db, pwd_quoted);
}
}

if (use_uid_arg && use_pwd_arg) {
spprintf(&ldb, 0, "%s;UID=%s;PWD=%s;", db, uid_quoted, pwd_quoted);
spprintf(&ldb, 0, "%.*s;UID=%s;PWD=%s;", (int) base_len, db, uid_quoted, pwd_quoted);
}

if (uid_quoted && should_quote_uid) {
Expand Down
20 changes: 20 additions & 0 deletions ext/odbc/tests/gh_odbc_dsn_immutability.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
--TEST--
odbc_connect() must not mutate the caller's DSN string
--EXTENSIONS--
odbc
--FILE--
<?php
/* The '=' selects the connection-string form and the trailing ';' is what the
* UID/PWD assembly used to overwrite with a NUL, in the caller's own buffer. */
$dsn = 'Driver=DoesNotExist;Database=x;SS001;';
const DSN_CONST = 'Driver=DoesNotExist;Database=x;SS001_CONST;';

@odbc_connect($dsn, 'u', 'p');
@odbc_connect(DSN_CONST, 'u', 'p');

var_dump($dsn);
var_dump(DSN_CONST);
?>
--EXPECT--
string(37) "Driver=DoesNotExist;Database=x;SS001;"
string(43) "Driver=DoesNotExist;Database=x;SS001_CONST;"
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,6 @@ array(3) {
}
Destroy [%s,%s]

Warning: unlink(%s): No such file or directory in %s on line %d
Warning: unlink(): No such file or directory in %s on line %d
Close [%s,PHPSESSID]
bool(true)
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,6 @@ GC [0]
bool(true)
Destroy [%s,PHPT-%d]

Warning: unlink(%s): No such file or directory in %s on line %d
Warning: unlink(): No such file or directory in %s on line %d
Close [%s,PHPSESSID]
bool(true)
39 changes: 28 additions & 11 deletions ext/soap/php_http.c
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,23 @@ static zend_string *get_http_headers(php_stream *socketd);
#define smart_str_append_const(str, const) \
smart_str_appendl(str,const,sizeof(const)-1)

static void soap_smart_str_append_header_value(smart_str *dest, const zend_string *value, const char *header_name)
{
const char *src = ZSTR_VAL(value);
size_t len = ZSTR_LEN(value);
size_t i = 0;
while (i < len && src[i] != '\r' && src[i] != '\n') {
i++;
}
if (i < len) {
smart_str_appendl(dest, src, i);
php_error_docref(NULL, E_WARNING,
"Header %s value contains newline characters and has been truncated", header_name);
} else {
smart_str_append(dest, value);
}
}

/* Proxy HTTP Authentication */
bool proxy_authentication(zval* this_ptr, smart_str* soap_headers)
{
Expand Down Expand Up @@ -607,25 +624,25 @@ bool make_http_soap_request(
smart_str_append_const(&soap_headers, "\r\n"
"Connection: Keep-Alive\r\n");
}
zend_string *ua_str = NULL;

tmp = Z_CLIENT_USER_AGENT_P(this_ptr);
if (Z_TYPE_P(tmp) == IS_STRING) {
if (Z_STRLEN_P(tmp) > 0) {
smart_str_append_const(&soap_headers, "User-Agent: ");
smart_str_append(&soap_headers, Z_STR_P(tmp));
smart_str_append_const(&soap_headers, "\r\n");
}
ua_str = Z_STR_P(tmp);
} else if (context &&
(tmp = php_stream_context_get_option(context, "http", "user_agent")) != NULL &&
Z_TYPE_P(tmp) == IS_STRING) {
if (Z_STRLEN_P(tmp) > 0) {
ua_str = Z_STR_P(tmp);
} else if (FG(user_agent)) {
ua_str = FG(user_agent);
}

if (ua_str) {
if (ZSTR_LEN(ua_str) > 0) {
smart_str_append_const(&soap_headers, "User-Agent: ");
smart_str_append(&soap_headers, Z_STR_P(tmp));
soap_smart_str_append_header_value(&soap_headers, ua_str, "User-Agent");
smart_str_append_const(&soap_headers, "\r\n");
}
} else if (FG(user_agent)) {
smart_str_append_const(&soap_headers, "User-Agent: ");
smart_str_append(&soap_headers, FG(user_agent));
smart_str_append_const(&soap_headers, "\r\n");
} else {
smart_str_append_const(&soap_headers, "User-Agent: PHP-SOAP/"PHP_VERSION"\r\n");
}
Expand Down
47 changes: 47 additions & 0 deletions ext/soap/tests/user_agent_header_injection.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
--TEST--
SoapClient must truncate a user_agent that contains newline characters
--EXTENSIONS--
soap
--SKIPIF--
<?php
if (!file_exists(__DIR__ . "/../../../sapi/cli/tests/php_cli_server.inc")) {
echo "skip sapi/cli/tests/php_cli_server.inc required but not found";
}
?>
--FILE--
<?php

include __DIR__ . "/../../../sapi/cli/tests/php_cli_server.inc";

$args = ["-d", "extension_dir=" . ini_get("extension_dir"), "-d", "extension=" . (substr(PHP_OS, 0, 3) == "WIN" ? "php_" : "") . "soap." . PHP_SHLIB_SUFFIX];
if (php_ini_loaded_file()) {
$args[] = "-c";
$args[] = php_ini_loaded_file();
}
$code = <<<'PHP'
$content = trim(file_get_contents("php://input")) . PHP_EOL;
PHP;

php_cli_server_start($code, null, $args);

ini_set('user_agent', "evil\r\nX-Injected: yes");

$client = new SoapClient(NULL, [
'location' => 'http://' . PHP_CLI_SERVER_ADDRESS,
'uri' => 'misc-uri',
'trace' => true,
]);

$client->__soapCall("foo", []);
echo $client->__getLastRequestHeaders();

?>
--EXPECTF--
Warning: SoapClient::__doRequest(): Header User-Agent value contains newline characters and has been truncated in %s on line %d
POST / HTTP/1.1
Host: localhost:%d
Connection: Keep-Alive
User-Agent: evil
Content-Type: text/xml; charset=utf-8
SOAPAction: "misc-uri#foo"
Content-Length: %d
2 changes: 1 addition & 1 deletion ext/sockets/sockets.c
Original file line number Diff line number Diff line change
Expand Up @@ -2154,7 +2154,7 @@ PHP_FUNCTION(socket_set_option)

// TCP_USER_TIMEOUT unsigned int
if (timeout < 0 || timeout > UINT_MAX) {
zend_argument_value_error(4, "must be of between 0 and %u", UINT_MAX);
zend_argument_value_error(4, "must be between 0 and %u", UINT_MAX);
RETURN_THROWS();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,6 @@ var_dump($retval_3 === $timeout);
socket_close($socket);
?>
--EXPECTF--
socket_setopt(): Argument #4 ($value) must be of between 0 and %d
socket_setopt(): Argument #4 ($value) must be between 0 and %d
bool(true)
bool(true)
4 changes: 2 additions & 2 deletions ext/sockets/tests/socket_setoption_tcpusertimeout_64bit.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ var_dump($retval_3 === $timeout);
socket_close($socket);
?>
--EXPECTF--
socket_setopt(): Argument #4 ($value) must be of between 0 and %d
socket_setopt(): Argument #4 ($value) must be of between 0 and %d
socket_setopt(): Argument #4 ($value) must be between 0 and %d
socket_setopt(): Argument #4 ($value) must be between 0 and %d
bool(true)
bool(true)
2 changes: 1 addition & 1 deletion ext/sqlite3/sqlite3.c
Original file line number Diff line number Diff line change
Expand Up @@ -469,7 +469,7 @@ PHP_METHOD(SQLite3, escapeString)
zend_string *sql;
char *ret;

if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS(), "S", &sql)) {
if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS(), "P", &sql)) {
RETURN_THROWS();
}

Expand Down
35 changes: 35 additions & 0 deletions ext/sqlite3/tests/gh_sqlite3_escapestring_nul.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
--TEST--
SQLite3::escapeString() rejects strings with embedded NUL bytes
--EXTENSIONS--
sqlite3
--FILE--
<?php

$cases = [
"\0",
"a\0b",
"secret\0x",
"foo\0\0bar",
];

foreach ($cases as $in) {
try {
SQLite3::escapeString($in);
echo "FAIL: no exception for ", bin2hex($in), "\n";
} catch (ValueError $e) {
echo "ValueError: ", $e->getMessage(), "\n";
}
}

echo "ok: ", SQLite3::escapeString("ok"), "\n";
echo "quote: ", SQLite3::escapeString("test''%"), "\n";
echo "empty: ", var_export(SQLite3::escapeString(""), true), "\n";
?>
--EXPECT--
ValueError: SQLite3::escapeString(): Argument #1 ($string) must not contain any null bytes
ValueError: SQLite3::escapeString(): Argument #1 ($string) must not contain any null bytes
ValueError: SQLite3::escapeString(): Argument #1 ($string) must not contain any null bytes
ValueError: SQLite3::escapeString(): Argument #1 ($string) must not contain any null bytes
ok: ok
quote: test''''%
empty: ''
8 changes: 4 additions & 4 deletions ext/standard/tests/file/copy_variation4.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ Destination file name => %s/
Size of source file => int(1500)
Size of destination file => int(%d)

Warning: unlink(%s): %s
Warning: unlink(): %s

-- Iteration 2 --
Existence of destination file before copy => bool(true)
Expand All @@ -103,7 +103,7 @@ Destination file name => %s/
Size of source file => int(1500)
Size of destination file => int(%d)

Warning: unlink(%s): %s
Warning: unlink(): %s

-- Iteration 3 --
Existence of destination file before copy => bool(true)
Expand All @@ -115,7 +115,7 @@ Destination file name => %s/
Size of source file => int(1500)
Size of destination file => int(%d)

Warning: unlink(%s): %s
Warning: unlink(): %s

-- Iteration 4 --
Existence of destination file before copy => bool(true)
Expand All @@ -127,7 +127,7 @@ Destination file name => %s/
Size of source file => int(1500)
Size of destination file => int(%d)

Warning: unlink(%s): %s
Warning: unlink(): %s

-- Iteration 5 --
Existence of destination file before copy => bool(false)
Expand Down
4 changes: 2 additions & 2 deletions ext/standard/tests/file/copy_variation5-win32.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ Destination file name => %s/CopY5.TMP
Size of source file => int(1500)
Size of destination file => int(1500)

Warning: unlink(%s/COPY5.TMP): No such file or directory in %s on line %d
Warning: unlink(): No such file or directory in %s on line %d

Warning: unlink(%s/CopY5.TMP): No such file or directory in %s on line %d
Warning: unlink(): No such file or directory in %s on line %d
*** Done ***
2 changes: 1 addition & 1 deletion ext/standard/tests/file/mkdir_rmdir_error.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,6 @@ echo "Done\n";
--EXPECTF--
*** Testing rmdir() on non-existent directory ***

Warning: rmdir(temp): No such file or directory in %s on line %d
Warning: rmdir(): No such file or directory in %s on line %d
bool(false)
Done
Original file line number Diff line number Diff line change
Expand Up @@ -1596,7 +1596,7 @@ bool(true)
*** Testing rmdir() on a non-empty directory ***
bool(true)

Warning: rmdir(%s/mkdir私はガラスを食べられます/): Directory not empty in %s on line %d
Warning: rmdir(): Directory not empty in %s on line %d
bool(false)

*** Testing mkdir() and rmdir() for binary safe functionality ***
Expand Down
2 changes: 1 addition & 1 deletion ext/standard/tests/file/mkdir_rmdir_variation-win32.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -1596,7 +1596,7 @@ bool(true)
*** Testing rmdir() on a non-empty directory ***
bool(true)

Warning: rmdir(%s/mkdir/): Directory not empty in %s on line %d
Warning: rmdir(): Directory not empty in %s on line %d
bool(false)

*** Testing mkdir() and rmdir() for binary safe functionality ***
Expand Down
Loading