Ban*_*nex 4 x86-64 pointer-arithmetic access-violation aslr
在符合AMD64标准的体系结构中,地址需要在取消引用之前采用规范形式.
在64位模式中,如果微架构的地址位63到最重要的实现位被设置为全1或全零,则认为地址是规范形式.
现在,当前操作系统和体系结构中最有意义的实现位是第47位.这给我们留下了48位的地址空间.
特别是当启用ASLR时,用户程序可能会收到第47位设置的地址.
如果使用指针标记等优化并且高位用于存储信息,则程序必须确保将第48位至第63位设置回取消引用地址之前的第47位.
但请考虑以下代码:
int main()
{
int* intArray = new int[100];
int* it = intArray;
// Fill the array with any value.
for (int i = 0; i < 100; i++)
{
*it = 20;
it++;
}
delete [] intArray;
return 0;
}
Run Code Online (Sandbox Code Playgroud)
现在考虑的intArray
是,说:
0000 0000 0000 0000 0 111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1100
设置it
到intArray
并增加it
一次,并考虑sizeof(int) == 4
,它将成为:
0000 0000 0000 0000 1 000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000
第47位是粗体.这里发生的是由指针算术检索的第二个指针是无效的,因为不是规范形式.正确的地址应该是:
1111 1111 1111 1111 1 000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000
程序如何处理这个问题?操作系统是否保证永远不会分配内存范围不会因第47位而异的内存?
规范地址规则意味着64位虚拟地址空间中存在巨大的漏洞.2 ^ 47-1 与其上方的下一个有效地址不连续,因此单个将不包括任何不可用的64位地址范围.mmap
+----------+
| 2^64-1 | 0xffffffffffffffff
| ... |
| 2^64-2^47| 0xffff800000000000
+----------+
| |
| unusable |
| |
+----------+
| 2^47-1 | 0x00007fffffffffff
| ... |
| 0 | 0x0000000000000000
+----------+
Run Code Online (Sandbox Code Playgroud)
换一种说法:
操作系统是否保证永远不会分配内存范围不会因第47位而异的内存?
是.当前硬件支持的48位地址空间是实现细节.规范地址规则确保未来的系统可以支持更多的虚拟地址位,而不会破坏任何重要程度的向后兼容性.你只需要一个compat标志,让操作系统不给任何具有高位的内存区域,而不是完全相同.未来的硬件不需要支持任何类型的标志来忽略高地址位,因为高位中的垃圾当前是错误的.
有趣的事实:Linux默认将堆栈映射到较低范围的有效地址的顶部.
例如
$ gdb /bin/ls
...
(gdb) b _start
Function "_start" not defined.
Make breakpoint pending on future shared library load? (y or [n]) y
Breakpoint 1 (_start) pending.
(gdb) r
Starting program: /bin/ls
Breakpoint 1, 0x00007ffff7dd9cd0 in _start () from /lib64/ld-linux-x86-64.so.2
(gdb) p $rsp
$1 = (void *) 0x7fffffffd850
(gdb) exit
$ calc
2^47-1
0x7fffffffffff
Run Code Online (Sandbox Code Playgroud)