首页 > 编程语言 >Chromium源码阅读(11):了解chromium base的raw-ptr

Chromium源码阅读(11):了解chromium base的raw-ptr

时间:2024-07-19 17:29:26浏览次数:28  
标签:11 漏洞 raw 源码 内存 UaF Chromium ptr 指针

chromium base的rawptr.h引起了我的注意,常见的ptr封装有refed_ptr、weak_ptr等。raw_ptr还要封装吗?那一定是有特殊的意义。

果然,raw_ptr.md引入眼帘:
原文地址:https://chromium.googlesource.com/chromium/src/+/HEAD/base/memory/raw_ptr.md

raw_ptr简介

raw_ptr与原始指针相比,它的内存安全性有所提高。在USE_RAW_PTR_BACKUP_REF_IMPL 关闭的平台上,它的行为与原始指针一样,而在 USE_RAW_PTR_BACKUP_REF_IMPL 打开时,它几乎与原始指针一样。主要区别在于,当启用 USE_RAW_PTR_BACKUP_REF_IMPL 时,raw_ptr对安全性有益,因为它可以防止大量释放后使用 (UaF) 漏洞被利用。它通过
隔离释放的内存(只要 存在raw_ptr指向它的悬空指针),并污染它内存(使用0xEF…EF 模式)来实现这一点。

raw_ptr的使用场景:

当在类或结构体中定义成员变量时,如果可能的话,推荐使用raw_ptr而不是直接使用T*。raw_ptr是一种专门设计的指针类型,它具有比原始指针更明确的语义和规则,有助于减少内存管理和生命周期管理中的错误。
说明中指出,Renderer-only代码是一个例外。这意味着在渲染器(renderer)特有的代码中,可能出于性能或其他特殊原因,继续使用传统的T*原始指针是合理的。渲染器代码通常与图形处理、渲染管线紧密相关,可能需要更细粒度的控制和优化,因此可能不适合或不必要强制使用raw_ptr。

什么是 (UaF) 漏洞

(UaF) 漏洞是Chromium最常见的漏洞之一,而且危害很大。
“释放后使用”(Use-after-Free,简称UaF)是一种常见的内存管理错误,通常出现在程序设计中对动态分配的内存处理不当的情况下。这种类型的漏洞发生在程序员释放了一块内存后,但在释放后仍然尝试访问或使用这块内存。当这种情况发生时,可能会引发几种不同的问题:

  1. 未定义行为:由于内存已经被释放,其内容可能已被操作系统重用或清空。因此,任何试图读取或写入这块内存的操作都将导致不可预测的结果。

  2. 数据损坏:如果释放后的内存被重用并分配给了另一个对象,那么对原对象的引用可能无意中修改新对象的数据,从而导致数据损坏。

  3. 程序崩溃:如果尝试访问的内存不再属于程序,操作系统可能会检测到非法访问并终止程序。

  4. 安全漏洞:攻击者可能利用UaF漏洞来执行任意代码或控制程序的执行流程。例如,攻击者可以通过精心构造的输入来控制释放后内存的地址,然后在程序再次使用这块内存时注入恶意代码。

UaF漏洞之所以危险,是因为它们往往难以检测和调试。程序可能在某些情况下运行正常,而在其他情况下则会失败,这取决于释放后的内存是否被重用以及何时被重用。因此,这类漏洞经常被黑客利用来进行攻击,尤其是在网络应用和操作系统内核中。

为了防止UaF漏洞,开发者应该遵循良好的编程实践,比如:

  • 在释放内存后立即置空指向它的指针,避免悬挂指针的产生。
  • 使用智能指针或RAII(Resource Acquisition Is Initialization)资源管理技术,以自动化的方式管理内存生命周期。
  • 使用现代编程语言或库,它们提供了内置的内存管理机制,如垃圾回收。
  • 运行时检查和调试工具,如AddressSanitizer(ASan)、Valgrind等,可以帮助识别潜在的UaF问题。

在修复已知的UaF漏洞时,通常需要仔细审查代码,确保在释放内存后不再有任何对该内存的引用,并更新相关的数据结构和指针状态。

raw_ptr如何防止 (UaF) 漏洞

主要是在raw_ptr析构时,尝试访问自己指向的内存,如果目标内存是无效值,那么就及时出发崩溃。
这段代码体现如下:

// 在raw_ptr析构或赋值时,会调用:
Impl::ReleaseWrappedPtr(wrapped_ptr_);
// 其实现的核心代码如下:

template <typename T>
static void ProbeForLowSeverityLifetimeIssue(T* wrapped_ptr) {
	if (!MayDangle && wrapped_ptr) {
		const volatile void* probe_ptr =
		reinterpret_cast<const volatile void*>(wrapped_ptr);
		if (!LikelySmuggledScalar(probe_ptr) &&
		!EndOfAliveAllocation(probe_ptr, IsAdjustablePtr)) {
			reinterpret_cast<const volatile uint8_t*>(probe_ptr)[0];
		}
	}
}

名为ProbeForLowSeverityLifetimeIssue的目标函数目的是检测一个给定指针(T* wrapped_ptr)是否可能指向一个过早释放或生命周期管理不当的对象,从而可能导致Use-after-Free(UaF)等问题。让我们逐行解析这段代码:

  1. 函数签名:

    template <typename T>
    static void ProbeForLowSeverityLifetimeIssue(T* wrapped_ptr) {
    

    这是一个静态成员函数,使用模板参数T,意味着它可以接受任何类型的指针作为参数。

  2. 条件检查:

    if (!MayDangle && wrapped_ptr) {
    

    这里有两个条件:

    • !MayDangle: 这个条件检查是否允许指针悬空(即指向已释放内存)。如果MayDangle为真,那么这段代码将不会执行进一步的检查,因为它认为指针可能已经无效。
    • wrapped_ptr: 检查传入的指针是否非空,即是否指向一个有效的内存地址。
  3. 创建探针指针:

    const volatile void* probe_ptr =
        reinterpret_cast<const volatile void*>(wrapped_ptr);
    

    创建一个新的void*类型的指针probe_ptr,它被声明为const volatile,这表示该指针指向的内存内容不会被修改,但可能在任何时候发生变化(例如,在多线程环境中)。reinterpret_cast用于将wrapped_ptr转换为void*类型,使其可以用于后续的通用内存操作。

  4. 检查是否可能是走私的标量:

    if (!LikelySmuggledScalar(probe_ptr) &&
        !EndOfAliveAllocation(probe_ptr, IsAdjustablePtr)) {
    

    这里有两个函数调用:

    • LikelySmuggledScalar(probe_ptr): 这个函数检查probe_ptr是否可能指向一个被非法移动或复制的标量值。
    • EndOfAliveAllocation(probe_ptr, IsAdjustablePtr): 这个函数检查probe_ptr是否指向一个活动内存分配的末尾,IsAdjustablePtr可能是一个标识符或函数,用于判断probe_ptr是否是一个可调整的指针。
  5. 访问内存:

    reinterpret_cast<const volatile uint8_t*>(probe_ptr)[0];
    

    如果上述两个条件都不满足,即probe_ptr既不是走私的标量子对象,也不是活动内存分配的末尾,那么这里尝试访问probe_ptr指向的内存的第一个字节。这通常用于触发潜在的未定义行为,如访问已释放内存,以便在运行时捕捉到错误。

这段代码的主要意图是探测wrapped_ptr指向的内存是否有潜在的生命周期问题,例如是否指向一个已经释放的内存区域,或是否指向一个可能被不当移动的对象。如果存在这样的问题,上述代码可能会触发运行时错误,帮助开发者定位和修复潜在的UaF漏洞。需要注意的是,volatile关键字的使用在这里可能更多是为了防止编译器优化掉不必要的内存访问,以确保代码能正确地触发潜在的错误。

了解其他预防 (UaF) 漏洞的方法

防止Use-after-Free(UaF)漏洞被利用的策略并不单一,而是采用多种技术和方法相结合。下面是一些关键的原理和方法:

  1. 分区内存分配(Partitioned Memory Allocation):

    • 这种方法将内存分成多个独立的区域(或分区),每个分区有自己的分配器。当一个对象被释放时,其地址空间只在相应的分区中被标记为可用,而不是在整个堆中。这样可以减少UaF漏洞的利用机会,因为攻击者即使控制了某个分区的内存分配,也很难影响到其他分区中的对象。
  2. 随机化内存布局(Address Space Layout Randomization, ASLR):

    • ASLR是一种安全机制,它随机化程序的内存布局,包括代码段、数据段、堆和栈的位置。这增加了攻击者预测特定内存位置的难度,从而降低了UaF漏洞被利用的可能性。
  3. 内存标签扩展(Memory Tagging Extensions, MTE):

    • MTE是现代处理器的一种特性,它为内存块添加了额外的标签信息。当内存被释放后,其标签会被清除或更改。随后,如果尝试访问带有不同标签的内存,则会触发异常。这可以有效地检测UaF情况。
  4. 智能指针(Smart Pointers):

    • 在现代编程语言中,如C++,智能指针(如std::unique_ptrstd::shared_ptr)的使用可以自动管理内存的生命周期。当最后一个指向某个对象的智能指针超出作用域或被显式销毁时,对象的内存会被自动释放,从而避免UaF漏洞。
  5. 监控和验证(Monitoring and Validation):

    • 一些工具和库在运行时监控内存访问模式,当检测到UaF行为时会抛出异常或错误。例如,AddressSanitizer(ASan)是一个用于检测内存错误的工具,包括UaF。
  6. 内存屏障(Memory Barriers):

    • 在并发环境中,内存屏障可以确保内存操作的顺序性和可见性,防止因并发操作导致的UaF漏洞。
  7. 代码审查和静态分析:

    • 代码审查和使用静态分析工具可以帮助开发者在代码投入生产前发现潜在的UaF漏洞。

防止UaF漏洞的核心在于确保在释放内存后,没有任何代码路径可以继续访问这块内存,同时确保新的内存分配不会重用这块已释放的内存,除非它已经经过适当的重置或初始化。通过上述方法和技术的综合运用,可以极大地降低UaF漏洞被利用的风险。

raw_ptr的实现还依赖了partition_alloc库。由此可见raw_ptr避免 (UaF) 漏洞不止上一节提到的方法,应该是多管齐下的。

言归正传,raw_ptr使用原则如下

应将raw_ptr<T>将其视为一个原始的C++指针。具体而言:

  • 自行初始化它,不要假设构造函数会默认初始化它(可能初始化也可能不初始化)。(始终使用raw_ptr<T> member_ = nullptr;的初始化形式,而不是所谓的统一初始化形式(空括号)raw_ptr<T> member_{};,后者的意义会随实现的不同而变化。)

  • 不要假设移动操作会清空指针(可能清空也可能不清空)。

  • 内存的所有者必须在适当的时候释放内存,不要假设raw_ptr<T>会替你释放它(它不会)。与std::unique_ptr<T>base::scoped_refptr<T>等不同,它不会管理分配对象的所有权或生命周期。

    • 如果指针是内存的所有者,应考虑使用替代的智能指针。
  • 不要假设raw_ptr<T>会保护你免于过早释放内存(它可能能做到,但存在陷阱;其中一个陷阱是,解引用会导致其他类型的未定义行为)。

  • 不要赋值无效的、非空地址(这包括曾有效现已释放的内存、Win32句柄等)。你只能为raw_ptr<T>赋值在赋值时刻仍有效的内存地址。例外情况包括:

    • 指向有效分配末尾的指针(但不能超出末尾的1字节)
    • 指向地址空间最后一页的指针,例如用作哨兵的reinterpret_cast<void*>(-1)
  • 不要直接初始化或赋值raw_ptr<T>所指向的内存(例如reinterpret_cast<ClassWithRawPtr*>(buffer)memcpy(reinterpret_cast<void*>(&obj_with_raw_ptr), buffer))。

  • 不要在多个线程中并发地赋值给raw_ptr<T>,即便赋值的是相同的值。

  • 不要依赖移动操作后指针的旧值。与原始指针不同,raw_ptr<T>在移动操作后可能会被清空。

  • 不要在raw_ptr<T>被析构后继续使用它。与原始指针不同,raw_ptr<T>在析构时可能会被清空。例如,当字段排序导致指针字段在使用该指针字段的类字段析构前就被析构时(参见[奇异问题])。

  • 不要在构造函数运行前就对raw_ptr<T>进行赋值。这可能发生在基类的构造函数使用了派生类中尚未初始化的字段时(参见[MiraclePtr的应用])。

其中一些操作即使在没有raw_ptr<T>的世界里也会导致未定义行为(UB),但你可能侥幸不会遇到任何后果(例如参见[字段析构顺序])。然而,在raw_ptr<T>的世界里,可能会发生隐晦的崩溃。这些崩溃经常表现为SEGV(段错误)或在RawPtrBackupRefImpl::AcquireInternal()RawPtrBackupRefImpl::ReleaseInternal()中的CHECK错误,你也可能遭遇内存破坏或无声地丧失UaF(释放后使用)保护。

结语

理解raw_ptr的引入背景和实现细节,对阅读chromium源码很有帮助。

标签:11,漏洞,raw,源码,内存,UaF,Chromium,ptr,指针
From: https://blog.csdn.net/hebhljdx/article/details/140554549

相关文章

  • Chromium源码阅读(10):了解Log模块
    Chromium许多日志被TraceEvent代替了,因此TraceEvent出现的频率要比Log高很多。但是也有不少场景使用Log。在blink,Log的实现由blink/base提供,而chromium的日志由blink/render/core/base/logging.h提供。一些底层的日志由absel的log模块提供。说实话,日志模块的实现数量有点......
  • [附开题]flask框架的基于web的线上考试管理系统的设计与实现n1qn5(python+源码)
    本系统(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。系统程序文件列表开题报告内容研究背景随着互联网技术的飞速发展,教育领域正经历着深刻的变革。传统的线下考试模式逐渐显露出其局限性,如组织成本高、效率低下、资源分配不均等问......
  • [附开题]flask框架的基于web的小区疫情防控信息管理系统ca4gz(python+源码)
    本系统(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。系统程序文件列表开题报告内容研究背景近年来,全球公共卫生事件频发,特别是新冠疫情的爆发,对社区管理提出了前所未有的挑战。小区作为城市的基本单元,其疫情防控的效率和效果直接关......
  • 【开源分享】2024PHP在线客服系统源码(全新UI+终身使用+安装教程)
    PHP在线客服系统核心功能用户留言协同工作:留言后,用户能够享受在线咨询、订单查询等服务;登录状态也用于权限控制,确保不同用户访问合适的资源。咨询处理作用:提供实时或异步的客服咨询功能,允许用户向客服发送问题并接收回复。重要性:是客服系统的核心功能,直接影响用户体验和满意......
  • Windows 11 version 23H2 中文版、英文版 (x64、ARM64) 下载 (updated Jul 2024)
    Windows11version23H2中文版、英文版(x64、ARM64)下载(updatedJul2024)Windows11,version23H2,企业版arm64x64请访问原文链接:https://sysin.org/blog/windows-11/,查看最新版。原创作品,转载请保留出处。作者主页:sysin.orgWindows11目前版本所有的日期都按照I......
  • Windows 11 version 22H2 中文版、英文版 (x64、ARM64) 下载 (updated Jul 2024)
    Windows11version22H2中文版、英文版(x64、ARM64)下载(updatedJul2024)Windows11,version22H2,企业版arm64x64请访问原文链接:https://sysin.org/blog/windows-11/,查看最新版。原创作品,转载请保留出处。作者主页:sysin.orgWindows11目前版本所有的日期都按照I......
  • 【MATLAB源码-第149期】基于MATLAB的2ASK,2FSK,2PSK,2DPSK等相干解调仿真,输出各节点波
    操作环境:MATLAB2022a1、算法描述2ASK(二进制幅移键控)、2FSK(二进制频移键控)、2PSK(二进制相移键控)和2DPSK(二进制差分相移键控)是数字调制技术中的基本调制方式,它们在无线通信、数据传输等领域有着广泛的应用。相干解调是这些调制方式中一个重要的解调技术,它要求接收端的本地振......
  • 【MATLAB源码-第147期】基于matlab的QPSK调制解调在AWGN信道,瑞利信道,莱斯信道理论与实
    操作环境:MATLAB2022a1、算法描述四相位移键控(QPSK,QuadraturePhaseShiftKeying)是一种重要的数字调制技术,它通过改变信号的相位来传输数据。与其他调制技术相比,QPSK在相同的带宽条件下能够传输更多的数据,因而在现代通信系统中得到了广泛应用。本文将详细探讨QPSK在高斯白......
  • gin的生命周期——源码学习
    目录结构先来了解下其目录结构:.├──binding依据HTTP请求Accept解析响应数据格式│├──binding.go│├──......├──ginS├──internal├──render依据解析的HTTP请求Accept响应格式生成响应│├──data.go│├──html.go│......
  • CF1139D
    https://www.luogu.com.cn/problem/CF1139D(暂时没有解释,咕咕咕~~~)最重要的部分是对式子的化简,令\(X\)为步数:\[\begin{align}E[X]&=\sum_jjP(X=j)\\&=\sum_j\sum_i^j1P(X=j)\\&=\sum_i\sum_{j\geqi}P(X=j)\\&=\sum_iP(X\geqi)\\&=1+\sum......