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 有两个阶段的放宽,具体取决于内核如何使用用户地址
内核未访问但用于地址空间管理的用户地址(例如,
mprotect()
,madvise()
)。在此上下文中允许使用有效标记指针,但存在以下例外brk()
,mmap()
和mremap()
的new_address
参数,因为它们有可能与现有用户地址别名。注意:此行为在 v5.6 中发生了更改,因此一些早期的内核可能会错误地接受
brk()
、mmap()
和mremap()
系统调用的有效标记指针。UFFDIO_*
ioctl()``s 的 range.start
、start
和dst
参数用于从userfaultfd()
获得的文件描述符,因为随后通过读取文件描述符获得的故障地址将取消标记,否则可能会使不识别标记的程序感到困惑。注意:此行为在 v5.14 中发生了更改,因此一些早期的内核可能会错误地接受此系统调用的有效标记指针。
内核访问的用户地址(例如
write()
)。此 ABI 放宽默认情况下处于禁用状态,应用程序线程需要通过prctl()
显式启用它,如下所示PR_SET_TAGGED_ADDR_CTRL
:为调用线程启用或禁用 AArch64 标记地址 ABI。(unsigned int) arg2
参数是一个位掩码,描述了使用的控制模式PR_TAGGED_ADDR_ENABLE
:启用 AArch64 标记地址 ABI。默认状态为禁用。
参数
arg3
、arg4
和arg5
必须为 0。PR_GET_TAGGED_ADDR_CTRL
:获取调用线程的 AArch64 标记地址 ABI 的状态。参数
arg2
、arg3
、arg4
和arg5
必须为 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;
}