Envoy HTTP/3 崩溃修复深度拆解:HTTP/3 上游空指针与 CVE-2026-48521 完整指南
2026/9/14 8:23:12 网站建设 项目流程

Envoy HTTP/3 崩溃修复深度拆解:HTTP/3 上游空指针与 CVE-2026-48521 完整指南

【免费下载链接】envoyCloud-native high-performance edge/middle/service proxy项目地址: https://gitcode.com/GitHub_Trending/en/envoy

集群启用 auto_config 自动协商、上游又通过 Alt-Svc 通告 HTTP/3 时,Envoy 会因空指针解引用直接崩溃,即 CVE-2026-48521。这项 Envoy HTTP/3 崩溃修复的触发条件、协商机制、正确配置与回归验证,一次讲清。

崩溃现场:CVE-2026-48521 的三行 changelog

修复记录 http3__fixed-crash-due-to-null-deref.rst 只写了三行,但信息量足够完整:

  • CVE 编号:CVE-2026-48521,对应安全公告 GHSA-5vff-j9p4-38j3;
  • 受影响配置模式:集群HttpProtocolOptions使用auto_config分支,由 ALPN(应用层协议协商,即 TLS 握手中双方互相报出可接受的协议列表)与上游决定协议;
  • 后果:abnormal process termination,进程级崩溃,不是优雅退出。

对代理而言,进程就是所有在途流的容器:一次崩溃意味着该实例上所有连接瞬间切断,下游看到成片的连接重置,上游看到成片的请求中断。对边缘网关来说这是请求级事故,恢复窗口等于编排系统重新拉起实例的时间——在这之前,流量要么丢弃要么绕行。

协议协商的条件门:auto_config ALPN 协商与 HTTP/3 的差异

auto_config对应http_protocol_options.proto中的AutoHttpConfig分支:HTTP/1 与 HTTP/2 是"默认选手",只要传输套接字支持 ALPN,用哪个由协商结果决定(默认列表"h2,http/1.1"),上游不支持 ALPN 时回退 HTTP/1。HTTP/3 不同,它有双重门:集群必须显式声明http3_protocol_options并且上游必须通过 Alt-Svc 头(RFC 7838)或 HTTPS DNS 资源记录通告过自己支持 HTTP/3。没有任何通告,Envoy 就不会对那个主机发起 QUIC 连接。

协议启用条件回退行为
HTTP/1声明即可上游不支持 ALPN 时启用
HTTP/2ALPN 协商到h2协商失败时让位给 HTTP/1
HTTP/3声明 + 上游 Alt-Svc/DNS RR 通告无通告或 UDP 受阻时回退 TCP

QUIC 跑在 UDP 上,而不少网络设备无差别拦截 UDP,这类网络里 HTTP/3 连接尝试会持续失败。于是上游链路必须"记住失败",避免发起注定失败的连接——架构文档source/docs/http3_upstream.md对这条要求有完整描述,它也正是本次崩溃的直接土壤。

双池竞速与失败记忆:ConnectivityGrid 双池里的三个角色

整套机制由连接池层三个协作对象支撑,空指针就藏在它们之间的接缝里。

ConnectivityGrid:300ms 双池竞速

ConnectivityGrid本身是一个 ConnectionPool,内部包装两个真实池:Http3ConnPoolImpl(QUIC)与HttpConnPoolImplMixed(TCP)。conn_pool_grid.cc 中newStream的分派核心:

if (shouldAttemptHttp3() && options.can_use_http3_) { pool = getOrCreateHttp3Pool(); // QUIC 池优先 } else { pool = getOrCreateHttp2Pool(); // 未通告或不支持时走 TCP }

HTTP/3 已通告且可用时,请求先入 QUIC 池;kDefaultTimeoutMs(300ms)内未成功,同一请求再进入 TCP 混合池,谁先成功谁胜出。若状态追踪器判定 HTTP/3 已 broken 或最近失败,delay_tcp_attempt直接置 false,TCP 路径不再等待。http3_pool_http2_pool_全部惰性创建——首次调用前它们都是 nullptr,任何绕过 getOrCreate 直接解引用的回调路径都是空指针

HttpServerPropertiesCache:Alt-Svc 通告的账本

http_server_properties_cache_impl.cc 记录哪些主机通告了 HTTP/3;当前只存储与请求相同主机名和端口的通告,避免跨主机误用。行为由AlternateProtocolsCacheOptions控制:

  • name:缓存实例标识,不同组件引用同名缓存时各字段必须一致,否则配置加载失败;
  • max_entries:默认 1024,淘汰为近似值且每个 worker 独立执行,实际条目数可略超配置;
  • prepopulated_entries:以 7 天生命周期预填 HTTP/3 条目,让 Envoy 对未通告的上游也尝试 HTTP/3。

该缓存被网格与追踪器经由alternate_protocols_智能指针引用,名称对不上或 origin 未就绪时,这个引用就是空的。

Http3StatusTracker:Pending → Broken → FailedRecently → Confirmed

http3_status_tracker_impl.cc 是 HTTP/3 失败后的"记忆":markHttp3Broken()开启退避定时器DefaultExpirationTime * (1 << consecutive_broken_count_),即 1s → 2s → 4s 逐次翻倍;MaxConsecutiveBrokenCount = 17封顶,最长退避约 2^17 秒;定时器到期状态从Broken转入FailedRecentlymarkHttp3Confirmed()则把计数清零。

文档与代码存在一处已知出入:架构文档 http3_upstream.md 写的是"首次损坏 5 分钟、再次损坏翻倍、上限 1 天",而源码DefaultExpirationTime{1}是 1 秒起步、2^17 秒封顶——以源码为准

追踪器经getHttp3StatusTracker()惰性获取,若状态查询先于其与网格的关联发生,检查的就是空指针。

⚠️ HTTP/3 上游空指针:风险点与修复推断

官方只披露了"修复异常终止"这一结果,未披露 patch 细节;以下是基于代码结构的推断:

  1. 惰性池成员。newStream先经getOrCreateHttp3Pool()建池,随后WrapperCallbacks的异步回调会跨池触发tryAnotherConnection();这些回调路径中任何一处读取http3_pool_/http3_alternate_pool_时其仍为 nullptr,即触发崩溃。
  2. 追踪器与缓存未就绪。状态追踪器关联到alternate_protocols_HttpServerPropertiesCache::Origin;缓存未建立或 origin 解析失败时,后续状态查询同样可能空引用。
  3. 配置校验兜底。解析层已强制"auto_config + http3_protocol_options 必须伴随 alternate_protocols_cache_options",否则直接拒绝加载(config.cc 报错)。这堵住了最典型的空状态入口,但运行时时序仍靠代码自身保证。

此类修复的工程形态通常是双件:崩溃路径上加判空或提前返回,再补一个钉死该时序的回归测试。

完整配置:三处缺一不可

启用这条链路需要集群、过滤器、网络三处配合,缺任何一处都会静默退化或加载失败。

集群侧:auto_config 全量声明三种协议

static_resources: clusters: - name: upstream_with_h3 type: STRICT_DNS load_assignment: endpoints: - lb_endpoints: - endpoint: address: socket_address: address: upstream.example.com port_value: 443 transport_socket: name: envoy.transport_sockets.tls typed_config: "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext typed_extension_protocol_options: envoy.extensions.upstreams.http.v3.HttpProtocolOptions: "@type": type.googleapis.com/envoy.extensions/upstreams/http/v3.HttpProtocolOptions auto_config: http_protocol_options: {} http2_protocol_options: {} http3_protocol_options: {} alternate_protocols_cache_options: name: default_alternate_protocols_cache
  • 三种协议选项必须全部出现,auto_config依赖 ALPN,三个槽位缺一个都会被校验拒绝;
  • http3_protocol_options只代表"允许" HTTP/3,是否真正启用仍取决于上游通告;
  • alternate_protocols_cache_options.name是缓存实例标识,与过滤器侧引用不一致会加载失败;max_entries省略时用默认 1024;
  • 传输套接字必须支持 ALPN(TLS UpstreamTlsContext),使用不支持 ALPN 的传输套接字会导致整个配置加载失败。

过滤器侧:让 Alt-Svc 头真正入库

http_filters: - name: envoy.filters.http.alternate_protocols_cache typed_config: "@type": type.googleapis.com/envoy.extensions.filters.http.alternate_protocols_cache.v3.FilterConfig

没有这个过滤器,alt-svc 响应头不会被解析,缓存永远为空。注意:过滤器配置里的alternate_protocols_cache_options字段已标记废弃(计划 3.0 移除),注释明确说明该字段被忽略——过滤器直接使用请求路由所对应集群声明的缓存。规范写法是集群级声明、名称保持一致。

回退行为:UDP 被拦截时的降级

网络拦截 UDP 时,HTTP/3 连接尝试全部失败,状态追踪器把该上游标记为 Broken 并进入指数退避,请求直接走 TCP 池(HTTP/2/1)——这正是本次修复所保护的路径。在 HTTP/3 正常的网络上,退避定时器到期后追踪器转回FailedRecently并重新探测,避免无谓的 TCP 尝试。

回归验证:两条测试路径

两个测试目标分别钉死"回退链不崩溃"与"退避值正确"两个性质:

测试文件覆盖场景Bazel 命令
test/common/http/conn_pool_grid_test.ccSuccess:通告存在 → QUIC 池 → Confirmed;DoubleFailureThenSuccessSerial:h3 池失败 → 交替池失败 → h2 池成功且不向上抛错bazel test //test/common/http:conn_pool_grid_test
test/common/http/http3_status_tracker_impl_test.ccMarkBrokenWithBackoff:1s→2s→4s→8s;MarkBrokenWithBackoffMax:2^17s 封顶;Confirmed 重置计数bazel test //test/common/http:http3_status_tracker_impl_test

即 conn_pool_grid_test.cc 与 http3_status_tracker_impl_test.cc 两个目标。升级后运行它们作为验收基线:前者保证双池竞速的失败回退链完整可走,后者保证退避参数与源码实现一致。

运维行动清单

按优先级排序,第一条是唯一彻底的解:

  • 升级到包含修复的版本:对auto_config且上游可能通告 HTTP/3 的实例,升级前空指针始终存在;
  • 审视auto_config配置完整性:确认三种协议选项与alternate_protocols_cache_options均在,避免加载失败或缓存引用错位;
  • 确认 UDP 可达性:上游网络拦截 UDP 时,退避机制会自动承担 HTTP/3→TCP 回退,不必手工调整超时;
  • 跟踪回归测试:升级后运行上文两个目标,确认回退链与退避行为仍被测试覆盖。

对代理实例而言,配置只能让回退路径更短,崩溃本身要靠升级后的二进制来堵。

【免费下载链接】envoyCloud-native high-performance edge/middle/service proxy项目地址: https://gitcode.com/GitHub_Trending/en/envoy

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询