]> git.ipfire.org Git - thirdparty/openssl.git/commitdiff
Reject CR/LF in HTTP request components
authorOpenSSL Machine <openssl-machine@openssl.org>
Wed, 29 Apr 2026 13:53:25 +0000 (22:53 +0900)
committerEugene Syromiatnikov <esyr@openssl.org>
Mon, 11 May 2026 07:42:58 +0000 (09:42 +0200)
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)

crypto/http/http_client.c
doc/man3/OSSL_HTTP_REQ_CTX.pod
doc/man3/OSSL_HTTP_transfer.pod
test/http_test.c

index f9f7bff0d11d522b7059f77121cecd28654f740e..34d3a2cccc9c92fdd4b9406dc192abdaefebf055 100644 (file)
@@ -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;
     }
index 210f33801caef0e0d1d263a60650068ca15b3519..e530d1b3fb09380c9ae3a3b81ff3a10c86458ef4 100644 (file)
@@ -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>
index eaa0986666fc2352a81fa1602deca5c40df3bcf0..6c03e347fbc956792ac1cab0132b6bdd98e1da6b 100644 (file)
@@ -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>,
index e70e132e2319622e1487fbd2150058207fc4fd97..2776a6f7a183c530886ee799dccd6a1c90ce743d 100644 (file)
@@ -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);