Clés de protection du stockage
Les clés de protection du stockage fournissent un mécanisme qui vous permet d'améliorer la fiabilité de vos programmes.
Les clés de protection s'appliquent aux pages de mémoire et fonctionnent au niveau de la granularité de la page, à l'instar de la sous-routine mprotect , qui peut être utilisée pour protéger en lecture ou en écriture une ou plusieurs pages. Toutefois, avec les clés de stockage, vous pouvez marquer des sections de vos données pour des niveaux spécifiques de protection des accès en lecture et en écriture. La protection par des clés de stockage est une fonction non seulement de la page de données, mais également de l'unité d'exécution qui tente d'accéder. Vous pouvez activer certains chemins de code bien définis pour accéder aux données qui ne sont pas disponibles pour votre programme de plus grande taille, encapsulant ainsi les données de programme critiques et les protégeant contre les dommages accidentels.
Etant donné que l'accès aux pages protégées par clé est un attribut de l'unité d'exécution en cours d'exécution, ce mécanisme s'étend naturellement aux applications à unités d'exécution multiples, mais avec la restriction que celles-ci utilisent uniquement des pthreads 1: 1 (ou portée système). L'approche de la sous-routine mprotect ne fonctionne pas de manière fiable dans un environnement à unités d'exécution multiples, car vous devez supprimer la protection de toutes les unités d'exécution lorsque vous souhaitez accorder l'accès à une unité d'exécution. Vous pouvez utiliser les deux mécanismes simultanément, et les deux sont entièrement appliqués ; par conséquent, votre programme ne peut pas écrire sur une page protégée contre l'écriture même si une clé de protection l'autorise autrement.
- Encapsulez complètement les données privées de votre programme, en limitant l'accès aux chemins de code sélectionnés.
- Protégez les données privées de votre programme contre les dommages accidentels en exécutant toujours avec l'accès en lecture accordé, mais en accordant l'accès en écriture uniquement lorsque vous avez l'intention de modifier les données. Cela peut être particulièrement utile lorsque le code d'un moteur de coeur permet d'appeler du code non sécurisé.
- Lorsque plusieurs clés privées sont disponibles, une granularité supplémentaire de la protection des données est possible.
Vous pouvez simplifier le débogage en concevant votre application en gardant à l'esprit la protection des clés. La définition d'une clé de protection d'une page et la définition de votre jeu de clés d'utilisateur actif sont des appels système, et donc des opérations relativement coûteuses. Vous devez concevoir votre programme de sorte que la fréquence de ces opérations ne soit pas excessive.
Clés de protection utilisateur
- Les pages qui sont exportées en lecture seule à partir du noyau continueront d'être visibles pour votre programme. Ces pages possèdent une clé de protection UKEY_SYSTEM. Cette clé de protection n'est pas une clé de protection qui est sous le contrôle de votre programme, mais elle est toujours accessible par votre programme.
- Toutes les pages de mémoire de votre programme sont initialement affectées à la clé publique de l'utilisateur. Comme indiqué ci-dessus, l'accès au stockage de la clé 0 est toujours accordé, ce qui en fait la clé publique de l'utilisateur.
- Vous pouvez définir des clés de protection uniquement pour vos données normales et partagées. Vous ne pouvez pas, par exemple, protéger des données de bibliothèque, une faible quantité de mémoire partagée avec le noyau ou du texte de programme.
- En fonction du matériel sous-jacent et du choix administratif, seul un nombre limité de clés privées utilisateur (généralement une seule) sont disponibles. Lorsque votre programme affecte une clé privée à une ou plusieurs de ses pages, les données de ces pages ne sont plus disponibles par défaut. Vous devez accorder explicitement un accès en lecture ou en écriture à ces données en entourant les chemins de code qui nécessitent un accès avec des appels à un nouveau service pour gérer votre jeu de clés utilisateur actif.
- Le matériel physique prend probablement en charge des clés de protection supplémentaires qui ne peuvent pas être utilisées comme clés de protection utilisateur.
- Aucun privilège spécial n'est nécessaire pour affecter des clés de protection à une page. La seule condition requise est l'accès en écriture à la page.
- Il n'y a pas de contrôle du droit d'exécution avec les clés de protection.
Si votre programme accède à des données protégées par clé en violation des droits d'accès exprimés dans son jeu de clés utilisateur actif, il reçoit un signal SIGSEGV , comme c'est déjà le cas pour la violation de pages protégées en lecture ou en écriture. Si vous choisissez de gérer ce signal, sachez que les gestionnaires de signaux sont appelés sans accès aux clés privées. Le code de traitement des signaux doit ajouter tous les droits d'accès nécessaires au jeu de clés de l'utilisateur actif avant de référencer les données protégées par clé.
Les processus enfant, créés par l'appel système fork , héritent logiquement de la mémoire et de l'état d'exécution de leur parent. Cela inclut la clé de protection associée à chaque page, ainsi que le jeu de clés utilisateur actif de l'unité d'exécution parent au moment de la bifurcation.
Régions protégées par des clés d'utilisateur
- Région de données
- Région de pile par défaut
- Régions mmaped
- Mémoire partagée connectée à la sous-routine shmat() , sauf dans les cas ci-dessous
- Ces catégories de pages ne peuvent pas utiliser les clés de protection:
- Fichiers ed shmatet mémoire partagée réservée
- Grandes pages (non paginables)
- Texte du programme
- Faible mémoire partagée en lecture seule avec le noyau
Configuration système requise pour la protection par clé
- S'exécute sur un matériel physique qui offre une protection par clé de stockage
- Exécution du noyau 64 bits
- Activer l'utilisation des clés de protection utilisateur
Configuration du programme requise pour la protection des clés
- Se déclare conscient des clés utilisateur et détermine le nombre de clés de protection utilisateur disponibles, le cas échéant, à l'aide de la sous-routine ukey_enable .
- Organisez ses données protégées sur les limites de la page.
- Affectez une clé privée à chaque page que vous souhaitez protéger à l'aide de la sous-routine ukey_protect .
- Si vous protégez les données malloc, n'oubliez pas de les déprotéger avant de les libérer.
- Préparez un ou plusieurs jeux de clés avec la sous-routine ukeyset_init .
- Ajoutez éventuellement les clés requises à votre jeu de clés à l'aide de la sous-routine ukeyset_add_key , afin d'activer les futurs accès en lecture ou en écriture selon les besoins.
- Activez un jeu de clés à l'aide de la sous-routine ukeyset_activate pour accorder les droits d'accès définis dans un jeu de clés.
- Inclure toutes les unités d'exécution M: N (portée du processus)
- Être capable de faire l'objet d'un point de contrôle (par exemple, CHECKPOINT = yes dans l'environnement)
- Gestionnaires de signaux recevant une structure ucontext_t . Le jeu de clés utilisateur précédemment actif se trouve dans ucontext_t.__extctx.__ukeys, un tableau de deux points contenant une valeur de jeu de clés utilisateur 64 bits
- Structures de contexte utilisateur compilées avec __EXTABI__ définies (utilisées par setcontext, getcontext, makecontext, swapcontext)
Sous-routines
| Sous-routine | Descriptif |
|---|---|
| sysconf | A utiliser avec _SC_AIX_UKEYS pour déterminer le nombre de clés d'utilisateur prises en charge (peut être appelé sur les anciennes versions d' AIX) |
| activation_utilisateur | Activez l'environnement de programmation prenant en compte les clés utilisateur pour votre processus et indiquez le nombre de clés utilisateur disponibles |
| ukeyset_init | Initialisez un jeu de clés utilisateur, qui représentera un ensemble de droits d'accès à votre ou vos clés privées |
| clé_ajout_clé | Ajouter un accès en lecture et / ou en écriture pour une clé spécifiée à un jeu de clés |
| clé_remove_utilisateur | Supprimer un accès en écriture ou un accès en écriture, ou les deux, pour une clé spécifiée d'un jeu de clés |
| ensemble_ajout_clés | Ajouter tous les droits d'accès d'un jeu de clés à un autre |
| ukeyset_remove_set | Supprimer tous les droits d'accès d'un jeu de clés d'un autre |
| activation_jeu_clés | Appliquer les droits d'accès d'un jeu de clés à l'unité d'exécution en cours d'exécution |
| ukeyset_ismember | Tester si un droit d'accès donné est contenu dans un jeu de clés |
| ukey_setjmp | Forme étendue de setjmp qui conserve le jeu de clés actif (utilise une structure ukey_jmp_buf ) |
| pthread_attr_getukeyset_np | Obtention de l'attribut keyset d'un pthread |
| pthread_attr_setukeyset_np | Définir l'attribut keyset pour un pthread |
| ukey_protect | Définition d'une clé de protection utilisateur pour une plage de mémoire utilisateur alignée sur la page |
| clé_get_clé | Extraire la clé de protection utilisateur pour une adresse spécifiée |
Débogage
- Lors du débogage d'un programme en cours d'exécution:
- La sous-commande ukeyset affiche le jeu de clés actif.
- La sous-commande ukeyvalue affiche la clé de protection associée à un emplacement de mémoire donné.
- Lors du débogage d'un fichier core, la sous-commande ukeyexcept signale le jeu de clés actif, l'adresse effective de l'exception de clé et la clé de stockage impliquée.
Détails du matériel
- Le AMR est un registre de 64 bits comprenant des paires de 32 bits, une paire par clé, pour un maximum de 32 clés numérotées de 0 à 31.
- Le premier bit de chaque paire représente l'accès en écriture à la clé numérotée correspondante.
- De même, le deuxième bit de chaque paire représente l'accès en lecture à la clé numérotée correspondante.
- Une valeur de bit de 0 accorde l'accès correspondant et une valeur de bit de 1 l'interdit.
- La paire de bits qui accorde l'accès à la clé 0 n'est pas contrôlée par votre programme. La clé d'utilisateur 0 est la clé publique de l'utilisateur, et toutes les unités d'exécution ont toujours un accès complet aux données de cette clé, sans tenir compte de vos paramètres dans le jeu de clés de l'utilisateur actif.
- Toutes les autres paires de bits représentent des clés privées utilisateur, qui, sous réserve de disponibilité, vous permettent de protéger vos données comme vous le souhaitez.
exemple de programme
Voici un exemple de programme prenant en compte les clés d'utilisateur:
#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);
}