AArch64 带标签地址 ABI

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

Catalin Marinas <catalin.marinas@arm.com>

日期:2019年8月21日

本文档描述了 AArch64 Linux 上带标签地址 ABI(Tagged Address ABI)的用法和语义。

1. 简介

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

2. AArch64 带标签地址 ABI

从内核系统调用接口的角度以及出于本文档的目的,“有效的带标签指针”是指高字节可能非零的指针,它引用通过以下方式之一获取的用户进程地址空间中的地址:

  • mmap() 系统调用,其中满足以下条件之一:

    • 标志设置了 MAP_ANONYMOUS 位,或者

    • 文件描述符指向普通文件(包括由 memfd_create() 返回的文件)或 /dev/zero

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

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

根据内核如何使用用户地址,AArch64 带标签地址 ABI 有两个放宽阶段:

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

    • brk()mmap() 以及 mremap()new_address 参数,因为这些可能会与现有的用户地址发生别名冲突。

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

    • The range.start, start and dst arguments to the UFFDIO_* ioctl()``s used on a file descriptor obtained from ``userfaultfd(), as fault addresses subsequently obtained by reading the file descriptor will be untagged, which may otherwise confuse tag-unaware programs.

      注意:此行为在 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) 会返回 -EINVALsysctl 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;
}