proof carrying code (pcc) - cs.unibo.itbabaoglu/courses/security05-06/lucidi/pcc.pdf · il processo...

33
Proof Carrying Code (PCC) Vedi: Necula-Lee www.cs.unibo.it/~laneve/html/metodi1/paper3.ps visione tradizionale: il software è codice eseguibile visione più appropriata: il software è codice eseguibile e verificabile A tal fine: il software porta con se un certificato (la prova) con il quale è possibile verificare la sua correttezza dinamica 1

Upload: doannhi

Post on 27-Oct-2018

224 views

Category:

Documents


0 download

TRANSCRIPT

Proof Carrying Code (PCC)

Vedi: Necula-Lee www.cs.unibo.it/~laneve/html/metodi1/paper3.ps

visione tradizionale: il software è codice eseguibile

visione più appropriata: il software è codice eseguibile e verificabile

A tal fine:

il software porta con se un certificato (la prova) con il quale è possibile verificare la sua correttezza dinamica

1

i certificati sono già presenti nella tecnologia attuale: con le firme digitali

software

firma digitale

server

verifica della firma digitale

CPU

vantaggio: veloce, semplice, sicuro

svantaggio: non c’ è alcuna garanzia sulla correttezza del codice (ma solo sulla provenienza)

2

la verifica di codice in Java:

Interpreter JIT Compiler

CPU

Java Bytecode Verifier

software

server

vantaggio: general purpose

svantaggio: il JBV è limitato nelle verifiche, l’Interpreter/JIT è costoso e/o grande

3

il desiderata :

CPU

Theorem Prover

software

server

vantaggio: potente

svantaggio: difficile da implementare

4

la visione pcc: Prove Esplicite

vantaggi: server

software

prova di correttezza

verificatore di correttezza

CPU

dimostratore di correttezza

1. le prove hanno una dimensione ragionevole (0-10% del codice)

2. il verificatore di correttezza è semplice e piccolo ( 50KB)

3. non è necessario che il dimostratore di correttezza sia “affidabile”

Questioni a cui bisogna rispondere:

1. come funziona il “dimostratore di correttezza” ?

2. come si verifica la “correttezza” ?

3. cosa dimostra la “prova di correttezza” ? 5

il processo PCC:

1. definizione formale della politica di correttezza. Il produttore di codice definisce in maniera formale e pubblicizza una politica di correttezza (ad esempio, il proprio codice termina, oppure non produce memory leaks, etc.) che desidera venga rispettata dal codice che intende eseguire.

2. certificazione. Il produttore compila il codice e dimostra che il proprio codice è corretto rispetto alla politica pubblicizzata.

3. invio. La dimostrazione viene inviata assieme al codice ad ogni consumatore.

4. validazione. Il consumatore verifica che il codice è corretto secondo la certificazione allegata.

6

esempio: sicurezza della memoria

A è un array (il primo indirizzo) di n elementi di tipo booleano.

correttezza: - A memorizza soltanto booleani - se la funzione termina allora restituisce un booleano - 0 e 1 sono i soli booleani - le letture e le scritture sono ammesse tra A+1 e A + n

politica di correttezza pubblicizzata dal produttore: (assiomi e regole di inferenza)

A : array(T,L) I > 0 I ≤ L A : array(T,L) I > 0 I ≤ L E : T

sel( A + I) : T safewr(A + I, E)

I ≤ E I > 0 0 : bool 1 : bool E ≤ E I - 1 ≤ E

7

(typerd) (wr)

(bool0) (bool1) (leqid) (dec)

codice inviato dal produttore

codice eseguibile + annotazioni + prova

scheletro di prova

bool1 leqid typerd IP0: a0 : array(bool, n0) IP2: i1 ≤ n0 IP3: i1 ≥ 0

. . . .

PRE: a: array(bool,n) r := 1 i := n

L0 : INV : r: bool && i ≤ nif i ≤ 0 goto L1t := a + i t := *t r := r && t i := i –1 goto L0

L1 : return r POST : r : bool

8

9

osservazione: il codice contiene annotazioni all’inizio ed alla fine e in prossimità dei

cicli (vedremo poi chi inserisce tali annotazioni).

le annotazioni (interessanti) – gli invarianti – sono generati da salti all’indietro (cicli)

lo scheletro di prova è una sequenza di regole (determinismo e assenza di backtracking – cf. programmazione logica)

Generazione delle Condizioni di Verifica (Verification Condition – VC)

il codice viene scandito da una procedura. Quando si incontra una annotazione, la procedura genera un predicato (assunzione) che viene utilizzato dal verificatore per dimostrare la politica di correttezza. Il verificatore consta di due parti:

1. politiche di correttezza

2. assunzioni precedentemente verificate.

graficamente:

Ricostruttore di ProveVerificatore

producer user

software annotazione

prova di correttezza

n CVVCGe

Assiomatizzazione (Politica di Correttezza)

il generatore di condizioni di verifica (VCGen) è una procedura che deriva predicati secondo lo stile di Floyd-Hoare (che è dettagliato in seguito)

il verificatore è un programma logico (scritto in Prolog)

10

comportamento di VCGen e del verificatore

registri simbolici a : a0

n : n0 i : i0 r : r0 t : t0

assunzioni

PRE: a: array(bool,n) r := 1 i := n

L0 : INV : r: bool && i ≤ n if i ≤ 0 goto L1t := a + i t := *t r := r && t i := i –1 goto L0

L1 : return r POST : r : bool

11

registri simbolici a : a0

n : n0 i : i0 r : r0 t : t0

assunzioni (IP0) a0 : array(bool, n0)

PRE: a: array(bool,n) r := 1 i := n

L0 : INV : r: bool && i ≤ n if i ≤ 0 goto L1t := a + i t := *t r := r && t i := i –1 goto L0

L1 : return r POST : r : bool

12

registri simbolici a : a0

n : n0 i : n0 r : 1 t : t0

assunzioni (IP0) a0 : array(bool, n0)

PRE: a: array(bool,n) r := 1 i := n

L0 : INV : r: bool && i ≤ n if i ≤ 0 goto L1t := a + i t := *t r := r && t i := i –1 goto L0

L1 : return r POST : r : bool

osservazione: I valori simbolici di i ed r sono cambiati.

osservazione: ad L0 si incontra una annotazione di tipo INV

occorre invocare il verificatore per dimostrarne la validità

13

il verificatore: Assiomi e Regole di Inferenza

a0 : array(bool, n0)

Verificatore

(Scheletro di) Prova

bool1 leqid typerd IP0

IP2

IP3

. . . .

osservazione: Il verificatore tenta di derivare l’invariante INV attraverso la politica di correttezza e lo scheletro di prova.

INV è istanziato come: 1 : bool && n0 ≤ n0

che segue utilizzando (bool1) e (leqid), come specificato nello scheletro di prova.

14

la verifica continua con il generatore di condizioni di verifica:

registri simbolici a : a0

n : n0 i : i1 r : r1 t : t0

assunzioni (IP0) a0 : array(bool, n0) (IP1) r1 : bool (IP2) i1 ≤ n0

PRE: a: array(bool,n) r := 1 i := n

L0 : INV : r: bool && i ≤ n if i ≤ 0 goto L1t := a + i t := *t r := r && t i := i –1 goto L0

L1 : return r POST : r : bool

osservazione: i ed r hanno valori simbolici

15

Calcolando gli effetti delle istruzioni if i ≤ 0 goto L1t := a + i

otteniamo:

registri simbolici a : a0

n : n0 i : i1 r : r1 t : a0+i1

assunzioni (IP0) a0 : array(bool, n0) (IP1) r1 : bool (IP2) i1 ≤ n0 (IP3) i1 > 0

(IP4) at(L1) ⇒ (r : bool && i≤0)

PRE: a: array(bool,n) r := 1 i := n

L0 : INV : r: bool && i ≤ n if i ≤ 0 goto L1t := a + i t := *t r := r && t i := i –1 goto L0

L1 : return r POST : r : bool

osservazione: due assiomi sono stati aggiunti e il valore simbolico di t è stato modificato

16

la prossima istruzione da eseguire, t := *t

è problematica perchè avviene una lettura, quindi bisogna controllare che la lettura riguarda una cella dell’array

registri simbolici a : a0

n : n0 i : i1 r : r1 t : sel(a0+i1)

assunzioni (IP0) a0 : array(bool, n0) (IP1) r1 : bool (IP2) i1 ≤ n0 (IP3) i1 ≥ 0

(IP4) at(L1) ⇒ (r : bool && i<0)

PRE: a: array(bool,n) r := 1 i := n

L0 : INV : r: bool && i ≤ n if i < 0 goto L1t := a + i t := *t r := r && t i := i –1 goto L0

L1 : return r POST : r : bool

17

per verificare sel(a0 + i1) si invoca il verificatore:

Assiomi e Regole di Inferenza

a0 : array(bool, n0) r1 : bool i1 ≤ n0 i1 ≥ 0 at(L1) ⇒ (r : bool && i<0)

(Scheletro di) Prova

bool1 leqid typerd IP0

IP2

IP3

. . . .

Verificatore

osservazione: sel(a0 + i1) segue da (typerd) con ipotesi IP0, IP2 e IP3.

la verifica continua in questo modo alla fine della verifica il codice è pronto per essere eseguito

18

la logica di Floyd-Hoare

è il cuore del generatore di condizioni di verifica (VCGen).

la logica di Floyd-Hoare (1967) è un sistema definito per verificare la correttezza di programmi scritti con diagrammi di flusso.

con questa tecnica si derivano triple:

φ P ψ

dove P è il programma da verificare, mentre φ e ψ sono formule (di solito in logica dei predicati). Il significato di questa tripla è il seguente:

correttezza parziale: se il programma P inizia in una configurazione che soddisfa la proprietà φ e la sua esecuzione termina, allora la configurazione finale verifica la proprietà ψ

osservazione: il programma può non terminare

19

Le regole seguenti sono una istanziazione della tecnica di Floyd-Hoare a bytecode come quello dell’esempio precedente P[i] = x := e φi P[i] φi+1 dom(P)=1..n

φ { e/x } P[i] φ φ1 P φn+1 (φ && e) P[L] ψ P[i] = if e goto L

φ P[i] (φ && e) P[i] = return φ P[L] ψ P[i] = goto L

φ P[i] φ φ P[i] ξ

φ ψ ψ P[i] χ χ ξ

φ P[i] ξ

20

esempio. La somma di due numeri con le operazioni di incremento e decremento.

1 if (x = 0) goto 5 2 y := y+1 3 x := x-1 4 goto 1 5 return

proprietà da dimostrare: (P è il programma di sopra) { x + y = m + n && x ≥ 0 } P { y = m+n && x = 0 }

annotazioni: { x + y = m + n && x ≥ 0 } if (x = 0) goto 5

{ x + y = m + n && x > 0 } y := y+1 { x + y = m + n + 1 && x > 0 } x := x-1 { x + y = m + n && x ≥ 0 } goto 1 { x = 0 && x + y = m + n} return { x = 0 && x + y = m + n}

due problemi: 1. come individuare le formule

2. la regola φ ψ ψ P[i] χ χ ξ φ P ξ

21

1. L’ individuazione delle formule della logica di Floyd-Hoare la tecnica standard è di procedere dalla conclusione (la proprietà che si vuole dimostrare) verso la premessa (cf. Dijkstra weakest preconditions) osservazione: questa tecnica funziona bene se non ci sono salti

in caso di salti: . . .

instr_h . . . instr_k = goto h instr_k+1

. . . ed occorre che φ = ξ ciò può richiedere diverse iterazioni per individuare la formula corretta

Ad esempio, nel caso di sopra, si voleva dimostrare la proprietà:

POST { x = 0 && y = m + n}

Quindi i passi sono i seguenti:

22

if (x = 0) goto 5y := y+1 x := x-1 goto 1 return

{ x = 0 && x + y = m + n }

PASSO 1

if (x = 0) goto 5 y := y+1 x := x-1 goto 1

{ x = 0 && x + y = m + n}

return

{ x = 0 && x + y = m + n}

PASSO 2

φ if (x = 0) goto 5 y := y+1 x := x-1

φ goto 1

{ x = 0 && x + y = m + n}

return

{ x = 0 && x + y = m + n}

PASSO 3 23

φ if (x = 0) goto 5

φ { x-1/x }{ y+1/y }

y := y+1

φ { x-1/x } x := x-1

φ goto 1

{ x = 0 && x + y = m + n }

return

{ x = 0 && x + y = m + n }

PASSO 5

φ if (x = 0) goto 5 y := y+1

φ { x-1/x } x := x-1

φ goto 1

{ x = 0 && x + y = m + n }

return

{ x = 0 && x + y = m + n }

PASSO 4

24

per eseguire il prossimo passo, si osservi che deve esistere una ψ tale che:

ψ if (x = 0) goto 5 { ψ && x ≥ 0 }

osservazione: questa ψ deve soddisfare le seguenti due condizioni:

1. { ψ && x = 0} { x = 0 && x + y = m + n}

2. { ψ && x > 0 } φ { x-1/x }{ y+1/y }

ne segue che

ψ {x + y = m + n && x ≥ 0}

φ {x + y = m + n && x ≥ 0}

dunque otteniamo:

25

{ x + y = m + n && x ≥ 0 }

if (x = 0) goto 5

{ x + y = m + n && x ≥ 0 }{ x-1/x }{ y+1/y }

y := y+1

{ x + y = m + n && x ≥ 0 } { x-1/x } x := x-1

{ x + y = m + n && x ≥ 0 }

goto 1

{ x = 0 && x + y = m + n}

return

{ x = 0 && x + y = m + n}

PASSO 6

in questo semplice caso, il calcolo delle annotazioni termina senza ulteriori iterazioni (perchè φ ψ). In generale occorre iterare.

26

ossevazione: nel contesto PCC, il problema del calcolo delle annotazioni non è rilevante per quanto riguarda il server, visto che esso riceve il codice con le formule (le annotazioni).

verificare una prova è tanto semplice quanto fare type-checking

al contrario, il problema suddetto è rilevante dal punto di vista del produttore, che deve specificare le formule e fornire una prova.

2. La regola φ ψ ψ P[i] χ χ ξ

φ P ξ questa è la regola problematica del sistema di Floyd-Hoare perchè ci sono sistemi logici, ad esempio la logica dei predicati, per cui non esiste alcun algoritmo in grado di decidere “φ ψ”

in generale la verifica non è completamente automatica

27

esempio (performance dei filtri di pacchetti di rete): i filtri di pacchetti di rete sono delle procedure che controllano se un pacchetto in entrata (dalla rete) può essere interessante per l’applicazione utente o meno Questi filtri sono stati implementati utilizzando la tecnica PCC con i seguenti risultati:

lunghezza del codice annotato : 3-4 volte maggiore del bytecode

overhead per la verifica delle prove : 5% (minore delle firme digitali)

tempo per produrre la prova : 1-3 ms (che è ammortizzato presto)

28

Typed Assembly Languages (TAL)

Vedi: Morrisett-Crary-Walker-Grew www.cs.unibo.it/~laneve/html/metodi1/paper4.ps

Il problema di PCC è che tutto il carico del metodo è lasciato al code-producer, il quale deve

1. individuare le annotazioni giuste,

2. dimostrare la correttezza secondo la logica di Floyd-Hoare.

Per ovviare a questa critica è stata definita la seguente tecnica:

1. il programmatore scrive codice ad alto livello, arricchendo il codice con le proprietà attese, utilizzando un opportuno linguaggio di tipi.

2. gli assembly languages sono arricchiti con tipi espliciti che esprimono le proprietà.

29

3. il compilatore traduce le proprietà ad alto livello in tipi dell’assembly language, verificando che il codice è ben tipato.

4. la dimostazione di correttezza si riduce perciò al type-checking (verifica che è nota essere molto efficiente).

Graficamente, la situazione è illustrata dal diagramma seguente.

30

31

codice sorgente in

linguaggio di alto livello

inserisce invarianti attraverso: costrutti linguistici theorem proving ausilio del programmatore

front end

codice intermedio typing proof

generatore di codice

compilatore certificante

(preserva la prova) codice macchina typing proof

osservazione. Il codice intermedio ha annotazioni ( tipi) e il generatore di codice compila anche le annotazioni.

il codice intermedio è

una sequenza di istruzioni assembler I1 ; . . . ; In

ogni istruzione Ik può essere “etichettata”. In questo caso il formato dell’istruzione è:

L : code [ 1 ; . . . ; h ] Γ . { Ik1 ; . . . ; Ik

m }

ed il significato è che, quando si salta all’istruzione L, occorre che i registri e la pila soddisfino la formula:

1 . . . h . Γ

osservazione. La formula 1 . . . h . Γ è “polimorfa”: vale per ogni possibile istanza delle variabili 1 . . . h (di solito con tipi)

32

tecnologia attuale. Bytecode Verification (à la Java)

verifica che le risorse usate sono finite;

politiche di sicurezza (nessun messaggio sulla rete è inviato dopo la lettura dal disco locale);

correttezza.

problemi aperti.

maggiore controllo sulla memoria;

type & proof engeeneering (individuare la giusta sintassi dei tipi e il corretto sistema di inferenza);

. . .

33