AArch64 标记地址 ABI

作者:Vincenzo Frascino <vincenzo.frascino@arm.com>

Catalin Marinas <catalin.marinas@arm.com>

日期:2019 年 8 月 21 日

本文档描述了 AArch64 Linux 上标记地址 ABI 的用法和语义。

1. 简介

在 AArch64 上,TCR_EL1.TBI0 位默认设置为允许用户空间 (EL0) 通过具有非零高字节的 64 位指针执行内存访问。本文档描述了系统调用 ABI 的放宽,允许用户空间将某些标记指针传递给内核系统调用。

2. AArch64 标记地址 ABI

从内核系统调用接口的角度来看,并且为了本文档的目的,“有效的标记指针”是指具有潜在非零高字节的指针,该指针引用用户进程地址空间中的地址,该地址以下列方式之一获得

  • mmap() 系统调用,其中

    • 标志设置了 MAP_ANONYMOUS 位或

    • 文件描述符引用常规文件(包括由 memfd_create() 返回的那些文件)或 /dev/zero

  • brk() 系统调用(即在进程创建时程序中断的初始位置与其当前位置之间的堆区域)。

  • 在进程创建期间内核在进程地址空间中映射的任何内存,并且具有与上述 mmap() 相同的限制(例如,数据、bss、堆栈)。

AArch64 标记地址 ABI 有两个阶段的放宽,具体取决于内核如何使用用户地址

  1. 内核未访问但用于地址空间管理的用户地址(例如,mprotect()madvise())。在这种情况下,允许使用有效的标记指针,但以下情况除外

    • brk()mmap()mremap()new_address 参数,因为这些可能会与现有用户地址别名。

      注意:此行为在 v5.6 中已更改,因此一些较早的内核可能不正确地接受 brk()mmap()mremap() 系统调用的有效标记指针。

    • UFFDIO_* ioctl()``s 用于 ``userfaultfd() 获取的文件描述符的 range.startstartdst 参数,因为随后通过读取文件描述符获得的错误地址将是未标记的,这可能会混淆未感知标记的程序。

      注意:此行为在 v5.14 中已更改,因此一些较早的内核可能不正确地接受此系统调用的有效标记指针。

  2. 内核访问的用户地址(例如,write())。此 ABI 放宽默认情况下处于禁用状态,应用程序线程需要通过 prctl() 显式启用它,如下所示

    • PR_SET_TAGGED_ADDR_CTRL:为调用线程启用或禁用 AArch64 标记地址 ABI。

      (unsigned int) arg2 参数是一个位掩码,描述了使用的控制模式

      • PR_TAGGED_ADDR_ENABLE:启用 AArch64 标记地址 ABI。默认状态为禁用。

      参数 arg3arg4arg5 必须为 0。

    • PR_GET_TAGGED_ADDR_CTRL:获取调用线程的 AArch64 标记地址 ABI 的状态。

      参数 arg2arg3arg4arg5 必须为 0。

    上述 ABI 属性是线程范围的,在 clone() 和 fork() 上继承,并在 exec() 上清除。

    如果 AArch64 标记地址 ABI 通过 sysctl abi.tagged_addr_disabled=1 全局禁用,则调用 prctl(PR_SET_TAGGED_ADDR_CTRL, PR_TAGGED_ADDR_ENABLE, 0, 0, 0) 返回 -EINVAL。默认的 sysctl abi.tagged_addr_disabled 配置为 0。

当为线程启用 AArch64 标记地址 ABI 时,保证以下行为

  • 除了第 3 节中提到的情况外,所有系统调用都可以接受任何有效的标记指针。

  • 对于无效的标记指针,系统调用行为是未定义的:它可能会导致返回错误代码、引发(致命)信号或其他故障模式。

  • 对于有效的标记指针,系统调用行为与相应的未标记指针相同。

有关 AArch64 上标记指针含义的定义,请参见 AArch64 Linux 中的标记虚拟地址

3. AArch64 标记地址 ABI 异常

无论 ABI 放宽如何,以下系统调用参数都必须是未标记的

  • prctl(),而不是直接或间接作为参数传递给内核访问的用户数据的指针。

  • ioctl(),而不是直接或间接作为参数传递给内核访问的用户数据的指针。

  • shmat()shmdt()

  • brk()(自内核 v5.6 起)。

  • mmap()(自内核 v5.6 起)。

  • mremap()new_address 参数(自内核 v5.6 起)。

任何使用非零标记指针的尝试都可能导致返回错误代码、引发(致命)信号或其他故障模式。

4. 正确用法示例

#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/prctl.h>

#define PR_SET_TAGGED_ADDR_CTRL      55
#define PR_TAGGED_ADDR_ENABLE        (1UL << 0)

#define TAG_SHIFT            56

int main(void)
{
     int tbi_enabled = 0;
     unsigned long tag = 0;
     char *ptr;

     /* check/enable the tagged address ABI */
     if (!prctl(PR_SET_TAGGED_ADDR_CTRL, PR_TAGGED_ADDR_ENABLE, 0, 0, 0))
             tbi_enabled = 1;

     /* memory allocation */
     ptr = mmap(NULL, sysconf(_SC_PAGE_SIZE), PROT_READ | PROT_WRITE,
                MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
     if (ptr == MAP_FAILED)
             return 1;

     /* set a non-zero tag if the ABI is available */
     if (tbi_enabled)
             tag = rand() & 0xff;
     ptr = (char *)((unsigned long)ptr | (tag << TAG_SHIFT));

     /* memory access to a tagged address */
     strcpy(ptr, "tagged pointer\n");

     /* syscall with a tagged pointer */
     write(1, ptr, strlen(ptr));

     return 0;
}