요약·해설과 원문, 전문 번역을 서로 분리했습니다. API 이름, symbol, source path는 원문 표기를 사용합니다.
1. 요약·해설
원문의 핵심 논리와 kernel programming 관점의 보충 설명입니다. 아래의 전문 번역과는 별도로 작성했습니다.
2. 영어 원문 전체
번역 기준이 된 Linux v6.18.37 원문입니다. 줄 번호는 이 버전의 파일 좌표입니다.
원문 전체 펼치기
===============================================
Memory Tagging Extension (MTE) in AArch64 Linux
===============================================
Authors: Vincenzo Frascino <vincenzo.frascino@arm.com>
Catalin Marinas <catalin.marinas@arm.com>
Date: 2020-02-25
This document describes the provision of the Memory Tagging Extension
functionality in AArch64 Linux.
Introduction
============
ARMv8.5 based processors introduce the Memory Tagging Extension (MTE)
feature. MTE is built on top of the ARMv8.0 virtual address tagging TBI
(Top Byte Ignore) feature and allows software to access a 4-bit
allocation tag for each 16-byte granule in the physical address space.
Such memory range must be mapped with the Normal-Tagged memory
attribute. A logical tag is derived from bits 59-56 of the virtual
address used for the memory access. A CPU with MTE enabled will compare
the logical tag against the allocation tag and potentially raise an
exception on mismatch, subject to system registers configuration.
Userspace Support
=================
When ``CONFIG_ARM64_MTE`` is selected and Memory Tagging Extension is
supported by the hardware, the kernel advertises the feature to
userspace via ``HWCAP2_MTE``.
PROT_MTE
--------
To access the allocation tags, a user process must enable the Tagged
memory attribute on an address range using a new ``prot`` flag for
``mmap()`` and ``mprotect()``:
``PROT_MTE`` - Pages allow access to the MTE allocation tags.
The allocation tag is set to 0 when such pages are first mapped in the
user address space and preserved on copy-on-write. ``MAP_SHARED`` is
supported and the allocation tags can be shared between processes.
**Note**: ``PROT_MTE`` is only supported on ``MAP_ANONYMOUS`` and
RAM-based file mappings (``tmpfs``, ``memfd``). Passing it to other
types of mapping will result in ``-EINVAL`` returned by these system
calls.
**Note**: The ``PROT_MTE`` flag (and corresponding memory type) cannot
be cleared by ``mprotect()``.
**Note**: ``madvise()`` memory ranges with ``MADV_DONTNEED`` and
``MADV_FREE`` may have the allocation tags cleared (set to 0) at any
point after the system call.
Tag Check Faults
----------------
When ``PROT_MTE`` is enabled on an address range and a mismatch between
the logical and allocation tags occurs on access, there are three
configurable behaviours:
- *Ignore* - This is the default mode. The CPU (and kernel) ignores the
tag check fault.
- *Synchronous* - The kernel raises a ``SIGSEGV`` synchronously, with
``.si_code = SEGV_MTESERR`` and ``.si_addr = <fault-address>``. The
memory access is not performed. If ``SIGSEGV`` is ignored or blocked
by the offending thread, the containing process is terminated with a
``coredump``.
- *Asynchronous* - The kernel raises a ``SIGSEGV``, in the offending
thread, asynchronously following one or multiple tag check faults,
with ``.si_code = SEGV_MTEAERR`` and ``.si_addr = 0`` (the faulting
address is unknown).
- *Asymmetric* - Reads are handled as for synchronous mode while writes
are handled as for asynchronous mode.
The user can select the above modes, per thread, using the
``prctl(PR_SET_TAGGED_ADDR_CTRL, flags, 0, 0, 0)`` system call where ``flags``
contains any number of the following values in the ``PR_MTE_TCF_MASK``
bit-field:
- ``PR_MTE_TCF_NONE`` - *Ignore* tag check faults
(ignored if combined with other options)
- ``PR_MTE_TCF_SYNC`` - *Synchronous* tag check fault mode
- ``PR_MTE_TCF_ASYNC`` - *Asynchronous* tag check fault mode
If no modes are specified, tag check faults are ignored. If a single
mode is specified, the program will run in that mode. If multiple
modes are specified, the mode is selected as described in the "Per-CPU
preferred tag checking modes" section below.
The current tag check fault configuration can be read using the
``prctl(PR_GET_TAGGED_ADDR_CTRL, 0, 0, 0, 0)`` system call. If
multiple modes were requested then all will be reported.
Tag checking can also be disabled for a user thread by setting the
``PSTATE.TCO`` bit with ``MSR TCO, #1``.
**Note**: Signal handlers are always invoked with ``PSTATE.TCO = 0``,
irrespective of the interrupted context. ``PSTATE.TCO`` is restored on
``sigreturn()``.
**Note**: There are no *match-all* logical tags available for user
applications.
**Note**: Kernel accesses to the user address space (e.g. ``read()``
system call) are not checked if the user thread tag checking mode is
``PR_MTE_TCF_NONE`` or ``PR_MTE_TCF_ASYNC``. If the tag checking mode is
``PR_MTE_TCF_SYNC``, the kernel makes a best effort to check its user
address accesses, however it cannot always guarantee it. Kernel accesses
to user addresses are always performed with an effective ``PSTATE.TCO``
value of zero, regardless of the user configuration.
Excluding Tags in the ``IRG``, ``ADDG`` and ``SUBG`` instructions
-----------------------------------------------------------------
The architecture allows excluding certain tags to be randomly generated
via the ``GCR_EL1.Exclude`` register bit-field. By default, Linux
excludes all tags other than 0. A user thread can enable specific tags
in the randomly generated set using the ``prctl(PR_SET_TAGGED_ADDR_CTRL,
flags, 0, 0, 0)`` system call where ``flags`` contains the tags bitmap
in the ``PR_MTE_TAG_MASK`` bit-field.
**Note**: The hardware uses an exclude mask but the ``prctl()``
interface provides an include mask. An include mask of ``0`` (exclusion
mask ``0xffff``) results in the CPU always generating tag ``0``.
Per-CPU preferred tag checking mode
-----------------------------------
On some CPUs the performance of MTE in stricter tag checking modes
is similar to that of less strict tag checking modes. This makes it
worthwhile to enable stricter checks on those CPUs when a less strict
checking mode is requested, in order to gain the error detection
benefits of the stricter checks without the performance downsides. To
support this scenario, a privileged user may configure a stricter
tag checking mode as the CPU's preferred tag checking mode.
The preferred tag checking mode for each CPU is controlled by
``/sys/devices/system/cpu/cpu<N>/mte_tcf_preferred``, to which a
privileged user may write the value ``async``, ``sync`` or ``asymm``. The
default preferred mode for each CPU is ``async``.
To allow a program to potentially run in the CPU's preferred tag
checking mode, the user program may set multiple tag check fault mode
bits in the ``flags`` argument to the ``prctl(PR_SET_TAGGED_ADDR_CTRL,
flags, 0, 0, 0)`` system call. If both synchronous and asynchronous
modes are requested then asymmetric mode may also be selected by the
kernel. If the CPU's preferred tag checking mode is in the task's set
of provided tag checking modes, that mode will be selected. Otherwise,
one of the modes in the task's mode will be selected by the kernel
from the task's mode set using the preference order:
1. Asynchronous
2. Asymmetric
3. Synchronous
Note that there is no way for userspace to request multiple modes and
also disable asymmetric mode.
Initial process state
---------------------
On ``execve()``, the new process has the following configuration:
- ``PR_TAGGED_ADDR_ENABLE`` set to 0 (disabled)
- No tag checking modes are selected (tag check faults ignored)
- ``PR_MTE_TAG_MASK`` set to 0 (all tags excluded)
- ``PSTATE.TCO`` set to 0
- ``PROT_MTE`` not set on any of the initial memory maps
On ``fork()``, the new process inherits the parent's configuration and
memory map attributes with the exception of the ``madvise()`` ranges
with ``MADV_WIPEONFORK`` which will have the data and tags cleared (set
to 0).
The ``ptrace()`` interface
--------------------------
``PTRACE_PEEKMTETAGS`` and ``PTRACE_POKEMTETAGS`` allow a tracer to read
the tags from or set the tags to a tracee's address space. The
``ptrace()`` system call is invoked as ``ptrace(request, pid, addr,
data)`` where:
- ``request`` - one of ``PTRACE_PEEKMTETAGS`` or ``PTRACE_POKEMTETAGS``.
- ``pid`` - the tracee's PID.
- ``addr`` - address in the tracee's address space.
- ``data`` - pointer to a ``struct iovec`` where ``iov_base`` points to
a buffer of ``iov_len`` length in the tracer's address space.
The tags in the tracer's ``iov_base`` buffer are represented as one
4-bit tag per byte and correspond to a 16-byte MTE tag granule in the
tracee's address space.
**Note**: If ``addr`` is not aligned to a 16-byte granule, the kernel
will use the corresponding aligned address.
``ptrace()`` return value:
- 0 - tags were copied, the tracer's ``iov_len`` was updated to the
number of tags transferred. This may be smaller than the requested
``iov_len`` if the requested address range in the tracee's or the
tracer's space cannot be accessed or does not have valid tags.
- ``-EPERM`` - the specified process cannot be traced.
- ``-EIO`` - the tracee's address range cannot be accessed (e.g. invalid
address) and no tags copied. ``iov_len`` not updated.
- ``-EFAULT`` - fault on accessing the tracer's memory (``struct iovec``
or ``iov_base`` buffer) and no tags copied. ``iov_len`` not updated.
- ``-EOPNOTSUPP`` - the tracee's address does not have valid tags (never
mapped with the ``PROT_MTE`` flag). ``iov_len`` not updated.
**Note**: There are no transient errors for the requests above, so user
programs should not retry in case of a non-zero system call return.
``PTRACE_GETREGSET`` and ``PTRACE_SETREGSET`` with ``addr ==
``NT_ARM_TAGGED_ADDR_CTRL`` allow ``ptrace()`` access to the tagged
address ABI control and MTE configuration of a process as per the
``prctl()`` options described in
Documentation/arch/arm64/tagged-address-abi.rst and above. The corresponding
``regset`` is 1 element of 8 bytes (``sizeof(long))``).
Core dump support
-----------------
The allocation tags for user memory mapped with ``PROT_MTE`` are dumped
in the core file as additional ``PT_AARCH64_MEMTAG_MTE`` segments. The
program header for such segment is defined as:
:``p_type``: ``PT_AARCH64_MEMTAG_MTE``
:``p_flags``: 0
:``p_offset``: segment file offset
:``p_vaddr``: segment virtual address, same as the corresponding
``PT_LOAD`` segment
:``p_paddr``: 0
:``p_filesz``: segment size in file, calculated as ``p_mem_sz / 32``
(two 4-bit tags cover 32 bytes of memory)
:``p_memsz``: segment size in memory, same as the corresponding
``PT_LOAD`` segment
:``p_align``: 0
The tags are stored in the core file at ``p_offset`` as two 4-bit tags
in a byte. With the tag granule of 16 bytes, a 4K page requires 128
bytes in the core file.
Example of correct usage
========================
*MTE Example code*
.. code-block:: c
/*
* To be compiled with -march=armv8.5-a+memtag
*/
#include <errno.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/auxv.h>
#include <sys/mman.h>
#include <sys/prctl.h>
/*
* From arch/arm64/include/uapi/asm/hwcap.h
*/
#define HWCAP2_MTE (1 << 18)
/*
* From arch/arm64/include/uapi/asm/mman.h
*/
#define PROT_MTE 0x20
/*
* From include/uapi/linux/prctl.h
*/
#define PR_SET_TAGGED_ADDR_CTRL 55
#define PR_GET_TAGGED_ADDR_CTRL 56
# define PR_TAGGED_ADDR_ENABLE (1UL << 0)
# define PR_MTE_TCF_SHIFT 1
# define PR_MTE_TCF_NONE (0UL << PR_MTE_TCF_SHIFT)
# define PR_MTE_TCF_SYNC (1UL << PR_MTE_TCF_SHIFT)
# define PR_MTE_TCF_ASYNC (2UL << PR_MTE_TCF_SHIFT)
# define PR_MTE_TCF_MASK (3UL << PR_MTE_TCF_SHIFT)
# define PR_MTE_TAG_SHIFT 3
# define PR_MTE_TAG_MASK (0xffffUL << PR_MTE_TAG_SHIFT)
/*
* Insert a random logical tag into the given pointer.
*/
#define insert_random_tag(ptr) ({ \
uint64_t __val; \
asm("irg %0, %1" : "=r" (__val) : "r" (ptr)); \
__val; \
})
/*
* Set the allocation tag on the destination address.
*/
#define set_tag(tagged_addr) do { \
asm volatile("stg %0, [%0]" : : "r" (tagged_addr) : "memory"); \
} while (0)
int main()
{
unsigned char *a;
unsigned long page_sz = sysconf(_SC_PAGESIZE);
unsigned long hwcap2 = getauxval(AT_HWCAP2);
/* check if MTE is present */
if (!(hwcap2 & HWCAP2_MTE))
return EXIT_FAILURE;
/*
* Enable the tagged address ABI, synchronous or asynchronous MTE
* tag check faults (based on per-CPU preference) and allow all
* non-zero tags in the randomly generated set.
*/
if (prctl(PR_SET_TAGGED_ADDR_CTRL,
PR_TAGGED_ADDR_ENABLE | PR_MTE_TCF_SYNC | PR_MTE_TCF_ASYNC |
(0xfffe << PR_MTE_TAG_SHIFT),
0, 0, 0)) {
perror("prctl() failed");
return EXIT_FAILURE;
}
a = mmap(0, page_sz, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (a == MAP_FAILED) {
perror("mmap() failed");
return EXIT_FAILURE;
}
/*
* Enable MTE on the above anonymous mmap. The flag could be passed
* directly to mmap() and skip this step.
*/
if (mprotect(a, page_sz, PROT_READ | PROT_WRITE | PROT_MTE)) {
perror("mprotect() failed");
return EXIT_FAILURE;
}
/* access with the default tag (0) */
a[0] = 1;
a[1] = 2;
printf("a[0] = %hhu a[1] = %hhu\n", a[0], a[1]);
/* set the logical and allocation tags */
a = (unsigned char *)insert_random_tag(a);
set_tag(a);
printf("%p\n", a);
/* non-zero tag access */
a[0] = 3;
printf("a[0] = %hhu a[1] = %hhu\n", a[0], a[1]);
/*
* If MTE is enabled correctly the next instruction will generate an
* exception.
*/
printf("Expecting SIGSEGV...\n");
a[16] = 0xdd;
/* this should not be printed in the PR_MTE_TCF_SYNC mode */
printf("...haven't got one\n");
return EXIT_FAILURE;
}
3. 한국어 전문 번역
영어 원문의 문단 순서와 의미를 유지한 전체 번역입니다. 코드, 함수명, symbol과 URL은 원문 표기를 유지합니다.
MTE 구조와 tag 비교
1-25문서 제목은 `Memory Tagging Extension (MTE) in AArch64 Linux`입니다. 작성자는 Vincenzo Frascino와 Catalin Marinas이며 날짜는 `2020-02-25`입니다.
ARMv8.5 processor의 MTE는 ARMv8.0 virtual-address tagging인 TBI(Top Byte Ignore) 위에 구축됩니다. Physical address space의 16-byte granule마다 4-bit allocation tag에 software가 접근할 수 있게 합니다.
해당 memory range는 `Normal-Tagged` memory attribute로 map해야 합니다. Memory access에 사용한 virtual address의 bit `59-56`에서 logical tag를 얻고, MTE가 활성화된 CPU는 이를 allocation tag와 비교합니다. 불일치하면 system-register 설정에 따라 exception을 일으킬 수 있습니다.
Userspace 지원과 PROT_MTE
26-57`CONFIG_ARM64_MTE`를 선택하고 hardware가 MTE를 지원하면 kernel은 `HWCAP2_MTE`로 기능을 userspace에 광고합니다.
User process가 allocation tag에 접근하려면 `mmap()` 또는 `mprotect()`의 새 `prot` flag인 `PROT_MTE`로 address range의 Tagged memory attribute를 켜야 합니다. `PROT_MTE` page는 MTE allocation tag 접근을 허용합니다.
- 처음 user address space에 map할 때 allocation tag는 `0`이며 copy-on-write에서도 보존됩니다.
- `MAP_SHARED`를 지원하고 process 사이에 allocation tag를 공유할 수 있습니다.
- `PROT_MTE`는 `MAP_ANONYMOUS`와 RAM 기반 file mapping(`tmpfs`, `memfd`)에서만 지원합니다. 다른 mapping type은 `-EINVAL`을 반환합니다.
- `PROT_MTE` flag와 대응 memory type은 `mprotect()`로 제거할 수 없습니다.
- `MADV_DONTNEED` 또는 `MADV_FREE`를 적용한 `madvise()` range는 system call 이후 언제든 allocation tag가 `0`으로 clear될 수 있습니다.
Tag check fault mode
58-118`PROT_MTE` range에 접근할 때 logical tag와 allocation tag가 다르면 다음 동작을 thread별로 선택할 수 있습니다.
| Mode | 동작 |
|---|---|
| Ignore | 기본 mode이며 CPU와 kernel이 tag-check fault를 무시합니다. |
| Synchronous | Access를 수행하지 않고 즉시 `SIGSEGV`, `SEGV_MTESERR`, 실제 fault address를 보고합니다. Signal이 ignore 또는 block되면 process를 coredump와 함께 종료합니다. |
| Asynchronous | 하나 이상의 fault 뒤 offending thread에 비동기 `SIGSEGV`, `SEGV_MTEAERR`, `.si_addr = 0`을 보고합니다. |
| Asymmetric | Read는 synchronous, write는 asynchronous 방식으로 처리합니다. |
`prctl(PR_SET_TAGGED_ADDR_CTRL, flags, 0, 0, 0)`의 `PR_MTE_TCF_MASK` bit-field로 mode를 선택합니다.
| Flag | 의미 |
|---|---|
| `PR_MTE_TCF_NONE` | Fault 무시. 다른 option과 함께 있으면 무시됩니다. |
| `PR_MTE_TCF_SYNC` | Synchronous mode |
| `PR_MTE_TCF_ASYNC` | Asynchronous mode |
Mode를 지정하지 않으면 fault를 무시하고, 하나를 지정하면 그 mode로 실행합니다. 여러 mode를 지정하면 per-CPU preferred mode 절의 규칙으로 선택합니다. 현재 설정은 `prctl(PR_GET_TAGGED_ADDR_CTRL, 0, 0, 0, 0)`으로 읽으며 여러 mode를 요청했다면 모두 보고됩니다.
User thread는 `MSR TCO, #1`로 `PSTATE.TCO`를 설정해 tag checking을 끌 수도 있습니다. Signal handler는 중단 context와 무관하게 항상 `PSTATE.TCO = 0`으로 시작하고 `sigreturn()`에서 복원됩니다. User application에 사용할 수 있는 match-all logical tag는 없습니다.
`read()` 같은 kernel의 user-address 접근은 user mode가 `PR_MTE_TCF_NONE` 또는 `PR_MTE_TCF_ASYNC`이면 검사하지 않습니다. `PR_MTE_TCF_SYNC`이면 best effort로 검사하지만 항상 보장할 수는 없습니다. Kernel의 user-address 접근은 user 설정과 관계없이 effective `PSTATE.TCO = 0`으로 수행됩니다.
IRG·ADDG·SUBG tag 제외
119-132Architecture는 `GCR_EL1.Exclude` bit-field로 `IRG`, `ADDG`, `SUBG`가 무작위 생성할 특정 tag를 제외할 수 있게 합니다. Linux 기본값은 `0` 이외의 모든 tag를 제외합니다.
User thread는 `prctl(PR_SET_TAGGED_ADDR_CTRL, flags, 0, 0, 0)`의 `PR_MTE_TAG_MASK` bit-field에 include할 tag bitmap을 넣습니다. Hardware는 exclude mask를 쓰지만 `prctl()`은 include mask를 받습니다. Include mask `0`, 즉 exclusion mask `0xffff`는 CPU가 항상 tag `0`을 생성하게 합니다.
Per-CPU preferred tag-check mode
133-165일부 CPU에서는 엄격한 MTE mode와 덜 엄격한 mode의 성능이 비슷합니다. 이 경우 privileged user는 성능 손실 없이 더 나은 오류 검출을 얻도록 CPU의 preferred mode를 더 엄격하게 설정할 수 있습니다.
CPU별 설정은 `/sys/devices/system/cpu/cpu<N>/mte_tcf_preferred`에서 제어하며 privileged user가 `async`, `sync`, `asymm`을 쓸 수 있습니다. 기본 preferred mode는 `async`입니다.
Program은 `PR_SET_TAGGED_ADDR_CTRL`의 flags에 여러 fault-mode bit를 설정해 CPU preferred mode를 허용할 수 있습니다. Sync와 async를 모두 요청하면 kernel이 asymmetric도 선택할 수 있습니다. CPU preferred mode가 task의 허용 집합에 있으면 그것을 선택하고, 아니면 다음 순서로 task 집합에서 선택합니다.
- 1. Asynchronous
- 2. Asymmetric
- 3. Synchronous
Userspace가 여러 mode를 요청하면서 asymmetric mode만 별도로 금지하는 방법은 없습니다.
execve·fork 초기 상태
166-181`execve()` 후 새 process는 다음 상태로 시작합니다.
- `PR_TAGGED_ADDR_ENABLE = 0`, 즉 disabled
- 선택한 tag-check mode가 없어 fault를 무시함
- `PR_MTE_TAG_MASK = 0`, 즉 모든 tag 제외
- `PSTATE.TCO = 0`
- 초기 memory map 어디에도 `PROT_MTE`가 설정되지 않음
`fork()`의 새 process는 parent의 설정과 memory-map attribute를 상속합니다. 단 `MADV_WIPEONFORK`를 적용한 `madvise()` range는 data와 tag를 모두 `0`으로 clear합니다.
ptrace tag·regset interface
182-226`PTRACE_PEEKMTETAGS`와 `PTRACE_POKEMTETAGS`는 tracer가 tracee address space의 tag를 읽거나 설정하게 합니다. 호출 형식은 `ptrace(request, pid, addr, data)`입니다.
- `request`: `PTRACE_PEEKMTETAGS` 또는 `PTRACE_POKEMTETAGS`
- `pid`: tracee PID
- `addr`: tracee address space의 address. 16-byte granule에 aligned되지 않으면 kernel이 대응 aligned address를 사용합니다.
- `data`: tracer address space의 `iov_len` 길이 buffer를 `iov_base`가 가리키는 `struct iovec` pointer
- `iov_base`의 tag는 byte당 4-bit tag 하나로 표현되며 tracee의 16-byte MTE tag granule 하나에 대응합니다.
| Return | 의미 |
|---|---|
| `0` | Tag를 복사하고 전송 수로 `iov_len`을 갱신합니다. 접근 불가 또는 유효 tag 부재로 요청보다 작을 수 있습니다. |
| `-EPERM` | 지정 process를 trace할 수 없습니다. |
| `-EIO` | Tracee address range에 접근할 수 없고 tag를 복사하지 않았습니다. `iov_len`은 바뀌지 않습니다. |
| `-EFAULT` | Tracer의 `struct iovec` 또는 `iov_base` 접근에서 fault가 발생했습니다. Tag와 `iov_len`은 바뀌지 않습니다. |
| `-EOPNOTSUPP` | Tracee address가 `PROT_MTE`로 map된 적이 없어 유효 tag가 없습니다. `iov_len`은 바뀌지 않습니다. |
이 request에는 transient error가 없으므로 system call이 0이 아닌 값을 반환했을 때 userspace가 retry해서는 안 됩니다.
`addr == NT_ARM_TAGGED_ADDR_CTRL`인 `PTRACE_GETREGSET`과 `PTRACE_SETREGSET`은 `Documentation/arch/arm64/tagged-address-abi.rst`와 이 문서의 `prctl()` option에 따른 tagged-address ABI control 및 MTE 설정에 접근합니다. 대응 regset은 8 byte인 `sizeof(long)` element 하나입니다.
Core dump의 MTE tag segment
227-249`PROT_MTE`로 map된 user memory의 allocation tag는 core file에 별도 `PT_AARCH64_MEMTAG_MTE` segment로 dump됩니다.
| Program-header field | 값 |
|---|---|
| `p_type` | `PT_AARCH64_MEMTAG_MTE` |
| `p_flags` | `0` |
| `p_offset` | Segment file offset |
| `p_vaddr` | 대응 `PT_LOAD` segment와 같은 virtual address |
| `p_paddr` | `0` |
| `p_filesz` | `p_mem_sz / 32`. 4-bit tag 두 개가 memory 32 byte를 담당합니다. |
| `p_memsz` | 대응 `PT_LOAD` segment와 같은 memory size |
| `p_align` | `0` |
Tag는 `p_offset`부터 byte당 4-bit tag 두 개로 저장됩니다. Tag granule이 16 byte이므로 4K page 하나는 core file에서 128 byte가 필요합니다.
올바른 MTE 사용 예제
250-375예제는 `-march=armv8.5-a+memtag`로 compile합니다. `AT_HWCAP2`의 `HWCAP2_MTE`를 확인하고 tagged-address ABI, sync·async fault mode, non-zero random tag를 `prctl()`로 허용합니다.
그 뒤 anonymous page를 map하고 `mprotect()`로 `PROT_MTE`를 켭니다. `IRG`로 random logical tag를 pointer에 넣고 `STG`로 allocation tag를 설정한 다음, 다음 granule을 잘못된 tag로 접근해 synchronous mode에서 `SIGSEGV`가 발생하는지 확인합니다.
/*
* To be compiled with -march=armv8.5-a+memtag
*/
#include <errno.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/auxv.h>
#include <sys/mman.h>
#include <sys/prctl.h>
/*
* From arch/arm64/include/uapi/asm/hwcap.h
*/
#define HWCAP2_MTE (1 << 18)
/*
* From arch/arm64/include/uapi/asm/mman.h
*/
#define PROT_MTE 0x20
/*
* From include/uapi/linux/prctl.h
*/
#define PR_SET_TAGGED_ADDR_CTRL 55
#define PR_GET_TAGGED_ADDR_CTRL 56
# define PR_TAGGED_ADDR_ENABLE (1UL << 0)
# define PR_MTE_TCF_SHIFT 1
# define PR_MTE_TCF_NONE (0UL << PR_MTE_TCF_SHIFT)
# define PR_MTE_TCF_SYNC (1UL << PR_MTE_TCF_SHIFT)
# define PR_MTE_TCF_ASYNC (2UL << PR_MTE_TCF_SHIFT)
# define PR_MTE_TCF_MASK (3UL << PR_MTE_TCF_SHIFT)
# define PR_MTE_TAG_SHIFT 3
# define PR_MTE_TAG_MASK (0xffffUL << PR_MTE_TAG_SHIFT)
/*
* Insert a random logical tag into the given pointer.
*/
#define insert_random_tag(ptr) ({ \
uint64_t __val; \
asm("irg %0, %1" : "=r" (__val) : "r" (ptr)); \
__val; \
})
/*
* Set the allocation tag on the destination address.
*/
#define set_tag(tagged_addr) do { \
asm volatile("stg %0, [%0]" : : "r" (tagged_addr) : "memory"); \
} while (0)
int main()
{
unsigned char *a;
unsigned long page_sz = sysconf(_SC_PAGESIZE);
unsigned long hwcap2 = getauxval(AT_HWCAP2);
/* check if MTE is present */
if (!(hwcap2 & HWCAP2_MTE))
return EXIT_FAILURE;
/*
* Enable the tagged address ABI, synchronous or asynchronous MTE
* tag check faults (based on per-CPU preference) and allow all
* non-zero tags in the randomly generated set.
*/
if (prctl(PR_SET_TAGGED_ADDR_CTRL,
PR_TAGGED_ADDR_ENABLE | PR_MTE_TCF_SYNC | PR_MTE_TCF_ASYNC |
(0xfffe << PR_MTE_TAG_SHIFT),
0, 0, 0)) {
perror("prctl() failed");
return EXIT_FAILURE;
}
a = mmap(0, page_sz, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (a == MAP_FAILED) {
perror("mmap() failed");
return EXIT_FAILURE;
}
/*
* Enable MTE on the above anonymous mmap. The flag could be passed
* directly to mmap() and skip this step.
*/
if (mprotect(a, page_sz, PROT_READ | PROT_WRITE | PROT_MTE)) {
perror("mprotect() failed");
return EXIT_FAILURE;
}
/* access with the default tag (0) */
a[0] = 1;
a[1] = 2;
printf("a[0] = %hhu a[1] = %hhu\n", a[0], a[1]);
/* set the logical and allocation tags */
a = (unsigned char *)insert_random_tag(a);
set_tag(a);
printf("%p\n", a);
/* non-zero tag access */
a[0] = 3;
printf("a[0] = %hhu a[1] = %hhu\n", a[0], a[1]);
/*
* If MTE is enabled correctly the next instruction will generate an
* exception.
*/
printf("Expecting SIGSEGV...\n");
a[16] = 0xdd;
/* this should not be printed in the PR_MTE_TCF_SYNC mode */
printf("...haven't got one\n");
return EXIT_FAILURE;
}
요약과 해설
memory-tagging-extension.rst:1-375MTE는 16-byte memory granule의 allocation tag와 pointer bit 59-56의 logical tag를 비교해 잘못된 memory access를 검출합니다. Linux ABI는 mapping type, thread별 fault mode, random tag 집합, debugger와 core-dump 형식까지 함께 규정합니다.
Pointer logical tag와 memory allocation tag의 비교 결과가 선택 mode에 따라 보고됩니다.
Mismatch가 발생했을 때 address 정확성과 실행 중단 시점이 다릅니다.