287 lines
9.9 KiB
ReStructuredText
287 lines
9.9 KiB
ReStructuredText
|
================================
|
||
|
Application Data Integrity (ADI)
|
||
|
================================
|
||
|
|
||
|
SPARC M7 processor adds the Application Data Integrity (ADI) feature.
|
||
|
ADI allows a task to set version tags on any subset of its address
|
||
|
space. Once ADI is enabled and version tags are set for ranges of
|
||
|
address space of a task, the processor will compare the tag in pointers
|
||
|
to memory in these ranges to the version set by the application
|
||
|
previously. Access to memory is granted only if the tag in given pointer
|
||
|
matches the tag set by the application. In case of mismatch, processor
|
||
|
raises an exception.
|
||
|
|
||
|
Following steps must be taken by a task to enable ADI fully:
|
||
|
|
||
|
1. Set the user mode PSTATE.mcde bit. This acts as master switch for
|
||
|
the task's entire address space to enable/disable ADI for the task.
|
||
|
|
||
|
2. Set TTE.mcd bit on any TLB entries that correspond to the range of
|
||
|
addresses ADI is being enabled on. MMU checks the version tag only
|
||
|
on the pages that have TTE.mcd bit set.
|
||
|
|
||
|
3. Set the version tag for virtual addresses using stxa instruction
|
||
|
and one of the MCD specific ASIs. Each stxa instruction sets the
|
||
|
given tag for one ADI block size number of bytes. This step must
|
||
|
be repeated for entire page to set tags for entire page.
|
||
|
|
||
|
ADI block size for the platform is provided by the hypervisor to kernel
|
||
|
in machine description tables. Hypervisor also provides the number of
|
||
|
top bits in the virtual address that specify the version tag. Once
|
||
|
version tag has been set for a memory location, the tag is stored in the
|
||
|
physical memory and the same tag must be present in the ADI version tag
|
||
|
bits of the virtual address being presented to the MMU. For example on
|
||
|
SPARC M7 processor, MMU uses bits 63-60 for version tags and ADI block
|
||
|
size is same as cacheline size which is 64 bytes. A task that sets ADI
|
||
|
version to, say 10, on a range of memory, must access that memory using
|
||
|
virtual addresses that contain 0xa in bits 63-60.
|
||
|
|
||
|
ADI is enabled on a set of pages using mprotect() with PROT_ADI flag.
|
||
|
When ADI is enabled on a set of pages by a task for the first time,
|
||
|
kernel sets the PSTATE.mcde bit fot the task. Version tags for memory
|
||
|
addresses are set with an stxa instruction on the addresses using
|
||
|
ASI_MCD_PRIMARY or ASI_MCD_ST_BLKINIT_PRIMARY. ADI block size is
|
||
|
provided by the hypervisor to the kernel. Kernel returns the value of
|
||
|
ADI block size to userspace using auxiliary vector along with other ADI
|
||
|
info. Following auxiliary vectors are provided by the kernel:
|
||
|
|
||
|
============ ===========================================
|
||
|
AT_ADI_BLKSZ ADI block size. This is the granularity and
|
||
|
alignment, in bytes, of ADI versioning.
|
||
|
AT_ADI_NBITS Number of ADI version bits in the VA
|
||
|
============ ===========================================
|
||
|
|
||
|
|
||
|
IMPORTANT NOTES
|
||
|
===============
|
||
|
|
||
|
- Version tag values of 0x0 and 0xf are reserved. These values match any
|
||
|
tag in virtual address and never generate a mismatch exception.
|
||
|
|
||
|
- Version tags are set on virtual addresses from userspace even though
|
||
|
tags are stored in physical memory. Tags are set on a physical page
|
||
|
after it has been allocated to a task and a pte has been created for
|
||
|
it.
|
||
|
|
||
|
- When a task frees a memory page it had set version tags on, the page
|
||
|
goes back to free page pool. When this page is re-allocated to a task,
|
||
|
kernel clears the page using block initialization ASI which clears the
|
||
|
version tags as well for the page. If a page allocated to a task is
|
||
|
freed and allocated back to the same task, old version tags set by the
|
||
|
task on that page will no longer be present.
|
||
|
|
||
|
- ADI tag mismatches are not detected for non-faulting loads.
|
||
|
|
||
|
- Kernel does not set any tags for user pages and it is entirely a
|
||
|
task's responsibility to set any version tags. Kernel does ensure the
|
||
|
version tags are preserved if a page is swapped out to the disk and
|
||
|
swapped back in. It also preserves that version tags if a page is
|
||
|
migrated.
|
||
|
|
||
|
- ADI works for any size pages. A userspace task need not be aware of
|
||
|
page size when using ADI. It can simply select a virtual address
|
||
|
range, enable ADI on the range using mprotect() and set version tags
|
||
|
for the entire range. mprotect() ensures range is aligned to page size
|
||
|
and is a multiple of page size.
|
||
|
|
||
|
- ADI tags can only be set on writable memory. For example, ADI tags can
|
||
|
not be set on read-only mappings.
|
||
|
|
||
|
|
||
|
|
||
|
ADI related traps
|
||
|
=================
|
||
|
|
||
|
With ADI enabled, following new traps may occur:
|
||
|
|
||
|
Disrupting memory corruption
|
||
|
----------------------------
|
||
|
|
||
|
When a store accesses a memory localtion that has TTE.mcd=1,
|
||
|
the task is running with ADI enabled (PSTATE.mcde=1), and the ADI
|
||
|
tag in the address used (bits 63:60) does not match the tag set on
|
||
|
the corresponding cacheline, a memory corruption trap occurs. By
|
||
|
default, it is a disrupting trap and is sent to the hypervisor
|
||
|
first. Hypervisor creates a sun4v error report and sends a
|
||
|
resumable error (TT=0x7e) trap to the kernel. The kernel sends
|
||
|
a SIGSEGV to the task that resulted in this trap with the following
|
||
|
info::
|
||
|
|
||
|
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
|
||
|
-------------------------
|
||
|
|
||
|
When a store accesses a memory location that has TTE.mcd=1,
|
||
|
the task is running with ADI enabled (PSTATE.mcde=1), and the ADI
|
||
|
tag in the address used (bits 63:60) does not match the tag set on
|
||
|
the corresponding cacheline, a memory corruption trap occurs. If
|
||
|
MCD precise exception is enabled (MCDPERR=1), a precise
|
||
|
exception is sent to the kernel with TT=0x1a. The kernel sends
|
||
|
a SIGSEGV to the task that resulted in this trap with the following
|
||
|
info::
|
||
|
|
||
|
siginfo.si_signo = SIGSEGV;
|
||
|
siginfo.errno = 0;
|
||
|
siginfo.si_code = SEGV_ADIPERR;
|
||
|
siginfo.si_addr = addr; /* address that caused trap */
|
||
|
siginfo.si_trapno = 0;
|
||
|
|
||
|
NOTE:
|
||
|
ADI tag mismatch on a load always results in precise trap.
|
||
|
|
||
|
|
||
|
MCD disabled
|
||
|
------------
|
||
|
|
||
|
When a task has not enabled ADI and attempts to set ADI version
|
||
|
on a memory address, processor sends an MCD disabled trap. This
|
||
|
trap is handled by hypervisor first and the hypervisor vectors this
|
||
|
trap through to the kernel as Data Access Exception trap with
|
||
|
fault type set to 0xa (invalid ASI). When this occurs, the kernel
|
||
|
sends the task SIGSEGV signal with following info::
|
||
|
|
||
|
siginfo.si_signo = SIGSEGV;
|
||
|
siginfo.errno = 0;
|
||
|
siginfo.si_code = SEGV_ACCADI;
|
||
|
siginfo.si_addr = addr; /* address that caused trap */
|
||
|
siginfo.si_trapno = 0;
|
||
|
|
||
|
|
||
|
Sample program to use ADI
|
||
|
-------------------------
|
||
|
|
||
|
Following sample program is meant to illustrate how to use the ADI
|
||
|
functionality::
|
||
|
|
||
|
#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);
|
||
|
}
|