在HITCON CTF 2023的一道QEMU escape题目中,利用到了Huge Pages这项技术,借此复现题目的机会,来学习了解下这部分知识。
简介
当进程使用 RAM 时,CPU 会将其标记为已被该进程使用。为了提高效率,CPU 以块的形式分配 RAM——4K 字节是许多平台上的默认值。这些块被称为页面。页面可以交换到磁盘等。
由于进程地址空间是虚拟的,CPU和操作系统需要记住哪些页属于哪个进程,以及每个页存储在哪里。页面越多,查找内存映射位置所需的时间就越多。因而大多数当前的 CPU 架构都支持larger-than-default pages,这使得 CPU/OS 需要查找的条目更少。不同的操作系统对它有不同命名方式,在Linux上即为Huge Pages.
内存映射
首先回顾下Linux中的内存映射,下图展示了64位Linux系统中,虚拟内存与物理内存的转换方式,也体现了Linux的4级页表结构
偷的图,侵删😭
有图可知,Linux只使用了64位地址的前48位,并将其虚拟地址分为5个部分:
PGD索引
,为虚拟地址的39-47位(9bit),代表其在Page Global Directory
上的索引PUD索引
,为虚拟地址的30-38位(9bit),代表其在Page Upper Directory
上的索引PMD索引
,为虚拟地址的21-29位(9bit),代表其在Page Middle Diectory
上的索引PTE索引
,为虚拟地址的12-20位(9bit),代表其在Page Table
上的索引偏移量
,为虚拟地址的0-11位(12bit),代表其在对应Physical Page
上的偏移
此外,CPU有几个控制寄存器,如下图CR0-CR4,各自有不同的作用。其中CR3
的寄存器主要用于存放Page Global Directory
的物理地址。详细的的讲解可以看这篇文章:控制寄存器 (CR0 , CR1 , CR2 , CR3)
基于上述的知识我们可以分析下,虚拟地址转换物理地址流程。
- 从CR3寄存器中取得
Page Global Directory
的物理地址 - 利用虚拟地址中的
PGD索引
部分,在Page Global Directory
中取出Page Upper Directory
的物理地址 - 利用虚拟地址中的
PUD索引
部分,在Page Upper Directory
中取出Page Middle Diectory
的物理地址 - 利用虚拟地址中的
PMD索引
部分,在Page Middle Diectory
中取出Page Table
的物理地址 - 利用虚拟地址终端
PTE索引
部分,在Page Table
中取出对应物理内存页地址 - 此时虚拟地址所对应的物理地址就可以得到了,计算公式:物理地址 = 虚拟地址低12位offset + 物理内存页地址
Huge Pages原理
在明白了上述内存地址的划分后,Huge Page的原理就比较好理解了,即扩大物理页大小,减少页表层级。在Linux中,主要的HugePage
的大小为2MB(即12bit + 9bit)和1GB(即12bit + 9bit + 9bit),下面以2MB的HugePage
举例
可以很明显的看到由于页大小扩大至2MB,其虚拟地址中,表示偏移的部分扩大到了(12+9)bit。原先的4级页表也变为3级页表,同理,1G大小的页对应2级页表。所以此时 Page Middle Directory
直接指向映射的物理内存页地址。
这样我们也能够理解,为什么HugePage一般为2MB和1GB这样固定的大小。从另一个角度理解,我们也能看到这种地址划分方式与IP地址中A类,B类,C类地址的划分有异曲同工之妙。
如何设置 HugePages
在root
权限下,我们可以使用如下命令,设置HugePages的数量
1 | sysctl vm.nr_hugepages=32 #其中32代表个数 |
我们可以使用如下命令,查看是否设置成功
1 | cat /proc/meminfo | grep -i huge |
效果如图,能够显示出总共的Hugepages的数量,以及处于free状态的HugePages的数量