Claves de protección de almacenamiento

Las claves de protección de almacenamiento proporcionan un mecanismo para mejorar la fiabilidad de los programas.

Las claves de protección se aplican a las páginas de memoria y funcionan en el nivel de página de granularidad, similar a la subrutina mprotect , que se puede utilizar para proteger una o más páginas de lectura o escritura. Sin embargo, con las claves de almacenamiento puede marcar secciones de los datos para niveles específicos de protección de acceso de lectura y escritura. La protección mediante claves de almacenamiento es una función no sólo de la página de datos, sino también de la hebra que intenta acceder. Puede habilitar determinadas vías de acceso de código bien definidas para acceder a los datos que no están disponibles para el programa más grande, encapsulando así los datos críticos del programa y protegiéndolos contra daños accidentales.

Puesto que el acceso a las páginas protegidas por clave es un atributo de la hebra en ejecución, este mecanismo se amplía de forma natural a las aplicaciones multihebra, pero con la restricción de que estas utilicen solo pthreads 1: 1 (o ámbito del sistema). El enfoque de subrutina mprotect no funciona de forma fiable en un entorno multihebra, ya que debe eliminar la protección de todas las hebras cuando desee otorgar acceso a cualquier hebra. Puede utilizar ambos mecanismos simultáneamente, y ambos se aplican completamente; por lo tanto, el programa no puede escribir en una página protegida contra grabación aunque una clave de protección lo permita de otro modo.

Los usos de ejemplo de protección de claves incluyen:
  • Encapsula completamente los datos privados del programa, limitando el acceso a las vías de acceso de código seleccionadas.
  • Proteja los datos privados del programa de daños accidentales ejecutando siempre con acceso de lectura otorgado, pero otorgando acceso de escritura sólo cuando tenga la intención de modificar los datos. Esto puede ser especialmente útil cuando el código en un motor principal permite llamadas a código que no es de confianza.
  • Cuando hay varias claves privadas disponibles, es posible una granularidad adicional de la protección de datos.

Puede simplificar la depuración diseñando la aplicación teniendo en cuenta la protección de claves. El establecimiento de la clave de protección de una página y el establecimiento del conjunto de claves de usuario activo son llamadas al sistema y, por lo tanto, operaciones relativamente costosas. Debe diseñar el programa para que la frecuencia de estas operaciones no sea excesiva.

Claves de protección de usuario

Las siguientes directrices y consideraciones se aplican cuando se utilizan claves de protección:
  • Las páginas que se exportan de sólo lectura desde el kernel seguirán siendo visibles para el programa. Estas páginas tienen una clave de protección de UKEY_SYSTEM. Esta clave de protección no es una clave de protección que esté bajo el control del programa, pero siempre es accesible por el programa.
  • Todas las páginas de memoria del programa inicialmente tienen asignada la clave pública de usuario. Como se ha indicado anteriormente, el acceso al almacenamiento de clave 0 siempre se otorga, lo que hace que sea la clave pública de usuario.
  • Puede establecer claves de protección sólo para los datos normales y compartidos. Por ejemplo, no puede proteger los datos de la biblioteca, la poca memoria compartida con el kernel o el texto del programa.
  • En función del hardware subyacente y de la elección administrativa, sólo está disponible un número limitado de claves privadas de usuario (normalmente sólo una). Cuando el programa asigna una clave privada a una o varias de sus páginas, los datos de dichas páginas ya no están disponibles de forma predeterminada. Debe otorgar explícitamente acceso de lectura o escritura a estos datos rodeando las vías de acceso de código que requieren acceso con llamadas a un nuevo servicio para gestionar el conjunto de claves de usuario activo.
  • Es probable que el hardware físico soporte claves de protección adicionales que no están disponibles para su uso como claves de protección de usuario.
  • No se necesita ningún privilegio especial para asignar claves de protección a una página. El único requisito es el acceso de escritura actual a la página.
  • No hay control de autorización de ejecución con claves de protección.

Si su programa accede a datos protegidos por clave en violación de los derechos de acceso expresados en su conjunto de claves de usuario activo, recibe una señal SIGSEGV , como ya ocurre en el caso de violación de páginas protegidas contra lectura o grabación. Si elige manejar esta señal, tenga en cuenta que los manejadores de señal se invocan sin acceso a las claves privadas. El código de manejo de señales debe añadir los derechos de acceso necesarios al conjunto de claves de usuario activo antes de hacer referencia a los datos protegidos por clave.

Los procesos hijo, creados por la llamada al sistema fork , heredan lógicamente la memoria de su padre y el estado en ejecución. Esto incluye la clave de protección asociada con cada página, así como el conjunto de claves de usuario activo de la hebra padre en el momento de la bifurcación.

Regiones protegidas por claves de usuario

Las claves de protección de usuario pueden proteger páginas en las regiones siguientes:
  • Región de datos
  • Región de pila predeterminada
  • Regiones mmaped
  • Memoria compartida conectada con la subrutina shmat() , excepto como se indica a continuación
  • Estas categorías de páginas no pueden utilizar claves de protección:
    • Archivos ed de shmaty memoria compartida anclada
    • Páginas grandes (no paginables)
    • Texto de programa
    • Memoria baja compartida de sólo lectura con el kernel

Requisitos previos del sistema para la protección de claves

La protección de claves de almacenamiento es un mecanismo de protección de páginas privilegiadas específico del hardware que el kernel AIX® pone a disposición de los programas de aplicación. Para utilizar esta característica, el sistema debe:
  • Se está ejecutando en hardware físico que proporciona protección de claves de almacenamiento
  • Estar ejecutando el kernel de 64 bits
  • Habilitar el uso de claves de protección de usuario

Requisitos previos de programa para la protección de claves

Para utilizar claves de usuario, el programa debe:
  • Declare que reconoce la clave de usuario y determine cuántas claves de protección de usuario están disponibles, si las hay, con la subrutina ukey_enable .
  • Organice sus datos protegidos en los límites de página.
  • Asigne una clave privada a cada página que desee proteger con la subrutina ukey_protect .
  • Si proteges los datos de malloc, recuerda desprotegerlos antes de liberarlos.
  • Prepare uno o más conjuntos de claves con la subrutina ukeyset_init .
  • Posiblemente añada las claves necesarias al conjunto de claves con la subrutina ukeyset_add_key , para habilitar futuros accesos de lectura o escritura según sea necesario.
  • Active un conjunto de claves con la subrutina ukeyset_activate para otorgar los derechos de acceso definidos en un conjunto de claves.
El programa no debe:
  • Incluir cualquier M: N (ámbito de proceso) pthreads
  • Ser capaz de realizar un punto de comprobación en él (por ejemplo, tener CHECKPOINT = yes en el entorno)
Nota: Cuando un programa reconoce la clave de usuario, tiene un contexto adicional asociado para representar su conjunto de claves de usuario activo. Esto se puede ver en:
  • Manejadores de señales que reciben una estructura ucontext_t . El conjunto de claves de usuario activo anteriormente se encuentra en ucontext_t.__extctx.__ukeys, una matriz de dos puntos que contiene un valor de conjunto de claves de usuario de 64 bits
  • Estructuras de contexto de usuario compiladas con __EXTABI__ definidas (utilizadas por setcontext, getcontext, makecontext, swapcontext)

Subrutinas

Se proporcionan las siguientes subrutinas de kernel de AIX para utilizar claves de protección:
Subrutina Descripción
sysconf Utilícelo con _SC_AIX_UKEYS para determinar el número de claves de usuario soportadas (se puede llamar en versiones anteriores de AIX)
habilitación_ukey Habilite el entorno de programación con reconocimiento de claves de usuario para el proceso e informe de cuántas claves de usuario están disponibles
inicio_ukeyet Inicializar un conjunto de claves de usuario, que representará un conjunto de derechos de acceso a la clave o claves privadas
clave_add_ukey Añadir acceso de lectura o escritura, o ambos, para una clave especificada a un conjunto de claves
clave_eliminación_conjunto_claves Eliminar o escribir acceso, o ambos, para una clave especificada de un conjunto de claves
ukeyset_add_set Añadir todos los derechos de acceso de un conjunto de claves a otro
ukeyset_remove_set Eliminar todos los derechos de acceso de un conjunto de claves de otro
activo_ukeyset_activo Aplicar los derechos de acceso de un conjunto de claves a la hebra en ejecución
ukeyset_ismember Probar si un derecho de acceso determinado está contenido en un conjunto de claves
conjunto_ukey_jmp Formato ampliado de setjmp que conserva el conjunto de claves activo (utiliza una estructura ukey_jmp_buf )
pthread_attr_getukeyset_np Obtener el atributo de conjunto de claves de un pthread
pthread_attr_setukeyset_np Establecer el atributo de conjunto de claves para un pthread
protección_ukey_clave Establecer una clave de protección de usuario para un rango de memoria de usuario alineado con páginas
ukey_getkey Recuperar la clave de protección de usuario para una dirección especificada

Depurar

El mandato dbx añade soporte limitado para las claves de protección:
  • Al depurar un programa en ejecución:
    • El submandato ukeyset muestra el conjunto de claves activo.
    • El submandato ukeyvalue muestra la clave de protección asociada con una ubicación de memoria determinada.
  • Al depurar un archivo principal, el submandato ukeyexcept notifica el conjunto de claves activo, la dirección efectiva de la excepción de clave y la clave de almacenamiento implicada.

Detalles de hardware

El conjunto de claves de usuario activo en el contexto de ejecución de una hebra con reconocimiento de claves es paralelo al registro de máscara de autorización de hardware (AMR) real en formato, representado por el tipo de datos abstracto de ukeyset_t . Esta información se proporciona únicamente para fines de depuración. Utilice sólo los servicios de programación definidos para configurar el conjunto de claves de usuario activo.
  • El AMR es un registro de 64 bits que comprende pares de 32 bits, un par por clave, para un máximo de 32 claves numeradas de 0 a 31.
    • El primer bit de cada par representa el acceso de escritura a la clave numerada correspondiente.
    • De forma similar, el segundo bit de cada par representa el acceso de lectura a la clave numerada correspondiente.
  • Un valor de bit de 0 otorga el acceso correspondiente y un valor de bit de 1 lo deniega.
  • El programa no controla el par de bits que otorga acceso a la clave 0. La clave de usuario 0 es la clave pública de usuarioy todas las hebras siempre tienen acceso completo a los datos de esta clave, sin tener en cuenta los valores del conjunto de claves de usuario activo.
  • Todos los demás pares de bits representan claves privadas de usuario, que, en función de la disponibilidad, puede utilizar para proteger los datos como considere oportuno.

programa de ejemplo

A continuación se muestra un ejemplo de programa con reconocimiento de claves de usuario:

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