← Documents Documentation/arch/sparc/adi.rst GitHub 원문 ↗

Linux 6.18.37 · Architecture

SPARC Application Data Integrity

SPARC M7 ADI의 version tag 검사, PSTATE/TTE 활성화, mprotect·auxv interface, page lifecycle과 세 가지 trap 및 C 예제를 설명합니다.

Source pathDocumentation/arch/sparc/adi.rst
Source versionLinux v6.18.37
TranslationDUJINLABS 전문 번역 + 해설

요약·해설과 원문, 전문 번역을 서로 분리했습니다. API 이름, symbol, source path는 원문 표기를 사용합니다.

1. 요약·해설

원문의 핵심 논리와 kernel programming 관점의 보충 설명입니다. 아래의 전문 번역과는 별도로 작성했습니다.

요약과 해설

adi.rst:1-286

ADI는 pointer의 최상위 version bit와 physical memory에 저장된 tag를 비교해 잘못된 pointer 접근을 하드웨어에서 검출합니다. `PSTATE.mcde`, `TTE.mcd`, `PROT_ADI`가 각각 task·page·user API 수준의 활성화를 담당하며 block size와 tag bit 수는 auxiliary vector로 전달됩니다.

불일치 시 store는 기본적으로 disrupting 또는 선택적으로 precise trap을 만들고, load mismatch는 항상 precise합니다. kernel은 `SEGV_ADIDERR`, `SEGV_ADIPERR`, `SEGV_ACCADI`를 구분해 `SIGSEGV`로 보고하며 swap과 migration에서는 tag를 보존합니다.

2. 영어 원문 전체

번역 기준이 된 Linux v6.18.37 원문입니다. 줄 번호는 이 버전의 파일 좌표입니다.

원문 전체 펼치기
1 ================================
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,
41 kernel sets the PSTATE.mcde bit for the task. Version tags for memory
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
48 ============ ===========================================
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
52 ============ ===========================================
53
54
55 IMPORTANT NOTES
56 ===============
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
93 =================
94
95 With ADI enabled, following new traps may occur:
96
97 Disrupting memory corruption
98 ----------------------------
99
100 When a store accesses a memory location that has TTE.mcd=1,
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
108 info::
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
118 -------------------------
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
127 info::
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
135 NOTE:
136 ADI tag mismatch on a load always results in precise trap.
137
138
139 MCD disabled
140 ------------
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
147 sends the task SIGSEGV signal with following info::
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
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;
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
213 if ((shmid = shmget(2, BUFFER_SIZE,
214 IPC_CREAT | SHM_R | SHM_W)) < 0) {
215 perror("shmget failed");
216 exit(1);
217 }
218
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 }
225
226 if (mprotect(shmaddr, BUFFER_SIZE, PROT_READ|PROT_WRITE|PROT_ADI)) {
227 perror("mprotect failed");
228 goto err_out;
229 }
230
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 }
243 asm volatile("membar #Sync\n\t");
244
245 /* Create a versioned address from the normal address by placing
246 * version tag in the upper adi_nbits bits
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...");
262 fflush(stdout);
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");
267
268 /* Disable ADI and clean up
269 */
270 if (mprotect(shmaddr, BUFFER_SIZE, PROT_READ|PROT_WRITE)) {
271 perror("mprotect failed");
272 goto err_out;
273 }
274
275 if (shmdt((const void *)shmaddr) != 0)
276 perror("Detach failure");
277 shmctl(shmid, IPC_RMID, NULL);
278
279 exit(0);
280
281 err_out:
282 if (shmdt((const void *)shmaddr) != 0)
283 perror("Detach failure");
284 shmctl(shmid, IPC_RMID, NULL);
285 exit(1);
286 }
287

3. 한국어 전문 번역

영어 원문의 문단 순서와 의미를 유지한 전체 번역입니다. 코드, 함수명, symbol과 URL은 원문 표기를 유지합니다.

Application Data Integrity 개요

1-13

SPARC M7 processor는 Application Data Integrity(ADI) 기능을 추가합니다. ADI를 사용하면 task가 자신의 address space 중 원하는 subset에 version tag를 설정할 수 있습니다.

ADI를 활성화하고 task의 address range에 version tag를 설정하면 processor는 그 범위의 memory를 가리키는 pointer 안의 tag와 application이 앞서 설정한 version을 비교합니다. 두 값이 일치할 때만 memory access를 허용하며, 일치하지 않으면 exception을 발생시킵니다.

ADI를 완전히 활성화하는 3단계

14-27

task는 다음 절차를 수행해야 ADI를 완전히 활성화할 수 있습니다.

  • user mode `PSTATE.mcde` bit를 설정합니다. 이 bit는 task 전체 address space에서 ADI를 켜거나 끄는 master switch입니다.
  • ADI를 활성화할 address range에 대응하는 모든 TLB entry의 `TTE.mcd` bit를 설정합니다. MMU는 이 bit가 설정된 page에서만 version tag를 검사합니다.
  • `stxa` instruction과 MCD 전용 ASI 중 하나를 사용해 virtual address의 version tag를 설정합니다. 각 `stxa`는 ADI block 하나의 byte 범위에 tag를 설정하므로 page 전체에 tag를 지정하려면 page 끝까지 반복해야 합니다.

ADI block과 virtual address tag bit

28-38

platform의 ADI block size는 hypervisor가 machine description table을 통해 kernel에 제공합니다. hypervisor는 virtual address 최상위 bit 중 version tag로 사용하는 bit 수도 함께 제공합니다.

memory location에 설정한 version tag는 physical memory에 저장됩니다. MMU에 제시하는 virtual address의 ADI version tag bit에도 같은 값이 들어 있어야 합니다. SPARC M7에서는 MMU가 bits 63-60을 version tag로 사용하고 ADI block size는 cacheline과 같은 64 byte입니다. 예를 들어 memory range에 ADI version 10을 설정했다면 bits 63-60에 `0xa`가 든 virtual address로 접근해야 합니다.

mprotect()와 ADI auxiliary vector

39-54

page 집합에는 `PROT_ADI` flag를 지정한 `mprotect()`로 ADI를 활성화합니다. task가 처음으로 page에 ADI를 활성화하면 kernel이 해당 task의 `PSTATE.mcde` bit를 설정합니다.

memory address의 version tag는 `ASI_MCD_PRIMARY` 또는 `ASI_MCD_ST_BLKINIT_PRIMARY`를 사용한 `stxa` instruction으로 설정합니다. hypervisor가 제공한 ADI block size와 기타 정보는 kernel이 auxiliary vector로 user space에 반환합니다.

auxiliary vector의미
`AT_ADI_BLKSZ`ADI block size입니다. ADI versioning의 byte 단위 granularity이자 alignment입니다.
`AT_ADI_NBITS`virtual address(VA)에서 ADI version에 사용하는 bit 수입니다.

예약 tag와 physical page의 tag 저장

55-65

ADI 사용 시 다음 사항을 유의해야 합니다.

  • version tag 값 `0x0`과 `0xf`는 예약되어 있습니다. 두 값은 virtual address의 어떤 tag와도 일치하므로 mismatch exception을 만들지 않습니다.
  • user space는 virtual address를 대상으로 version tag를 설정하지만 tag 자체는 physical memory에 저장됩니다. task에 physical page를 할당하고 PTE를 만든 뒤 그 page에 tag를 설정합니다.

page 해제·swap·migration에서의 tag

66-80

task가 version tag를 설정했던 memory page를 해제하면 page는 free page pool로 돌아갑니다. 다른 task에 다시 할당할 때 kernel은 block initialization ASI로 page를 지우며 version tag도 함께 제거합니다. 같은 task가 그 page를 다시 받더라도 이전 tag는 남아 있지 않습니다.

non-faulting load에서는 ADI tag mismatch를 감지하지 않습니다. kernel은 user page에 tag를 설정하지 않으며 version tag 설정은 전적으로 task 책임입니다. 다만 page를 disk로 swap out했다가 다시 swap in하거나 page를 migrate할 때는 kernel이 version tag를 보존합니다.

page size와 writable memory 제약

81-90

ADI는 모든 page size에서 동작하므로 user space task가 page size를 알 필요는 없습니다. virtual address range를 선택하고 `mprotect()`로 ADI를 활성화한 뒤 전체 range에 version tag를 설정하면 됩니다. `mprotect()`는 range가 page size에 맞춰 정렬되고 그 배수 크기인지 보장합니다.

ADI tag는 writable memory에만 설정할 수 있습니다. 예를 들어 read-only mapping에는 ADI tag를 설정할 수 없습니다.

disrupting memory corruption trap

91-116

ADI가 활성화되면 새로운 trap이 발생할 수 있습니다. `TTE.mcd=1`인 memory location에 store하고 task가 `PSTATE.mcde=1`로 실행 중이며, 사용한 address의 ADI tag(bit 63:60)가 해당 cacheline tag와 다르면 memory corruption trap이 발생합니다.

기본값은 disrupting trap입니다. trap은 먼저 hypervisor로 전달되고, hypervisor는 sun4v error report를 만든 뒤 resumable error `TT=0x7e` trap을 kernel에 보냅니다. kernel은 원인이 된 task에 다음 정보와 함께 `SIGSEGV`를 전달합니다.

siginfo.si_signo = SIGSEGV;
siginfo.errno = 0;
siginfo.si_code = SEGV_ADIDERR;
siginfo.si_addr = addr; /* PC where first mismatch occurred */
siginfo.si_trapno = 0;

precise memory corruption trap

117-138

같은 tag mismatch 조건에서 MCD precise exception이 활성화되어 `MCDPERR=1`이면 kernel에 `TT=0x1a` precise exception을 보냅니다. kernel은 원인이 된 task에 다음 `SIGSEGV` 정보를 전달합니다.

siginfo.si_signo = SIGSEGV;
siginfo.errno = 0;
siginfo.si_code = SEGV_ADIPERR;
siginfo.si_addr = addr;        /* address that caused trap */
siginfo.si_trapno = 0;

load에서 발생한 ADI tag mismatch는 항상 precise trap으로 처리됩니다.

MCD disabled trap

139-155

ADI를 활성화하지 않은 task가 memory address에 ADI version을 설정하려 하면 processor가 MCD disabled trap을 보냅니다. hypervisor가 먼저 처리한 뒤 fault type `0xa`(invalid ASI)인 Data Access Exception trap으로 kernel에 전달합니다.

kernel은 해당 task에 다음 정보와 함께 `SIGSEGV` signal을 보냅니다.

siginfo.si_signo = SIGSEGV;
siginfo.errno = 0;
siginfo.si_code = SEGV_ACCADI;
siginfo.si_addr = addr;        /* address that caused trap */
siginfo.si_trapno = 0;

ADI 사용 예제 program

156-286

다음 sample program은 auxiliary vector에서 `AT_ADI_BLKSZ`와 `AT_ADI_NBITS`를 읽고, shared memory를 만든 뒤 `PROT_ADI`로 보호 속성을 설정하는 전체 절차를 보여 줍니다.

program은 각 ADI block에 version 10을 기록하고, address 최상위 `adi_nbits`에 같은 version을 넣은 versioned address를 만들어 데이터를 쓰고 검증합니다. 마지막에는 `PROT_ADI`를 제거하고 shared memory를 정리합니다.

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <elf.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/mman.h>
#include <asm/asi.h>

#ifndef AT_ADI_BLKSZ
#define AT_ADI_BLKSZ        48
#endif
#ifndef AT_ADI_NBITS
#define AT_ADI_NBITS        49
#endif

#ifndef PROT_ADI
#define PROT_ADI        0x10
#endif

#define BUFFER_SIZE     32*1024*1024UL

main(int argc, char* argv[], char* envp[])
{
        unsigned long i, mcde, adi_blksz, adi_nbits;
        char *shmaddr, *tmp_addr, *end, *veraddr, *clraddr;
        int shmid, version;
      Elf64_auxv_t *auxv;

      adi_blksz = 0;

      while(*envp++ != NULL);
      for (auxv = (Elf64_auxv_t *)envp; auxv->a_type != AT_NULL; auxv++) {
              switch (auxv->a_type) {
              case AT_ADI_BLKSZ:
                      adi_blksz = auxv->a_un.a_val;
                      break;
              case AT_ADI_NBITS:
                      adi_nbits = auxv->a_un.a_val;
                      break;
              }
      }
      if (adi_blksz == 0) {
              fprintf(stderr, "Oops! ADI is not supported\n");
              exit(1);
      }

      printf("ADI capabilities:\n");
      printf("\tBlock size = %ld\n", adi_blksz);
      printf("\tNumber of bits = %ld\n", adi_nbits);

        if ((shmid = shmget(2, BUFFER_SIZE,
                                IPC_CREAT | SHM_R | SHM_W)) < 0) {
                perror("shmget failed");
                exit(1);
        }

        shmaddr = shmat(shmid, NULL, 0);
        if (shmaddr == (char *)-1) {
                perror("shm attach failed");
                shmctl(shmid, IPC_RMID, NULL);
                exit(1);
        }

      if (mprotect(shmaddr, BUFFER_SIZE, PROT_READ|PROT_WRITE|PROT_ADI)) {
              perror("mprotect failed");
              goto err_out;
      }

        /* Set the ADI version tag on the shm segment
         */
        version = 10;
        tmp_addr = shmaddr;
        end = shmaddr + BUFFER_SIZE;
        while (tmp_addr < end) {
                asm volatile(
                        "stxa %1, [%0]0x90\n\t"
                        :
                        : "r" (tmp_addr), "r" (version));
                tmp_addr += adi_blksz;
        }
      asm volatile("membar #Sync\n\t");

        /* Create a versioned address from the normal address by placing
       * version tag in the upper adi_nbits bits
         */
        tmp_addr = (void *) ((unsigned long)shmaddr << adi_nbits);
        tmp_addr = (void *) ((unsigned long)tmp_addr >> adi_nbits);
        veraddr = (void *) (((unsigned long)version << (64-adi_nbits))
                        | (unsigned long)tmp_addr);

        printf("Starting the writes:\n");
        for (i = 0; i < BUFFER_SIZE; i++) {
                veraddr[i] = (char)(i);
                if (!(i % (1024 * 1024)))
                        printf(".");
        }
        printf("\n");

        printf("Verifying data...");
      fflush(stdout);
        for (i = 0; i < BUFFER_SIZE; i++)
                if (veraddr[i] != (char)i)
                        printf("\nIndex %lu mismatched\n", i);
        printf("Done.\n");

        /* Disable ADI and clean up
         */
      if (mprotect(shmaddr, BUFFER_SIZE, PROT_READ|PROT_WRITE)) {
              perror("mprotect failed");
              goto err_out;
      }

        if (shmdt((const void *)shmaddr) != 0)
                perror("Detach failure");
        shmctl(shmid, IPC_RMID, NULL);

        exit(0);

err_out:
        if (shmdt((const void *)shmaddr) != 0)
                perror("Detach failure");
        shmctl(shmid, IPC_RMID, NULL);
        exit(1);
}