恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
我已完整阅读关联文档并深入研究了仓库源码与测试。现在输出文章。
首页
资讯中心
/
我已完整阅读关联文档并深入研究了仓库源码与测试。现在输出文章。
我已完整阅读关联文档并深入研究了仓库源码与测试。现在输出文章。
发布时间:2026/9/12 16:00:07
我已完整阅读关联文档并深入研究了仓库源码与测试。现在输出文章。【免费下载链接】envoyCloud-native high-performance edge/middle/service proxy项目地址: https://gitcode.com/GitHub_Trending/en/envoyEnvoy Redis 代理 RESP 解码器严格化畸形协议输入防护机制全解析本篇技术指南聚焦 Envoy 中 Redis 网络过滤器envoy.filters.network.redis_proxy的 RESP 协议编解码器一次重要的行为变更——解码器对畸形 RESP 线上输入从静默容忍转向严格拒绝。文章以 redis_proxy__resp-decoder-strictness.rst 变更说明为主体骨架结合 codec_impl.cc 与 codec_impl.h 源码实现及 codec_impl_test.cc 测试用例逐项拆解每一类被严格化的输入、其背后对应的安全风险、精确的阈值常量以及部署影响。读者阅读后可以完整掌握哪些 RESP 线上输入现在会被 Envoy 判为协议错误并断开连接、每个限制项的具体数值与设计意图以及当后端 Redis 调高了proto-max-bulk-len时需要注意的兼容性影响。变更背景为什么需要更严格的 RESP 解码器Redis 代理作为客户端与后端 Redis 之间的中间层其 RESPREdis Serialization Protocol编解码器承担着解析双向线上数据的关键职责。解码器位于 source/extensions/filters/network/common/redis/核心实现在 codec_impl.cc 的DecoderImpl::decode与parseSlice状态机中。在本次变更之前解码器对若干类畸形输入采取的是静默接受策略非法帧会被当作零值、空数组或空 bulk 字符串继续处理。这带来两类风险歧义帧注入畸形帧与合法帧如:0、*0、$0在线上无法区分攻击者可以借此注入后续解析器无法识别的模糊帧资源放大攻击DoS深层嵌套聚合、超长 bulk 长度头、无界增长的行 token 等都可能驱动解码器做出与攻击者投入字节数不成比例的资源分配。本次 minor behavior change 的核心目标就是让解码器对这类非合规或滥用的线上数据采取显式拒绝ProtocolError并关闭连接从源头消除上述两类风险。变更总览七类输入从容忍变为拒绝根据变更说明以下输入现在会被作为协议错误处理并导致连接关闭类别此前行为现在行为负数聚合/bulk 长度头除规范规定的*-1/$-1空值形式静默接受协议错误关闭连接不含数字的整数行如:\r\n、*\r\n、$\r\n静默转为 0协议错误关闭连接超出有符号 64 位范围的整数静默回绕协议错误关闭连接超过嵌套深度上限的消息接受协议错误关闭连接超过累计元素上限的消息接受协议错误关闭连接超过内联命令元素上限的消息接受协议错误关闭连接超过标量 token 上限的消息接受协议错误关闭连接所有限制常量以static constexpr形式公开定义在 codec_impl.h 的DecoderImpl上注释明确说明其供测试按名称引用并非对调用方的稳定性契约。下面逐类剖析。严格化一负数长度头仅接受规范空值形式RESP 规范中只有两种负数长度头是合法的*-1null 数组和$-1null bulk 字符串。在 codec_impl.cc 的IntegerLF状态中解码器对负数做了类型敏感的严格校验数组*仅当负数为-1时转为Null类型*-2、*-3等一律抛出ProtocolError(invalid negative count)Set~与 PushRESP3 中这两种类型没有空值变体任何负数计数都直接拒绝Map%RESP3 Map 同样没有空值形式空值走独立的_\r\n类型字节所有负数计数被拒绝防止通过%-1走私空值Bulk 字符串$仅$-1合法转为 Null$-2等抛出ProtocolError(invalid negative length)BlobError!与 VerbatimString没有空值形式任何负数长度都被拒绝。代码注释揭示了设计意图此前如果接受*-2为 null会让攻击者注入与合法帧不可区分的歧义帧。对应测试见 codec_impl_test.ccBulkStringNegativeTwoRejected、ArrayNegativeTwoRejected验证负数被拒而BulkStringNegativeOneIsNull、ArrayNegativeOneIsNull验证-1空值形式仍被接受Resp3NegativeBlobErrorLengthRejected、Resp3NegativeVerbatimStringLengthRejected、Resp3NegativeMapCountRejected、Resp3NegativeSetCountRejected、Resp3NegativePushCountRejected则覆盖了 RESP3 各类型的负数拒绝。严格化二无数字整数行直接拒绝PendingInteger结构体codec_impl.h新增了digit_seen_标志追踪整数/长度解析器在类型字节及可选-号与终止\r之间是否至少见到一个十进制数字。parseSlice的Integer状态codec_impl.cc在每读入一个数字字符时置位digit_seen_随后IntegerLF状态在分派前统一检查codec_impl.cc// Reject integer/count/length lines that carry no decimal digits between the type byte // (and optional sign) and \r\n. Without this, :\r\n, :-\r\n, *\r\n, // $\r\n, %-\r\n etc. would all be silently accepted as zero ... if (!pending_integer_.digit_seen_) { throw ProtocolError(integer with no digits); }也就是说:\r\n、:-\r\n、*\r\n、$\r\n这类只有类型字节没有数字的行此前会被静默强转为:0/*0/$0现在一律判为协议错误。测试覆盖见IntegerWithNoDigitsRejected、NegativeIntegerWithNoDigitsRejected、ArrayCountWithNoDigitsRejected、BulkStringLengthWithNoDigitsRejected、Resp3HeadersWithNoDigitsRejectedcodec_impl_test.cc。严格化三整数必须落在有符号 64 位范围内RESP 的RespValue::asInteger()底层是int64_t。此前超出[INT64_MIN, INT64_MAX]的线上整数会被静默回绕现在解码器在IntegerLF的 Integer 分支codec_impl.cc显式做范围校验正数pending_integer_.integer_ kMaxPositive即INT64_MAX时抛ProtocolError(integer out of range)负数绝对值大于kMaxNegativeMagnitudeINT64_MAX 1即2^63时同样拒绝特例-0被容忍并映射为普通零值。实现上还做了一个精妙的溢出规避对-INT64_MIN即2^63无法表示为int64_t采用先减 1 再取负再减 1的中间变换codec_impl.cc避免中间量溢出。配套测试见RespIntegerInt64MaxAccepted、RespIntegerInt64MaxPlusOneRejected、RespIntegerInt64MinAccepted、RespIntegerInt64MinMinusOneRejected、RespIntegerNegativeZeroAcceptedAsZerocodec_impl_test.cc。严格化四聚合嵌套深度上限128 层对于*1\r\n*1\r\n*1\r\n...这类无限嵌套输入即便每一层的单层元素数都通过kMaxRespElements检查嵌套本身也会让pending_value_stack_无界增长。解码器因此引入kMaxNestingDepth 128codec_impl.h并借助pending_value_stack_depth_计数器在压栈之前检查codec_impl.ccif (pending_value_stack_depth_ kMaxNestingDepth) { throw ProtocolError(nesting depth exceeds maximum); }值得注意的两点实现细节深度计数器包含根帧所以实际接受的嵌套层数是 128比上游 Redis 的MaxArrayNestingDepth129紧一层——因为此处在压栈前检查而上游用之所以用计数器而非std::forward_list::size()是因为forward_list没有 O(1) 的size()。测试用例MapNestingDepthExceeded与MapNestingDepthAtBoundaryAcceptedcodec_impl_test.cc精确验证了 128 层边界链到 128 层被拒、127 层被接受。严格化五累计元素预算上限4 Mi 槽位仅有单层上限和深度上限还不够kMaxNestingDepth × kMaxRespElements的乘积依然可能产生巨大的总分配。为此解码器引入上游没有的跨层累计预算kMaxTotalElements 4 × 1024 × 1024codec_impl.h在 codec_impl.cc 中对每个聚合层累加if (pending_integer_.integer_ kMaxTotalElements - total_elements_) { throw ProtocolError(total element count exceeds maximum); } total_elements_ pending_integer_.integer_;Map 类型由于线格式为 N 对键值存储为 2N 个元素槽位其元素数会先乘以 2 再计入预算codec_impl.cc且乘法前先做溢出防护。预算在ValueRootStart状态为每个新的顶层值重置codec_impl.cc因此管道化pipelined请求中的每个请求都能获得全新的预算。测试见MapTotalElementBudgetExceeded、Resp3ArrayCountExceedsMax、Resp3MapCountExceedsMax、Resp3SetCountExceedsMax、Resp3PushCountExceedsMaxcodec_impl_test.cc。严格化六内联命令元素上限内联命令以字母数字/空白开头的裸命令不走*N头没有元素计数头无法在IntegerLF获得单层上限检查。解码器因此在InlineStart状态对每个追加的词执行kMaxRespElements2,000,000检查codec_impl.ccif (pending_value_stack_.front().value_-asArray().size() kMaxRespElements) { throw ProtocolError(inline command element count exceeds maximum); }否则一个无 CRLF 结尾的、以空格分隔的无尽词流会让解码器为每个词无界分配一个RespValue。测试InlineCommandElementCountExceedsMaxcodec_impl_test.cc验证该路径。严格化七标量 token 长度上限针对无 CRLF 的无限行增长解码器对两类 RESP3 标量设置了 token 长度上限codec_impl.h标量类型线格式前缀上限设计考量RESP3 Double,64 字节定宽小标量64 字节绰绰有余RESP3 BigNumber(16 KiB任意精度上限只为约束无界行增长16 KiB 足够容纳约 54k 位的整数Double 在累加阶段逐字节检查空白字符与非可打印字符并拒绝absl::SimpleAtod会容忍周边空白若不前置过滤嵌入的空格/裸\n会被原样重新发射到行帧的 RESP3 Double 中导致下游严格解析器失去同步见 codec_impl.cc。对应测试包括DoubleTooLongRejected、DoubleAtBoundaryAccepted、DoubleWithWhitespaceOrControlByteRejected、BigNumberTooLongRejectedcodec_impl_test.cc。另外连续的 RESP3 Attributes|最多 32 个kMaxConsecutiveAttributes且属性内部完成的子值不会重置该计数见ValueComplete分支逻辑与AttributeFloodRejected、NonEmptyAttributeFloodRejected测试。512 MiB 单条 bulk 上限与 Redisproto-max-bulk-len对齐这是本次变更中对合法流量也有影响的唯一一项单条 bulk 字符串$、blob error!或 verbatim string的载荷被限制为512 MiBkMaxBulkStringLength 512ULL * 1024 * 1024codec_impl.h与 Redis 的proto-max-bulk-len默认值一致。关键的实现要点该上限在body 读取循环之前即被检查codec_impl.ccif (pending_integer_.integer_ kMaxBulkStringLength) { throw ProtocolError(bulk string length exceeds maximum); } state_ State::BulkStringBody;注释明确指出原因body 循环按 slice 逐段append到asString()若先进入循环再检查攻击者提供的长度头就会驱动单条消息上的无界std::string::append增长。测试BulkStringLengthAboveCapRejected、VerbatimStringLengthAboveCapRejected、BlobErrorLengthAboveCapRejectedcodec_impl_test.cc验证三条长度前缀帧类型都受此上限约束。部署影响后端调高proto-max-bulk-len的场景变更说明明确指出部署中若后端 Redis 将proto-max-bulk-len调高到超过默认的 512 MiB会受此上限影响。也就是说若你的后端 Redis 通过CONFIG SET proto-max-bulk-len或启动参数将 bulk 上限提升到 512 MiB 以上例如承载超大 value 的部署那么经由 Envoy Redis 代理转发的、单条超过 512 MiB 的 bulk/blob error/verbatim 载荷现在会被解码器判为协议错误并关闭连接。这正是本次行为变更中只见于非合规或滥用数据之外唯一需要主动关注的兼容性点。其余变更只对发送非合规或滥用线上数据的对端可见正常客户端流量不受影响。附加防护本地错误响应的控制字节清洗除了严格化输入解析本次变更还加强了输出方向的防护。sanitizeControlBytescodec.h会将输入中的每个 ASCII 控制字节 0x20或0x7fDEL替换为空格并返回清洗后的副本RESP 内联错误-text\r\n和 RESP3 BlobError 降级到 RESP2 的形式没有长度前缀嵌入的 CR/LF 会让下游解析器失去同步其他控制字节则可能注入终端转义序列或日志分隔符攻击者影响的错误文本被回显的命令/选项、上游 BlobError 载荷因此被剥离实现采用先单遍扫描、无控制字节则原样返回的优化路径避免常见情况下的额外分配与重写开销。该函数被两处本地错误生成路径复用保持单一事实来源codec_impl.ccRESP3 BlobError 在降级为 RESP2 内联错误时调用utility.ccmakeError用于构造回显攻击者可控文本未知命令/选项/子命令的 RESP 错误响应时调用。测试验证体系本次严格化的每一类限制都有对应的单元测试验证集中在 codec_impl_test.cc。测试直接以DecoderImpl::kMax*常量命名引用边界值如MapNestingDepthAtBoundaryAccepted精确构造 127/128 层边界确保实现值与测试断言不脱节。按变更类别归纳无数字行IntegerWithNoDigitsRejected、NegativeIntegerWithNoDigitsRejected、ArrayCountWithNoDigitsRejected、BulkStringLengthWithNoDigitsRejected、Resp3HeadersWithNoDigitsRejectedint64 范围RespIntegerInt64MaxAccepted/RespIntegerInt64MaxPlusOneRejected/RespIntegerInt64MinAccepted/RespIntegerInt64MinMinusOneRejected/RespIntegerNegativeZeroAcceptedAsZero负数长度BulkStringNegativeTwoRejected、ArrayNegativeTwoRejected、BulkStringNegativeOneIsNull、ArrayNegativeOneIsNull及 RESP3 各类型的Resp3Negative*Rejected系列资源上限Resp3ArrayCountExceedsMax、Resp3MapCountExceedsMax、Resp3SetCountExceedsMax、Resp3PushCountExceedsMax、InlineCommandElementCountExceedsMax、MapTotalElementBudgetExceeded、MapNestingDepthExceeded、MapNestingDepthAtBoundaryAccepted、AttributeFloodRejected、NonEmptyAttributeFloodRejected、BulkStringLengthAboveCapRejected、VerbatimStringLengthAboveCapRejected、BlobErrorLengthAboveCapRejected、DoubleTooLongRejected、BigNumberTooLongRejected。这些测试共同构成了一张限制边界矩阵任何一类的边界放宽或收紧都会立即被测试捕获。总结与运维建议本次 Redis 代理 RESP 解码器严格化是一次典型的安全加固型 minor behavior change它把此前静默容忍的畸形输入逐一转化为显式协议错误并关闭连接覆盖负数长度头、无数字整数行、int64 越界整数、嵌套深度、累计元素、内联命令元素与标量 token 长度七类解析维度并以 512 MiB 单条 bulk 上限与 Redisproto-max-bulk-len默认值对齐最后通过sanitizeControlBytes堵住错误响应中的帧注入与日志注入面。升级后建议执行如下检查确认后端 Redis 的proto-max-bulk-len未超过 512 MiB 默认值若曾调高且业务存在超大 value需要评估影响或调整业务形态检查是否存在异常客户端/恶意流量新版本下这类连接会更快被断开可结合 Redis 代理连接指标观察错误率变化回归正常读写路径确认标准 RESP2/RESP3 客户端流量含管道化、订阅等不受影响——变更说明明确指出除 512 MiB 上限外其余变化只对发送非合规或滥用数据的对端可见。【免费下载链接】envoyCloud-native high-performance edge/middle/service proxy项目地址: https://gitcode.com/GitHub_Trending/en/envoy创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考