泄漏悬空越界

共 18 题
📑 题目列表 18 题
#
★★★

1. Valgrind memcheck 检测 UAF 与越界的影子内存设计?

Valgrind memcheck 如何通过影子内存设计检测 use-after-free(UAF)与越界访问?

  • 影子内存(shadow memory)的概念
  • 已定义/未定义位与地址位
  • UAF 与越界的检测原理

Valgrind memcheck 把所有内存都映射到一份"影子内存"(shadow memory),记录每个字节的"已定义/未定义"(V 位)和"已分配/未分配"(A 位)状态。程序每次读写都会同步更新和检查影子内存:读未初始化(未定义)内存会报错;访问已释放(UAF)或未分配区域(越界)会因 A 位不匹配而报错。memcheck 通过二进制插桩把所有内存访问替换为检查影子内存的指令,以此在运行时精确捕获 UAF 和越界,代价是数倍到数十倍的性能下降。

影子内存是"以空间换追踪"的经典设计:用一份元数据反映所有内存状态,插桩代码在每次访问时校验,从而精确定位非法访问。这是 memcheck 强大但慢(比 ASan 慢约 10-50 倍)的原因。

#
★★★

2. buffer overflow(栈溢出、堆溢出)的差异?

buffer overflow(栈溢出、堆溢出)的差异是什么?

  • 缓冲区的存放位置
  • 攻击目标与利用方式
  • 防护手段差异

栈溢出发生在栈上的缓冲区(如局部数组),溢出会覆盖相邻的返回地址、帧指针、局部变量,从而劫持控制流(如 ROP),攻击面是函数返回路径,利用相对直接。堆溢出发生在堆上的缓冲区(如 malloc 的对象),溢出会覆盖相邻堆块的元数据(size、next 指针)或相邻对象,实现任意写、堆破坏或利用 fastbin 等实现控制流劫持,利用更复杂。防护上,栈用 Stack Canary、NX、ASLR;堆用元数据完整性检查、隔离等。两者都可能被 W^X 与 ASLR 增强防御。

差别的核心是"溢出发生的位置导致攻击目标不同":栈溢出打返回地址,堆溢出打堆元数据/相邻对象。理解这一点是分析不同漏洞利用路径的关键。

#
★★★

3. double free 的危害与 glibc 检测机制?

double free(重复释放)的危害与 glibc 的检测机制是什么?

  • double free 造成的堆破坏
  • glibc 的检测(tcache key、校验)
  • 防御的局限

double free 指同一块内存被释放两次,会破坏堆的 free list(如把同一 chunk 两次放入链表),导致分配器元数据错乱、产生循环链表,进而被攻击者利用(如 tcache poisoning、unsorted bin attack),实现任意写或控制流劫持。glibc 提供检测机制:tcache 用 key 字段检测 double free(释放时若 key 指向 tcache 则审查链表);fastbin/unsorted 释放时校验 chunk 合法性(如 next chunk 的 size 与 prev_inuse 标志)。但检测并非绝对,可被精心构造的输入绕过(如篡改 key),因此 double free 仍是严重漏洞。

double free 的本质是"同一块内存两次进入空闲链表",其危害基于 free list 破坏。glibc 的检测是"尽力而为"的加固,无法根除,需靠代码层面保证(如置空指针、智能指针)。

#
★★★

4. memory leak(内存泄漏)的定义与典型场景?

memory leak(内存泄漏)的定义与典型场景是什么?

  • 泄漏的定义
  • 典型场景(丢失指针、循环引用)
  • 后果与检测

内存泄漏指程序分配了内存但失去对它的引用(指针丢失)或忘记释放,导致该内存永远无法被回收复用,随运行时间内存占用持续增长。典型场景:1)malloc 后忘记 free;2)重新分配指针覆盖旧指针(丢失引用);3)C++ 中指向动态内存的普通指针被覆盖;4)Rust/Java 中循环引用导致引用计数对象无法归零;5)全局容器不断增长。后果是内存耗尽、性能下降、OOM。检测用 Valgrind、ASan、LeakSanitizer 等。

泄漏的本质是"内存生命周期失去管理":要么引用丢失,要么引用循环无法终止。理解泄漏的触发条件是避免与检测的基础。

#
★★★

5. 为何 C/C++ 不自动内存管理而 Rust 通过所有权避免?

为什么 C/C++ 不自动内存管理而 Rust 通过所有权机制避免内存错误?

  • C/C++ 手动管理的灵活性与风险
  • Rust 所有权/借用/生命周期
  • 编译期检查 vs 运行时管理

C/C++ 追求性能与控制力,内存由程序员手动管理(malloc/free、new/delete),无自动回收,灵活但极易产生泄漏、悬空指针、UAF、double free 等错误。Rust 通过所有权(ownership)模型在编译期解决这些问题:每个值有唯一所有者,作用域结束自动 drop;借用(borrow)规则保证要么多个不可变借用、要么一个可变借用;生命周期(lifetime)确保借用不超出被借用对象。这些检查全部在编译期完成,运行时零开销,同时避免了 GC 的停顿,做到"内存安全 + 性能兼得"。

差异根源是"安全策略的时机":C/C++ 把内存安全交给运行时或程序员,Rust 把所有权规则固化到类型系统,在编译期强制执行,从而根除整类内存错误而不牺牲性能。

#
★★★

6. ASan 如何通过 quarantine(隔离区)检测 use-after-free,为什么释放后的内存不立即归还给分配器?

ASan 如何通过 quarantine(隔离区)检测 use-after-free?为什么释放后的内存不立即归还给分配器?

  • quarantine 的概念
  • UAF 检测原理
  • 延迟归还的原因

ASan(AddressSanitizer)在释放内存时,不立即把内存归还给分配器复用,而是放入一个"隔离区"(quarantine),并使该区域的内存标记为"已释放"状态(影子内存标记为 poisoned)。在隔离区期间,任何对这块内存的读写都会因影子内存不匹配而被 ASan 拦截并报 UAF 错误。只有当隔离区满(或超时)时,才把最老的块真正归还给分配器。延迟归还的原因:1)给 UAF 检测留出时间窗口,防止内存被复用后掩盖 UAF;2)保证检测的可靠性,避免"释放后复用"导致的漏报。

quarantine 是"延迟回收以扩大检测窗口"的思路:UAF 检测需要内存保持"已释放"标记,若立即复用则无法区分新旧访问。quarantine 大小决定检测窗口与内存/性能开销的权衡。

#
★★

7. Rust Box::leak 故意泄漏的实现?

Rust 的 Box::leak 如何实现故意泄漏?

  • Box::leak 的语义
  • 与普通 Box 的区别
  • 使用场景

Box::leak 将一个 Box 的所有权 "泄漏"(leak),返回一个指向数据的 &'static mut 引用,即该内存不再受所有权管理,也不会被自动 drop,生命周期变为 'static,直到进程结束。其实现是构造一个 Box 后,通过 mem::forget 或直接返回引用,跳过 drop。典型场景:在运行时构造一个静态生命周期的对象(如配置、堆分配的单例),或需要把数据交给不受生命周期约束的代码。这是有意为之的泄漏,与 bug 泄漏不同。

Box::leak 是"把编译期所有权豁免"的刻意操作:将堆内存的生命周期提升为 'static,放弃自动释放。它明确了"故意泄漏"与"意外泄漏"的区别,是安全语言中显式放弃所有权的手段。

#
★★

8. use-after-free(UAF)漏洞与 CVE-2022-0184 等示例?

use-after-free(UAF)漏洞是什么?CVE-2022-0184 等示例说明了什么?

  • UAF 的定义与触发
  • CVE-2022-0184 的示例
  • 根因与防护

use-after-free(UAF)指对象被释放后仍被引用访问,此时内存可能已被复用或处于未定义状态,导致逻辑错误、数据篡改或任意代码执行,是最常见的内存漏洞之一。CVE-2022-0184 是 Linux 内核 legacy_parse_param 函数的堆越界写(heap out-of-bounds write)漏洞,攻击者可控的 fsconfig 系统调用可触发越界写,进而绕过权限实现内核提权、容器逃逸。这类漏洞映射出内存安全类漏洞的普遍性与严重性:无论 UAF(释放与使用之间的生命周期管理失误)还是越界写(边界校验缺失),尤其在解除引用、错误处理、竞态(TOCTOU)路径中,都可能被利用为内核提权路径。

UAF 的根因是"对象的生命周期管理不同步",即释放与使用的时序错误。CVE-2022-0184 展示了一个看似普通的参数解析错误如何被利用为内核提权,说明 UAF 的严重性与防御(隔离、引用计数、及时置空)的必要性。

#
★★

9. ASan(AddressSanitizer)检测 out-of-bounds 原理?

ASan(AddressSanitizer)检测 out-of-bounds(越界)的原理是什么?

  • 红区(redzone)的设计
  • 影子内存映射
  • 越界访问的拦截

ASan 在分配对象时,在对象周围填充"红区"(redzone),并在影子内存中把红区标记为 poisoned(不可访问),而对象本身标记为 addressable。每次读写都会通过插桩检查影子内存:若访问落在红区(越界),影子内存为 poisoned 位,ASan 立即报错并给出越界位置。堆对象、栈对象、全局对象的红区分别覆盖,能检测堆越界、栈越界和全局越界。影子内存以约 1/8 的映射比例(4KB 对应 512B 影子)实现,开销为约 2x 内存和 2-3x 性能。

ASan 的核心是"红区 + 影子内存位图":越界访问必然触碰红区,影子内存把该区域标记为非法,插桩校验即可拦截。这是"以内存开销换精确检测"的典型方案。

#
★★

10. dangling pointer(悬空指针)的成因与 UB 表现?

dangling pointer(悬空指针)的成因与未定义行为(UB)表现是什么?

  • 悬空指针的成因
  • 访问悬空指针的 UB
  • 危害与防御

悬空指针指指向已释放或已失效内存的指针。成因:1)free/delete 后未置空指针;2)返回局部变量(栈)的地址;3)指向的函数作用域内对象超出生命周期;4)对象被析构但指针保留。访问悬空指针是未定义行为(UB):可能读到垃圾数据、崩溃(SIGSEGV)、静默返回错误值;若内存已被复用,还可能被利用为 UAF 漏洞。防御:释放后置空指针、使用智能指针、避免返回栈地址、用引用计数管理生命周期。

悬空指针的根源是"指针生命周期超过其指向对象生命周期"。UB 表现为"不可预测",因为内存状态未知,这正是其危险之处。

#
★★

11. 内存泄漏的检测,valgrind、ASan 与 heap profile 如何选择?

内存泄漏的检测方法有哪些?valgrind、ASan 与 heap profile 各自的原理与适用场景是什么?

  • Valgrind 的 leak-check
  • ASan + LeakSanitizer
  • heap profile 的采样统计

Valgrind memcheck 的 --leak-check=full 通过跟踪每次 malloc/free,在程序结束时扫描所有"仍可达/不可达"的堆块,精确报告泄漏点,慢但准确。ASan 搭配 LeakSanitizer(LSan)在运行时检测泄漏,速度快于 Valgrind,适合集成到测试。heap profile(如 tcmalloc、jemalloc 的 heap profiler,或 gperftools)通过采样统计每个调用栈的堆分配,生成分配热力图,适合定位"谁分配了最多内存"的持续增长问题,而非单个泄漏点。三者互补:Valgrind/LSan 找精确泄漏,heap profile 找分配热点。

检测泄漏分"找精确泄漏点"(Valgrind/LSan)与"定位分配热点"(heap profile)两条路线,前者重精度、后者重吞吐与总量,应根据场景选择。

#
★★

12. AddressSanitizer 的原理,影子内存与红区检测如何工作?

AddressSanitizer 的原理是什么?影子内存与红区如何检测?

  • 影子内存映射
  • 红区标记
  • 插桩校验

AddressSanitizer 的原理是"影子内存 + 红区 + 插桩":1)程序内存按 1/8 比例映射到影子内存,每个影子字节编码 8 个程序字节的 reachability(可访问/不可访问);2)编译器在每次访问前插入检查代码,查询影子内存确认地址是否可访问;3)分配对象时在前后填充红区并在影子内存中标记为不可访问,因此越界访问触碰红区即被拦截;4)释放后内存标记为 poisoned,配合 quarantine 检测 UAF。开销约 2-3x 性能、2x 内存。

ASan 把"内存是否可访问"编码进影子内存,用插桩在访问时校验,实现精确检测。红区是越界的"诱捕网",影子内存是校验的"依据",两者结合使 ASan 覆盖堆/栈/全局越界与 UAF。

#
★★

13. LeakSanitizer 的原理,基于栈回溯如何定位泄漏点,与 Valgrind 的 leak-check 在性能与精度上的差异如何?

LeakSanitizer 的原理是什么?如何基于栈回溯定位泄漏点?它与 Valgrind 的 leak-check 在性能与精度上有何差异?

  • LSan 的栈回溯与泄漏分类
  • 与 Valgrind 的性能对比
  • 精度差异

LeakSanitizer(LSan)在程序结束时扫描所有存活堆块,通过栈回溯(stack unwinding)记录每个分配点的调用栈,并对堆块按"仍可达/不可达"分类,不可达的堆块即为泄漏,报告其分配调用栈从而定位泄漏点。它通常与 ASan 一起使用,运行速度快(插桩 + 扫描,比 Valgrind 快 10-50 倍)。Valgrind 的 leak-check 用二进制插桩跟踪每个 malloc/free,精确但极慢(慢 20-50 倍)。精度上两者都能精确定位泄漏分配点,但 LSan 依赖于编译时插桩(需能 unwinding),Valgrind 无需重新编译、更通用但更慢。

LSan 用"栈回溯 + 可达性扫描"快速定位泄漏,Valgrind 用"全插桩跟踪"通用但慢。差异核心是"现代插桩技术(快、需编译)vs 传统二进制插桩(慢、通用)"。

#
★★

14. Rust 中 Rc/Arc 的循环引用为何造成内存泄漏,Weak 引用如何打破环?与 C++ shared_ptr 循环引用问题对比?

Rust 中 Rc/Arc 的循环引用为何造成内存泄漏?Weak 引用如何打破环?与 C++ shared_ptr 循环引用问题如何对比?

  • Rc/Arc 引用计数循环
  • Weak 打破循环
  • 与 C++ shared_ptr 的对比

Rc/Arc 使用引用计数,当对象互相引用形成环时,环内每个对象的引用计数永远不为 0(因为环内相互引用),因此永远不会被释放,造成内存泄漏。Rust 用 Weak(弱引用)打破环:Weak 不增加强引用计数,环中一个方向用 Weak 引用,使强引用计数能归零,根对象被释放。这与 C++ 的 shared_ptr 循环引用问题完全相同:shared_ptr 互相引用也会泄漏,需用 weak_ptr 打破。差异在于 Rust 编译器能静态检测借用规则,但循环引用仍需程序员用 Weak 手动处理,二者都依赖"弱引用打破强引用环"这一相同思想。

引用计数 GC 无法处理循环引用,是引用计数的固有缺陷。弱引用(Weak/weak_ptr)不参与"存活判定",从而打破环。Rust 与 C++ 在此问题上的解法一致,只是语言层面的强制程度不同。

#

15. mprotect() + electric fence 在堆溢出检测中的应用?

mprotect() 与 electric fence 如何在堆溢出检测中应用?

  • electric fence 的原理
  • mprotect 设置内存保护
  • 检测堆溢出的方式

Electric Fence(efence)是一种堆溢出检测工具,原理是为每个分配对象分配完整的页,并在对象前后放置用 mprotect(PROT_NONE) 保护的不可访问页面(guard page)。当程序越界访问 guard page 时,会触发 SIGSEGV,立即定位越界行为。其代价是每个对象至少占用整页(多个页),内存开销巨大、速度慢,但检测精确。它适合调试短小的程序,不适合生产。

Electric Fence 用"每对象一页 + mprotect 保护相邻页"来把越界访问变成可捕获的段错误,是"空间换检测精度"的经典思路,与现代 ASan 的红区思想一脉相承。

#

16. 悬空指针与 use-after-free,生命周期管理的工程手段有哪些?

悬空指针与 use-after-free 的生命周期管理工程手段有哪些?

  • 智能指针
  • 释放后置空
  • 所有权/借用

工程手段包括:1)释放后立即置空指针(delete后置 NULL),避免二次使用;2)使用智能指针(C++ unique_ptr/shared_ptr/weak_ptr,Rust 所有权)让生命周期自动管理;3)避免返回局部变量的地址;4)明确对象的生命周期与所有权,避免跨越生命周期使用;5)用 RAII 确保资源释放;6)用静态分析(clang-tidy)与动态检测(ASan、Valgrind)发现残留问题。核心是"让对象的生命周期有明确、可验证的边界"。

悬空指针/UAF 的根治靠"生命周期管理",而非事后修补。智能指针与所有权把"何时释放"固化到类型系统,是系统性解决手段。

#

17. 引用计数与智能指针,资源生命周期如何管理?

引用计数与智能指针如何管理资源生命周期?

  • 引用计数原理
  • 智能指针类型
  • 生命周期管理

引用计数为每个资源记录引用次数,计数归零时自动释放资源。C++ 智能指针:unique_ptr 独占所有权(不可拷贝只可移动),shared_ptr 共享所有权(引用计数),weak_ptr 弱引用不增加计数(配合 shared_ptr 打破循环)。Rust 中 Rc/Arc 对应 shared_ptr(Arc 线程安全),Weak 对应 weak_ptr。这些机制把"何时释放"从手动管理变为自动决策,配合 RAII 使资源在作用域结束时自动释放,减少泄漏与悬空指针。

引用计数智能指针把"生命周期"建模为"引用数",自动在归零时释放;不同智能指针(独占/共享/弱)覆盖不同所有权语义,是 C++/Rust 资源管理的核心工具。

#

18. 内存安全的语言机制,所有权与借用检查如何工作?

内存安全的语言机制(所有权与借用检查)是什么?

  • 所有权规则
  • 借用/生命周期
  • 编译期保证

Rust 通过所有权(ownership)与借用(borrowing)机制在编译期保证内存安全:1)每个值有唯一所有者,作用域结束自动 drop,杜绝泄漏与 double free;2)借用规则:要么多个不可变借用,要么一个可变借用,杜绝数据竞争;3)生命周期标注保证借用不超过被借用对象的存活范围,杜绝悬空引用。这些检查全部在编译期完成,零运行时开销,不需要 GC,从而同时获得内存安全与高性能。这是相比 C/C++ 的最大优势。

所有权/借用是"把内存安全前置到编译期"的突破:编译器证明程序内存安全,而非运行时兜底。这消除了整类内存错误(UAF、悬空、泄漏、数据竞争)而不牺牲性能。