Commit 4c62468c95 for openssl.org
commit 4c62468c9591b4b4c4afe4e5a12941c214179336
Author: OpenSSL Machine <openssl-machine@openssl.org>
Date: Wed Apr 29 22:53:25 2026 +0900
Reject CR/LF in HTTP request components
Reject CR and LF characters before serializing request lines and HTTP
headers. This prevents malformed URL or caller supplied components
from altering the generated HTTP request.
Resolves: https://github.com/openssl/openssl/issues/31099
Reviewed-by: Tomas Mraz <tomas@openssl.foundation>
Reviewed-by: Matt Caswell <matt@openssl.foundation>
Reviewed-by: Eugene Syromiatnikov <esyr@openssl.org>
Reviewed-by: David von Oheimb <david.von.oheimb@siemens.com>
MergeDate: Mon May 11 07:44:19 2026
(Merged from https://github.com/openssl/openssl/pull/31100)
diff --git a/crypto/http/http_client.c b/crypto/http/http_client.c
index f9f7bff0d1..34d3a2cccc 100644
--- a/crypto/http/http_client.c
+++ b/crypto/http/http_client.c
@@ -95,6 +95,16 @@ struct ossl_http_req_ctx_st {
/* Low-level HTTP API implementation */
+static int no_crlf(const char *component, const char *value)
+{
+ if (value != NULL && strpbrk(value, "\r\n") != NULL) {
+ ERR_raise_data(ERR_LIB_HTTP, ERR_R_PASSED_INVALID_ARGUMENT,
+ "CR or LF character in %s", component);
+ return 0;
+ }
+ return 1;
+}
+
OSSL_HTTP_REQ_CTX *OSSL_HTTP_REQ_CTX_new(BIO *wbio, BIO *rbio, int buf_size)
{
OSSL_HTTP_REQ_CTX *rctx;
@@ -184,6 +194,10 @@ int OSSL_HTTP_REQ_CTX_set_request_line(OSSL_HTTP_REQ_CTX *rctx, int method_POST,
ERR_raise(ERR_LIB_HTTP, ERR_R_PASSED_NULL_PARAMETER);
return 0;
}
+ if (!no_crlf("server", server)
+ || !no_crlf("port", port)
+ || !no_crlf("path", path))
+ return 0;
BIO_free(rctx->mem);
if ((rctx->mem = BIO_new(BIO_s_mem())) == NULL)
return 0;
@@ -237,6 +251,9 @@ int OSSL_HTTP_REQ_CTX_add1_header(OSSL_HTTP_REQ_CTX *rctx,
ERR_raise(ERR_LIB_HTTP, ERR_R_SHOULD_NOT_HAVE_BEEN_CALLED);
return 0;
}
+ if (!no_crlf("header name", name)
+ || !no_crlf("header value", value))
+ return 0;
if (BIO_puts(rctx->mem, name) <= 0)
return 0;
@@ -310,7 +327,7 @@ static int set1_content(OSSL_HTTP_REQ_CTX *rctx,
} else {
if (HAS_CASE_PREFIX(content_type, "text/"))
rctx->text = 1;
- if (BIO_printf(rctx->mem, "Content-Type: %s\r\n", content_type) <= 0)
+ if (!OSSL_HTTP_REQ_CTX_add1_header(rctx, "Content-Type", content_type))
return 0;
}
@@ -1444,11 +1461,11 @@ int OSSL_HTTP_proxy_connect(BIO *bio, const char *server, const char *port,
{
#undef BUF_SIZE
#define BUF_SIZE (8 * 1024)
- char *mbuf = OPENSSL_malloc(BUF_SIZE);
+ char *mbuf = NULL;
char *mbufp;
int read_len = 0;
int ret = 0;
- BIO *fbio = BIO_new(BIO_f_buffer());
+ BIO *fbio = NULL;
int rv;
time_t max_time = timeout > 0 ? time(NULL) + timeout : 0;
@@ -1459,8 +1476,11 @@ int OSSL_HTTP_proxy_connect(BIO *bio, const char *server, const char *port,
}
if (port == NULL || *port == '\0')
port = OSSL_HTTPS_PORT;
+ if (!no_crlf("server", server) || !no_crlf("port", port))
+ goto end;
- if (mbuf == NULL || fbio == NULL) {
+ if ((mbuf = OPENSSL_malloc(BUF_SIZE)) == NULL
+ || (fbio = BIO_new(BIO_f_buffer())) == NULL) {
BIO_printf(bio_err /* may be NULL */, "%s: out of memory", prog);
goto end;
}
diff --git a/doc/man3/OSSL_HTTP_REQ_CTX.pod b/doc/man3/OSSL_HTTP_REQ_CTX.pod
index 210f33801c..e530d1b3fb 100644
--- a/doc/man3/OSSL_HTTP_REQ_CTX.pod
+++ b/doc/man3/OSSL_HTTP_REQ_CTX.pod
@@ -86,9 +86,12 @@ For backward compatibility, I<path> may begin with C<http://> and thus convey
an absoluteURI. In this case it indicates HTTP proxy use and provides also the
server (and optionally the port) that the proxy shall forward the request to.
In this case the I<server> and I<port> arguments must be NULL.
+The I<server>, I<port>, and I<path> arguments must not contain CR or LF
+characters.
OSSL_HTTP_REQ_CTX_add1_header() adds header I<name> with value I<value> to the
context I<rctx>. It can be called more than once to add multiple header lines.
+The I<name> and I<value> arguments must not contain CR or LF characters.
For example, to add a C<Host> header for C<example.com> you would call:
OSSL_HTTP_REQ_CTX_add1_header(ctx, "Host", "example.com");
@@ -143,6 +146,7 @@ The HTTP header C<Content-Length> is filled out with the length of the request.
I<content_type> must be NULL if I<req> is NULL.
If I<content_type> isn't NULL,
the HTTP header C<Content-Type> is also added with the given string value.
+The I<content_type> argument must not contain CR or LF characters.
The header lines are added to the internal memory B<BIO> for the request header.
OSSL_HTTP_REQ_CTX_nbio() attempts to send the request prepared in I<rctx>
diff --git a/doc/man3/OSSL_HTTP_transfer.pod b/doc/man3/OSSL_HTTP_transfer.pod
index eaa0986666..6c03e347fb 100644
--- a/doc/man3/OSSL_HTTP_transfer.pod
+++ b/doc/man3/OSSL_HTTP_transfer.pod
@@ -158,6 +158,7 @@ pre-established with a TLS proxy using the HTTP CONNECT method,
optionally using proxy client credentials I<proxyuser> and I<proxypass>,
to connect with TLS protection ultimately to I<server> and I<port>.
If the I<port> argument is NULL or the empty string it defaults to "443".
+The I<server> and I<port> arguments must not contain CR or LF characters.
If the I<timeout> parameter is > 0 this indicates the maximum number of
seconds the connection setup is allowed to take.
A value <= 0 enables waiting indefinitely, i.e., no timeout.
@@ -178,6 +179,8 @@ else HTTP POST with the contents of I<req> and optional I<content_type>, where
the length of the data in I<req> does not need to be determined in advance: the
BIO will be read on-the-fly while sending the request, which supports streaming.
The optional list I<headers> may contain additional custom HTTP header lines.
+The I<path>, I<headers> names and values, and I<content_type> must not contain
+CR or LF characters.
The I<max_resp_len> parameter specifies the maximum allowed
response content length, where the value 0 indicates no limit.
For the meaning of the I<expected_content_type>, I<expect_asn1>, I<timeout>,
diff --git a/test/http_test.c b/test/http_test.c
index e70e132e23..2776a6f7a1 100644
--- a/test/http_test.c
+++ b/test/http_test.c
@@ -11,6 +11,7 @@
#include <openssl/http.h>
#include <openssl/pem.h>
#include <openssl/x509v3.h>
+#include <openssl/err.h>
#include <string.h>
#include "testutil.h"
@@ -422,6 +423,57 @@ static int test_http_url_invalid_path(void)
return test_http_url_invalid("https://[FF01::101]pkix");
}
+static int test_http_crlf_rejected(void)
+{
+ BIO *wbio = BIO_new(BIO_s_mem());
+ BIO *rbio = BIO_new(BIO_s_mem());
+ BIO *req = BIO_new(BIO_s_mem());
+ BIO *proxy_bio = BIO_new(BIO_s_mem());
+ OSSL_HTTP_REQ_CTX *rctx = NULL;
+ int res = 0;
+
+ if (!TEST_ptr(wbio)
+ || !TEST_ptr(rbio)
+ || !TEST_ptr(req)
+ || !TEST_ptr(proxy_bio)
+ || !TEST_int_eq(BIO_puts(req, "x"), 1)
+ || !TEST_ptr(rctx = OSSL_HTTP_REQ_CTX_new(wbio, rbio, 0)))
+ goto err;
+
+ ERR_clear_error();
+ res = TEST_false(OSSL_HTTP_REQ_CTX_set_request_line(rctx, 0 /* GET */,
+ NULL, NULL, "/path\r\nInjected: value"))
+ && TEST_false(OSSL_HTTP_REQ_CTX_set_request_line(rctx, 0 /* GET */,
+ "server\r\nInjected: value", "80", RPATH))
+ && TEST_false(OSSL_HTTP_REQ_CTX_set_request_line(rctx, 0 /* GET */,
+ "server", "80\r\nInjected: value", RPATH))
+ && TEST_true(OSSL_HTTP_REQ_CTX_set_request_line(rctx, 0 /* GET */,
+ NULL, NULL, RPATH))
+ && TEST_false(OSSL_HTTP_REQ_CTX_add1_header(rctx,
+ "X-Test\r\nInjected", "value"))
+ && TEST_false(OSSL_HTTP_REQ_CTX_add1_header(rctx,
+ "X-Test", "value\r\nInjected: value"))
+ && TEST_false(OSSL_HTTP_set1_request(rctx, RPATH, NULL,
+ "text/plain\r\nInjected: value", req,
+ NULL, 0 /* expect_asn1 */, 0 /* max_resp_len */,
+ 0 /* timeout */, 0 /* keep_alive */))
+ && TEST_false(OSSL_HTTP_proxy_connect(proxy_bio,
+ "server\r\nInjected: value", "443", NULL, NULL,
+ 0 /* timeout */, NULL, NULL))
+ && TEST_false(OSSL_HTTP_proxy_connect(proxy_bio,
+ "server", "443\r\nInjected: value", NULL, NULL,
+ 0 /* timeout */, NULL, NULL));
+
+err:
+ ERR_clear_error();
+ OSSL_HTTP_REQ_CTX_free(rctx);
+ BIO_free(wbio);
+ BIO_free(rbio);
+ BIO_free(req);
+ BIO_free(proxy_bio);
+ return res;
+}
+
static int test_http_get_txt(void)
{
return test_http_method(1 /* GET */, 1, HTTP_STATUS_CODE_OK);
@@ -609,6 +661,7 @@ int setup_tests(void)
ADD_TEST(test_http_url_invalid_prefix);
ADD_TEST(test_http_url_invalid_port);
ADD_TEST(test_http_url_invalid_path);
+ ADD_TEST(test_http_crlf_rejected);
ADD_TEST(test_http_get_txt);
ADD_TEST(test_http_get_txt_redirected);