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

yiguolei pushed a commit to branch branch-4.2
in repository https://gitbox.apache.org/repos/asf/doris.git

commit 51d4b709b7a09549b70997e7e001060a927cacf5
Author: github-actions[bot] 
<41898282+github-actions[bot]@users.noreply.github.com>
AuthorDate: Thu Oct 15 09:29:19 2026 +0800

    branch-4.1: [fix](load) Reject malformed numeric HTTP headers instead of 
killing BE #68462 (#69081)
    
    Cherry-picked from #68462
    
    Co-authored-by: Xin Liao <[email protected]>
---
 be/src/service/http/action/http_stream.cpp       | 18 ++++++++----
 be/src/service/http/action/jeprofile_actions.cpp | 12 ++++++--
 be/src/service/http/action/stream_load_2pc.cpp   |  3 ++
 be/src/service/http/utils.cpp                    | 11 +++++++-
 be/src/util/string_util.cpp                      | 12 ++++++++
 be/src/util/string_util.h                        |  2 ++
 be/test/service/http/http_utils_test.cpp         | 22 +++++++++++++++
 be/test/service/http/stream_load_test.cpp        | 36 ++++++++++++++++++++++++
 be/test/util/string_util_test.cpp                | 15 ++++++++++
 9 files changed, 123 insertions(+), 8 deletions(-)

diff --git a/be/src/service/http/action/http_stream.cpp 
b/be/src/service/http/action/http_stream.cpp
index 92cdf6b9c0b..9a17dfa218b 100644
--- a/be/src/service/http/action/http_stream.cpp
+++ b/be/src/service/http/action/http_stream.cpp
@@ -268,8 +268,14 @@ void HttpStreamAction::on_chunk_data(HttpRequest* req) {
     if (ctx == nullptr || !ctx->status.ok()) {
         return;
     }
-    if (!req->header(HTTP_WAL_ID_KY).empty()) {
-        ctx->wal_id = std::stoll(req->header(HTTP_WAL_ID_KY));
+    const auto& wal_id_str = req->header(HTTP_WAL_ID_KY);
+    if (!wal_id_str.empty()) {
+        auto wal_id = safe_stoll(wal_id_str, HTTP_WAL_ID_KY);
+        if (!wal_id.has_value()) {
+            ctx->status = std::move(wal_id).error();
+            return;
+        }
+        ctx->wal_id = wal_id.value();
     }
     struct evhttp_request* ev_req = req->get_evhttp_request();
     auto evbuf = evhttp_request_get_input_buffer(ev_req);
@@ -447,9 +453,11 @@ Status HttpStreamAction::_handle_group_commit(HttpRequest* 
req,
     if (config::wait_internal_group_commit_finish) {
         group_commit_mode = "sync_mode";
     }
-    int64_t content_length = req->header(HttpHeaders::CONTENT_LENGTH).empty()
-                                     ? 0
-                                     : 
std::stoll(req->header(HttpHeaders::CONTENT_LENGTH));
+    int64_t content_length = 0;
+    const auto& content_length_str = req->header(HttpHeaders::CONTENT_LENGTH);
+    if (!content_length_str.empty()) {
+        content_length = DORIS_TRY(safe_stoll(content_length_str, 
HttpHeaders::CONTENT_LENGTH));
+    }
     if (content_length < 0) {
         std::stringstream ss;
         ss << "This http load content length <0 (" << content_length
diff --git a/be/src/service/http/action/jeprofile_actions.cpp 
b/be/src/service/http/action/jeprofile_actions.cpp
index e8c36b00cf1..862f5f07d73 100644
--- a/be/src/service/http/action/jeprofile_actions.cpp
+++ b/be/src/service/http/action/jeprofile_actions.cpp
@@ -20,6 +20,7 @@
 #include <jemalloc/jemalloc.h>
 #include <unistd.h>
 
+#include <cstdint>
 #include <string>
 
 #include "agent/utils.h"
@@ -30,6 +31,7 @@
 #include "service/http/http_handler_with_auth.h"
 #include "service/http/http_headers.h"
 #include "service/http/http_request.h"
+#include "util/string_util.h"
 
 namespace doris {
 
@@ -92,9 +94,15 @@ void SetJeHeapProfileResetActions::handle(HttpRequest* req) {
     req->add_output_header(HttpHeaders::CONTENT_TYPE, HEADER_JSON.c_str());
     if (compile_check(req)) {
         const auto& lg_sample_str = req->param("reset_value");
-        size_t lg_sample = std::stol(lg_sample_str);
+        auto parsed_lg_sample = safe_stoll(lg_sample_str, "reset_value");
+        if (!parsed_lg_sample.has_value()) {
+            HttpChannel::send_reply(req, HttpStatus::BAD_REQUEST,
+                                    
parsed_lg_sample.error().to_string_no_stack() + "\n");
+            return;
+        }
+        int64_t lg_sample = parsed_lg_sample.value();
         if (lg_sample > 0 && 
HeapProfiler::instance()->check_enable_heap_profiler() &&
-            HeapProfiler::instance()->heap_profiler_reset(lg_sample)) {
+            
HeapProfiler::instance()->heap_profiler_reset(static_cast<size_t>(lg_sample))) {
             HttpChannel::send_reply(req, HttpStatus::OK,
                                     fmt::format("Jemalloc reset all memory 
profile statistics and "
                                                 "update the sample rate:{}\n",
diff --git a/be/src/service/http/action/stream_load_2pc.cpp 
b/be/src/service/http/action/stream_load_2pc.cpp
index 15ca8066cf9..4a20b0f8b93 100644
--- a/be/src/service/http/action/stream_load_2pc.cpp
+++ b/be/src/service/http/action/stream_load_2pc.cpp
@@ -82,6 +82,9 @@ void StreamLoad2PCAction::handle(HttpRequest* req) {
     if (!parse_basic_auth(*req, &ctx->auth)) {
         LOG(WARNING) << "parse basic authorization failed.";
         status = Status::InternalError("no valid Basic authorization");
+        status_result = status.to_json();
+        HttpChannel::send_reply(req, HttpStatus::OK, status_result);
+        return;
     }
 
     status = _exec_env->stream_load_executor()->operate_txn_2pc(ctx.get());
diff --git a/be/src/service/http/utils.cpp b/be/src/service/http/utils.cpp
index 746eabff165..8f49505981c 100644
--- a/be/src/service/http/utils.cpp
+++ b/be/src/service/http/utils.cpp
@@ -46,6 +46,7 @@
 #include "util/md5.h"
 #include "util/path_util.h"
 #include "util/security.h"
+#include "util/string_util.h"
 #include "util/url_coding.h"
 
 namespace doris {
@@ -111,7 +112,15 @@ bool parse_basic_auth(const HttpRequest& req, AuthInfo* 
auth) {
     } else if (!auth_token.empty()) {
         auth->token = auth_token;
     } else if (!auth_code.empty()) {
-        auth->auth_code = std::stoll(auth_code); // deprecated
+        // auth_code comes straight from a request header, a malformed one is 
just an
+        // invalid credential and must not throw out of the http callback. The 
value is a
+        // credential that is_sensitive_header() masks elsewhere, so it is not 
logged.
+        auto parsed_auth_code = safe_stoll(auth_code, HTTP_AUTH_CODE);
+        if (!parsed_auth_code.has_value()) {
+            LOG(WARNING) << "parse auth code failed, malformed " << 
HTTP_AUTH_CODE << " header";
+            return false;
+        }
+        auth->auth_code = parsed_auth_code.value(); // deprecated
     } else if (!valid_basic_auth) {
         return false;
     }
diff --git a/be/src/util/string_util.cpp b/be/src/util/string_util.cpp
index 259392b020d..0588a78aaa3 100644
--- a/be/src/util/string_util.cpp
+++ b/be/src/util/string_util.cpp
@@ -43,4 +43,16 @@ Result<int> safe_stoi(const std::string& input, const 
std::string& name) {
                 std::string("'{}' value out of range: '{}', {}"), name, input, 
e.what()));
     }
 }
+
+Result<int64_t> safe_stoll(const std::string& input, const std::string& name) {
+    try {
+        return std::stoll(input);
+    } catch (const std::invalid_argument& e) {
+        return ResultError(Status::Error<ErrorCode::INVALID_ARGUMENT>(
+                std::string("Invalid format of '{}': '{}', {}"), name, input, 
e.what()));
+    } catch (const std::out_of_range& e) {
+        return ResultError(Status::Error<ErrorCode::INVALID_ARGUMENT>(
+                std::string("'{}' value out of range: '{}', {}"), name, input, 
e.what()));
+    }
+}
 } // namespace doris
diff --git a/be/src/util/string_util.h b/be/src/util/string_util.h
index 54a93fff8c6..6cf7f7c2956 100644
--- a/be/src/util/string_util.h
+++ b/be/src/util/string_util.h
@@ -24,6 +24,7 @@
 #include <boost/tokenizer.hpp>
 #include <cctype>
 #include <cstddef>
+#include <cstdint>
 #include <map>
 #include <set>
 #include <sstream>
@@ -143,6 +144,7 @@ public:
 
 size_t hash_of_path(const std::string& identifier, const std::string& path);
 Result<int> safe_stoi(const std::string& input, const std::string& name);
+Result<int64_t> safe_stoll(const std::string& input, const std::string& name);
 using StringCaseSet = std::set<std::string, StringCaseLess>;
 using StringCaseUnorderedSet = std::unordered_set<std::string, 
StringCaseHasher, StringCaseEqual>;
 template <class T>
diff --git a/be/test/service/http/http_utils_test.cpp 
b/be/test/service/http/http_utils_test.cpp
index ba096bc153f..9f7942043a4 100644
--- a/be/test/service/http/http_utils_test.cpp
+++ b/be/test/service/http/http_utils_test.cpp
@@ -21,7 +21,9 @@
 
 #include <string>
 
+#include "common/utils.h"
 #include "gtest/gtest_pred_impl.h"
+#include "service/http/http_common.h"
 #include "service/http/http_headers.h"
 #include "service/http/http_request.h"
 #include "service/http/utils.h"
@@ -96,4 +98,24 @@ TEST_F(HttpUtilsTest, parse_basic_auth) {
     }
 }
 
+TEST_F(HttpUtilsTest, parse_auth_code) {
+    {
+        HttpRequest req(_evhttp_req);
+        req._headers.emplace(HttpHeaders::AUTHORIZATION, 
encode_basic_auth("doris", "passwd"));
+        req._headers.emplace(HTTP_AUTH_CODE, "1234567890123");
+        AuthInfo auth;
+        EXPECT_TRUE(parse_basic_auth(req, &auth));
+        EXPECT_EQ(1234567890123, auth.auth_code);
+    }
+    {
+        // a malformed auth code is rejected instead of throwing out of the 
http callback
+        HttpRequest req(_evhttp_req);
+        req._headers.emplace(HttpHeaders::AUTHORIZATION, 
encode_basic_auth("doris", "passwd"));
+        req._headers.emplace(HTTP_AUTH_CODE, "not_a_number");
+        AuthInfo auth;
+        EXPECT_FALSE(parse_basic_auth(req, &auth));
+        EXPECT_EQ(-1, auth.auth_code);
+    }
+}
+
 } // namespace doris
diff --git a/be/test/service/http/stream_load_test.cpp 
b/be/test/service/http/stream_load_test.cpp
index 93dd9d044c7..1405092f401 100644
--- a/be/test/service/http/stream_load_test.cpp
+++ b/be/test/service/http/stream_load_test.cpp
@@ -195,4 +195,40 @@ TEST_F(StreamLoadTest, 
HttpStreamBodySizeLimitPlusOneErrorIncludesExactBytes) {
               "this load is reasonable");
     evhttp_request_free(evhttp_req);
 }
+
+// A malformed Content-Length used to escape as an exception from the libevent 
header
+// callback and take the whole BE process down.
+TEST_F(StreamLoadTest, HttpStreamMalformedContentLengthIsRejected) {
+    for (const auto* content_length : {"not_a_number", "9223372036854775808"}) 
{
+        auto* evhttp_req = evhttp_request_new(nullptr, nullptr);
+        HttpRequest req(evhttp_req);
+        req.set_header(HTTP_GROUP_COMMIT, "async_mode");
+        req.set_header(HttpHeaders::CONTENT_LENGTH, content_length);
+
+        HttpStreamAction action(nullptr);
+        auto ctx = std::make_shared<StreamLoadContext>(nullptr);
+        auto status = action._handle_group_commit(&req, ctx);
+
+        EXPECT_TRUE(status.is<ErrorCode::INVALID_ARGUMENT>()) << 
content_length;
+        evhttp_request_free(evhttp_req);
+    }
+}
+
+TEST_F(StreamLoadTest, HttpStreamMalformedWalIdIsRejected) {
+    auto* evhttp_req = evhttp_request_new(nullptr, nullptr);
+    HttpRequest req(evhttp_req);
+    req.set_header(HTTP_WAL_ID_KY, "not_a_number");
+
+    HttpStreamAction action(nullptr);
+    auto ctx = std::make_shared<StreamLoadContext>(nullptr);
+    req.set_handler(&action);
+    req.set_handler_ctx(ctx);
+
+    action.on_chunk_data(&req);
+
+    EXPECT_TRUE(ctx->status.is<ErrorCode::INVALID_ARGUMENT>());
+    // ctx carries no ExecEnv, so detach it before ~HttpRequest calls 
free_handler_ctx()
+    req.set_handler_ctx(nullptr);
+    evhttp_request_free(evhttp_req);
+}
 } // namespace doris
diff --git a/be/test/util/string_util_test.cpp 
b/be/test/util/string_util_test.cpp
index 77b090be4c2..8a8bb3a92da 100644
--- a/be/test/util/string_util_test.cpp
+++ b/be/test/util/string_util_test.cpp
@@ -200,4 +200,19 @@ TEST_F(StringUtilTest, normal) {
     }
 }
 
+TEST_F(StringUtilTest, safe_stoll) {
+    EXPECT_EQ(0, safe_stoll("0", "content-length").value());
+    EXPECT_EQ(-1, safe_stoll("-1", "content-length").value());
+    // beyond the range of safe_stoi
+    EXPECT_EQ(9223372036854775807LL, safe_stoll("9223372036854775807", 
"content-length").value());
+
+    auto invalid = safe_stoll("not_a_number", "content-length");
+    EXPECT_FALSE(invalid.has_value());
+    EXPECT_TRUE(invalid.error().is<ErrorCode::INVALID_ARGUMENT>());
+
+    auto out_of_range = safe_stoll("9223372036854775808", "content-length");
+    EXPECT_FALSE(out_of_range.has_value());
+    EXPECT_TRUE(out_of_range.error().is<ErrorCode::INVALID_ARGUMENT>());
+}
+
 } // namespace doris


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

Reply via email to