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 位指针执行内存访问。本文档介绍了 syscall ABI 的放宽,允许用户空间将某些标记指针传递给内核 syscall。

2. AArch64 标记地址 ABI

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

  • mmap() syscall,其中

    • 标志具有 MAP_ANONYMOUS 位设置或

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

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

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

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

  1. 内核未访问但用于地址空间管理的用户地址(例如,mprotect(), madvise())。在此上下文中允许使用有效标记指针,但存在以下例外

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

      注意:此行为在 v5.6 中发生了更改,因此一些早期的内核可能会错误地接受 brk()mmap()mremap() 系统调用的有效标记指针。

    • UFFDIO_* ioctl()``s range.startstartdst 参数用于从 userfaultfd() 获得的文件描述符,因为随后通过读取文件描述符获得的故障地址将取消标记,否则可能会使不识别标记的程序感到困惑。

      注意:此行为在 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() 上清除。

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

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

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

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

  • 有效标记指针的 syscall 行为与相应的未标记指针相同。

有关 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;
}