Storage protect keys

Storage protect keys provide a mechanism for you to improve the reliability of your programs.

Protect keys apply to memory pages and work at the page level of granularity, similar to the mprotect subroutine, which can be used to read- or write-protect one or more pages. However, with storage keys you can mark sections of your data for specific levels of read and write access protection. Protection by storage keys is a function not only of the data page, but also of the thread attempting access. You can enable certain well-defined code paths to access data that is unavailable to your larger program, thereby encapsulating critical program data and protecting it against accidental damage.

Because access to key-protected pages is an attribute of the running thread, this mechanism extends naturally to multithreaded applications, but with the restriction that these use only 1:1 (or system scope) pthreads. The mprotect subroutine approach does not work reliably in a multithreaded environment, because you have to remove protection for all threads when you want to grant access to any thread. You can use both mechanisms simultaneously, and both are fully enforced; therefore, your program cannot write to a write-protected page even if a protect key would otherwise allow this.

Protect keys sample uses include:
  • Encapsulate your program's private data completely, limiting access to just selected code paths.
  • Protect your program's private data from accidental damage by always running with read access granted, but granting write access only when you intend to modify the data. This can be especially useful when code in a core engine allows calls out to untrusted code.
  • When multiple private keys are available, additional granularity of data protection is possible.

You can simplify debugging by designing your application with key protection in mind. Setting a page's protect key and setting your active user keyset are both system calls, and therefore relatively expensive operations. You should design your program so that the frequency of these operations is not excessive.

User protect keys

The following guidelines and considerations apply when using protect keys:
  • Pages that are exported read-only from the kernel will continue to be visible to your program. These pages have a protect key of UKEY_SYSTEM. This protect key is not a protect key that is under your program's control, but is always accessible by your program.
  • All of your program's memory pages initially have the user public key assigned to them. As noted above, access to key 0 storage is always granted, making this the user public key.
  • You can set protect keys only for your normal and shared data. You cannot, for example, protect library data, low memory shared with the kernel, or program text.
  • Depending on the underlying hardware and administrative choice, only a limited number of user private keys (typically just one) are available. When your program assigns a private key to one or more of its pages, the data in those pages is no longer available by default. You must explicitly grant read or write access to this data by surrounding code paths that require access with calls to a new service to manage your active user keyset.
  • The physical hardware likely supports additional protect keys that are not available for use as user protect keys.
  • No special privilege is needed to assign protect keys to a page. The only requirement is current write access to the page.
  • There is no control of execute authority with protect keys.

If your program accesses key protected data in violation of the access rights expressed in its active user keyset, it receives a SIGSEGV signal, as is already the case for violating read- or write-protected pages. If you choose to handle this signal, be aware that signal handlers are invoked without access to private keys. Signal handling code must add any needed access rights to the active user keyset before referencing key-protected data.

Child processes, created by the fork system call, logically inherit their parent's memory and running state. This includes the protect key associated with each page, as well as the parent thread's active user keyset at the time of fork.

Regions protected by user keys

User protect keys can protect pages in the following regions:
  • Data region
  • Default stack region
  • mmaped regions
  • Shared memory attached with the shmat() subroutine, except as below
  • These categories of pages cannot use protect keys:
    • shmated files and pinned shared memory
    • Large (nonpageable) pages
    • Program text
    • Low memory shared read-only with the kernel

System prerequisites for key protection

Storage key protection is a hardware-specific privileged page protection mechanism that is made available by the AIX® kernel for use in application programs. To use this feature, your system must:
  • Be running on physical hardware that provides storage key protection
  • Be running the 64-bit kernel
  • Enable the use of user protect keys

Program prerequisites for key protection

To use user keys, your program must:
  • Declare itself user-key aware and determine how many user protect keys are available, if any, with the ukey_enable subroutine.
  • Organize its protected data on page boundaries.
  • Assign a private key to each page you want to protect with the ukey_protect subroutine.
  • If you protect the malloc'd data, remember to unprotect it before you free it.
  • Prepare one or more keysets with the ukeyset_init subroutine.
  • Possibly add the required keys to your keyset with the ukeyset_add_key subroutine, to enable future read or write accesses as required.
  • Make a keyset active with the ukeyset_activate subroutine to grant the access rights defined in a keyset.
Your program must not:
  • Include any M:N (process scope) pthreads
  • Be able to have a checkpoint performed on it (for example have CHECKPOINT=yes in the environment)
Note: When a program is user-key aware, it has additional context associated with it to represent its active user keyset. This can be seen in:
  • Signal handlers receiving a ucontext_t structure. The previously active user keyset is in ucontext_t.__extctx.__ukeys, an array of two ints containing a 64-bit user keyset value
  • User context structures compiled with __EXTABI__ defined (used by setcontext, getcontext, makecontext, swapcontext)

Subroutines

The following new AIX kernel subroutines are provided for using protect keys:
Subroutine Description
sysconf Use with _SC_AIX_UKEYS to determine the number of user keys supported (can be called on older versions of AIX)
ukey_enable Enable the user-key aware programming environment for your process, and report how many user keys are available
ukeyset_init Initialize a user keyset, which will represent a set of access rights to your private key or keys
ukeyset_add_key Add read or write access, or both for a specified key to a keyset
ukeyset_remove_key Remove or write access, or both for a specified key from a keyset
ukeyset_add_set Add all the access rights in one keyset to another
ukeyset_remove_set Remove all the access rights in one keyset from another
ukeyset_activate Apply the access rights in a keyset to the running thread
ukeyset_ismember Test if a given access right is contained in a keyset
ukey_setjmp Extended form of setjmp that preserves the active keyset (uses a ukey_jmp_buf structure)
pthread_attr_getukeyset_np Get the keyset attribute of a pthread
pthread_attr_setukeyset_np Set the keyset attribute for a pthread
ukey_protect Set a user protect key for a page-aligned range of user memory
ukey_getkey Retrieve the user protect key for a specified address

Debugging

The dbx command adds limited support for protect keys:
  • When debugging a running program:
    • The ukeyset subcommand displays the active keyset.
    • The ukeyvalue subcommand displays the protect key associated with a given memory location.
  • When debugging a core file, the ukeyexcept subcommand reports the active keyset, effective address of the key exception, and the storage key involved.

Hardware details

The active user keyset in the running context of a key-aware thread parallels the actual hardware authority mask register (AMR) in format, represented by the ukeyset_t abstract data type. This information is provided for debugging purposes only. Use only the defined programming services to set up your active user keyset.
  • The AMR is a 64-bit register comprising 32-bit pairs, one pair per key, for a maximum of 32 keys numbered 0 through 31.
    • The first bit of each pair represents write access to the corresponding numbered key.
    • Similarly, the second bit of each pair represents read access to the corresponding numbered key.
  • A bit value of 0 grants the corresponding access, and a bit value of 1 denies it.
  • The bit pair granting access to key 0 is not controlled by your program. User key 0 is the user public key, and all threads always have full access to data in this key, without regard to your settings in the active user keyset.
  • All the other bit pairs represent user private keys, which, subject to availability, you can use to protect your data as you see fit.

Sample program

The following is a sample user-key aware program:

#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
#include <sys/ukeys.h>
#include <sys/syspest.h>
#include <sys/signal.h> 
#include <sys/vminfo.h>

#define ROUND_UP(size,psize)    ((size)+(psize)-1 & ~((psize)-1))

/*
 * This is an example skeleton for a user key aware program.
 *
 * The private_data_1 structure will map a malloc'd key protected area
 * which the main program can access freely, while the "untrusted"
 * subroutine will only have read access.
 */
struct private_data_1 {
        int some_data;
};
struct private_data_1 *p1;      /* pointer to structure for protected data */

ukeyset_t keyset1RW;            /* keyset to give signal handler access */

/*
 * The untrusted function here should successfully read protected data.
 *
 * When the count is 0, it will just return so the caller can write
 * the incremented value back to the protected field.
 *
 * When the count is 1, it will try to update the protected field itself.
 * This should result in a SIGSEGV.
 */
int untrusted(struct private_data_1 *p1) {
        int count = p1->some_data;      /* We can read protected data */
        if (count == 1)
                p1->some_data = count;  /* But should not be able to write it */
        return count + 1;
}

/*
 * Signal handler to catch the deliberate protection violation in the
 * untrusted function above when count == 1.
 * Note that the handler is entered with NO access to our private data.
 */
void handler(int signo, siginfo_t *sip, void *ucp) {
        printf("siginfo: signo %d code %d\n", sip->si_signo, sip->si_code);
        (void)ukeyset_activate(keyset1RW, UKA_REPLACE_KEYS);
        exit(1);
}

main() {
        int nkeys;
        int pagesize = 4096;            /* hardware data page size */
        int padded_protsize_1;          /* page padded size of protected data */
        struct vm_page_info page_info;

        ukey_t key1 = UKEY_PRIVATE1;
        ukeyset_t keyset1W, oldset;
        int rc;
        int count = 0;

        struct sigaction sa;

        /*
         * Attempt to become user key aware.
         */
        nkeys = ukey_enable();
        if (nkeys == -1) {
                perror("ukey_enable");
                exit(1);
        }
        assert(nkeys >= 2);

        /*
         * Determine the data region page size.
         */
        page_info.addr = (long)&p1;             /* address in data region */
        rc = vmgetinfo(&page_info, VM_PAGE_INFO, sizeof(struct vm_page_info));

        if (rc)
                perror("vmgetinfo");
        else
                pagesize = page_info.pagesize;  /* pick up actual page size */

        /*
         * We need to allocate page aligned, page padded storage
         * for any area that is going to be key protected.
         */
        padded_protsize_1 = ROUND_UP(sizeof(struct private_data_1), pagesize);
        rc = posix_memalign((void **)&p1, pagesize, padded_protsize_1);
        if (rc) {
                perror("posix_memalign");
                exit(1);
        }

        /*
         * Initialize the private data.
         * We can do this before protecting it if we want.
         *
         * Note that the pointer to the private data is in public storage.
         * We only protect the data itself.
         */
        p1->some_data = count;

        /*
         * Construct keysets to use to access the protected structure.
         * Note that these keysets will be in public storage.
         */
        rc = ukeyset_init(&keyset1W, 0);
        if (rc) {
                perror("ukeyset_init");
                exit(1);
        }

        rc = ukeyset_add_key(&keyset1W, key1, UK_WRITE);        /* WRITE */
        if (rc) {
                perror("ukeyset_add_key 1W");
                exit(1);
        }

        keyset1RW = keyset1W;
        rc = ukeyset_add_key(&keyset1RW, key1, UK_READ);        /* R/W */
        if (rc) {
                perror("ukeyset_add_key 1R");
                exit(1);
        }

        /*
         * Restrict access to the private data by applying a private key
         * to the page(s) containing it.
         */
        rc = ukey_protect(p1, padded_protsize_1, key1);
        if (rc) {
                perror("ukey_protect");
                exit(1);
        }

        /*
         * Allow our general code to reference the private data R/W.
         */
        oldset = ukeyset_activate(keyset1RW, UKA_ADD_KEYS);
        if (oldset == UKSET_INVALID) {
                printf("ukeyset_activate failed\n");
                exit(1);
        }

        /*
         * Set up a signal handler for SIGSEGV, to catch the deliberate
         * key violation in the untrusted code.
         */
        sa.sa_sigaction = handler;
        SIGINITSET(sa.sa_mask);
        sa.sa_flags = SA_SIGINFO;
        rc = sigaction(SIGSEGV, &sa, 0);
        if (rc) {
                perror("sigaction");
                exit(1);
        }

        /*
         * Program's main processing loop.
         */
        while (count < 2) {
                /*
                 * When we need to run "untrusted" code, change access
                 * to the private data to R/O by removing write access.
                 */
                (void)ukeyset_activate(keyset1W, UKA_REMOVE_KEYS);

                /*
                 * Call untrusted subroutine here.  It can only read
                 * the protected data passed to it.
                 */
                count = untrusted(p1);

                /*
                 * Restore our full access to private data.
                 */
                (void)ukeyset_activate(keyset1W, UKA_ADD_KEYS);

                p1->some_data = count;
        }
ukey_protect(p1, padded_protsize_1, UKEY_PUBLIC);
        free(p1);
        exit(0);
}