File php7-CVE-2025-1861.patch of Package php7.37963
From ac1a054bb3eb5994a199e8b18cca28cbabf5943e Mon Sep 17 00:00:00 2001
From: Jakub Zelenka <bukka@php.net>
Date: Tue, 4 Mar 2025 09:01:34 +0100
Subject: [PATCH] Fix GHSA-52jp-hrpf-2jff: http redirect location truncation
It converts the allocation of location to be on heap instead of stack
and errors if the location length is greater than 8086 bytes.
---
ext/standard/http_fopen_wrapper.c | 87 ++++++++++++-------
.../tests/http/ghsa-52jp-hrpf-2jff-001.phpt | 58 +++++++++++++
.../tests/http/ghsa-52jp-hrpf-2jff-002.phpt | 55 ++++++++++++
3 files changed, 168 insertions(+), 32 deletions(-)
create mode 100644 ext/standard/tests/http/ghsa-52jp-hrpf-2jff-001.phpt
create mode 100644 ext/standard/tests/http/ghsa-52jp-hrpf-2jff-002.phpt
Index: php-7.4.33/ext/standard/http_fopen_wrapper.c
===================================================================
--- php-7.4.33.orig/ext/standard/http_fopen_wrapper.c
+++ php-7.4.33/ext/standard/http_fopen_wrapper.c
@@ -70,15 +70,16 @@
#include "php_fopen_wrappers.h"
-#define HTTP_HEADER_BLOCK_SIZE 1024
-#define PHP_URL_REDIRECT_MAX 20
-#define HTTP_HEADER_USER_AGENT 1
-#define HTTP_HEADER_HOST 2
-#define HTTP_HEADER_AUTH 4
-#define HTTP_HEADER_FROM 8
-#define HTTP_HEADER_CONTENT_LENGTH 16
-#define HTTP_HEADER_TYPE 32
-#define HTTP_HEADER_CONNECTION 64
+#define HTTP_HEADER_BLOCK_SIZE 1024
+#define HTTP_HEADER_MAX_LOCATION_SIZE 8182 /* 8192 - 10 (size of "Location: ") */
+#define PHP_URL_REDIRECT_MAX 20
+#define HTTP_HEADER_USER_AGENT 1
+#define HTTP_HEADER_HOST 2
+#define HTTP_HEADER_AUTH 4
+#define HTTP_HEADER_FROM 8
+#define HTTP_HEADER_CONTENT_LENGTH 16
+#define HTTP_HEADER_TYPE 32
+#define HTTP_HEADER_CONNECTION 64
#define HTTP_WRAPPER_HEADER_INIT 1
#define HTTP_WRAPPER_REDIRECTED 2
@@ -138,17 +139,15 @@ typedef struct _php_stream_http_response
size_t file_size;
bool error;
bool follow_location;
- char location[HTTP_HEADER_BLOCK_SIZE];
+ char *location;
+ size_t location_len;
} php_stream_http_response_header_info;
static void php_stream_http_response_header_info_init(
php_stream_http_response_header_info *header_info)
{
- header_info->transfer_encoding = NULL;
- header_info->file_size = 0;
- header_info->error = false;
+ memset(header_info, 0, sizeof(php_stream_http_response_header_info));
header_info->follow_location = 1;
- header_info->location[0] = '\0';
}
/* Trim white spaces from response header line and update its length */
@@ -274,7 +273,22 @@ static zend_string *php_stream_http_resp
* RFC 7238 defines 308: http://tools.ietf.org/html/rfc7238 */
header_info->follow_location = 0;
}
- strlcpy(header_info->location, last_header_value, sizeof(header_info->location));
+ size_t last_header_value_len = strlen(last_header_value);
+ if (last_header_value_len > HTTP_HEADER_MAX_LOCATION_SIZE) {
+ header_info->error = true;
+ php_stream_wrapper_log_error(wrapper, options,
+ "HTTP Location header size is over the limit of %d bytes",
+ HTTP_HEADER_MAX_LOCATION_SIZE);
+ zend_string_efree(last_header_line_str);
+ return NULL;
+ }
+ if (header_info->location_len == 0) {
+ header_info->location = emalloc(last_header_value_len + 1);
+ } else if (header_info->location_len <= last_header_value_len) {
+ header_info->location = erealloc(header_info->location, last_header_value_len + 1);
+ }
+ header_info->location_len = last_header_value_len;
+ memcpy(header_info->location, last_header_value, last_header_value_len + 1);
} else if (!strncasecmp(last_header_line, "Content-Type:", sizeof("Content-Type:")-1)) {
php_stream_notify_info(context, PHP_STREAM_NOTIFY_MIME_TYPE_IS, last_header_value, 0);
} else if (!strncasecmp(last_header_line, "Content-Length:", sizeof("Content-Length:")-1)) {
@@ -557,6 +571,8 @@ finish:
}
}
+ php_stream_http_response_header_info_init(&header_info);
+
if (stream == NULL)
goto out;
@@ -934,8 +950,6 @@ finish:
}
}
- php_stream_http_response_header_info_init(&header_info);
-
/* read past HTTP headers */
while (!php_stream_eof(stream)) {
size_t http_header_line_length;
@@ -1005,29 +1019,28 @@ finish:
last_header_line_str, NULL, NULL, response_code, response_header, &header_info);
}
- if (!reqok || (header_info.location[0] != '\0' && header_info.follow_location)) {
+ if (!reqok || (header_info.location != NULL && header_info.follow_location)) {
if (!header_info.follow_location || (((options & STREAM_ONLY_GET_HEADERS) || ignore_errors) && redirect_max <= 1)) {
goto out;
}
- if (header_info.location[0] != '\0')
+ if (header_info.location != NULL)
php_stream_notify_info(context, PHP_STREAM_NOTIFY_REDIRECTED, header_info.location, 0);
php_stream_close(stream);
stream = NULL;
- if (header_info.location[0] != '\0') {
+ if (header_info.location != NULL) {
- char new_path[HTTP_HEADER_BLOCK_SIZE];
- char loc_path[HTTP_HEADER_BLOCK_SIZE];
+ char *new_path = NULL;
- *new_path='\0';
if (strlen(header_info.location) < 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) &&
strncasecmp(header_info.location, "ftps://", sizeof("ftps://")-1)))
{
+ char *loc_path = NULL;
if (*header_info.location != '/') {
if (*(header_info.location+1) != '\0' && resource->path) {
char *s = strrchr(ZSTR_VAL(resource->path), '/');
@@ -1045,31 +1058,35 @@ finish:
if (resource->path &&
ZSTR_VAL(resource->path)[0] == '/' &&
ZSTR_VAL(resource->path)[1] == '\0') {
- snprintf(loc_path, sizeof(loc_path) - 1, "%s%s",
- ZSTR_VAL(resource->path), header_info.location);
+ spprintf(&loc_path, 0, "%s%s", ZSTR_VAL(resource->path), header_info.location);
} else {
- snprintf(loc_path, sizeof(loc_path) - 1, "%s/%s",
- ZSTR_VAL(resource->path), header_info.location);
+ spprintf(&loc_path, 0, "%s/%s", ZSTR_VAL(resource->path), header_info.location);
}
} else {
- snprintf(loc_path, sizeof(loc_path) - 1, "/%s", header_info.location);
+ spprintf(&loc_path, 0, "/%s", header_info.location);
}
} else {
- strlcpy(loc_path, header_info.location, sizeof(loc_path));
+ loc_path = header_info.location;
+ header_info.location = NULL;
}
if ((use_ssl && resource->port != 443) || (!use_ssl && resource->port != 80)) {
- snprintf(new_path, sizeof(new_path) - 1, "%s://%s:%d%s", ZSTR_VAL(resource->scheme), ZSTR_VAL(resource->host), resource->port, loc_path);
+ spprintf(&new_path, 0, "%s://%s:%d%s", ZSTR_VAL(resource->scheme),
+ ZSTR_VAL(resource->host), resource->port, loc_path);
} else {
- snprintf(new_path, sizeof(new_path) - 1, "%s://%s%s", ZSTR_VAL(resource->scheme), ZSTR_VAL(resource->host), loc_path);
+ spprintf(&new_path, 0, "%s://%s%s", ZSTR_VAL(resource->scheme),
+ ZSTR_VAL(resource->host), loc_path);
}
+ efree(loc_path);
} else {
- strlcpy(new_path, header_info.location, sizeof(new_path));
+ new_path = header_info.location;
+ header_info.location = NULL;
}
php_url_free(resource);
/* check for invalid redirection URLs */
if ((resource = php_url_parse(new_path)) == NULL) {
php_stream_wrapper_log_error(wrapper, options, "Invalid redirect URL! %s", new_path);
+ efree(new_path);
goto out;
}
@@ -1081,6 +1098,7 @@ finish:
while (s < e) { \
if (iscntrl(*s)) { \
php_stream_wrapper_log_error(wrapper, options, "Invalid redirect URL! %s", new_path); \
+ efree(new_path); \
goto out; \
} \
s++; \
@@ -1096,6 +1114,7 @@ finish:
stream = php_stream_url_wrap_http_ex(
wrapper, new_path, mode, options, opened_path, context,
--redirect_max, HTTP_WRAPPER_REDIRECTED, response_header STREAMS_CC);
+ efree(new_path);
} else {
php_stream_wrapper_log_error(wrapper, options, "HTTP request failed! %s", tmp_line);
}
@@ -1108,6 +1127,10 @@ out:
efree(http_header_line);
}
+ if (header_info.location != NULL) {
+ efree(header_info.location);
+ }
+
if (resource) {
php_url_free(resource);
}