Skip to content

Commit d82d687

Browse files
committed
Fix bug #69337 (php_stream_url_wrap_http_ex() type-confusion vulnerability)
1 parent 1defbb2 commit d82d687

File tree

1 file changed

+43
-36
lines changed

1 file changed

+43
-36
lines changed

ext/standard/http_fopen_wrapper.c

Lines changed: 43 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
| Sara Golemon <[email protected]> |
2020
+----------------------------------------------------------------------+
2121
*/
22-
/* $Id$ */
22+
/* $Id$ */
2323

2424
#include "php.h"
2525
#include "php_globals.h"
@@ -152,7 +152,7 @@ php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper, char *path,
152152
}
153153

154154
if (strncasecmp(resource->scheme, "http", sizeof("http")) && strncasecmp(resource->scheme, "https", sizeof("https"))) {
155-
if (!context ||
155+
if (!context ||
156156
php_stream_context_get_option(context, wrapper->wops->label, "proxy", &tmpzval) == FAILURE ||
157157
Z_TYPE_PP(tmpzval) != IS_STRING ||
158158
Z_STRLEN_PP(tmpzval) <= 0) {
@@ -168,7 +168,7 @@ php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper, char *path,
168168
transport_string = estrndup(Z_STRVAL_PP(tmpzval), Z_STRLEN_PP(tmpzval));
169169
} else {
170170
/* Normal http request (possibly with proxy) */
171-
171+
172172
if (strpbrk(mode, "awx+")) {
173173
php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "HTTP wrapper does not support writeable connections");
174174
php_url_free(resource);
@@ -207,11 +207,11 @@ php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper, char *path,
207207
stream = php_stream_xport_create(transport_string, transport_len, options,
208208
STREAM_XPORT_CLIENT | STREAM_XPORT_CONNECT,
209209
NULL, &timeout, context, &errstr, NULL);
210-
210+
211211
if (stream) {
212212
php_stream_set_option(stream, PHP_STREAM_OPTION_READ_TIMEOUT, 0, &timeout);
213213
}
214-
214+
215215
if (errstr) {
216216
php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "%s", errstr);
217217
efree(errstr);
@@ -328,7 +328,7 @@ php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper, char *path,
328328
/* avoid buffering issues while reading header */
329329
if (options & STREAM_WILL_CAST)
330330
chunk_size = php_stream_set_chunk_size(stream, 1);
331-
331+
332332
/* avoid problems with auto-detecting when reading the headers -> the headers
333333
* are always in canonical \r\n format */
334334
eol_detect = stream->flags & (PHP_STREAM_FLAG_DETECT_EOL | PHP_STREAM_FLAG_EOL_MAC);
@@ -359,7 +359,7 @@ php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper, char *path,
359359
}
360360
}
361361
}
362-
362+
363363
if (context && php_stream_context_get_option(context, "http", "protocol_version", &tmpzval) == SUCCESS) {
364364
SEPARATE_ZVAL(tmpzval);
365365
convert_to_double_ex(tmpzval);
@@ -420,7 +420,7 @@ php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper, char *path,
420420

421421
if (context && php_stream_context_get_option(context, "http", "header", &tmpzval) == SUCCESS) {
422422
tmp = NULL;
423-
423+
424424
if (Z_TYPE_PP(tmpzval) == IS_ARRAY) {
425425
HashPosition pos;
426426
zval **tmpheader = NULL;
@@ -460,42 +460,42 @@ php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper, char *path,
460460
strip_header(user_headers, tmp, "content-type:");
461461
}
462462

463-
if ((s = strstr(tmp, "user-agent:")) &&
464-
(s == tmp || *(s-1) == '\r' || *(s-1) == '\n' ||
463+
if ((s = strstr(tmp, "user-agent:")) &&
464+
(s == tmp || *(s-1) == '\r' || *(s-1) == '\n' ||
465465
*(s-1) == '\t' || *(s-1) == ' ')) {
466466
have_header |= HTTP_HEADER_USER_AGENT;
467467
}
468468
if ((s = strstr(tmp, "host:")) &&
469-
(s == tmp || *(s-1) == '\r' || *(s-1) == '\n' ||
469+
(s == tmp || *(s-1) == '\r' || *(s-1) == '\n' ||
470470
*(s-1) == '\t' || *(s-1) == ' ')) {
471471
have_header |= HTTP_HEADER_HOST;
472472
}
473473
if ((s = strstr(tmp, "from:")) &&
474-
(s == tmp || *(s-1) == '\r' || *(s-1) == '\n' ||
474+
(s == tmp || *(s-1) == '\r' || *(s-1) == '\n' ||
475475
*(s-1) == '\t' || *(s-1) == ' ')) {
476476
have_header |= HTTP_HEADER_FROM;
477477
}
478478
if ((s = strstr(tmp, "authorization:")) &&
479-
(s == tmp || *(s-1) == '\r' || *(s-1) == '\n' ||
479+
(s == tmp || *(s-1) == '\r' || *(s-1) == '\n' ||
480480
*(s-1) == '\t' || *(s-1) == ' ')) {
481481
have_header |= HTTP_HEADER_AUTH;
482482
}
483483
if ((s = strstr(tmp, "content-length:")) &&
484-
(s == tmp || *(s-1) == '\r' || *(s-1) == '\n' ||
484+
(s == tmp || *(s-1) == '\r' || *(s-1) == '\n' ||
485485
*(s-1) == '\t' || *(s-1) == ' ')) {
486486
have_header |= HTTP_HEADER_CONTENT_LENGTH;
487487
}
488488
if ((s = strstr(tmp, "content-type:")) &&
489-
(s == tmp || *(s-1) == '\r' || *(s-1) == '\n' ||
489+
(s == tmp || *(s-1) == '\r' || *(s-1) == '\n' ||
490490
*(s-1) == '\t' || *(s-1) == ' ')) {
491491
have_header |= HTTP_HEADER_TYPE;
492492
}
493493
/* remove Proxy-Authorization header */
494494
if (use_proxy && use_ssl && (s = strstr(tmp, "proxy-authorization:")) &&
495-
(s == tmp || *(s-1) == '\r' || *(s-1) == '\n' ||
495+
(s == tmp || *(s-1) == '\r' || *(s-1) == '\n' ||
496496
*(s-1) == '\t' || *(s-1) == ' ')) {
497497
char *p = s + sizeof("proxy-authorization:") - 1;
498-
498+
499499
while (s > tmp && (*(s-1) == ' ' || *(s-1) == '\t')) s--;
500500
while (*p != 0 && *p != '\r' && *p != '\n') p++;
501501
while (*p == '\r' || *p == '\n') p++;
@@ -534,7 +534,7 @@ php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper, char *path,
534534
}
535535

536536
tmp = (char*)php_base64_encode((unsigned char*)scratch, strlen(scratch), NULL);
537-
537+
538538
if (snprintf(scratch, scratch_len, "Authorization: Basic %s\r\n", tmp) > 0) {
539539
php_stream_write(stream, scratch, strlen(scratch));
540540
php_stream_notify_info(context, PHP_STREAM_NOTIFY_AUTH_REQUIRED, NULL, 0);
@@ -552,7 +552,7 @@ php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper, char *path,
552552

553553
/* Send Host: header so name-based virtual hosts work */
554554
if ((have_header & HTTP_HEADER_HOST) == 0) {
555-
if ((use_ssl && resource->port != 443 && resource->port != 0) ||
555+
if ((use_ssl && resource->port != 443 && resource->port != 0) ||
556556
(!use_ssl && resource->port != 80 && resource->port != 0)) {
557557
if (snprintf(scratch, scratch_len, "Host: %s:%i\r\n", resource->host, resource->port) > 0)
558558
php_stream_write(stream, scratch, strlen(scratch));
@@ -563,7 +563,7 @@ php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper, char *path,
563563
}
564564
}
565565

566-
if (context &&
566+
if (context &&
567567
php_stream_context_get_option(context, "http", "user_agent", &ua_zval) == SUCCESS &&
568568
Z_TYPE_PP(ua_zval) == IS_STRING) {
569569
ua_str = Z_STRVAL_PP(ua_zval);
@@ -575,9 +575,9 @@ php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper, char *path,
575575
#define _UA_HEADER "User-Agent: %s\r\n"
576576
char *ua;
577577
size_t ua_len;
578-
578+
579579
ua_len = sizeof(_UA_HEADER) + strlen(ua_str);
580-
580+
581581
/* ensure the header is only sent if user_agent is not blank */
582582
if (ua_len > sizeof(_UA_HEADER)) {
583583
ua = emalloc(ua_len + 1);
@@ -591,7 +591,7 @@ php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper, char *path,
591591
if (ua) {
592592
efree(ua);
593593
}
594-
}
594+
}
595595
}
596596

597597
if (user_headers) {
@@ -649,8 +649,12 @@ php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper, char *path,
649649

650650
{
651651
zval **rh;
652-
zend_hash_find(EG(active_symbol_table), "http_response_header", sizeof("http_response_header"), (void **) &rh);
652+
if(zend_hash_find(EG(active_symbol_table), "http_response_header", sizeof("http_response_header"), (void **) &rh) != SUCCESS || Z_TYPE_PP(rh) != IS_ARRAY) {
653+
php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "HTTP request failed, http_response_header overwritten");
654+
goto out;
655+
}
653656
response_header = *rh;
657+
Z_ADDREF_P(response_header);
654658
}
655659

656660
if (!php_stream_eof(stream)) {
@@ -706,9 +710,9 @@ php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper, char *path,
706710
php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "HTTP request failed, unexpected end of socket!");
707711
goto out;
708712
}
709-
713+
710714
/* read past HTTP headers */
711-
715+
712716
http_header_line = emalloc(HTTP_HEADER_BLOCK_SIZE);
713717

714718
while (!body && !php_stream_eof(stream)) {
@@ -738,7 +742,7 @@ php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper, char *path,
738742
follow_location = Z_LVAL_PP(tmpzval);
739743
} else if (!(response_code >= 300 && response_code < 304 || 307 == response_code || 308 == response_code)) {
740744
/* we shouldn't redirect automatically
741-
if follow_location isn't set and response_code not in (300, 301, 302, 303 and 307)
745+
if follow_location isn't set and response_code not in (300, 301, 302, 303 and 307)
742746
see http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.1
743747
RFC 7238 defines 308: http://tools.ietf.org/html/rfc7238 */
744748
follow_location = 0;
@@ -778,7 +782,7 @@ php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper, char *path,
778782
MAKE_STD_ZVAL(http_header);
779783

780784
ZVAL_STRINGL(http_header, http_header_line, http_header_line_length, 1);
781-
785+
782786
zend_hash_next_index_insert(Z_ARRVAL_P(response_header), &http_header, sizeof(zval *), NULL);
783787
}
784788
} else {
@@ -803,10 +807,10 @@ php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper, char *path,
803807
char loc_path[HTTP_HEADER_BLOCK_SIZE];
804808

805809
*new_path='\0';
806-
if (strlen(location)<8 || (strncasecmp(location, "http://", sizeof("http://")-1) &&
807-
strncasecmp(location, "https://", sizeof("https://")-1) &&
808-
strncasecmp(location, "ftp://", sizeof("ftp://")-1) &&
809-
strncasecmp(location, "ftps://", sizeof("ftps://")-1)))
810+
if (strlen(location)<8 || (strncasecmp(location, "http://", sizeof("http://")-1) &&
811+
strncasecmp(location, "https://", sizeof("https://")-1) &&
812+
strncasecmp(location, "ftp://", sizeof("ftp://")-1) &&
813+
strncasecmp(location, "ftps://", sizeof("ftps://")-1)))
810814
{
811815
if (*location != '/') {
812816
if (*(location+1) != '\0' && resource->path) {
@@ -820,7 +824,7 @@ php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper, char *path,
820824
*s = '/';
821825
}
822826
}
823-
s[1] = '\0';
827+
s[1] = '\0';
824828
if (resource->path && *(resource->path) == '/' && *(resource->path + 1) == '\0') {
825829
snprintf(loc_path, sizeof(loc_path) - 1, "%s%s", resource->path, location);
826830
} else {
@@ -893,18 +897,21 @@ php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper, char *path,
893897

894898
if (stream) {
895899
if (header_init) {
896-
zval_add_ref(&response_header);
897900
stream->wrapperdata = response_header;
901+
} else {
902+
if(response_header) {
903+
Z_DELREF_P(response_header);
904+
}
898905
}
899906
php_stream_notify_progress_init(context, 0, file_size);
900-
907+
901908
/* Restore original chunk size now that we're done with headers */
902909
if (options & STREAM_WILL_CAST)
903910
php_stream_set_chunk_size(stream, chunk_size);
904911

905912
/* restore the users auto-detect-line-endings setting */
906913
stream->flags |= eol_detect;
907-
914+
908915
/* as far as streams are concerned, we are now at the start of
909916
* the stream */
910917
stream->position = 0;

0 commit comments

Comments
 (0)