WeakPtr 与 Raw 指针UAF 如何识别、如何处理、以及 Chromium 的设计哲学本文聚焦Use-After-Free 在底层如何被「看见」、看见之后怎么办、以及Weak / Raw / RefPtr 为何并存。1. 先选契约再选指针Chromium 并不是用一种智能指针解决所有生命周期问题而是按语义分工契约典型 API你在说什么设计目标回调执行时对象必须还在scoped_refptr、WrapRefCounted(this)「任务跑完前别析构」引用计数延长生命对象可能提前没了weak_factory_.GetWeakPtr()「关了窗口/Shutdown 就别再调我」不拥有失效当 null我保证异步触发前对象仍有效Unretained(raw)、裸T*「信我不会先 free」零开销错了就 UAF没有「万能指针」WeakPtr故意不靠 refcount 保活——否则与scoped_refptr重复。Unretained故意不在 Release 里做有效性检查——否则与 WeakPtr 重复。raw_ptrT主要服务类成员字段的内存安全BackupRefPtr不能当成 PostTask 里Unretained(this)的替代品。业务侧CloudModuleUpdater::CollectAllDownloadInfo崩溃本质是用了 Unretained 的契约却处在对象可能先销毁的时序里。2. WeakPtrUAF 如何被「识别」WeakPtr不检测「这块内存是否已被 free」也不阻止free。它维护的是逻辑状态factory 关联的 Flag 是否已被 invalidate。2.1 内部是什么每个WeakPtrT大致包含// base/memory/weak_ptr.h概念结构internal::WeakReference ref_;// 指向共享 Flagref-countedT*ptr_;// 创建 WeakPtr 时记下的地址不拥有对象GetWeakPtr()时// base/memory/weak_ptr.hWeakPtrTGetWeakPtr(){returnWeakPtrT(weak_reference_owner_.GetRef(),reinterpret_castT*(ptr_));}所有从同一WeakPtrFactory发出的 WeakPtr共享同一个 Flag。2.2 「有效」判定的源码// base/memory/weak_ptr.hT*get()const{returnref_.IsValid()?ptr_:nullptr;}explicitoperatorbool()const{returnget()!nullptr;}T*operator-()const{CHECK(ref_.IsValid());returnptr_;}// base/memory/weak_ptr.ccboolWeakReference::Flag::IsValid()const{DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);return!invalidated_.IsSet();}boolWeakReference::Flag::MaybeValid()const{return!invalidated_.IsSet();// 无 sequence DCHECK可跨线程「偷看」}识别 UAF 的方式不是分析T*指向的堆块而是invalidated_是否被置位。2.3 Flag 何时 invalidate// factory / 对象析构WeakReferenceOwner::~WeakReferenceOwner(){flag_-Invalidate();}// 显式 ShutDownvoidWeakPtrFactoryT::InvalidateWeakPtrs(){weak_reference_owner_.Invalidate();// 并换一个新 Flag之后 GetWeakPtr() 走新的一代}对象析构时weak_factory_通常放在成员列表最后先析构 → 所有已发出的 WeakPtr同时失效。注意ptr_里可能仍是旧地址但get()已返回nullptr不会通过 WeakPtr 再去访问。2.4 发现「无效」后怎么处理用法行为if (weak)/weak.get()无效 →nullptr业务returnBindOnce(..., weak)作 receiver运行前get()无效 →整段回调不执行weak-Method()CHECK(IsValid())Debug 下主动崩溃误用检测哲学在 WeakPtr 路径上UAF 被语义化成「指针已失效」——Prefer跳过而不是硬解引用。2.5 跨线程传递 vs 解引用base/memory/weak_ptr.h原文要点Weak pointers may be passed safely between sequences, but must always be dereferenced and invalidated on the same SequencedTaskRunner ...操作跨 UI ↔ file拷贝、放进PostTask/BindOnce✅if (weak)/weak-/weak.get()⚠️ 应在factory 所在 sequence单测NonOwnerThreadDereferencesWeakPtrAfterReference主线程先解引用绑定 sequence 后后台线程再get()→DCHECK death。推荐file 线程只传递WeakPtr回 UI 再BindOnce(..., weak)或if (weak)。2.6 业务示例EmbedWebView// embed_web_view.hbase::WeakPtrEmbedWebViewGetWeakPtr(){returnweak_factory_.GetWeakPtr();}// embed_web_delegate.ccHandleGetSearchEngineList(web_view-GetWeakPtr(),invoke_id);voidEmbedWebDelegate::HandleGetSearchEngineList(base::WeakPtrEmbedWebViewweb_view,intinvoke_id){if(!web_view||!web_view-GetWebContents())return;// TemplateURLService 未 loaded 时再 BindOnce(..., web_view, ...)}不延长EmbedWebView生命关窗后if (!web_view)直接 return——这是 WeakPtr 的典型契约。3. Raw / UnretainedUAF 如何被「识别」3.1 裸指针本身无识别T*、Unretained(this)在闭包里只存地址。对象 free 后继续用 → 经典 UAFRelease 下可能静默踩毒内存。// 崩溃链示例cloud_module_updater.ccfile_task_runner_-PostTask(FROM_HERE,base::BindOnce(CloudModuleUpdater::CollectAllDownloadInfoOnFileThread,base::Unretained(this),// receiver与 WeakPtr 无关weak_factory_.GetWeakPtr()));对非 static 成员的BindOnce第一个参数是receiver当this第二个才是weakptr形参。崩溃常在Unretained receiver不是 WeakPtr 参数CollectDownloadInfo(i)仍走 receiver 的this。3.2 DebugUnretainedDanglingRawPtrDetectedCrashChromium 在 Debug及启用 BackupRefPtr 检测时对Unretained 包装的 raw 指针挂钩 PartitionAllocdangling 检测识别流程简化对象释放时若仍有 Unretained/raw_ptr包装指着该地址 → 标记为dangling异步任务执行、解包 Unretained解引用→UnretainedDanglingRawPtrDetectedCrash。哲学Unretained 仍是「我保证它还活着」Debug 用crash 报告惩罚违反契约——不是替你做生命周期管理。3.3raw_ptrT成员字段层不是 PostTask 万能盾摘自base/memory/raw_ptr.mdBackupRefPtr只要有 dangling raw_ptr 指着释放的内存会被 quarantine 0xEF 毒化 解引用 dangling 不必然立刻崩但提高后续崩溃概率 仍是 Undefined Behavior维度WeakPtrraw_ptr 成员Unretained层次逻辑失效标记内存 quarantine / 毒化无Debug dangling 钩子主要场景异步可能取消类/struct 字段PostTask receiverRelease 安全跳过回调缓解 exploit仍可能 UAF4. scoped_refptr第三种哲学——用 refcount 避免 freeRefCountedThreadSafeWrapRefCounted(this)file_task_runner_-PostTask(FROM_HERE,base::BindOnce(CloudModuleUpdater::CollectAllDownloadInfoOnFileThread,base::WrapRefCounted(this),weak_factory_.GetWeakPtr()));时机引用计数WrapRefCounted(this)进闭包1file 任务结束、闭包析构-1scoped_refptr析构业务代码无显式 ReleaseModuleHost::cloud_module_长期持有始终≥1直到 owner 析构识别 UAF 的方式不是 Flag而是refcount 0 就不析构——从根上避免「对象已 free 仍回调」。哲学回调执行期间对象必须活着时用 refptr 比 WeakPtr 更直接WeakPtr 只绑Done管不到file 段 receiver 的this。5. 三层对照谁在哪抓 UAF┌─────────────────────────────────────┐ 逻辑层 │ WeakPtr: Flag invalidate │ (是否允许访问) │ → if (!weak) skip / CHECK │ └─────────────────────────────────────┘ ┌─────────────────────────────────────┐ 所有权层 │ scoped_refptr: refcount 0 │ (对象能否析构) │ → 有 ref 就不 destroy │ └─────────────────────────────────────┘ ┌─────────────────────────────────────┐ 内存层 │ raw_ptr BRP / Unretained dangling │ (free 后是否踩) │ → quarantine DetectedCrash │ └─────────────────────────────────────┘WeakPtrUnretainedraw_ptr 成员scoped_refptr如何「识别」Flag invalidDebug dangling 钩子BRP quarantinerefcount如何处理当 null / 跳过Debug crash毒化/延迟回收延长生命是否阻止析构否否否仅延迟回收块是Release 误用跳过回调可能 UAF缓解利用有 ref 则安全6. 选型决策表写代码时用你的目标建议只修 PostTask dangling类已是 RefCountedfile 段必须跑完Unretained→WrapRefCounted(this)窗口/组件可能已销毁Widget、DelegateGetWeakPtr()if (!weak)receiver 勿 Unretained严格跨线程 file 不写成员snapshot 或PostTaskAndReply同步代码、生命周期清晰raw / 引用跨异步慎用 Unretained类成员裸指针优先raw_ptrT非 Renderer 进程规范反模式Unretained(this)GetWeakPtr()混在同一BindOnce——看起来像双保险crash 往往在receiver上WeakPtr 管不到。7. 业务侧两个典型案例7.1 CloudModuleUpdaterRefCounted file PostTask问题Unretained(this)作 receiverShutdown 后 file 任务仍跑 → dangling crash。最小修复WrapRefCounted(this)GetWeakPtr()仍可只给DoneOnUIThread。教训RefCounted 类 「任务期间必须活着」→ refptr不是 WeakPtr alone。7.2 EmbedWebDelegateEmbedWebView 异步 TemplateURLService模式web_view-GetWeakPtr()入口与RegisterOnLoadedCallback均if (!web_view)。教训对象可能先没 → WeakPtr不要Unretained web_view。8. 结语三句话WeakPtr用共享Flag表达「对象/logical owner 已失效」——不拥有、不阻止析构UAF 在 API 层变成null。Raw / Unretained默认零检查Debug 靠dangling 检测抓违规不替你做异步生命周期。scoped_refptr用refcount保证「有引用就不 free」——适合RefCounted 回调期间必须执行的链路。先想清楚「回调跑的时候对象是否还必须活着」再选 Weak、Raw 还是 RefPtr——这比背 API 名字更重要。9. 延伸阅读团队笔记weak_ptr_unretained_refptr_async_callbacks.md可选 demochrome/browser/product/component_updater/*_weak_ptr_demo_unittest.cc仓库内路径对外分享时可删上游base/memory/weak_ptr.h、base/memory/raw_ptr.md