地址规范形式和指针算术

时间:2016-08-16 14:35:56

标签: x86-64 access-violation pointer-arithmetic aslr

在符合AMD64标准的体系结构中,地址需要在取消引用之前采用规范形式。

来自Intel manual, section 3.3.7.1

  

在64位模式下,如果地址被认为是规范形式   地址位63到最重要的实现位   微体系结构设置为全部或全部为零。

现在,当前操作系统和体系结构中最有意义的实现位是第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;
}

现在考虑intArray,比如说:

<00> 0000 0000 0000 0000 0 111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1100

it设置为intArray并增加it一次,然后考虑sizeof(int) == 4,它将成为:

<00> 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位而异的内存?

1 个答案:

答案 0 :(得分:7)

规范地址规则意味着64位虚拟地址空间中存在巨大的漏洞。 2 ^ 47-1 与其上方的下一个有效地址连续,因此单个mmap将不包含任何不可用的64-范围位地址。

+----------+
| 2^64-1   |   0xffffffffffffffff
| ...      |
| 2^64-2^47|   0xffff800000000000
+----------+
|          |
| unusable |
|          |
+----------+
| 2^47-1   |   0x00007fffffffffff
| ...      |
| 0        |   0x0000000000000000
+----------+

换句话说:

  

操作系统是否保证永远不会分配内存范围不会因第47位而异的内存?

是。当前硬件支持的48位地址空间是实现细节。规范地址规则确保未来的系统可以支持更多的虚拟地址位,而不会破坏任何重要程度的向后兼容性。你只需要一个compat标志,让操作系统不给任何具有高位的内存区域,而不是完全相同。未来的硬件不需要支持任何类型的标志来忽略高地址位,因为高位中的垃圾当前是错误的。

有趣的事实:Linux默认将堆栈映射到较低范围的有效地址的顶部。

e.g。

$ 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
相关问题