2024年起,DARKNAVY公众号开启新的专栏「技术研报」,主要面向攻防研究领域的技术读者,不定期发布深蓝技术实践中的一些思索。
以下为深蓝技术研报的第一篇。
前言
已了解MTE原理的读者可跳过此章节。
MTE利用ARMv8的TBI (Top-Byte Ignore) 特性,使用指针的高4 bits存储tag,在每个进程中有一段专用的内存用于存储tag。当为内存指定了某个tag后,程序必须带上正确的tag访问内存,若tag错误,程序抛出错误信号SIGSEGV,如下图所示:
指令集提供了系列指令来操作tag,此处举例说明MTE的基本用法:
; x0 is a pointer
irg x1, x0
stg x1, [x1]
ldr x0, [x1]
if (PA_LIKELY(use_tagging)) {
// Ensure the MTE-tag of the memory pointed by other provisioned slot is
// unguessable. They will be returned to the app as is, and the MTE-tag
// will only change upon calling Free().
next_slot_ptr =
TagMemoryRangeRandomly(next_slot, TagSizeForSlot(root, slot_size));
void* retagged_slot_start = internal::TagMemoryRangeIncrement(
ObjectToTaggedSlotStart(object), tag_size);
// Incrementing the MTE-tag in the memory range invalidates the |object|'s
// tag, so it must be retagged.
object = TaggedSlotStartToObject(retagged_slot_start);
victim = tcache_get (tc_idx);
return tag_new_usable (victim);
// ...
victim = _int_malloc (ar_ptr, bytes);
// ...
victim = tag_new_usable (victim);
/* Mark the chunk as belonging to the library again. */
(void)tag_region (chunk2mem (p), memsize (p));
ar_ptr = arena_for_chunk (p);
_int_free (ar_ptr, p, 0);
对于这样的分配策略,大有一种“一力降十会”的感觉。在性能和安全的权衡之间Glibc选择了安全:无论是任何的分配大小、任何分配的来源 (tcache、fastbin、smallbin...),都会被重新打上随机的tag。
if (Header->ClassId) {
if (!TSDRegistry.getDisableMemInit()) {
uptr TaggedBegin, TaggedEnd;
const uptr OddEvenMask = computeOddEvenMaskForPointerMaybe(
Options, reinterpret_cast<uptr>(getBlockBegin(Ptr, Header)),
Header->ClassId);
// Exclude the previous tag so that immediate use after free is
// detected 100% of the time.
setRandomTag(Ptr, Size, OddEvenMask | (1UL << PrevTag), &TaggedBegin,
&TaggedEnd);
}
}
computeOddEvenMaskForPointerMaybe
被用于计算奇偶标签掩码:uptr computeOddEvenMaskForPointerMaybe(const Options &Options, uptr Ptr,
uptr ClassId) {
if (!Options.get(OptionBit::UseOddEvenTags))
return 0;
// If a chunk's tag is odd, we want the tags of the surrounding blocks to be
// even, and vice versa. Blocks are laid out Size bytes apart, and adding
// Size to Ptr will flip the least significant set bit of Size in Ptr, so
// that bit will have the pattern 010101... for consecutive blocks, which we
// can use to determine which tag mask to use.
return 0x5555U << ((Ptr >> SizeClassMap::getSizeLSBByClassId(ClassId)) & 1);
}
这种配置涉及到UAF检测和缓冲区溢出检测之间的权衡。启用UseOddEvenTags时,相邻堆块的tag奇偶性不同,这断绝了随机分配的tag恰好相同的可能性,从而提高了检测缓冲区溢出的可能性。然而,另一方面,这种情况下每次随机分配的tag的奇偶性是固定的,这导致其标记空间减半,使得UAF更加难以被检测出来。
声明:此表格仅对比了各个堆分配器中MTE的实现,并不能代表堆分配器整体的安全性。
带tag的最大堆块大小
堆块的分配和释放作为一个整体在此项评估。PartitionAlloc在重用缓存中的堆块时,并不会重新生成新的tag,而是继续沿用旧的tag,而释放时仅将tag加一;而另外两个堆分配器都完成了对tag的重新生成。
/* Quickly check that the freed pointer matches the tag for the memory.
This gives a useful double-free detection. */
if (__glibc_unlikely (mtag_enabled))
*(volatile char *)mem;
攻防演进至此,攻击者从多年前一个栈溢出即可攻破系统,至如今需要环环相扣的漏洞来突破系统防御的层层壁垒,攻守形势逆转。但我们也看到,即使像MTE这样先进的技术也存在盲点,内存安全仍道阻且长,我们期待未来更为精彩的发展。
[1] https://developer.arm.com/-/media/Arm%20Developer%20Community/PDF/Arm_Memory_Tagging_Extension_Whitepaper.pdf
[2] https://blog.google/products/pixel/google-pixel-8-pro/
[3] https://googleprojectzero.blogspot.com/2023/11/first-handset-with-mte-on-market.html
[4] https://googleprojectzero.blogspot.com/2023/08/mte-as-implemented-part-1.html
[5] https://github.com/microsoft/MSRC-Security-Research/blob/master/presentations/2019_09_CppCon/CppCon2019%20-%20Killing%20Uninitialized%20Memory.pdf
[6] https://googleprojectzero.blogspot.com/2019/04/virtually-unlimited-memory-escaping.html
[7] https://securitylab.github.com/research/one_day_short_of_a_fullchain_renderer/
[8] https://bugs.chromium.org/p/chromium/issues/detail?id=1512538
[9] https://chromium.googlesource.com/chromium/src/+/main/base/memory/raw_ptr.md
点击“阅读原文”
直达 DARKNAVY 技术博客