Commit | Line | Data |
---|---|---|
5d5db1c9 | 1 | ================================ |
74a04967 KA |
2 | Application Data Integrity (ADI) |
3 | ================================ | |
4 | ||
5 | SPARC M7 processor adds the Application Data Integrity (ADI) feature. | |
6 | ADI allows a task to set version tags on any subset of its address | |
7 | space. Once ADI is enabled and version tags are set for ranges of | |
8 | address space of a task, the processor will compare the tag in pointers | |
9 | to memory in these ranges to the version set by the application | |
10 | previously. Access to memory is granted only if the tag in given pointer | |
11 | matches the tag set by the application. In case of mismatch, processor | |
12 | raises an exception. | |
13 | ||
14 | Following steps must be taken by a task to enable ADI fully: | |
15 | ||
16 | 1. Set the user mode PSTATE.mcde bit. This acts as master switch for | |
17 | the task's entire address space to enable/disable ADI for the task. | |
18 | ||
19 | 2. Set TTE.mcd bit on any TLB entries that correspond to the range of | |
20 | addresses ADI is being enabled on. MMU checks the version tag only | |
21 | on the pages that have TTE.mcd bit set. | |
22 | ||
23 | 3. Set the version tag for virtual addresses using stxa instruction | |
24 | and one of the MCD specific ASIs. Each stxa instruction sets the | |
25 | given tag for one ADI block size number of bytes. This step must | |
26 | be repeated for entire page to set tags for entire page. | |
27 | ||
28 | ADI block size for the platform is provided by the hypervisor to kernel | |
29 | in machine description tables. Hypervisor also provides the number of | |
30 | top bits in the virtual address that specify the version tag. Once | |
31 | version tag has been set for a memory location, the tag is stored in the | |
32 | physical memory and the same tag must be present in the ADI version tag | |
33 | bits of the virtual address being presented to the MMU. For example on | |
34 | SPARC M7 processor, MMU uses bits 63-60 for version tags and ADI block | |
35 | size is same as cacheline size which is 64 bytes. A task that sets ADI | |
36 | version to, say 10, on a range of memory, must access that memory using | |
37 | virtual addresses that contain 0xa in bits 63-60. | |
38 | ||
39 | ADI is enabled on a set of pages using mprotect() with PROT_ADI flag. | |
40 | When ADI is enabled on a set of pages by a task for the first time, | |
810edcd5 | 41 | kernel sets the PSTATE.mcde bit for the task. Version tags for memory |
74a04967 KA |
42 | addresses are set with an stxa instruction on the addresses using |
43 | ASI_MCD_PRIMARY or ASI_MCD_ST_BLKINIT_PRIMARY. ADI block size is | |
44 | provided by the hypervisor to the kernel. Kernel returns the value of | |
45 | ADI block size to userspace using auxiliary vector along with other ADI | |
46 | info. Following auxiliary vectors are provided by the kernel: | |
47 | ||
5d5db1c9 | 48 | ============ =========================================== |
74a04967 KA |
49 | AT_ADI_BLKSZ ADI block size. This is the granularity and |
50 | alignment, in bytes, of ADI versioning. | |
51 | AT_ADI_NBITS Number of ADI version bits in the VA | |
5d5db1c9 | 52 | ============ =========================================== |
74a04967 KA |
53 | |
54 | ||
5d5db1c9 MCC |
55 | IMPORTANT NOTES |
56 | =============== | |
74a04967 KA |
57 | |
58 | - Version tag values of 0x0 and 0xf are reserved. These values match any | |
59 | tag in virtual address and never generate a mismatch exception. | |
60 | ||
61 | - Version tags are set on virtual addresses from userspace even though | |
62 | tags are stored in physical memory. Tags are set on a physical page | |
63 | after it has been allocated to a task and a pte has been created for | |
64 | it. | |
65 | ||
66 | - When a task frees a memory page it had set version tags on, the page | |
67 | goes back to free page pool. When this page is re-allocated to a task, | |
68 | kernel clears the page using block initialization ASI which clears the | |
69 | version tags as well for the page. If a page allocated to a task is | |
70 | freed and allocated back to the same task, old version tags set by the | |
71 | task on that page will no longer be present. | |
72 | ||
73 | - ADI tag mismatches are not detected for non-faulting loads. | |
74 | ||
75 | - Kernel does not set any tags for user pages and it is entirely a | |
76 | task's responsibility to set any version tags. Kernel does ensure the | |
77 | version tags are preserved if a page is swapped out to the disk and | |
78 | swapped back in. It also preserves that version tags if a page is | |
79 | migrated. | |
80 | ||
81 | - ADI works for any size pages. A userspace task need not be aware of | |
82 | page size when using ADI. It can simply select a virtual address | |
83 | range, enable ADI on the range using mprotect() and set version tags | |
84 | for the entire range. mprotect() ensures range is aligned to page size | |
85 | and is a multiple of page size. | |
86 | ||
87 | - ADI tags can only be set on writable memory. For example, ADI tags can | |
88 | not be set on read-only mappings. | |
89 | ||
90 | ||
91 | ||
92 | ADI related traps | |
5d5db1c9 | 93 | ================= |
74a04967 KA |
94 | |
95 | With ADI enabled, following new traps may occur: | |
96 | ||
97 | Disrupting memory corruption | |
5d5db1c9 | 98 | ---------------------------- |
74a04967 | 99 | |
810edcd5 | 100 | When a store accesses a memory location that has TTE.mcd=1, |
74a04967 KA |
101 | the task is running with ADI enabled (PSTATE.mcde=1), and the ADI |
102 | tag in the address used (bits 63:60) does not match the tag set on | |
103 | the corresponding cacheline, a memory corruption trap occurs. By | |
104 | default, it is a disrupting trap and is sent to the hypervisor | |
105 | first. Hypervisor creates a sun4v error report and sends a | |
106 | resumable error (TT=0x7e) trap to the kernel. The kernel sends | |
107 | a SIGSEGV to the task that resulted in this trap with the following | |
5d5db1c9 | 108 | info:: |
74a04967 KA |
109 | |
110 | siginfo.si_signo = SIGSEGV; | |
111 | siginfo.errno = 0; | |
112 | siginfo.si_code = SEGV_ADIDERR; | |
113 | siginfo.si_addr = addr; /* PC where first mismatch occurred */ | |
114 | siginfo.si_trapno = 0; | |
115 | ||
116 | ||
117 | Precise memory corruption | |
5d5db1c9 | 118 | ------------------------- |
74a04967 KA |
119 | |
120 | When a store accesses a memory location that has TTE.mcd=1, | |
121 | the task is running with ADI enabled (PSTATE.mcde=1), and the ADI | |
122 | tag in the address used (bits 63:60) does not match the tag set on | |
123 | the corresponding cacheline, a memory corruption trap occurs. If | |
124 | MCD precise exception is enabled (MCDPERR=1), a precise | |
125 | exception is sent to the kernel with TT=0x1a. The kernel sends | |
126 | a SIGSEGV to the task that resulted in this trap with the following | |
5d5db1c9 | 127 | info:: |
74a04967 KA |
128 | |
129 | siginfo.si_signo = SIGSEGV; | |
130 | siginfo.errno = 0; | |
131 | siginfo.si_code = SEGV_ADIPERR; | |
132 | siginfo.si_addr = addr; /* address that caused trap */ | |
133 | siginfo.si_trapno = 0; | |
134 | ||
5d5db1c9 MCC |
135 | NOTE: |
136 | ADI tag mismatch on a load always results in precise trap. | |
74a04967 KA |
137 | |
138 | ||
139 | MCD disabled | |
5d5db1c9 | 140 | ------------ |
74a04967 KA |
141 | |
142 | When a task has not enabled ADI and attempts to set ADI version | |
143 | on a memory address, processor sends an MCD disabled trap. This | |
144 | trap is handled by hypervisor first and the hypervisor vectors this | |
145 | trap through to the kernel as Data Access Exception trap with | |
146 | fault type set to 0xa (invalid ASI). When this occurs, the kernel | |
5d5db1c9 | 147 | sends the task SIGSEGV signal with following info:: |
74a04967 KA |
148 | |
149 | siginfo.si_signo = SIGSEGV; | |
150 | siginfo.errno = 0; | |
151 | siginfo.si_code = SEGV_ACCADI; | |
152 | siginfo.si_addr = addr; /* address that caused trap */ | |
153 | siginfo.si_trapno = 0; | |
154 | ||
155 | ||
156 | Sample program to use ADI | |
157 | ------------------------- | |
158 | ||
159 | Following sample program is meant to illustrate how to use the ADI | |
5d5db1c9 MCC |
160 | functionality:: |
161 | ||
162 | #include <unistd.h> | |
163 | #include <stdio.h> | |
164 | #include <stdlib.h> | |
165 | #include <elf.h> | |
166 | #include <sys/ipc.h> | |
167 | #include <sys/shm.h> | |
168 | #include <sys/mman.h> | |
169 | #include <asm/asi.h> | |
170 | ||
171 | #ifndef AT_ADI_BLKSZ | |
172 | #define AT_ADI_BLKSZ 48 | |
173 | #endif | |
174 | #ifndef AT_ADI_NBITS | |
175 | #define AT_ADI_NBITS 49 | |
176 | #endif | |
177 | ||
178 | #ifndef PROT_ADI | |
179 | #define PROT_ADI 0x10 | |
180 | #endif | |
181 | ||
182 | #define BUFFER_SIZE 32*1024*1024UL | |
183 | ||
184 | main(int argc, char* argv[], char* envp[]) | |
185 | { | |
186 | unsigned long i, mcde, adi_blksz, adi_nbits; | |
187 | char *shmaddr, *tmp_addr, *end, *veraddr, *clraddr; | |
188 | int shmid, version; | |
74a04967 KA |
189 | Elf64_auxv_t *auxv; |
190 | ||
191 | adi_blksz = 0; | |
192 | ||
193 | while(*envp++ != NULL); | |
194 | for (auxv = (Elf64_auxv_t *)envp; auxv->a_type != AT_NULL; auxv++) { | |
195 | switch (auxv->a_type) { | |
196 | case AT_ADI_BLKSZ: | |
197 | adi_blksz = auxv->a_un.a_val; | |
198 | break; | |
199 | case AT_ADI_NBITS: | |
200 | adi_nbits = auxv->a_un.a_val; | |
201 | break; | |
202 | } | |
203 | } | |
204 | if (adi_blksz == 0) { | |
205 | fprintf(stderr, "Oops! ADI is not supported\n"); | |
206 | exit(1); | |
207 | } | |
208 | ||
209 | printf("ADI capabilities:\n"); | |
210 | printf("\tBlock size = %ld\n", adi_blksz); | |
211 | printf("\tNumber of bits = %ld\n", adi_nbits); | |
212 | ||
5d5db1c9 MCC |
213 | if ((shmid = shmget(2, BUFFER_SIZE, |
214 | IPC_CREAT | SHM_R | SHM_W)) < 0) { | |
215 | perror("shmget failed"); | |
216 | exit(1); | |
217 | } | |
74a04967 | 218 | |
5d5db1c9 MCC |
219 | shmaddr = shmat(shmid, NULL, 0); |
220 | if (shmaddr == (char *)-1) { | |
221 | perror("shm attach failed"); | |
222 | shmctl(shmid, IPC_RMID, NULL); | |
223 | exit(1); | |
224 | } | |
74a04967 KA |
225 | |
226 | if (mprotect(shmaddr, BUFFER_SIZE, PROT_READ|PROT_WRITE|PROT_ADI)) { | |
227 | perror("mprotect failed"); | |
228 | goto err_out; | |
229 | } | |
230 | ||
5d5db1c9 MCC |
231 | /* Set the ADI version tag on the shm segment |
232 | */ | |
233 | version = 10; | |
234 | tmp_addr = shmaddr; | |
235 | end = shmaddr + BUFFER_SIZE; | |
236 | while (tmp_addr < end) { | |
237 | asm volatile( | |
238 | "stxa %1, [%0]0x90\n\t" | |
239 | : | |
240 | : "r" (tmp_addr), "r" (version)); | |
241 | tmp_addr += adi_blksz; | |
242 | } | |
74a04967 KA |
243 | asm volatile("membar #Sync\n\t"); |
244 | ||
5d5db1c9 | 245 | /* Create a versioned address from the normal address by placing |
74a04967 | 246 | * version tag in the upper adi_nbits bits |
5d5db1c9 MCC |
247 | */ |
248 | tmp_addr = (void *) ((unsigned long)shmaddr << adi_nbits); | |
249 | tmp_addr = (void *) ((unsigned long)tmp_addr >> adi_nbits); | |
250 | veraddr = (void *) (((unsigned long)version << (64-adi_nbits)) | |
251 | | (unsigned long)tmp_addr); | |
252 | ||
253 | printf("Starting the writes:\n"); | |
254 | for (i = 0; i < BUFFER_SIZE; i++) { | |
255 | veraddr[i] = (char)(i); | |
256 | if (!(i % (1024 * 1024))) | |
257 | printf("."); | |
258 | } | |
259 | printf("\n"); | |
260 | ||
261 | printf("Verifying data..."); | |
74a04967 | 262 | fflush(stdout); |
5d5db1c9 MCC |
263 | for (i = 0; i < BUFFER_SIZE; i++) |
264 | if (veraddr[i] != (char)i) | |
265 | printf("\nIndex %lu mismatched\n", i); | |
266 | printf("Done.\n"); | |
74a04967 | 267 | |
5d5db1c9 MCC |
268 | /* Disable ADI and clean up |
269 | */ | |
74a04967 KA |
270 | if (mprotect(shmaddr, BUFFER_SIZE, PROT_READ|PROT_WRITE)) { |
271 | perror("mprotect failed"); | |
272 | goto err_out; | |
273 | } | |
274 | ||
5d5db1c9 MCC |
275 | if (shmdt((const void *)shmaddr) != 0) |
276 | perror("Detach failure"); | |
277 | shmctl(shmid, IPC_RMID, NULL); | |
74a04967 | 278 | |
5d5db1c9 | 279 | exit(0); |
74a04967 | 280 | |
5d5db1c9 MCC |
281 | err_out: |
282 | if (shmdt((const void *)shmaddr) != 0) | |
283 | perror("Detach failure"); | |
284 | shmctl(shmid, IPC_RMID, NULL); | |
285 | exit(1); | |
286 | } |