summaryrefslogtreecommitdiff
path: root/Documentation/arm64/tagged-address-abi.rst
blob: cbc4d4500241852d43d1f9fe4bea7729021b49ba (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
==========================
AArch64 TAGGED ADDRESS ABI
==========================

Authors: Vincenzo Frascino <vincenzo.frascino@arm.com>
         Catalin Marinas <catalin.marinas@arm.com>

Date: 21 August 2019

This document describes the usage and semantics of the Tagged Address
ABI on AArch64 Linux.

1. Introduction
---------------

On AArch64 the ``TCR_EL1.TBI0`` bit is set by default, allowing
userspace (EL0) to perform memory accesses through 64-bit pointers with
a non-zero top byte. This document describes the relaxation of the
syscall ABI that allows userspace to pass certain tagged pointers to
kernel syscalls.

2. AArch64 Tagged Address ABI
-----------------------------

From the kernel syscall interface perspective and for the purposes of
this document, a "valid tagged pointer" is a pointer with a potentially
non-zero top-byte that references an address in the user process address
space obtained in one of the following ways:

- ``mmap()`` syscall where either:

  - flags have the ``MAP_ANONYMOUS`` bit set or
  - the file descriptor refers to a regular file (including those
    returned by ``memfd_create()``) or ``/dev/zero``

- ``brk()`` syscall (i.e. the heap area between the initial location of
  the program break at process creation and its current location).

- any memory mapped by the kernel in the address space of the process
  during creation and with the same restrictions as for ``mmap()`` above
  (e.g. data, bss, stack).

The AArch64 Tagged Address ABI has two stages of relaxation depending on
how the user addresses are used by the kernel:

1. User addresses not accessed by the kernel but used for address space
   management (e.g. ``mprotect()``, ``madvise()``). The use of valid
   tagged pointers in this context is allowed with the exception of
   ``brk()``, ``mmap()`` and the ``new_address`` argument to
   ``mremap()`` as these have the potential to alias with existing
   user addresses.

   NOTE: This behaviour changed in v5.6 and so some earlier kernels may
   incorrectly accept valid tagged pointers for the ``brk()``,
   ``mmap()`` and ``mremap()`` system calls.

2. User addresses accessed by the kernel (e.g. ``write()``). This ABI
   relaxation is disabled by default and the application thread needs to
   explicitly enable it via ``prctl()`` as follows:

   - ``PR_SET_TAGGED_ADDR_CTRL``: enable or disable the AArch64 Tagged
     Address ABI for the calling thread.

     The ``(unsigned int) arg2`` argument is a bit mask describing the
     control mode used:

     - ``PR_TAGGED_ADDR_ENABLE``: enable AArch64 Tagged Address ABI.
       Default status is disabled.

     Arguments ``arg3``, ``arg4``, and ``arg5`` must be 0.

   - ``PR_GET_TAGGED_ADDR_CTRL``: get the status of the AArch64 Tagged
     Address ABI for the calling thread.

     Arguments ``arg2``, ``arg3``, ``arg4``, and ``arg5`` must be 0.

   The ABI properties described above are thread-scoped, inherited on
   clone() and fork() and cleared on exec().

   Calling ``prctl(PR_SET_TAGGED_ADDR_CTRL, PR_TAGGED_ADDR_ENABLE, 0, 0, 0)``
   returns ``-EINVAL`` if the AArch64 Tagged Address ABI is globally
   disabled by ``sysctl abi.tagged_addr_disabled=1``. The default
   ``sysctl abi.tagged_addr_disabled`` configuration is 0.

When the AArch64 Tagged Address ABI is enabled for a thread, the
following behaviours are guaranteed:

- All syscalls except the cases mentioned in section 3 can accept any
  valid tagged pointer.

- The syscall behaviour is undefined for invalid tagged pointers: it may
  result in an error code being returned, a (fatal) signal being raised,
  or other modes of failure.

- The syscall behaviour for a valid tagged pointer is the same as for
  the corresponding untagged pointer.


A definition of the meaning of tagged pointers on AArch64 can be found
in Documentation/arm64/tagged-pointers.rst.

3. AArch64 Tagged Address ABI Exceptions
-----------------------------------------

The following system call parameters must be untagged regardless of the
ABI relaxation:

- ``prctl()`` other than pointers to user data either passed directly or
  indirectly as arguments to be accessed by the kernel.

- ``ioctl()`` other than pointers to user data either passed directly or
  indirectly as arguments to be accessed by the kernel.

- ``shmat()`` and ``shmdt()``.

Any attempt to use non-zero tagged pointers may result in an error code
being returned, a (fatal) signal being raised, or other modes of
failure.

4. Example of correct usage
---------------------------
.. code-block:: c

   #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;
   }