[http] Fix out-of-bounds read on empty Location header

An empty Location header allocates a single byte for the NUL
terminator, so reading location[1] in the relative-redirect branch
over-reads heap memory and could append a garbage-derived path to the
redirect target instead of the correct host root. Use location_len
instead of strlen, and skip the relative join when location_len is 0,
so the second byte is never read.

Closes GH-23467
This commit is contained in:
Ilia Alshanetsky committed 2026-08-27 08:18:14 -04:00
1 parent 7d6523170e
commit 8196275133
3 files changed
+40 -2

No files matched your search

+2
View File
@@ -59,6 +59,8 @@ PHP NEWS
(Weilin Du)
- Standard:
. Fixed an out-of-bounds read when following a redirect response with an
empty Location header. (iliaal)
. Fixed a memory leak in array_merge_recursive() when the recursive merge of
an object converted to an array fails. (David Carlier)
+2 -2
View File
@@ -1052,7 +1052,7 @@ finish:
char *new_path = NULL;
if (strlen(header_info.location) < 8 ||
if (header_info.location_len < 8 ||
(strncasecmp(header_info.location, "http://", sizeof("http://")-1) &&
strncasecmp(header_info.location, "https://", sizeof("https://")-1) &&
strncasecmp(header_info.location, "ftp://", sizeof("ftp://")-1) &&
@@ -1060,7 +1060,7 @@ finish:
{
char *loc_path = NULL;
if (*header_info.location != '/') {
if (*(header_info.location+1) != '\0' && resource->path) {
if (header_info.location_len > 0 && resource->path) {
char *s = strrchr(ZSTR_VAL(resource->path), '/');
if (!s) {
s = ZSTR_VAL(resource->path);
@@ -0,0 +1,36 @@
--TEST--
Empty Location header must not over-read when building the redirect target
--FILE--
<?php
$serverCode = <<<'CODE'
$server = stream_socket_server("tcp://127.0.0.1:0", $errno, $errstr);
phpt_notify_server_start($server);
for ($n = 0; $n < 4; $n++) {
$conn = stream_socket_accept($server, 10);
if (!$conn) {
break;
}
$req = fgets($conn);
while (trim(fgets($conn)) !== '') {}
$uri = explode(' ', $req)[1];
if ($n < 3) {
fwrite($conn, "HTTP/1.1 302 Found\r\nLocation:\r\nContent-Length: 0\r\n\r\n");
} else {
$body = "uri=$uri";
fwrite($conn, "HTTP/1.1 200 OK\r\nContent-Length: " . strlen($body) . "\r\n\r\n$body");
}
fclose($conn);
}
CODE;
$clientCode = <<<'CODE'
$ctx = stream_context_create(['http' => ['follow_location' => 1]]);
echo @file_get_contents("http://{{ ADDR }}/a/b", false, $ctx), "\n";
CODE;
include sprintf("%s/../../../openssl/tests/ServerClientTestCase.inc", __DIR__);
ServerClientTestCase::getInstance()->run($clientCode, $serverCode);
?>
--EXPECT--
uri=/