This is an automated email from the ASF dual-hosted git repository.

chenBright pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/brpc.git


The following commit(s) were added to refs/heads/master by this push:
     new 1665ebed Skip header fields containing CR/LF in http serialization 
(#3387)
1665ebed is described below

commit 1665ebedfd767f72d9f46ebe3241037221e6dd27
Author: UB <[email protected]>
AuthorDate: Mon Aug 24 13:28:48 2026 +0530

    Skip header fields containing CR/LF in http serialization (#3387)
    
    * skip header fields containing CR/LF in http serialization
    
    * Gate CR/LF header check behind a flag; log at WARNING with sanitized name
    
    Signed-off-by: ubeddulla khan <[email protected]>
    
    * Log the skipped header value alongside its name
    
    Signed-off-by: ubeddulla khan <[email protected]>
    
    * Use separate IOBufs for request and response in the CR/LF header test
    
    Signed-off-by: ubeddulla khan <[email protected]>
    
    ---------
    
    Signed-off-by: ubeddulla khan <[email protected]>
---
 src/brpc/details/http_message.cpp   | 48 +++++++++++++++++++++++++++++++---
 test/brpc_http_message_unittest.cpp | 52 +++++++++++++++++++++++++++++++++++++
 2 files changed, 96 insertions(+), 4 deletions(-)

diff --git a/src/brpc/details/http_message.cpp 
b/src/brpc/details/http_message.cpp
index 13beb67a..45f77bab 100644
--- a/src/brpc/details/http_message.cpp
+++ b/src/brpc/details/http_message.cpp
@@ -23,6 +23,7 @@
 #include "butil/scoped_lock.h"
 #include "butil/endpoint.h"
 #include "butil/base64.h"
+#include "butil/binary_printer.h"                // ToPrintable
 #include "bthread/bthread.h"                    // bthread_usleep
 #include "brpc/log.h"
 #include "brpc/reloadable_flags.h"
@@ -38,6 +39,9 @@ DEFINE_bool(http_verbose, false,
             "[DEBUG] Print EVERY http request/response");
 DEFINE_int32(http_verbose_max_body_length, 512,
              "[DEBUG] Max body length printed when -http_verbose is on");
+DEFINE_bool(http_check_outbound_header_crlf, true,
+            "Skip outbound http header fields whose name or value contains "
+            "CR/LF to prevent request/response splitting.");
 DECLARE_int64(socket_max_unwritten_bytes);
 DECLARE_uint64(max_body_size);
 
@@ -578,6 +582,18 @@ std::ostream& operator<<(std::ostream& os, const 
http_parser& parser) {
 
 #define BRPC_CRLF "\r\n"
 
+// A header field-name or field-value carrying a raw CR or LF lets whoever
+// controls it close the current line and inject extra header fields (or a
+// body) into the serialized message, i.e. HTTP request/response splitting.
+// The inbound parser already refuses these bytes; the outbound path drops
+// such fields so a value forwarded from an untrusted source can't smuggle
+// headers. Gated by -http_check_outbound_header_crlf so it can be turned
+// off on hot paths that never forward untrusted header values.
+static bool HeaderHasCRLF(const std::string& s) {
+    return FLAGS_http_check_outbound_header_crlf &&
+        s.find_first_of("\r\n") != std::string::npos;
+}
+
 // Request format
 // Request       = Request-Line              ; Section 5.1
 //                 *(( general-header        ; Section 4.5
@@ -645,11 +661,23 @@ void MakeRawHttpRequest(butil::IOBuf* request,
         os << BRPC_CRLF;
     }
     if (!h->content_type().empty()) {
-        os << "Content-Type: " << h->content_type()
-           << BRPC_CRLF;
+        if (HeaderHasCRLF(h->content_type())) {
+            LOG(WARNING) << "Skip Content-Type `"
+                         << butil::ToPrintable(h->content_type())
+                         << "' containing CR/LF to avoid injection";
+        } else {
+            os << "Content-Type: " << h->content_type()
+               << BRPC_CRLF;
+        }
     }
     for (HttpHeader::HeaderIterator it = h->HeaderBegin();
          it != h->HeaderEnd(); ++it) {
+        if (HeaderHasCRLF(it->first) || HeaderHasCRLF(it->second)) {
+            LOG(WARNING) << "Skip header `" << butil::ToPrintable(it->first)
+                         << ": " << butil::ToPrintable(it->second)
+                         << "' containing CR/LF to avoid injection";
+            continue;
+        }
         os << it->first << ": " << it->second << BRPC_CRLF;
     }
     if (h->GetHeader("Accept") == nullptr) {
@@ -734,11 +762,23 @@ void MakeRawHttpResponse(butil::IOBuf* response,
         }
     }
     if (!is_invalid_content && !h->content_type().empty()) {
-        os << "Content-Type: " << h->content_type()
-           << BRPC_CRLF;
+        if (HeaderHasCRLF(h->content_type())) {
+            LOG(WARNING) << "Skip Content-Type `"
+                         << butil::ToPrintable(h->content_type())
+                         << "' containing CR/LF to avoid injection";
+        } else {
+            os << "Content-Type: " << h->content_type()
+               << BRPC_CRLF;
+        }
     }
     for (HttpHeader::HeaderIterator it = h->HeaderBegin();
          it != h->HeaderEnd(); ++it) {
+        if (HeaderHasCRLF(it->first) || HeaderHasCRLF(it->second)) {
+            LOG(WARNING) << "Skip header `" << butil::ToPrintable(it->first)
+                         << ": " << butil::ToPrintable(it->second)
+                         << "' containing CR/LF to avoid injection";
+            continue;
+        }
         os << it->first << ": " << it->second << BRPC_CRLF;
     }
     os << BRPC_CRLF;  // CRLF before content
diff --git a/test/brpc_http_message_unittest.cpp 
b/test/brpc_http_message_unittest.cpp
index bd2ac5ed..87f3b373 100644
--- a/test/brpc_http_message_unittest.cpp
+++ b/test/brpc_http_message_unittest.cpp
@@ -703,6 +703,58 @@ TEST(HttpMessageTest, serialize_http_response) {
         << butil::ToPrintable(response);
 }
 
+TEST(HttpMessageTest, serialize_header_with_crlf_is_not_injected) {
+    // A header value carrying CR/LF must not terminate the current line and
+    // introduce extra header fields (HTTP request/response splitting).
+    butil::EndPoint ep;
+    ASSERT_EQ(0, butil::str2endpoint("127.0.0.1:1234", &ep));
+
+    brpc::HttpHeader req_header;
+    req_header.set_method(brpc::HTTP_METHOD_POST);
+    req_header.SetHeader("X-Evil", "a\r\nInjected: 1");
+    butil::IOBuf req_content;
+    req_content.append("data");
+    butil::IOBuf request;
+    MakeRawHttpRequest(&request, &req_header, ep, &req_content);
+    std::string request_str = request.to_string();
+    ASSERT_EQ(std::string::npos, request_str.find("Injected: 1")) << 
request_str;
+
+    brpc::HttpHeader res_header;
+    res_header.SetHeader("X-Evil", "a\r\nInjected: 1");
+    butil::IOBuf res_content;
+    res_content.append("data");
+    butil::IOBuf response;
+    MakeRawHttpResponse(&response, &res_header, &res_content);
+    std::string response_str = response.to_string();
+    ASSERT_EQ(std::string::npos, response_str.find("Injected: 1")) << 
response_str;
+}
+
+TEST(HttpMessageTest, serialize_content_type_with_crlf_is_not_injected) {
+    // Content-Type goes through the same emission path and must be dropped
+    // (not written) when it carries CR/LF.
+    butil::EndPoint ep;
+    ASSERT_EQ(0, butil::str2endpoint("127.0.0.1:1234", &ep));
+
+    brpc::HttpHeader req_header;
+    req_header.set_method(brpc::HTTP_METHOD_POST);
+    req_header.set_content_type("text/plain\r\nInjected: 1");
+    butil::IOBuf req_content;
+    req_content.append("data");
+    butil::IOBuf request;
+    MakeRawHttpRequest(&request, &req_header, ep, &req_content);
+    std::string request_str = request.to_string();
+    ASSERT_EQ(std::string::npos, request_str.find("Injected: 1")) << 
request_str;
+
+    brpc::HttpHeader res_header;
+    res_header.set_content_type("text/plain\r\nInjected: 1");
+    butil::IOBuf res_content;
+    res_content.append("data");
+    butil::IOBuf response;
+    MakeRawHttpResponse(&response, &res_header, &res_content);
+    std::string response_str = response.to_string();
+    ASSERT_EQ(std::string::npos, response_str.find("Injected: 1")) << 
response_str;
+}
+
 TEST(HttpMessageTest, http_1_1_request_without_host) {
     brpc::FLAGS_allow_http_1_1_request_without_host = false;
     {


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to