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

wasphin 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 3f35c2c7 fix(redis): avoid formatted command overread (#3570)
3f35c2c7 is described below

commit 3f35c2c77ae327ef4dbb700b2a4f181ecba1ccce
Author: darion-yaphet <[email protected]>
AuthorDate: Sun Oct 4 22:54:53 2026 +0800

    fix(redis): avoid formatted command overread (#3570)
    
    Retry formatted values in dynamically sized storage when vsnprintf reports
    an output larger than the stack buffer. Add a vector-based RedisRequest
    component overload and use it for authentication and cluster command
    construction while retaining the existing format APIs.
    
    Add coverage for wide numeric formatting, binary-safe components, and
    AUTH/SELECT credential encoding.
    
    Verified with RedisCommandFormatTest.* and
    RedisClusterChannelTest.basic_routing_and_multi_key_commands.
---
 docs/cn/redis_client.md                 |  2 +-
 docs/en/redis_client.md                 |  2 +-
 src/brpc/policy/redis_authenticator.cpp | 22 +++++++++++++++-------
 src/brpc/redis.cpp                      |  5 +++++
 src/brpc/redis.h                        |  3 +++
 src/brpc/redis_cluster.cpp              |  4 ++--
 src/brpc/redis_command.cpp              | 22 ++++++++++++++++++++--
 test/brpc_redis_unittest.cpp            | 33 +++++++++++++++++++++++++++++++++
 8 files changed, 80 insertions(+), 13 deletions(-)

diff --git a/docs/cn/redis_client.md b/docs/cn/redis_client.md
index 681e16f5..e4c573de 100644
--- a/docs/cn/redis_client.md
+++ b/docs/cn/redis_client.md
@@ -128,7 +128,7 @@ bool AddCommandByComponents(const butil::StringPiece* 
components, size_t n);
 
 > 注意,AddCommand和AddCommandV的fmt参数如果设置错误,有可能导致程序crash或者数据泄露,请谨慎设置。不应将受用户输入影响的内容作为fmt参数进行调用!
 
-AddCommandByComponents类似hiredis中的redisCommandArgv,用户通过数组指定命令中的每一个部分。这个方法对AddCommand和AddCommandV可能发生的转义问题免疫,且效率最高。如果你在使用AddCommand和AddCommandV时出现了“Unmatched
 quote”,“无效格式”等问题且无法定位,可以试下这个方法。
+AddCommandByComponents类似hiredis中的redisCommandArgv,用户可通过数组或`std::vector<butil::StringPiece>`指定命令中的每个部分。这个方法对AddCommand和AddCommandV可能发生的转义问题免疫。如果你在使用AddCommand和AddCommandV时出现“Unmatched
 quote”“无效格式”等问题且无法定位,可以试下这个方法。
 
 
如果AddCommand\*失败,后续的AddCommand\*和CallMethod都会失败。一般来说不用判AddCommand*的结果,失败后自然会通过RPC失败体现出来。
 
diff --git a/docs/en/redis_client.md b/docs/en/redis_client.md
index 8638502e..da8fa869 100644
--- a/docs/en/redis_client.md
+++ b/docs/en/redis_client.md
@@ -129,7 +129,7 @@ Formatting is compatible with hiredis, namely `%b` 
corresponds to binary data (p
 
 > Note that if the `fmt` parameter of `AddCommand` and `AddCommandV` is set 
 > incorrectly, it may cause the program to crash or lead to data leakage. 
 > Please set it with caution. Should not use user input-influenced content as 
 > the `fmt` parameter!
 
-`AddCommandByComponents` is similar to `redisCommandArgv` in hiredis. Users 
specify each part of the command in an array, which is immune to escaping 
issues often occurring in `AddCommand` and `AddCommandV`. If you encounter 
errors such as "Unmatched quote" or "invalid format" when using `AddCommand` 
and `AddCommandV`, try this method.
+`AddCommandByComponents` is similar to `redisCommandArgv` in hiredis. Users 
specify each part of the command in an array or 
`std::vector<butil::StringPiece>`, which is immune to escaping issues often 
occurring in `AddCommand` and `AddCommandV`. If you encounter errors such as 
"Unmatched quote" or "invalid format" when using `AddCommand` and 
`AddCommandV`, try this method.
 
 If `AddCommand*` fails, subsequent `AddCommand*` and `CallMethod` also fail. 
In general, there is no need to check return value of `AddCommand*`, since the 
RPC fails directly anyway.
 
diff --git a/src/brpc/policy/redis_authenticator.cpp 
b/src/brpc/policy/redis_authenticator.cpp
index 152d9832..399a67b6 100644
--- a/src/brpc/policy/redis_authenticator.cpp
+++ b/src/brpc/policy/redis_authenticator.cpp
@@ -17,22 +17,30 @@
 
 #include "brpc/policy/redis_authenticator.h"
 
-#include "butil/base64.h"
 #include "butil/iobuf.h"
-#include "butil/string_printf.h"
-#include "butil/sys_byteorder.h"
-#include "brpc/redis_command.h"
+#include "brpc/redis.h"
 
 namespace brpc {
 namespace policy {
 
 int RedisAuthenticator::GenerateCredential(std::string* auth_str) const {
-    butil::IOBuf buf;
+    RedisRequest request;
     if (!passwd_.empty()) {
-        brpc::RedisCommandFormat(&buf, "AUTH %s", passwd_.c_str());
+        const std::vector<butil::StringPiece> components = {"AUTH", passwd_};
+        if (!request.AddCommandByComponents(components)) {
+            return -1;
+        }
     }
     if (db_ >= 0) {
-        brpc::RedisCommandFormat(&buf, "SELECT %d", db_);
+        const std::string db = std::to_string(db_);
+        const std::vector<butil::StringPiece> components = {"SELECT", db};
+        if (!request.AddCommandByComponents(components)) {
+            return -1;
+        }
+    }
+    butil::IOBuf buf;
+    if (!request.SerializeTo(&buf)) {
+        return -1;
     }
     *auth_str = buf.to_string();
     return 0;
diff --git a/src/brpc/redis.cpp b/src/brpc/redis.cpp
index d4a2108b..050f28d7 100644
--- a/src/brpc/redis.cpp
+++ b/src/brpc/redis.cpp
@@ -120,6 +120,11 @@ bool RedisRequest::AddCommandByComponents(const 
butil::StringPiece* components,
     }
 }
 
+bool RedisRequest::AddCommandByComponents(
+        const std::vector<butil::StringPiece>& components) {
+    return AddCommandByComponents(components.data(), components.size());
+}
+
 bool RedisRequest::AddCommandWithArgs(const char* fmt, ...) {
     if (_has_error) {
         return false;
diff --git a/src/brpc/redis.h b/src/brpc/redis.h
index 6a57715d..01b31c80 100644
--- a/src/brpc/redis.h
+++ b/src/brpc/redis.h
@@ -20,6 +20,7 @@
 #define BRPC_REDIS_H
 
 #include <unordered_map>
+#include <vector>
 
 #include "brpc/destroyable.h"
 #include "brpc/nonreflectable_message.h"
@@ -66,6 +67,8 @@ public:
     //   butil::StringPiece components[] = { "set", "key", "value" };
     //   request.AddCommandByComponents(components, arraysize(components));
     bool AddCommandByComponents(const butil::StringPiece* components, size_t 
n);
+    // Convenient when the components are already held in a vector.
+    bool AddCommandByComponents(const std::vector<butil::StringPiece>& 
components);
 
     // Add a command with variadic args to this request.
     // The reason that adding so many overloads rather than using ... is that
diff --git a/src/brpc/redis_cluster.cpp b/src/brpc/redis_cluster.cpp
index c2c1ed6b..ebc0820d 100644
--- a/src/brpc/redis_cluster.cpp
+++ b/src/brpc/redis_cluster.cpp
@@ -666,7 +666,7 @@ bool RedisClusterChannel::SendToEndpoint(const std::string& 
endpoint,
         for (size_t i = 0; i < args.size(); ++i) {
             components.push_back(args[i]);
         }
-        if (!request.AddCommandByComponents(&components[0], 
components.size())) {
+        if (!request.AddCommandByComponents(components)) {
             cntl->SetFailed(EREQUEST, "Fail to build redis command");
             return false;
         }
@@ -1064,7 +1064,7 @@ bool RedisClusterChannel::BuildRedisRequest(const 
std::vector<std::string>& args
     for (size_t i = 0; i < args.size(); ++i) {
         components.push_back(args[i]);
     }
-    return request->AddCommandByComponents(&components[0], components.size());
+    return request->AddCommandByComponents(components);
 }
 
 void RedisClusterChannel::AppendIntegerReply(butil::IOBuf* buf, int64_t value) 
{
diff --git a/src/brpc/redis_command.cpp b/src/brpc/redis_command.cpp
index 62b9bd1c..c0ff6477 100644
--- a/src/brpc/redis_command.cpp
+++ b/src/brpc/redis_command.cpp
@@ -17,6 +17,7 @@
 
 #include <cctype>
 #include <limits>
+#include <vector>
 
 #include "butil/logging.h"
 #include "brpc/log.h"
@@ -257,10 +258,27 @@ RedisCommandFormatV(butil::IOBuf* outbuf, const char* 
fmt, va_list ap) {
                 if (_l < sizeof(_format)-2) {
                     memcpy(_format, c, _l);
                     _format[_l] = '\0';
-                    int plen = vsnprintf(_printed, sizeof(_printed), _format, 
_cpy);
-                    if (plen > 0) {
+                    va_list _retry;
+                    va_copy(_retry, _cpy);
+                    const int plen = vsnprintf(_printed, sizeof(_printed), 
_format, _cpy);
+                    if (plen < 0) {
+                        va_end(_retry);
+                        va_end(_cpy);
+                        return butil::Status(EINVAL, "Failed to format redis 
command");
+                    }
+                    if (static_cast<size_t>(plen) >= sizeof(_printed)) {
+                        std::vector<char> full(static_cast<size_t>(plen) + 1);
+                        const int written = vsnprintf(full.data(), 
full.size(), _format, _retry);
+                        if (written != plen) {
+                            va_end(_retry);
+                            va_end(_cpy);
+                            return butil::Status(EINVAL, "Failed to format 
redis command");
+                        }
+                        compbuf.append(full.data(), plen);
+                    } else if (plen > 0) {
                         compbuf.append(_printed, plen);
                     }
+                    va_end(_retry);
                     /* Update current position (note: outer blocks
                      * increment c twice so compensate here) */
                     c = _p - 1;
diff --git a/test/brpc_redis_unittest.cpp b/test/brpc_redis_unittest.cpp
index b1c57067..67ab8b98 100644
--- a/test/brpc_redis_unittest.cpp
+++ b/test/brpc_redis_unittest.cpp
@@ -139,6 +139,39 @@ protected:
     void TearDown() {}
 };
 
+TEST(RedisCommandFormatTest, wide_numeric_conversion) {
+    butil::IOBuf buf;
+    ASSERT_TRUE(brpc::RedisCommandFormat(&buf, "SET key %100d tail %d", 7, 
9).ok());
+
+    const std::string padded_value(99, ' ');
+    const std::string expected =
+        std::string("*5\r\n$3\r\nSET\r\n$3\r\nkey\r\n$100\r\n") +
+        padded_value + "7\r\n$4\r\ntail\r\n$1\r\n9\r\n";
+    EXPECT_EQ(expected, buf.to_string());
+}
+
+TEST(RedisCommandFormatTest, vector_components_preserve_binary_payload) {
+    const std::string value("a\0b", 3);
+    const std::vector<butil::StringPiece> components = {
+        "SET", "key", butil::StringPiece(value.data(), value.size())};
+    brpc::RedisRequest request;
+    ASSERT_TRUE(request.AddCommandByComponents(components));
+
+    butil::IOBuf buf;
+    ASSERT_TRUE(request.SerializeTo(&buf));
+    std::string expected = "*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$3\r\n";
+    expected.append(value).append("\r\n");
+    EXPECT_EQ(expected, buf.to_string());
+}
+
+TEST(RedisCommandFormatTest, authenticator_encodes_password_and_database) {
+    brpc::policy::RedisAuthenticator authenticator("a b", 3);
+    std::string credential;
+    ASSERT_EQ(0, authenticator.GenerateCredential(&credential));
+    EXPECT_EQ("*2\r\n$4\r\nAUTH\r\n$3\r\na b\r\n"
+              "*2\r\n$6\r\nSELECT\r\n$1\r\n3\r\n", credential);
+}
+
 void AssertReplyEqual(const brpc::RedisReply& reply1,
                       const brpc::RedisReply& reply2) {
     if (&reply1 == &reply2) {


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

Reply via email to