为什么我无法在 64 位内核上 mmap(MAP_FIXED)32 位 Linux 进程中的最高虚拟页面?

2024-10-31 08:38:00
admin
原创
178
摘要:问题描述:在尝试在 Linux 上的用户空间中测试是否允许访问跨越 x86 中零边界的内存?时,我编写了一个 32 位测试程序,尝试映射 32 位虚拟地址空间的低页和高页。之后echo 0 | sudo tee /proc/sys/vm/mmap_min_addr,我可以映射零页,但我不知道为什么我不能映射-...

问题描述:

在尝试在 Linux 上的用户空间中测试是否允许访问跨越 x86 中零边界的内存?时,我编写了一个 32 位测试程序,尝试映射 32 位虚拟地址空间的低页和高页。

之后echo 0 | sudo tee /proc/sys/vm/mmap_min_addr,我可以映射零页,但我不知道为什么我不能映射-4096,即(void*)0xfffff000最高页。 为什么mmap2((void*)-4096)返回-ENOMEM

strace ./a.out 
execve("./a.out", ["./a.out"], 0x7ffe08827c10 /* 65 vars */) = 0
strace: [ Process PID=1407 runs in 32 bit mode. ]
....
mmap2(0xfffff000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = -1 ENOMEM (Cannot allocate memory)
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0

另外,在 中什么检查会拒绝它linux/mm/mmap.c,为什么要这样设计?这是为了确保创建指向一个对象后指针不会回绕并破坏指针比较吗?因为 ISO C 和 C++ 允许创建指向一个对象后指针,但不允许在对象外部创建指针。


我在 64 位内核(Arch Linux 上为 4.12.8-2-ARCH)下运行,因此 32 位用户空间有整个 4GiB 可用。(与 64 位内核上的 64 位代码不同,或者与 32 位内核不同,在 32 位内核中,2:2 或 3:1 用户/内核分割会使高页面成为内核地址。)

我还没有尝试过从最小静态可执行文件(没有 CRT 启动或 libc,只有 asm),因为我认为这不会有什么不同。CRT 启动系统调用看起来都没有什么可疑之处。


在断点处停止时,我检查了/proc/PID/maps。顶部页面尚未被使用。堆栈包含第二高的页面,但顶部页面未映射。

00000000-00001000 rw-p 00000000 00:00 0             ### the mmap(0) result
08048000-08049000 r-xp 00000000 00:15 3120510                 /home/peter/src/SO/a.out
08049000-0804a000 r--p 00000000 00:15 3120510                 /home/peter/src/SO/a.out
0804a000-0804b000 rw-p 00001000 00:15 3120510                 /home/peter/src/SO/a.out
f7d81000-f7f3a000 r-xp 00000000 00:15 1511498                 /usr/lib32/libc-2.25.so
f7f3a000-f7f3c000 r--p 001b8000 00:15 1511498                 /usr/lib32/libc-2.25.so
f7f3c000-f7f3d000 rw-p 001ba000 00:15 1511498                 /usr/lib32/libc-2.25.so
f7f3d000-f7f40000 rw-p 00000000 00:00 0 
f7f7c000-f7f7e000 rw-p 00000000 00:00 0 
f7f7e000-f7f81000 r--p 00000000 00:00 0                       [vvar]
f7f81000-f7f83000 r-xp 00000000 00:00 0                       [vdso]
f7f83000-f7fa6000 r-xp 00000000 00:15 1511499                 /usr/lib32/ld-2.25.so
f7fa6000-f7fa7000 r--p 00022000 00:15 1511499                 /usr/lib32/ld-2.25.so
f7fa7000-f7fa8000 rw-p 00023000 00:15 1511499                 /usr/lib32/ld-2.25.so
fffdd000-ffffe000 rw-p 00000000 00:00 0                       [stack]

是否存在未出现的 VMA 区域,maps但仍然说服内核拒绝该地址?我查看了ENOMEM中的出现情况linux/mm/mmapc.,但需要阅读的代码很多,所以也许我错过了什么。是保留了某些高地址范围的东西,还是因为它在堆栈旁边?

以其他顺序进行系统调用没有帮助(但 PAGE_ALIGN 和类似的宏经过仔细编写,以避免在屏蔽之前回绕,因此无论如何都不太可能发生这种情况。)


完整源代码,编译为gcc -O3 -fno-pie -no-pie -m32 address-wrap.c

#include <sys/mman.h>

//void *mmap(void *addr, size_t len, int prot, int flags,
//           int fildes, off_t off);

int main(void) {
    volatile unsigned *high =
        mmap((void*)-4096L, 4096, PROT_READ | PROT_WRITE,
             MAP_FIXED|MAP_PRIVATE|MAP_ANONYMOUS,
             -1, 0);
    volatile unsigned *zeropage =
        mmap((void*)0, 4096, PROT_READ | PROT_WRITE,
             MAP_FIXED|MAP_PRIVATE|MAP_ANONYMOUS,
             -1, 0);


    return (high == MAP_FAILED) ? 2 : *high;
}

(我省略了尝试取消引用的部分(int*)-2,因为当 mmap 失败时它只会导致段错误。)


解决方案 1:

mmap 函数最终会调用do_mmap或do_brk_flags来执行满足内存分配请求的实际工作。这些函数又会调用get_unmapped_area。在该函数中,会进行检查以确保分配的内存不会超出用户地址空间限制,该限制由TASK_SIZE定义。我引用了以下代码:

 * There are a few constraints that determine this:
 *
 * On Intel CPUs, if a SYSCALL instruction is at the highest canonical
 * address, then that syscall will enter the kernel with a
 * non-canonical return address, and SYSRET will explode dangerously.
 * We avoid this particular problem by preventing anything executable
 * from being mapped at the maximum canonical address.
 *
 * On AMD CPUs in the Ryzen family, there's a nasty bug in which the
 * CPUs malfunction if they execute code from the highest canonical page.
 * They'll speculate right off the end of the canonical space, and
 * bad things happen.  This is worked around in the same way as the
 * Intel problem.

#define TASK_SIZE_MAX   ((1UL << __VIRTUAL_MASK_SHIFT) - PAGE_SIZE)

#define IA32_PAGE_OFFSET    ((current->personality & ADDR_LIMIT_3GB) ? \n                    0xc0000000 : 0xFFFFe000)

#define TASK_SIZE       (test_thread_flag(TIF_ADDR32) ? \nIA32_PAGE_OFFSET : TASK_SIZE_MAX)

在具有 48 位虚拟地址空间的处理器上,__VIRTUAL_MASK_SHIFT为 47。

请注意,TASK_SIZE根据当前进程在 32 位上是 32 位、在 64 位上是 32 位还是在 64 位上是 64 位来指定。对于 32 位进程,保留两个页面;一个用于vsyscall 页面,另一个用作保护页面。本质上,vsyscall 页面不能取消映射,因此用户地址空间的最高地址实际上是 0xFFFFe000。对于 64 位进程,保留一个保护页面。这些页面仅在 64 位 Intel 和 AMD 处理器上保留,因为只有在这些处理器上才SYSCALL使用该机制。

以下是执行的检查get_unmapped_area

if (addr > TASK_SIZE - len)
     return -ENOMEM;
相关推荐
  政府信创国产化的10大政策解读一、信创国产化的背景与意义信创国产化,即信息技术应用创新国产化,是当前中国信息技术领域的一个重要发展方向。其核心在于通过自主研发和创新,实现信息技术应用的自主可控,减少对外部技术的依赖,并规避潜在的技术制裁和风险。随着全球信息技术竞争的加剧,以及某些国家对中国在科技领域的打压,信创国产化显...
工程项目管理   1565  
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   1354  
  信创国产芯片作为信息技术创新的核心领域,对于推动国家自主可控生态建设具有至关重要的意义。在全球科技竞争日益激烈的背景下,实现信息技术的自主可控,摆脱对国外技术的依赖,已成为保障国家信息安全和产业可持续发展的关键。国产芯片作为信创产业的基石,其发展水平直接影响着整个信创生态的构建与完善。通过不断提升国产芯片的技术实力、产...
国产信创系统   21  
  信创生态建设旨在实现信息技术领域的自主创新和安全可控,涵盖了从硬件到软件的全产业链。随着数字化转型的加速,信创生态建设的重要性日益凸显,它不仅关乎国家的信息安全,更是推动产业升级和经济高质量发展的关键力量。然而,在推进信创生态建设的过程中,面临着诸多复杂且严峻的挑战,需要深入剖析并寻找切实可行的解决方案。技术创新难题技...
信创操作系统   27  
  信创产业作为国家信息技术创新发展的重要领域,对于保障国家信息安全、推动产业升级具有关键意义。而国产芯片作为信创产业的核心基石,其研发进展备受关注。在信创国产芯片的研发征程中,面临着诸多复杂且艰巨的难点,这些难点犹如一道道关卡,阻碍着国产芯片的快速发展。然而,科研人员和相关企业并未退缩,积极探索并提出了一系列切实可行的解...
国产化替代产品目录   28  
热门文章
项目管理软件有哪些?
云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用