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 9e9c626a Fix streaming RPC frame validation (#3481)
9e9c626a is described below
commit 9e9c626abfc2c62ceee90a47a880551a1c1b40ff
Author: Xiaofeng Wang <[email protected]>
AuthorDate: Mon Aug 24 15:53:18 2026 +0800
Fix streaming RPC frame validation (#3481)
---
src/brpc/controller.cpp | 8 +-
src/brpc/policy/baidu_rpc_protocol.cpp | 18 ++++-
src/brpc/policy/streaming_rpc_protocol.cpp | 13 ++-
src/brpc/socket.cpp | 7 ++
src/brpc/socket.h | 4 +
src/brpc/stream.cpp | 19 ++++-
src/brpc/stream_impl.h | 3 +
test/brpc_streaming_rpc_unittest.cpp | 122 +++++++++++++++++++++++++++++
8 files changed, 185 insertions(+), 9 deletions(-)
diff --git a/src/brpc/controller.cpp b/src/brpc/controller.cpp
index 6c3ecee3..629332f4 100644
--- a/src/brpc/controller.cpp
+++ b/src/brpc/controller.cpp
@@ -1745,7 +1745,13 @@ void Controller::HandleStreamConnection(Socket
*host_socket) {
if(!ptrs[i]) continue;
Stream* extra_stream = ptrs[i].get();
_remote_stream_settings->set_stream_id(extra_stream_ids[i - 1]);
- extra_stream->SetHostSocket(host_socket);
+ if (extra_stream->SetHostSocket(host_socket) != 0) {
+ SetFailed(EREQUEST, "Fail to bind response stream=%" PRIu64,
+ extra_stream_ids[i - 1]);
+ Stream::SetFailed(_request_streams, _error_code,
+ "%s", _error_text.c_str());
+ return;
+ }
extra_stream->SetConnected(_remote_stream_settings);
}
}
diff --git a/src/brpc/policy/baidu_rpc_protocol.cpp
b/src/brpc/policy/baidu_rpc_protocol.cpp
index b74fb180..f39463e8 100644
--- a/src/brpc/policy/baidu_rpc_protocol.cpp
+++ b/src/brpc/policy/baidu_rpc_protocol.cpp
@@ -372,7 +372,14 @@ void SendRpcResponse(int64_t correlation_id, Controller*
cntl,
Stream* s = stream_ptr.get();
StreamSettings *stream_settings = meta.mutable_stream_settings();
s->FillSettings(stream_settings);
- s->SetHostSocket(sock);
+ if (s->SetHostSocket(sock) != 0) {
+ cntl->SetFailed(EINVAL, "Fail to bind stream=%" PRIu64
+ " to %s", response_stream_id,
+ sock->description().c_str());
+ Stream::SetFailed(response_stream_ids, EINVAL,
+ "%s", cntl->ErrorText().c_str());
+ return;
+ }
for (size_t i = 1; i < response_stream_ids.size(); ++i) {
stream_settings->mutable_extra_stream_ids()->Add(response_stream_ids[i]);
}
@@ -438,8 +445,13 @@ void SendRpcResponse(int64_t correlation_id, Controller*
cntl,
StreamUniquePtr extra_stream_ptr;
if (Stream::Address(extra_stream_id, &extra_stream_ptr) == 0) {
Stream* extra_stream = extra_stream_ptr.get();
- extra_stream->SetHostSocket(sock);
- extra_stream->SetConnected();
+ if (extra_stream->SetHostSocket(sock) == 0) {
+ extra_stream->SetConnected();
+ } else {
+ Stream::SetFailed(extra_stream_id, EINVAL,
+ "Fail to bind stream to %s",
+ sock->description().c_str());
+ }
} else {
LOG(WARNING) << "Stream=" << extra_stream_id
<< " was closed before sending response";
diff --git a/src/brpc/policy/streaming_rpc_protocol.cpp
b/src/brpc/policy/streaming_rpc_protocol.cpp
index bdad1f23..155ec188 100644
--- a/src/brpc/policy/streaming_rpc_protocol.cpp
+++ b/src/brpc/policy/streaming_rpc_protocol.cpp
@@ -27,6 +27,7 @@
#include "butil/iobuf.h" // butil::IOBuf
#include "butil/raw_pack.h" // RawPacker RawUnpacker
#include "brpc/log.h"
+#include "brpc/server.h"
#include "brpc/socket.h" // Socket
#include "brpc/streaming_rpc_meta.pb.h" // StreamFrameMeta
#include "brpc/policy/most_common_message.h"
@@ -59,7 +60,7 @@ void PackStreamMessage(butil::IOBuf* out,
}
ParseResult ParseStreamingMessage(butil::IOBuf* source,
- Socket* socket, bool /*read_eof*/, const void*
/*arg*/) {
+ Socket* socket, bool /*read_eof*/, const void*
arg) {
char header_buf[12];
const size_t n = source->copy_to(header_buf, sizeof(header_buf));
if (n >= 4) {
@@ -90,6 +91,16 @@ ParseResult ParseStreamingMessage(butil::IOBuf* source,
source->pop_front(sizeof(header_buf) + body_size);
return MakeParseError(PARSE_ERROR_TRY_OTHERS);
}
+ if (arg != nullptr) {
+ // Stream frames are consumed here and never reach InputMessenger's
+ // authentication hook.
+ const Server* server = static_cast<const Server*>(arg);
+ if (server->options().auth != nullptr && !socket->IsAuthenticated()) {
+ LOG(WARNING) << "Reject streaming frame from unauthenticated "
+ << *socket;
+ return MakeParseError(PARSE_ERROR_ABSOLUTELY_WRONG);
+ }
+ }
source->pop_front(sizeof(header_buf));
butil::IOBuf meta_buf;
source->cutn(&meta_buf, meta_size);
diff --git a/src/brpc/socket.cpp b/src/brpc/socket.cpp
index 38727571..becb0944 100644
--- a/src/brpc/socket.cpp
+++ b/src/brpc/socket.cpp
@@ -2206,6 +2206,13 @@ void Socket::SetAuthentication(int error_code) {
}
}
+bool Socket::IsAuthenticated() const {
+ const uint64_t flag_error =
+ _auth_flag_error.load(butil::memory_order_acquire);
+ return (flag_error & AUTH_FLAG) &&
+ (int32_t)(flag_error & 0xFFFFFFFFul) == 0;
+}
+
AuthContext* Socket::mutable_auth_context() {
if (_auth_context != nullptr) {
LOG(FATAL) << "Impossible! This function is supposed to be called "
diff --git a/src/brpc/socket.h b/src/brpc/socket.h
index 6d321f8b..bbde3ecd 100644
--- a/src/brpc/socket.h
+++ b/src/brpc/socket.h
@@ -556,6 +556,10 @@ public:
// `FightAuthentication', otherwise it's regarded as an error
void SetAuthentication(int error_code);
+ // Returns true iff authentication over this socket has completed
+ // successfully, i.e. `SetAuthentication(0)' was called.
+ bool IsAuthenticated() const;
+
// Since some protocols are not able to store correlation id in their
// headers (such as nova-pbrpc, http), we have to store it here. Note
// that there can only be 1 RPC call on this socket at any time, otherwise
diff --git a/src/brpc/stream.cpp b/src/brpc/stream.cpp
index 8f5ef3c5..8db93523 100644
--- a/src/brpc/stream.cpp
+++ b/src/brpc/stream.cpp
@@ -54,6 +54,7 @@ Stream::Stream(Forbidden f)
, _local_consumed(0)
, _atomic_local_consumed(0)
, _parse_rpc_response(false)
+ , _server_accepted_stream(false)
, _pending_buf(nullptr)
, _start_idle_timer_us(0)
, _idle_timer(0) {
@@ -94,6 +95,7 @@ int Stream::OnCreated(const StreamOptions& options,
_local_consumed = 0;
_atomic_local_consumed.store(0, butil::memory_order_relaxed);
_parse_rpc_response = parse_rpc_response;
+ _server_accepted_stream = (remote_settings != nullptr);
_pending_buf = nullptr;
_start_idle_timer_us = 0;
_idle_timer = 0;
@@ -584,11 +586,20 @@ void Stream::SetConnected(const StreamSettings*
remote_settings) {
int Stream::OnReceived(const StreamFrameMeta& fm, butil::IOBuf *buf, Socket*
sock) {
if (!_connected.load(butil::memory_order_acquire)) {
- // Before connection is published, let the locked slow path initialize
- // the host socket or confirm that another thread already did so.
- if (SetHostSocket(sock) != 0) {
+ if (_server_accepted_stream) {
+ BAIDU_SCOPED_LOCK(_connect_mutex);
+ if (_host_socket == nullptr || _host_socket->id() != sock->id()) {
+ LOG(WARNING) << "stream=" << id()
+ << " dropped a frame from a foreign socket";
+ return -1;
+ }
+ } else if (SetHostSocket(sock) != 0) {
return -1;
}
+ } else if (_host_socket == nullptr || _host_socket->id() != sock->id()) {
+ LOG(WARNING) << "stream=" << id()
+ << " dropped a frame from a foreign socket";
+ return -1;
}
switch (fm.frame_type()) {
@@ -758,7 +769,7 @@ int Stream::SetHostSocket(Socket* host_socket) {
return -1;
}
if (_host_socket != nullptr) {
- return 0;
+ return _host_socket->id() == host_socket->id() ? 0 : -1;
}
SocketUniquePtr ptr;
diff --git a/src/brpc/stream_impl.h b/src/brpc/stream_impl.h
index 06e50448..4d3dd0c5 100644
--- a/src/brpc/stream_impl.h
+++ b/src/brpc/stream_impl.h
@@ -159,6 +159,9 @@ friend class VersionedRefWithId<Stream>;
StreamSettings _remote_settings;
bool _parse_rpc_response;
+ // Server-accepted streams must be bound by their creating RPC, never by
+ // the first frame that happens to carry their id.
+ bool _server_accepted_stream;
bthread::ExecutionQueueId<butil::IOBuf*> _consumer_queue;
butil::IOBuf* _pending_buf;
int64_t _start_idle_timer_us;
diff --git a/test/brpc_streaming_rpc_unittest.cpp
b/test/brpc_streaming_rpc_unittest.cpp
index 46401d6d..2e4c046a 100644
--- a/test/brpc_streaming_rpc_unittest.cpp
+++ b/test/brpc_streaming_rpc_unittest.cpp
@@ -81,6 +81,128 @@ protected:
test::EchoResponse response;
};
+class StreamingRpcAuthenticator : public brpc::Authenticator {
+public:
+ int GenerateCredential(std::string* auth_str) const override {
+ *auth_str = "credential";
+ return 0;
+ }
+
+ int VerifyCredential(const std::string& auth_str,
+ const butil::EndPoint&,
+ brpc::AuthContext*) const override {
+ return auth_str == "credential" ? 0 : brpc::ERPCAUTH;
+ }
+};
+
+class AuthenticatedStreamHandler : public brpc::StreamInputHandler {
+public:
+ int on_received_messages(brpc::StreamId,
+ butil::IOBuf* const messages[],
+ size_t size) override {
+ for (size_t i = 0; i < size; ++i) {
+ if (messages[i]->to_string() == "authenticated stream frame") {
+ _received.store(true, std::memory_order_release);
+ }
+ }
+ return 0;
+ }
+
+ void on_idle_timeout(brpc::StreamId) override {}
+ void on_closed(brpc::StreamId) override {}
+ void on_failed(brpc::StreamId, int, const std::string&) override {}
+
+ bool received() const {
+ return _received.load(std::memory_order_acquire);
+ }
+
+private:
+ std::atomic<bool> _received{false};
+};
+
+TEST_F(StreamingRpcTest, reject_stream_frame_from_unauthenticated_socket) {
+ StreamingRpcAuthenticator auth;
+ brpc::Server server;
+ brpc::ServerOptions server_options;
+ server_options.auth = &auth;
+ ASSERT_EQ(0, server.Start(0, &server_options));
+
+ brpc::SocketId socket_id;
+ brpc::SocketOptions socket_options;
+ ASSERT_EQ(0, brpc::Socket::Create(socket_options, &socket_id));
+ brpc::SocketUniquePtr socket;
+ ASSERT_EQ(0, brpc::Socket::Address(socket_id, &socket));
+
+ brpc::StreamFrameMeta frame_meta;
+ frame_meta.set_stream_id(brpc::INVALID_STREAM_ID);
+ frame_meta.set_frame_type(brpc::FRAME_TYPE_CLOSE);
+ butil::IOBuf frame;
+ brpc::policy::PackStreamMessage(&frame, frame_meta, nullptr);
+ const size_t frame_size = frame.size();
+
+ brpc::ParseResult result = brpc::policy::ParseStreamingMessage(
+ &frame, socket.get(), false, &server);
+ ASSERT_EQ(brpc::PARSE_ERROR_ABSOLUTELY_WRONG, result.error());
+ ASSERT_EQ(frame_size, frame.size());
+}
+
+TEST_F(StreamingRpcTest, authenticate_before_exchanging_stream_frames) {
+ StreamingRpcAuthenticator auth;
+ AuthenticatedStreamHandler handler;
+ brpc::StreamOptions server_stream_options;
+ server_stream_options.handler = &handler;
+
+ brpc::Server server;
+ MyServiceWithStream service(server_stream_options);
+ ASSERT_EQ(0, server.AddService(&service, brpc::SERVER_DOESNT_OWN_SERVICE));
+ brpc::ServerOptions server_options;
+ server_options.auth = &auth;
+ ASSERT_EQ(0, server.Start(0, &server_options));
+
+ // A client without credentials is rejected before the service accepts its
+ // stream, and the server-side stream handler does not observe any frame.
+ brpc::Channel unauthenticated_channel;
+ brpc::ChannelOptions unauthenticated_options;
+ unauthenticated_options.max_retry = 0;
+ ASSERT_EQ(0, unauthenticated_channel.Init(
+ server.listen_address(), &unauthenticated_options));
+ brpc::Controller unauthenticated_cntl;
+ brpc::StreamId unauthenticated_stream;
+ ASSERT_EQ(0, StreamCreate(
+ &unauthenticated_stream, unauthenticated_cntl, nullptr));
+ brpc::ScopedStream unauthenticated_stream_guard(unauthenticated_stream);
+ test::EchoResponse unauthenticated_response;
+ test::EchoService_Stub unauthenticated_stub(&unauthenticated_channel);
+ unauthenticated_stub.Echo(&unauthenticated_cntl, &request,
+ &unauthenticated_response, nullptr);
+ ASSERT_TRUE(unauthenticated_cntl.Failed());
+ ASSERT_EQ(brpc::ERPCAUTH, unauthenticated_cntl.ErrorCode());
+ ASSERT_FALSE(handler.received());
+
+ // A normal client authenticates in the RPC service path before either
+ // endpoint exchanges stream frames.
+ brpc::ChannelOptions channel_options;
+ channel_options.auth = &auth;
+ brpc::Channel channel;
+ ASSERT_EQ(0, channel.Init(server.listen_address(), &channel_options));
+ brpc::Controller cntl;
+ brpc::StreamId request_stream;
+ ASSERT_EQ(0, StreamCreate(&request_stream, cntl, nullptr));
+ brpc::ScopedStream stream_guard(request_stream);
+ test::EchoService_Stub stub(&channel);
+ stub.Echo(&cntl, &request, &response, nullptr);
+ ASSERT_FALSE(cntl.Failed()) << cntl.ErrorText();
+
+ butil::IOBuf message;
+ message.append("authenticated stream frame");
+ ASSERT_EQ(0, brpc::StreamWrite(request_stream, message));
+ const int64_t deadline = butil::gettimeofday_us() + 3000000L;
+ while (!handler.received() && butil::gettimeofday_us() < deadline) {
+ usleep(1000);
+ }
+ ASSERT_TRUE(handler.received());
+}
+
struct BatchStreamFeedbackRaceState {
brpc::StreamId server_first_stream_id{brpc::INVALID_STREAM_ID};
brpc::StreamId server_extra_stream_id{brpc::INVALID_STREAM_ID};
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]