diff --git a/NEWS b/NEWS index 7dd05a2b1c72..0f3ba67434dc 100644 --- a/NEWS +++ b/NEWS @@ -46,6 +46,8 @@ PHP NEWS . Added the "filter.max_filter_count" stream context option for php://filter URLs. Using more than 16 filters without configuring this option is now deprecated. (Sjoerd Langkemper) + . Fixed bug GH-23006 (phpcredits() full-page HTML title says phpinfo()). + (Weilin Du) . The following functions now raise a ValueError when the $filename argument contains NUL bytes: fileperms(), fileinode(), filesize(), fileowner(), filegroup(), fileatime(), filemtime(), filectime(), filetype(), @@ -605,7 +607,8 @@ PHP NEWS . Added so_keepalive, tcp_keepidle, tcp_keepintvl and tcp_keepcnt stream socket context options. (Jakub Zelenka) . Added so_reuseaddr streams context socket option that allows disabling - address reuse. (Jakub Zelenka) + address resuse. (Jakub Zelenka) + . Added so_linger stream socket context option. (Jakub Zelenka) . Fixed bug GH-20370 (User stream filters could violate typed property constraints). (alexandre-daubois) . Allowed filtered streams to be casted as fd for select. (Jakub Zelenka) diff --git a/UPGRADING b/UPGRADING index 9389c7b04b20..ced365480707 100644 --- a/UPGRADING +++ b/UPGRADING @@ -382,6 +382,10 @@ PHP 8.6 UPGRADE NOTES . Added stream socket context options so_keepalive, tcp_keepidle, tcp_keepintvl and tcp_keepcnt that allow setting socket keepalive options. + . Added stream socket context option so_linger that sets SO_LINGER on TCP + sockets. A positive value enables lingering for that many seconds, zero + or a negative value disables it. Values above 65535 are clamped as the + linger time is limited to an unsigned short on some platforms. . Allowed casting casting filtered streams as file descriptor for select. . Added the "write_seek_mode stream" filter parameter for the bz2, iconv, zlib, and string stream filters. This parameter must be set via an diff --git a/UPGRADING.INTERNALS b/UPGRADING.INTERNALS index dcc21206d89f..6554c752b8af 100644 --- a/UPGRADING.INTERNALS +++ b/UPGRADING.INTERNALS @@ -164,6 +164,7 @@ PHP 8.6 INTERNALS UPGRADE NOTES . EG(in_autoload) was renamed to EG(autoload_current_classnames) and no longer is a pointer, but a directly embedded HashTable struct. . Extended php_stream_filter_ops with seek method. + . php_print_info_htmlhead() now takes a title argument. . zend_argument_error_variadic() now takes a new 'function' parameter. . The param argument in the php_verror() function has been removed. . The php_stream_wrapper_log_error() signature changed from diff --git a/ext/intl/dateformat/dateformat_helpers.cpp b/ext/intl/dateformat/dateformat_helpers.cpp index d10acb7273fe..5d93536807a9 100644 --- a/ext/intl/dateformat/dateformat_helpers.cpp +++ b/ext/intl/dateformat/dateformat_helpers.cpp @@ -14,6 +14,8 @@ #include "../intl_cppshims.h" +#include + #include #include diff --git a/ext/openssl/tests/stream_cafile_no_valid_certs.phpt b/ext/openssl/tests/stream_cafile_no_valid_certs.phpt new file mode 100644 index 000000000000..4622dfbf5765 --- /dev/null +++ b/ext/openssl/tests/stream_cafile_no_valid_certs.phpt @@ -0,0 +1,42 @@ +--TEST-- +SSL cafile stream containing no valid certificates +--EXTENSIONS-- +openssl +--SKIPIF-- + +--FILE-- + [ + 'cafile' => 'file://%s', + ]]); + var_dump(stream_socket_client( + 'ssl://{{ ADDR }}', + timeout: 2, + context: $context, + )); +CODE; +$clientCode = sprintf($clientCode, __DIR__ . '/plain.txt'); + +include 'ServerClientTestCase.inc'; +ServerClientTestCase::getInstance()->run($clientCode, $serverCode); +?> +--EXPECTF-- +Warning: stream_socket_client(): no valid certs found cafile stream: '%s' in %sServerClientTestCase.inc(%d) : eval()'d code on line 4 + +Warning: stream_socket_client(): Failed to enable crypto in %sServerClientTestCase.inc(%d) : eval()'d code on line 4 + +Warning: stream_socket_client(): Unable to connect to ssl://127.0.0.1:%d (Unknown error) in %sServerClientTestCase.inc(%d) : eval()'d code on line 4 +bool(false) diff --git a/ext/openssl/xp_ssl.c b/ext/openssl/xp_ssl.c index 130d3717ccc9..269de9545388 100644 --- a/ext/openssl/xp_ssl.c +++ b/ext/openssl/xp_ssl.c @@ -947,15 +947,14 @@ static long php_openssl_load_stream_cafile(X509_STORE *cert_store, const char *c goto cert_start; } - stream_complete: { - php_stream_close(stream); - if (buffer_active == 1) { - BIO_free(buffer); - } +stream_complete: + if (certs_added == 0) { + php_stream_warn(stream, DecodingFailed, "no valid certs found cafile stream: '%s'", cafile); } - if (certs_added == 0) { - php_stream_warn(stream, DecodingFailed, "no valid certs found cafile stream: `%s'", cafile); + php_stream_close(stream); + if (buffer_active == 1) { + BIO_free(buffer); } return certs_added; diff --git a/ext/standard/credits.c b/ext/standard/credits.c index affed4adfee0..0aeb0780f38f 100644 --- a/ext/standard/credits.c +++ b/ext/standard/credits.c @@ -22,7 +22,7 @@ PHPAPI ZEND_COLD void php_print_credits(int flag) /* {{{ */ { if (!sapi_module.phpinfo_as_text && flag & PHP_CREDITS_FULLPAGE) { - php_print_info_htmlhead(); + php_print_info_htmlhead("phpcredits()"); } if (!sapi_module.phpinfo_as_text) { diff --git a/ext/standard/info.c b/ext/standard/info.c index dcb01bc08220..2029679998db 100644 --- a/ext/standard/info.c +++ b/ext/standard/info.c @@ -733,13 +733,13 @@ PHPAPI zend_string *php_get_uname(char mode) /* }}} */ /* {{{ php_print_info_htmlhead */ -PHPAPI ZEND_COLD void php_print_info_htmlhead(void) +PHPAPI ZEND_COLD void php_print_info_htmlhead(const char *title) { php_info_print("\n"); php_info_print(""); php_info_print("\n"); php_info_print_style(); - php_info_printf("PHP %s - phpinfo()", PHP_VERSION); + php_info_printf("PHP %s - %s", PHP_VERSION, title); php_info_print(""); php_info_print("\n"); php_info_print("
\n"); @@ -761,7 +761,7 @@ PHPAPI ZEND_COLD void php_print_info(int flag) zend_string *php_uname; if (!sapi_module.phpinfo_as_text) { - php_print_info_htmlhead(); + php_print_info_htmlhead("phpinfo()"); } else { php_info_print("phpinfo()\n"); } diff --git a/ext/standard/info.h b/ext/standard/info.h index 6d1603dd8346..38a9341e81a1 100644 --- a/ext/standard/info.h +++ b/ext/standard/info.h @@ -50,7 +50,7 @@ #define ZEND_LOGO_DATA_URI "data:image/png;base64,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" BEGIN_EXTERN_C() -PHPAPI void php_print_info_htmlhead(void); +PHPAPI void php_print_info_htmlhead(const char *title); PHPAPI void php_print_info(int flag); PHPAPI void php_print_style(void); PHPAPI void php_info_print_style(void); diff --git a/ext/standard/tests/general_functions/phpcredits_title.phpt b/ext/standard/tests/general_functions/phpcredits_title.phpt new file mode 100644 index 000000000000..6d306e609602 --- /dev/null +++ b/ext/standard/tests/general_functions/phpcredits_title.phpt @@ -0,0 +1,18 @@ +--TEST-- +phpcredits() full-page HTML title +--POST-- +dummy=x +--FILE-- +PHP ' . PHP_VERSION . ' - phpcredits()')); +var_dump(str_contains($output, 'PHP ' . PHP_VERSION . ' - phpinfo()')); +?> +--EXPECT-- +bool(true) +bool(true) +bool(false) diff --git a/ext/standard/tests/network/so_linger.phpt b/ext/standard/tests/network/so_linger.phpt new file mode 100644 index 000000000000..ee0561b77381 --- /dev/null +++ b/ext/standard/tests/network/so_linger.phpt @@ -0,0 +1,82 @@ +--TEST-- +stream_socket_server() and stream_socket_client() SO_LINGER context option test +--EXTENSIONS-- +sockets +--FILE-- + [ + 'so_linger' => 10, + ] +]); + +$server = stream_socket_server("tcp://127.0.0.1:0", $errno, $errstr, + STREAM_SERVER_BIND | STREAM_SERVER_LISTEN, $server_context); + +if (!$server) { + die('Unable to create server'); +} + +$addr = stream_socket_get_name($server, false); +$port = (int)substr(strrchr($addr, ':'), 1); + +// Test client with SO_LINGER enabled +$client_context = stream_context_create([ + 'socket' => [ + 'so_linger' => 8, + ] +]); + +$client = stream_socket_client("tcp://127.0.0.1:$port", $errno, $errstr, 30, + STREAM_CLIENT_CONNECT, $client_context); + +if (!$client) { + die('Unable to create client'); +} + +$accepted = stream_socket_accept($server, 1); + +if (!$accepted) { + die('Unable to accept connection'); +} + +// macOS expresses SO_LINGER in ticks, SO_LINGER_SEC in seconds. +$so_linger = defined('SO_LINGER_SEC') ? SO_LINGER_SEC : SO_LINGER; + +// Verify the listening socket +$listen_sock = socket_import_stream($server); +$listen_linger = socket_get_option($listen_sock, SOL_SOCKET, $so_linger); +echo "Listen SO_LINGER\n"; +var_dump($listen_linger['l_onoff'] > 0); +var_dump($listen_linger['l_linger']); + +// Verify server side (accepted connection, inherits from the listening socket) +$server_sock = socket_import_stream($accepted); +$server_linger = socket_get_option($server_sock, SOL_SOCKET, $so_linger); +echo "Server SO_LINGER\n"; +var_dump($server_linger['l_onoff'] > 0); +var_dump($server_linger['l_linger']); + +// Verify client side +$client_sock = socket_import_stream($client); +$client_linger = socket_get_option($client_sock, SOL_SOCKET, $so_linger); +echo "Client SO_LINGER\n"; +var_dump($client_linger['l_onoff'] > 0); +var_dump($client_linger['l_linger']); + +fclose($accepted); +fclose($client); +fclose($server); + +?> +--EXPECT-- +Listen SO_LINGER +bool(true) +int(10) +Server SO_LINGER +bool(true) +int(10) +Client SO_LINGER +bool(true) +int(8) diff --git a/main/network.c b/main/network.c index b4d3ded15196..836abaeac7dd 100644 --- a/main/network.c +++ b/main/network.c @@ -535,6 +535,24 @@ php_socket_t php_network_bind_socket_to_local_addr_ex(const char *host, unsigned /* Set socket values if provided */ if (sockvals != NULL) { +#ifdef SO_LINGER + if (sockvals->mask & PHP_SOCKVAL_SO_LINGER) { + /* l_linger is an unsigned short on Windows, so clamp rather than + * truncate: a truncated value may still be in range and would then + * be applied silently (e.g. 65536 becoming 0, an abortive close). */ + unsigned short secs = sockvals->linger > USHRT_MAX + ? USHRT_MAX : (unsigned short)sockvals->linger; + struct linger linger_val = { + .l_onoff = (sockvals->linger > 0), + .l_linger = sockvals->linger > 0 ? secs : 0 + }; +#ifdef SO_LINGER_SEC + setsockopt(sock, SOL_SOCKET, SO_LINGER_SEC, (char*)&linger_val, sizeof(linger_val)); +#else + setsockopt(sock, SOL_SOCKET, SO_LINGER, (char*)&linger_val, sizeof(linger_val)); +#endif + } +#endif #if defined(TCP_KEEPIDLE) if (sockvals->mask & PHP_SOCKVAL_TCP_KEEPIDLE) { setsockopt(sock, IPPROTO_TCP, TCP_KEEPIDLE, (char*)&sockvals->keepalive.keepidle, sizeof(sockvals->keepalive.keepidle)); @@ -1021,6 +1039,24 @@ php_socket_t php_network_connect_socket_to_host_ex(const char *host, unsigned sh /* Set socket values if provided */ if (sockvals != NULL) { +#ifdef SO_LINGER + if (sockvals->mask & PHP_SOCKVAL_SO_LINGER) { + /* l_linger is an unsigned short on Windows, so clamp rather than + * truncate: a truncated value may still be in range and would then + * be applied silently (e.g. 65536 becoming 0, an abortive close). */ + unsigned short secs = sockvals->linger > USHRT_MAX + ? USHRT_MAX : (unsigned short)sockvals->linger; + struct linger linger_val = { + .l_onoff = (sockvals->linger > 0), + .l_linger = sockvals->linger > 0 ? secs : 0 + }; +#ifdef SO_LINGER_SEC + setsockopt(sock, SOL_SOCKET, SO_LINGER_SEC, (char*)&linger_val, sizeof(linger_val)); +#else + setsockopt(sock, SOL_SOCKET, SO_LINGER, (char*)&linger_val, sizeof(linger_val)); +#endif + } +#endif #if defined(TCP_KEEPIDLE) if (sockvals->mask & PHP_SOCKVAL_TCP_KEEPIDLE) { setsockopt(sock, IPPROTO_TCP, TCP_KEEPIDLE, (char*)&sockvals->keepalive.keepidle, sizeof(sockvals->keepalive.keepidle)); diff --git a/main/php_network.h b/main/php_network.h index e6d3009a6c82..c966b7af0053 100644 --- a/main/php_network.h +++ b/main/php_network.h @@ -269,12 +269,14 @@ typedef struct { #define PHP_SOCKVAL_TCP_KEEPIDLE (1 << 1) #define PHP_SOCKVAL_TCP_KEEPCNT (1 << 2) #define PHP_SOCKVAL_TCP_KEEPINTVL (1 << 3) +#define PHP_SOCKVAL_SO_LINGER (1 << 4) #define PHP_SOCKVAL_IS_SET(sockvals, opt) ((sockvals)->mask & (opt)) typedef struct { unsigned int mask; int tcp_nodelay; + int linger; struct { int keepidle; int keepcnt; diff --git a/main/streams/xp_socket.c b/main/streams/xp_socket.c index 3eef731544de..a9e2fa2b1d05 100644 --- a/main/streams/xp_socket.c +++ b/main/streams/xp_socket.c @@ -759,6 +759,16 @@ static inline int php_tcp_sockop_bind(php_stream *stream, php_netstream_data_t * } #endif +#ifdef SO_LINGER + if (PHP_STREAM_XPORT_IS_TCP(stream) + && PHP_STREAM_CONTEXT(stream) + && (tmpzval = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "socket", "so_linger")) != NULL + ) { + sockvals.mask |= PHP_SOCKVAL_SO_LINGER; + sockvals.linger = (int)zval_get_long(tmpzval); + } +#endif + #ifdef SO_KEEPALIVE if (PHP_STREAM_XPORT_IS_TCP(stream) /* SO_KEEPALIVE is only applicable for TCP */ && PHP_STREAM_CONTEXT(stream) @@ -887,6 +897,16 @@ static inline int php_tcp_sockop_connect(php_stream *stream, php_netstream_data_ sockopts |= STREAM_SOCKOP_TCP_NODELAY; } +#ifdef SO_LINGER + if (PHP_STREAM_XPORT_IS_TCP(stream) + && PHP_STREAM_CONTEXT(stream) + && (tmpzval = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "socket", "so_linger")) != NULL + ) { + sockvals.mask |= PHP_SOCKVAL_SO_LINGER; + sockvals.linger = (int)zval_get_long(tmpzval); + } +#endif + #ifdef SO_KEEPALIVE if (PHP_STREAM_XPORT_IS_TCP(stream) /* SO_KEEPALIVE is only applicable for TCP */ && PHP_STREAM_CONTEXT(stream) diff --git a/sapi/cli/php_cli_server.c b/sapi/cli/php_cli_server.c index 85028cc03f7c..007718e37b9c 100644 --- a/sapi/cli/php_cli_server.c +++ b/sapi/cli/php_cli_server.c @@ -174,6 +174,7 @@ typedef struct php_cli_server_client { zend_string *addr_str; php_http_parser parser; bool request_read; + bool too_large_post; zend_string *current_header_name; zend_string *current_header_value; enum { HEADER_NONE=0, HEADER_FIELD, HEADER_VALUE } last_header_element; @@ -209,6 +210,7 @@ static const php_cli_server_http_response_status_code_pair template_map[] = { { 400, "

%s

Your browser sent a request that this server could not understand.

" }, { 404, "

%s

The requested resource %s was not found on this server.

" }, { 405, "

%s

Requested method not allowed.

" }, + { 413, "

%s

The request body exceeds the configured post_max_size of " ZEND_LONG_FMT " bytes.

" }, { 500, "

%s

The server is temporarily unavailable.

" }, { 501, "

%s

Request method not supported.

" } }; @@ -1779,17 +1781,35 @@ static int php_cli_server_client_read_request_on_headers_complete(php_http_parse break; } client->last_header_element = HEADER_NONE; + + if (parser->content_length > 0 + && SG(post_max_size) > 0 + && (zend_long) parser->content_length > SG(post_max_size)) { + client->request.protocol_version = parser->http_major * 100 + parser->http_minor; + client->too_large_post = true; + client->request_read = true; + return 2; + } + return 0; } static int php_cli_server_client_read_request_on_body(php_http_parser *parser, const char *at, size_t length) { php_cli_server_client *client = parser->data; - if (!client->request.content) { - client->request.content = pemalloc(parser->content_length, 1); - client->request.content_len = 0; + + /* length is bounded by the read buffer in php_cli_server_client_read_request() + * and content_len by post_max_size, so the sum below cannot overflow. */ + ZEND_ASSERT(length <= SIZE_MAX - client->request.content_len); + + if (SG(post_max_size) > 0 && client->request.content_len + length > (size_t) SG(post_max_size)) { + client->request.protocol_version = parser->http_major * 100 + parser->http_minor; + client->too_large_post = true; + client->request_read = true; + return 1; } - client->request.content = perealloc(client->request.content, client->request.content_len + length, 1); + + client->request.content = safe_perealloc(client->request.content, 1, client->request.content_len, length, 1); memmove(client->request.content + client->request.content_len, at, length); client->request.content_len += length; return 0; @@ -1866,7 +1886,7 @@ static int php_cli_server_client_read_request(php_cli_server_client *client, cha } client->parser.data = client; nbytes_consumed = php_http_parser_execute(&client->parser, &settings, buf, nbytes_read); - if (nbytes_consumed != (size_t)nbytes_read) { + if (nbytes_consumed != (size_t)nbytes_read && !client->too_large_post) { if (php_cli_server_log_level >= PHP_CLI_SERVER_LOG_ERROR) { if ((buf[0] & 0x80) /* SSLv2 */ || buf[0] == 0x16 /* SSLv3/TLSv1 */) { *errstr = estrdup("Unsupported SSL request"); @@ -1960,6 +1980,7 @@ static void php_cli_server_client_ctor(php_cli_server_client *client, php_cli_se php_http_parser_init(&client->parser, PHP_HTTP_REQUEST); client->request_read = false; + client->too_large_post = false; client->last_header_element = HEADER_NONE; client->current_header_name = NULL; @@ -1983,10 +2004,16 @@ static void php_cli_server_client_dtor(php_cli_server_client *client) /* {{{ */ pefree(client->addr, 1); zend_string_release_ex(client->addr_str, /* persistent */ true); + if (client->current_header_name) { + zend_string_release_ex(client->current_header_name, /* persistent */ true); + client->current_header_name = NULL; + } + if (client->current_header_value) { + zend_string_release_ex(client->current_header_value, /* persistent */ true); + client->current_header_value = NULL; + } + if (client->content_sender_initialized) { - /* Headers must be set if we reached the content initialisation */ - assert(client->current_header_name == NULL); - assert(client->current_header_value == NULL); php_cli_server_content_sender_dtor(&client->content_sender); } } /* }}} */ @@ -2038,11 +2065,20 @@ static zend_result php_cli_server_send_error_page(php_cli_server *server, php_cl php_cli_server_buffer_append(&client->content_sender.buffer, chunk); } { - php_cli_server_chunk *chunk = php_cli_server_chunk_heap_new_self_contained(strlen(content_template) + ZSTR_LEN(escaped_request_uri) + 3 + strlen(status_string) + 1); - if (!chunk) { - goto fail; + php_cli_server_chunk *chunk; + if (status == 413) { + chunk = php_cli_server_chunk_heap_new_self_contained(strlen(content_template) + strlen(status_string) + MAX_LENGTH_OF_LONG + 1); + if (!chunk) { + goto fail; + } + snprintf(chunk->data.heap.p, chunk->data.heap.len, content_template, status_string, SG(post_max_size)); + } else { + chunk = php_cli_server_chunk_heap_new_self_contained(strlen(content_template) + ZSTR_LEN(escaped_request_uri) + 3 + strlen(status_string) + 1); + if (!chunk) { + goto fail; + } + snprintf(chunk->data.heap.p, chunk->data.heap.len, content_template, status_string, ZSTR_VAL(escaped_request_uri)); } - snprintf(chunk->data.heap.p, chunk->data.heap.len, content_template, status_string, ZSTR_VAL(escaped_request_uri)); chunk->data.heap.len = strlen(chunk->data.heap.p); php_cli_server_buffer_append(&client->content_sender.buffer, chunk); } @@ -2641,6 +2677,9 @@ static zend_result php_cli_server_recv_event_read_request(php_cli_server *server if (client->request.request_method == PHP_HTTP_NOT_IMPLEMENTED) { return php_cli_server_send_error_page(server, client, 501); } + if (client->too_large_post) { + return php_cli_server_send_error_page(server, client, 413); + } php_cli_server_poller_remove(&server->poller, POLLIN, client->sock); return php_cli_server_dispatch(server, client); case 0: diff --git a/sapi/cli/php_http_parser.c b/sapi/cli/php_http_parser.c index ac3c19d5d963..6d548ac1ec58 100644 --- a/sapi/cli/php_http_parser.c +++ b/sapi/cli/php_http_parser.c @@ -20,6 +20,7 @@ */ #include #include +#include #include "php_http_parser.h" @@ -27,6 +28,10 @@ # define MIN(a,b) ((a) < (b) ? (a) : (b)) #endif +#ifndef SSIZE_MAX +# define SSIZE_MAX PTRDIFF_MAX +#endif + #define CALLBACK2(FOR) \ do { \ @@ -1230,8 +1235,10 @@ size_t php_http_parser_execute (php_http_parser *parser, case h_content_length: if (ch == ' ') break; if (ch < '0' || ch > '9') goto error; - parser->content_length *= 10; - parser->content_length += ch - '0'; + if (parser->content_length > (SSIZE_MAX - (ch - '0')) / 10) { + goto error; + } + parser->content_length = parser->content_length * 10 + (ch - '0'); break; /* Transfer-Encoding: chunked */ @@ -1337,7 +1344,7 @@ size_t php_http_parser_execute (php_http_parser *parser, break; default: - return p - data; /* Error */ + goto error; } } @@ -1386,7 +1393,9 @@ size_t php_http_parser_execute (php_http_parser *parser, to_read = MIN((size_t)(pe - p), (size_t)parser->content_length); if (to_read > 0) { - if (settings->on_body) settings->on_body(parser, p, to_read); + if (settings->on_body && 0 != settings->on_body(parser, p, to_read)) { + goto error; + } p += to_read - 1; parser->content_length -= to_read; if (parser->content_length == 0) { @@ -1400,7 +1409,9 @@ size_t php_http_parser_execute (php_http_parser *parser, case s_body_identity_eof: to_read = pe - p; if (to_read > 0) { - if (settings->on_body) settings->on_body(parser, p, to_read); + if (settings->on_body && 0 != settings->on_body(parser, p, to_read)) { + goto error; + } p += to_read - 1; } break; @@ -1435,8 +1446,10 @@ size_t php_http_parser_execute (php_http_parser *parser, goto error; } - parser->content_length *= 16; - parser->content_length += c; + if (parser->content_length > (SSIZE_MAX - c) / 16) { + goto error; + } + parser->content_length = parser->content_length * 16 + c; break; } @@ -1473,7 +1486,9 @@ size_t php_http_parser_execute (php_http_parser *parser, to_read = MIN((size_t)(pe - p), (size_t)(parser->content_length)); if (to_read > 0) { - if (settings->on_body) settings->on_body(parser, p, to_read); + if (settings->on_body && 0 != settings->on_body(parser, p, to_read)) { + goto error; + } p += to_read - 1; } diff --git a/sapi/cli/tests/gh22003.phpt b/sapi/cli/tests/gh22003.phpt new file mode 100644 index 000000000000..de6ade1c9e13 --- /dev/null +++ b/sapi/cli/tests/gh22003.phpt @@ -0,0 +1,96 @@ +--TEST-- +GH-22003 (CLI server: overflow in Content-Length parser + post_max_size enforcement) +--SKIPIF-- + +--FILE-- + +--EXPECT-- +over post_max_size: 413 +shows configured limit: yes +over limit with body bytes: 413 +content-length overflow: rejected +chunked overflow: rejected +chunked over post_max_size: 413 +oversize chunk size: rejected +chunked within limit: 200 OK +follow-up: 200 OK