pkeys(7)

NUME
pkeys - prezentare generală a cheilor de protecție a memoriei

DESCRIERE
Cheile de protecție a memoriei (pkeys) sunt o extensie a permisiunilor de memorie existente bazate pe pagini. Permisiunile normale de pagini care utilizează tabele de pagini necesită apeluri de sistem costisitoare și invalidări ale TLB (Translation Lookaside Buffer) atunci când se schimbă permisiunile. Cheile de protecție a memoriei oferă un mecanism de modificare a protecțiilor fără a necesita modificarea tabelelor de pagini la fiecare modificare a permisiunilor.

Pentru a utiliza pkeys, software-ul trebuie mai întâi să „eticheteze” o pagină din tabelele de pagini cu o cheie de protecție „pkey”. După ce această etichetă este plasată, o aplicație trebuie doar să modifice conținutul unui registru pentru a elimina accesul la scriere sau tot accesul la o pagină etichetată.

Cheile de protecție funcționează împreună cu permisiunile existente PROT_READ, PROT_WRITE și PROT_EXEC transmise apelurilor de sistem, cum ar fi mprotect(2) și mmap(2), dar acționează întotdeauna pentru a restricționa și mai mult aceste mecanisme tradiționale de permisiune.

În cazul în care un proces efectuează un acces care încalcă restricțiile cheii de protecție, acesta primește un semnal SIGSEGV. A se vedea sigaction(2) pentru detalii privind informațiile disponibile cu acest semnal.

Pentru a utiliza caracteristica pkeys, procesorul trebuie să o accepte, iar nucleul trebuie să conțină suport pentru această caracteristică pe un anumit procesor. De la începutul anului 2016 sunt acceptate doar viitoarele procesoare Intel x86, iar acest hardware suportă 16 chei de protecție în fiecare proces. Cu toate acestea, „pkey 0” este utilizată drept cheie implicită, astfel încât sunt disponibile maximum 15 pentru utilizarea efectivă a aplicațiilor. Cheia implicită este atribuită oricărei regiuni de memorie pentru care nu a fost atribuită în mod explicit o cheie de protecție prin pkey_mprotect(2).

Cheile de protecție au potențialul de a adăuga un nivel de securitate și fiabilitate la aplicații. Dar ele nu au fost concepute în primul rând ca o caracteristică de securitate. De exemplu, WRPKRU este o instrucțiune complet neprivilegiată, astfel încât cheile de protecție sunt inutile în orice caz în care un atacator controlează registrul PKRU sau poate executa instrucțiuni arbitrare.

Aplicațiile trebuie să fie foarte atente pentru a se asigura că nu „li se scurg” cheile de protecție.De exemplu, înainte de a apela pkey_free(2), aplicația ar trebui să se asigure că nu există nicio memorie care să aibă atribuită acea cheie de protecție „pkey”. În cazul în care aplicația a lăsat atribuită cheia de protecție eliberată, un viitor utilizator al acelei chei de protecție ar putea modifica din greșeală permisiunile unei structuri de date nerelaționată, ceea ce ar putea avea un impact asupra securității sau stabilității. În prezent, nucleul permite ca pkey_free(2) să fie apelat pentru cheile de acces în uz, deoarece efectuarea verificărilor suplimentare necesare pentru a interzice acest lucru ar avea implicații asupra performanței procesorului sau a memoriei. Implementarea verificărilor necesare este lăsată la latitudinea aplicațiilor. Aplicațiile pot pune în aplicare aceste verificări prin căutarea în fișierul /proc/pid/smaps a regiunilor de memorie cărora le-a fost atribuită cheia „pkey”. Mai multe detalii pot fi găsite în proc(5).

Orice aplicație care dorește să utilizeze chei de protecție trebuie să poată funcționa fără acestea. Acestea ar putea fi indisponibile deoarece hardware-ul pe care rulează aplicația nu le acceptă, codul nucleului nu conține suport, suportul nucleului a fost dezactivat sau deoarece toate cheile au fost alocate, poate de către o bibliotecă pe care o utilizează aplicația. Se recomandă ca aplicațiile care doresc să utilizeze chei de protecție să apeleze pur și simplu pkey_alloc(2) și să testeze dacă apelul reușește, în loc să încerce să detecteze suportul pentru această caracteristică în orice alt mod.

Deși nu este necesar, suportul hardware pentru cheile de protecție poate fi enumerat cu instrucțiunea cpuid. Detalii despre cum se face acest lucru pot fi găsite în „Intel Software Developers Manual”. Nucleul efectuează această enumerare și expune informațiile în /proc/cpuinfo sub câmpul „flags”. Șirul „pku” din acest câmp indică suportul hardware pentru chei de protecție, iar șirul „ospke” indică faptul că nucleul conține și a activat suportul pentru chei de protecție.

Aplicațiile care utilizează fire de execuție și chei de protecție trebuie să fie deosebit de atente. Firele moștenesc drepturile cheilor de protecție ale părintelui în momentul apelului de sistem clone(2). Aplicațiile ar trebui fie să se asigure că propriile permisiuni sunt adecvate pentru firele-copil în momentul în care se apelează clone(2), fie să se asigure că fiecare fir-copil poate efectua propria inițializare a drepturilor cheilor de protecție.

  Comportamentul gestionarului de semnale
De fiecare dată când este invocat un gestionar de semnal (inclusiv semnalele imbricate), firului i se atribuie temporar un set nou, implicit, de drepturi de cheie de protecție care înlocuiesc drepturile din contextul întrerupt. Acest lucru înseamnă că aplicațiile trebuie să restabilească din nou drepturile de cheie de protecție dorite la intrarea într-un gestionar de semnal dacă drepturile dorite diferă de cele implicite. Drepturile oricărui context întrerupt sunt restabilite la returnarea gestionarului de semnal.

Acest comportament al semnalului este neobișnuit și se datorează faptului că registrul x86 PKRU (care stochează drepturile de acces la cheia de protecție) este gestionat cu același mecanism hardware (XSAVE) care gestionează registrele în virgulă mobilă. Comportamentul semnalului este același cu cel al registrelor în virgulă mobilă.

  Apeluri de sistem cu chei de protecție
Nucleul Linux implementează următoarele apeluri de sistem legate de chei de protecție: pkey_mprotect(2), pkey_alloc(2) și pkey_free(2).

Apelurile de sistem Linux pentru pkey sunt disponibile numai dacă nucleul a fost configurat și construit cu opțiunea CONFIG_X86_INTEL_MEMORY_PROTECTION_KEYS.

EXEMPLE
Programul de mai jos alocă o pagină de memorie cu permisiuni de citire și scriere. Apoi scrie câteva date în memorie și le citește cu succes înapoi. După aceea, încearcă să aloce o cheie de protecție și interzice accesul la pagină prin utilizarea instrucțiunii WRPKRU. Apoi încearcă să acceseze pagina, ceea ce ne așteptăm acum să provoace un semnal fatal pentru aplicație.

    $ ./a.out
    memoria tampon conține: 73
    pe cale de a citi din nou memoria tampon...
    Eroare de segmentare (conținutul memoriei a fost descărcat)

  Sursa programului
    #define _GNU_SOURCE
    #include <err.h>
    #include <unistd.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <sys/mman.h>

    int
    main(void)
    {
        int status;
        int pkey;
        int *buffer;

        /*
         * Alocă o pagină de memorie.
         */
        buffer = mmap(NULL, getpagesize(), PROT_READ | PROT_WRITE,
                      MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
        if (buffer == MAP_FAILED)
            err(EXIT_FAILURE, "mmap");

        /*
         * Introduce niște date aleatorii în pagină (întotdeauna în regulă pentru a le accesa).
         */
        *buffer = __LINE__;
        printf("memoria tampon conține: %d\n", *buffer);

        /*
         * Alocă o cheie de protecție:
         */
        pkey = pkey_alloc(0, 0);
        if (pkey == -1)
            err(EXIT_FAILURE, "pkey_alloc");

        /*
         * Dezactivează accesul la orice memorie cu „pkey” stabilită,
         * chiar dacă nu există niciuna în acest moment.
         */
        status = pkey_set(pkey, PKEY_DISABLE_ACCESS);
        if (status)
            err(EXIT_FAILURE, "pkey_set");

        /*
         * Stabilește cheia de protecție pe „memorie tampon”.
         * Rețineți că este în continuare de citire/scriere în ceea ce privește mprotect(),
         * iar precedentul pkey_set() are prioritate față de acesta.
         */
        status = pkey_mprotect(buffer, getpagesize(),
                               PROT_READ | PROT_WRITE, pkey);
        if (status == -1)
            err(EXIT_FAILURE, "pkey_mprotect");

        printf("pe cale de a citi din nou memoria tampon...\n");

        /*
         * Aceasta se va prăbuși, deoarece am interzis accesul.
         */
        printf("memoria tampon conține: %d\n", *buffer);

        status = pkey_free(pkey);
        if (status == -1)
            err(EXIT_FAILURE, "pkey_free");

        exit(EXIT_SUCCESS);
    }

CONSULTAȚI ȘI
pkey_alloc(2), pkey_free(2), pkey_mprotect(2), sigaction(2)

TRADUCERE
Traducerea în limba română a acestui manual a fost făcută de Remus-Gabriel Chelu <remusgabriel.chelu@disroot.org>

Această traducere este documentație gratuită; citiți Licența publică generală GNU Versiunea 3 (https://www.gnu.org/licenses/gpl-3.0.html) sau o versiune ulterioară cu privire la condiții privind drepturile de autor. NU se asumă NICIO RESPONSABILITATE.

Dacă găsiți erori în traducerea acestui manual, vă rugăm să trimiteți un e-mail la translation-team-ro@lists.sourceforge.net.