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 d54ffe98 reject CR/LF in SetHttpURL and SetH2Path (#3588)
d54ffe98 is described below

commit d54ffe98f1ed956b1756800876100027894d7f80
Author: sahvx655-wq <[email protected]>
AuthorDate: Fri Oct 9 11:54:57 2026 +0530

    reject CR/LF in SetHttpURL and SetH2Path (#3588)
    
    Both parsers copied control characters into _host/_path/_query/_fragment, 
which MakeRawHttpRequest and GenerateH2Path then wrote verbatim into the Host 
header, the request line and the HTTP/2 :path.
---
 src/brpc/policy/http2_rpc_protocol.cpp |  4 ++--
 src/brpc/uri.cpp                       | 22 ++++++++++++++++++++++
 test/brpc_uri_unittest.cpp             | 32 ++++++++++++++++++++++++++++++++
 3 files changed, 56 insertions(+), 2 deletions(-)

diff --git a/src/brpc/policy/http2_rpc_protocol.cpp 
b/src/brpc/policy/http2_rpc_protocol.cpp
index 060b25fa..004430af 100644
--- a/src/brpc/policy/http2_rpc_protocol.cpp
+++ b/src/brpc/policy/http2_rpc_protocol.cpp
@@ -1405,8 +1405,8 @@ int 
H2StreamContext::ConsumeHeaders(butil::IOBufBytesIterator& it) {
                                    << ", stream_id=" << _stream_id;
                         _rejected_error = H2_PROTOCOL_ERROR;
                     } else if (h.uri().SetH2Path(pair.value) != 0) {
-                        // Including path/query/fragment. The only way this
-                        // fails is too many query parameters.
+                        // Including path/query/fragment. Fails on a CR/LF
+                        // in the value or too many query parameters.
                         LOG(ERROR) << h.uri().status().error_cstr()
                                    << ", stream_id=" << _stream_id;
                         _rejected_error = H2_ENHANCE_YOUR_CALM;
diff --git a/src/brpc/uri.cpp b/src/brpc/uri.cpp
index 6b10464b..90263ff2 100644
--- a/src/brpc/uri.cpp
+++ b/src/brpc/uri.cpp
@@ -17,6 +17,7 @@
 
 
 #include <ctype.h>                         // isalnum
+#include <string.h>                        // strpbrk
 #include <unordered_set>
 #include <gflags/gflags.h>
 #include "brpc/log.h"
@@ -180,10 +181,26 @@ static const char g_url_parsing_fast_action_map_raw[] = {
 static const char* const g_url_parsing_fast_action_map =
     g_url_parsing_fast_action_map_raw + 128;
 
+// CR and LF are never valid in a URL and must not survive parsing: every
+// component is written verbatim into the serialized message later on, the
+// host/port into the "Host" header and the :authority pseudo-header, and
+// path/query/fragment into the HTTP/1 request line (PrintWithoutHost) and
+// the HTTP/2 :path (GenerateH2Path). Either byte there appends headers to
+// the request or starts a second one. Only the parsers below reject it; the
+// set_*() overwriters are documented as taking already-valid input.
+static bool has_crlf(const char* s) {
+    return strpbrk(s, "\r\n") != nullptr;
+}
+
 // This implementation is faster than http_parser_parse_url() and allows
 // ignoring of scheme("http://";)
 int URI::SetHttpURL(const char* url) {
     Clear();
+
+    if (has_crlf(url)) {
+        _st.set_error(EINVAL, "Invalid CR/LF in url");
+        return -1;
+    }
     
     const char* p = url;
     // skip heading blanks
@@ -441,6 +458,11 @@ int URI::SetH2Path(const char* h2_path) {
     _initialized_query_map = false;
     _query_map.clear();
 
+    if (has_crlf(h2_path)) {
+        _st.set_error(EINVAL, "Invalid CR/LF in :path");
+        return -1;
+    }
+
     const char* p = h2_path;
     const char* start = p;
     for (; *p && *p != '?' && *p != '#'; ++p) {}
diff --git a/test/brpc_uri_unittest.cpp b/test/brpc_uri_unittest.cpp
index b1be2c55..1fb43696 100644
--- a/test/brpc_uri_unittest.cpp
+++ b/test/brpc_uri_unittest.cpp
@@ -346,6 +346,38 @@ TEST(URITest, invalid_spaces) {
     ASSERT_STREQ("Invalid space in fragment", uri.status().error_cstr());
 }
 
+TEST(URITest, invalid_crlf) {
+    brpc::URI uri;
+    // A CR/LF that reaches _host, _path, _query or _fragment is written
+    // verbatim into the "Host" header and the request line, which appends
+    // headers to the outbound request or starts a second one. None of these
+    // contains a space, so the "Invalid space" checks cannot catch them.
+    ASSERT_EQ(-1, uri.SetHttpURL("http://a.com\r\nX-Injected:1/s";));
+    ASSERT_STREQ("Invalid CR/LF in url", uri.status().error_cstr());
+    ASSERT_EQ(-1, uri.SetHttpURL("http://a.com/s\r\nX-Injected:1";));
+    ASSERT_STREQ("Invalid CR/LF in url", uri.status().error_cstr());
+    ASSERT_EQ(-1, uri.SetHttpURL("http://a.com/s?wd=uri\r\nX-Injected:1";));
+    ASSERT_STREQ("Invalid CR/LF in url", uri.status().error_cstr());
+    ASSERT_EQ(-1, uri.SetHttpURL("http://a.com/s#frag\r\nX-Injected:1";));
+    ASSERT_STREQ("Invalid CR/LF in url", uri.status().error_cstr());
+    // A bare CR or LF is enough, the pair is not required.
+    ASSERT_EQ(-1, uri.SetHttpURL("http://a.com/s\nX-Injected:1";));
+    ASSERT_EQ(-1, uri.SetHttpURL("http://a.com/s\rX-Injected:1";));
+    // Nothing of the rejected URL is left behind.
+    ASSERT_EQ("", uri.host());
+    ASSERT_EQ("", uri.path());
+
+    ASSERT_EQ(-1, uri.SetH2Path("/s\r\nX-Injected:1"));
+    ASSERT_STREQ("Invalid CR/LF in :path", uri.status().error_cstr());
+    ASSERT_EQ(-1, uri.SetH2Path("/s?wd=uri\r\nX-Injected:1"));
+    ASSERT_STREQ("Invalid CR/LF in :path", uri.status().error_cstr());
+    ASSERT_EQ(-1, uri.SetH2Path("/s#frag\r\nX-Injected:1"));
+    ASSERT_STREQ("Invalid CR/LF in :path", uri.status().error_cstr());
+    ASSERT_EQ("", uri.path());
+    // The next path clears the failure rather than inheriting it.
+    ASSERT_EQ(0, uri.SetH2Path("/s?wd=uri")) << uri.status();
+}
+
 TEST(URITest, invalid_query) {
     brpc::URI uri;
     ASSERT_EQ(0, uri.SetHttpURL("http://a.b.c/?a-b-c:def";));


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

Reply via email to