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