news 2026/9/11 18:26:14

Envoy 动态模块 HTTP 过滤器 use-after-free 崩溃修复:recreate_stream 场景下的延迟销毁机制深度解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Envoy 动态模块 HTTP 过滤器 use-after-free 崩溃修复:recreate_stream 场景下的延迟销毁机制深度解析

Envoy 动态模块 HTTP 过滤器 use-after-free 崩溃修复:recreate_stream 场景下的延迟销毁机制深度解析

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

本篇技术指南围绕 Envoy 动态模块(Dynamic Modules)HTTP 过滤器的一项关键 bug 修复展开:当事件钩子(例如envoy_dynamic_module_callback_http_filter_recreate_stream)在模块自身栈上结束 HTTP 流并拆除过滤器链时,曾经会触发 use-after-free 崩溃。读完本文,你将理解该崩溃产生的完整链路、修复方案(利用 dispatcher 延迟删除队列实现 in-module filter 的延迟销毁)以及模块开发者在实现"重建流"类功能时必须遵循的生命周期约束,并能在源码层面定位每一处关键实现。

一、背景:动态模块 HTTP 过滤器与 in-module filter 的生命周期

Envoy 的动态模块(Dynamic Modules)机制允许以共享库(C/C++、Rust、Go 等)形式加载 HTTP 过滤器,模块与 Envoy 主进程之间通过稳定的 C ABI 交互。在 Envoy 侧,每个 HTTP 流对应一个DynamicModuleHttpFilterC++ 对象(filter.h),它负责把 Envoy 的Http::StreamFilter回调(decodeHeadersencodeData等)转发到模块内由envoy_dynamic_module_on_http_filter_new创建的 in-module filter 对象(envoy_dynamic_module_type_http_filter_module_ptr)。

正常的销毁时序是:HTTP 流结束或被重置时,Envoy 调用DynamicModuleHttpFilter::onDestroy(),随后通过envoy_dynamic_module_on_http_filter_destroy通知模块释放 in-module filter(见 abi.h 中该 ABI 的注释:它"为每个 HTTP 流在过滤器销毁时调用")。

二、问题定位:recreate_stream 在模块栈上拆除过滤器链导致 use-after-free

本次修复的 bug 记录在 changelogs/current/bug_fixes/dynamic_modules__http-filter-deferred-destroy.rst,其核心症状是:动态模块 HTTP 过滤器中的 use-after-free 崩溃

2.1 触发路径:envoy_dynamic_module_callback_http_filter_recreate_stream

ABI 中定义了重建流的回调(abi.h):

bool envoy_dynamic_module_callback_http_filter_recreate_stream( envoy_dynamic_module_type_http_filter_envoy_ptr filter_envoy_ptr, envoy_dynamic_module_type_module_http_header* headers, size_t headers_size);

该回调用于实现内部重定向(internal redirect)或请求重试:调用成功后,当前过滤器链会被销毁,并基于(可选的新)请求头创建一个新流。ABI 文档明确说明:调用成功后过滤器应从当前事件钩子返回StopIteration,in-module filter 在钩子返回前保持有效。

2.2 崩溃机制:栈上的销毁与正在执行的钩子竞争

崩溃的根源在于销毁发生的执行上下文

  1. 模块在某个事件钩子(如decodeHeaders)内部调用recreate_stream
  2. Envoy 侧随即开始拆除当前过滤器链,即同步执行onDestroy()→ 销毁 in-module filter;
  3. 但此时该钩子本身仍在模块自己的调用栈上运行,recreate_stream是在模块自身的栈上完成了过滤器链的拆除与 in-module filter 的释放;
  4. 钩子返回后仍可能访问已经释放的 in-module filter 对象,于是触发use-after-free崩溃。

这正是 changelog 中所描述的:"An event hook that ends the stream ... tears the filter chain down on the module's own stack, which freed the in-module filter the hook was still running on."

三、修复方案:通过 dispatcher 延迟删除列表延迟销毁 in-module filter

修复的核心思路是:不再让 in-module filter 在钩子栈上被同步销毁,而是将其销毁操作投递到 worker dispatcher 的延迟删除(deferred deletion)列表,确保envoy_dynamic_module_on_http_filter_destroy只在其他所有事件钩子返回之后执行

修复后的 ABI 文档(abi.h)如此描述新语义:

Envoy 从 worker dispatcher 的延迟删除列表运行该回调,因此它绝不会在同一个过滤器的另一个事件钩子仍在栈上时被调用。钩子可以通过例如envoy_dynamic_module_callback_http_filter_recreate_stream结束流,这会在钩子返回前拆除过滤器链。Envoy 不会在该钩子返回前销毁 in-module filter,且模块在拆除后进行的回调都是安全的。

3.1 源码实现:destroy()中的延迟投递

关键实现在 filter.cc 的DynamicModuleHttpFilter::destroy()

// A module event hook can end the stream, which tears the filter chain down on the module's own // stack, so the in-module filter has to outlive that hook. Deferring the destroy hook also keeps // this filter alive, since the module can still call back into it from the hook. if (dispatcher.has_value()) { if (DynamicModuleHttpFilterSharedPtr self = weak_from_this().lock()) { Event::DeferredTaskUtil::deferredRun( *dispatcher, [self = std::move(self), in_module_filter]() { self->config_->on_http_filter_destroy_(in_module_filter); }); return; } } config_->on_http_filter_destroy_(in_module_filter);

这里有两处精妙设计:

  • shared_ptr自持延长 C++ 过滤器生命周期:lambda 捕获了weak_from_this().lock()得到的shared_ptr,使DynamicModuleHttpFilter本身在延迟任务执行前不会被析构——因为"模块仍可能在钩子中回调它"。
  • 延迟到DeferredTaskUtil::deferredRun:其实现(deferred_task.h)将任务包装为DeferredDeletable对象并交给dispatcher.deferredDelete(),任务在未来的事件循环周期执行——即"在之前所有 DeferredDeletable 对象被销毁之后"。此时栈上不再有活跃的事件钩子,调用on_http_filter_destroy_是绝对安全的。

3.2onDestroy()的配套调整

filter.cc 中的onDestroy()在触发destroy(worker_dispatcher)之前做了两件重要准备:

void DynamicModuleHttpFilter::onDestroy() { destroyed_ = true; // Read before the cache is cleared below, because destroy() defers the module hook onto it. OptRef<Event::Dispatcher> worker_dispatcher = makeOptRefFromPtr(dispatcher()); // Clear the cached dispatcher so any concurrent foreign-thread `commit()` short-circuits. cached_dispatcher_.store(nullptr, std::memory_order_release); ... destroy(worker_dispatcher); }
  • 先读取 dispatcher 缓存,再将其置空:置空后,其他线程通过DynamicModuleHttpFilterScheduler::commit()跨线程投递事件时会因拿不到 dispatcher 而直接短路返回(见 filter.h 中commit()的实现),避免向已拆除的流投递事件;
  • destroyed_ = true用于拒绝拆除后新发起的异步操作。

四、配套防护:拆除后拒绝新 callout、安全回收存量 callout

延迟销毁解决了钩子栈上的 use-after-free,但还不够:模块在拆除后仍可能发起新的 HTTP callout(异步请求),或存在尚未完成的存量 callout。修复为此增加了两层防护,与 changelog 中"teardown 之后启动的 HTTP callouts 会被拒绝,而不是比过滤器存活得更久"的描述一一对应。

4.1destroyed_标志:拒绝新建 callout 与流式 callout

sendHttpCallout()(发起一次性异步请求)与startHttpStream()(发起流式异步请求)在入口处都检查destroyed_

// A callout registered after destroy() has drained the pending ones would never be cancelled. if (destroyed_) { return envoy_dynamic_module_type_http_callout_init_result_CannotCreateRequest; }

从源码结构看,这一检查可以推断是为了防止两类后果:其一,新 callout 一旦注册进http_callouts_/http_stream_callouts_映射表,而该表随后被destroy()清空,回调将永远得不到清理,形成泄漏;其二,回调触发时若 in-module filter 已释放,会再次引入 use-after-free。因此拆除后直接以CannotCreateRequest拒绝,从源头掐断。

4.2destroy()内的存量清理

在真正投递on_http_filter_destroy_之前,destroy()会:

  • 先把in_module_filter_置空("先与模块解绑,使后续任何路径都无法重入事件钩子"),随后所有异步回调(onSuccessonFailureonHeadersonDataonReset等)中的if (!filter.in_module_filter_) return;守卫都会直接返回,不再解引用已拆除的流;
  • 循环取消所有 pending 的一次性 callout:对每个HttpCalloutCallback调用request->cancel()
  • 循环 reset 所有 pending 的流式 callout:对每个HttpStreamCalloutCallback调用stream->reset()
  • decoder_callbacks_encoder_callbacks_置空,并复位 watermark 回调注册标志。

这与 changelog 中"需要被拆除的流的回调不再解引用它"(callbacks that need the torn-down stream no longer dereference it)完全吻合。

五、对模块开发者的实践启示

结合上述修复,动态模块开发者在实现recreate_stream(内部重定向/重试)等"结束流"型功能时应遵循以下约束:

  1. 钩子内调用recreate_stream后立即返回StopIteration(ABI 文档明确要求),不要继续访问 in-module filter 的内部状态;
  2. 不要在拆除后启动新的 HTTP callout:从修复后的行为看,它们会被 Envoy 以CannotCreateRequest拒绝——这是有意设计的安全边界,而非可绕过的问题;
  3. 销毁回调(envoy_dynamic_module_on_http_filter_destroy)现在保证在事件钩子全部返回后才执行:模块可以在其中安全地释放资源,不必担心与正在执行的钩子竞争;
  4. envoy_dynamic_module_on_http_filter_stream_completedestroy的语义差异值得注意(abi.h):前者可能在另一个事件钩子还在栈上时被内联调用(因为结束流的钩子会内联完成流),而后者则绝不会——这正是本次修复所确立的时序保证。

六、总结

本次修复解决的是动态模块机制中一个微妙的生命周期竞态:结束流的钩子(如recreate_stream)在模块栈上拆除过滤器链,与钩子自身仍在使用 in-module filter 之间的矛盾。解决方案是双管齐下:

  • 时序上:将envoy_dynamic_module_on_http_filter_destroy投递到 worker dispatcher 的延迟删除队列(DeferredTaskUtil::deferredRun),确保其在所有事件钩子返回后才运行,同时以shared_ptr自持保证 C++ 侧过滤器在延迟期间存活;
  • 行为上:拆除后通过destroyed_标志拒绝新建 callout、清空 dispatcher 缓存短路跨线程事件、取消全部存量 callout 与流,并依赖in_module_filter_置空让所有异步回调优雅退出。

对动态模块的维护者与使用者而言,理解"销毁发生在延迟删除队列而非钩子栈上"这一约定,是正确实现内部重定向、请求重试等高级流控制能力的基石。相关核心代码可继续在 source/extensions/filters/http/dynamic_modules/filter.cc、source/extensions/filters/http/dynamic_modules/filter.h、source/common/event/deferred_task.h 以及 ABI 定义 source/extensions/dynamic_modules/abi/abi.h 中深入研读。

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

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/11 18:25:21

Spark ML在TB级销售预测中的实践与优化

1. 项目背景与目标去年接手公司销售预测任务时&#xff0c;我面临一个典型的数据科学难题&#xff1a;如何利用历史销售数据构建可靠的预测模型。传统Excel表格和简单线性回归已经无法满足业务需求&#xff0c;我们需要处理的是TB级的交易记录、数百个SKU以及复杂的季节性因素。…

作者头像 李华
网站建设 2026/9/11 18:25:09

永磁同步电机离线辨识原理与仿真建模实践

简介&#xff1a;这是一份面向电机控制工程师与电气专业学生的永磁同步电机离线辨识仿真模型资源。离线辨识是获取电阻、电感等关键参数、支撑矢量控制与直接转矩控制策略的重要手段&#xff0c;资源提供了从模型搭建、参数估计到结果验证的完整工具链。压缩包共5个文件&#x…

作者头像 李华
网站建设 2026/9/11 18:24:55

电销外包按通话分钟计费还是按线索计费

可核验行业规范及2026年市场实测数据重要合规提示&#xff1a;电销外包业务需要遵守通信管理相关法规&#xff0c;严禁骚扰呼叫&#xff0c;服务商必须具备对应电信业务经营许可资质&#xff0c;企业合作前务必核验服务商资质。本评测为第三方客观评测&#xff0c;评测对象为【…

作者头像 李华