Prevedere il Buffer Overrun
usando Programmazione Lineare
e Analisi Statica
Buffer Overrun Detection
using Linear Programming
and Static Analysis
Analisi e Verifica di Programmi
Alberto De Lazzari - Fabio Pustetto
Introduzione

Scrivere oltre i limiti di un buffer in memoria
– C permette la manipolazione diretta dei puntatori senza alcun tipo di
controllo sui limiti
– Dati validi possono essere sovrascritti
– Hacker sfruttano per eseguire codice malvagio



Caricato in memoria come valido
acquisizione dei privilegi di root
Gli approcci per rilevare il buffer overrun possono essere di tipo
– Statico

esamina il codice sorgente per eliminare questo tipo di bug prima che il codice sia
distribuito
– Dinamico




previene gli “attacchi” basati sul bug
perciò non sono eliminati come nell’analisi statica
generalmente causano il crash dell’applicazione quando è rilevato un attacco
Controlli ridondanti portano ad overhead
Linee Guida:
La definizione e l’implementazione del tool che permette
l’analisi statica del codice, si divide nei seguenti punti:




Uso di analisi statica per modellare stringhe di codice C sorgente
come programma lineare
Definizione e implementazione di risolutori veloci e scalabili basati su
tecniche innovative della programmazione lineare. La soluzione al
programma lineare determina i limiti del buffer
Tecniche per rendere l’analisi del programma sensibile al contesto
Efficacia di altre tecniche di analisi come il partizionamento statico
per eliminare bug dal codice sorgente
-> tool deve essere scalabile
Tecniche dinamiche:
approfondimento

Proteggere l’indirizzo di ritorno sullo stack
– controllo d’integrità sui record evitando la modifica dell’indirizzo di
ritorno
– si può rilevare e prevenire un attacco di tipo stack smashing

Corrompere lo stack scrivendo oltre la sua fine, permettendo di cambiare
l’indirizzo di ritorno di una funzione
– Altre tecniche generalizzano il concetto precedente estendendolo a puntatori,
variabili locali e argomenti di funzioni

Proteggere tutti gli accessi ai puntatori
–
–
–
–
cifrandoli quando sono memorizzati
decifrandoli quando sono caricati nei registri della CPU
non evita la possibilità di distruggere il puntatore
impedisce di ottenere un valore di puntatore prevedibile in memoria

non si conosce la chiave di decifratura
Tecniche statiche:
approfondimento

suddivise in tre classi
1. tool guidati da annotazioni

utilizza annotazioni date dall’utente
– Es: pre/post-condizioni di una funzione
2. tool che usano l’analisi simbolica
3. tool che estraggono un modello dal
codice sorgente e lo utilizzano per
rilevare la presenza di bug
1. tool guidati da
annotazioni



CSSV converte un programma in C in un
programma intero, con asserzioni di correttezza
incluse e usa un algoritmo di analisi statica
conservativa per trovare manipolazioni erronee sugli
interi che direttamente si traducono in bug sul
codice C.
L’analisi è fatta sulla base di annotazioni (chiamate
contract) che sono usate per rendere l’analisi
interprocedurale
Split è simile a CSSV anche se usa un’analisi flowsensitive e pre/post-condizioni fornite dall’utente.
2. tool che usano l’analisi
simbolica

Alcuni tool (Archer, ARray CHeckER) eseguono il codice
in maniera
– Simbolica

permette di analizzare il codice senza conoscere l’esatto valore delle
variabili che si stanno manipolando
– Interprocedurale

permette di trattare una stessa variabile che si trova in metodi
diversi
– mantenendo l’informazione riguardo alle variabili in un database
man mano che l’esecuzione procede


l’esecuzione di statement può portare alla modifica dello stato del
programma
quando in uno statement si accede ad un buffer, Archer controlla,
attraverso il database, se l’accesso avviene nel rispetto dei limiti del
buffer
3. tool che estraggono un modello
dal codice sorgente e lo utilizzano
per rilevare la presenza di bug



Si estrae un modello dal codice sorgente
Si modellano le stringhe come tipi di dato
astratto e si trasforma il rilevamento del
buffer overrun in un problema di analisi sul
range di valori interi
Il tool presentato in questo paper rientra in
questa classe di tecniche introducendo
un’analisi dei puntatori più precisa
Architettura complessiva

plug-in per CodeSurfer
– tool utilizzato per la comprensione del codice
– costruisce una rappresentazione totale del programma
attraverso





grafo delle dipendenze di sistema composto dai grafi di
dipendenza di ciascuna procedura
grafo del controllo del flusso interprocedurale
alberi di sintassi astratta per le espressioni del programma
informazioni sui puntatori
Slicing
– partizionamento di un programma
– Tecnica per visualizzare le dipendenze
– restringe l’attenzione alle componenti di un programma
rilevanti per la valutazione di espressioni
– backward slicing / forward slicing
Generazione dei vincoli …

buf: puntatore a buffer modellato da 4 variabili di vincolo:
– buf!alloc!max
– buf!alloc!min

numero massimo e minimo di byte allocati per il buffer buf

numero massimo e minimo di byte usati da buf
– buf!used!max
– buf!used!min

i: variabile intera, modellata da 2 variabili di vincolo
– i!max

indica il massimo valore di i

denota il minimo valore di i
– i!min

I vincoli modellano il programma in maniera
– non sensibile al flusso

ignora l’ordine degli statement
– non sensibile al contesto

non fa differenze per molteplici punti di chiamata, alla stessa funzione
…

4 statement generano
vincoli
–
–
–
–

dichiarazioni di buffer
assegnamenti
chiamate a funzione
statement di ritorno
Es: alla riga 17 del codice
– strcpy(copy,buffer)



Risultano i vincoli (associati
ai puntatori dei buffer):
copy!used!max ≥
buffer!used!max
copy!used!min ≤
buffer!used!min
… alcuni chiarimenti


Poiché è ignorata l’analisi di flusso, i predicati come counter < 10
sono ignorati
Se generiamo un vincolo del tipo counter!max ≥ counter!max + 1
(generato da counter++), questo non può essere interpretato da un
risolutore di programmi lineari
– dunque questo statement è modellato trattandolo come due statement:
counter’ = counter + 1 e counter = counter’. I due vincoli catturano il
fatto che il puntatore è incrementato di uno e sono accettabili dal
risolutore, anche se il programma lineare risultante sarà non
ammissibile

Una variabile di programma che ottiene il suo valore dall’ambiente o
dall’input dell’utente in un modo non controllato, è considerata
insicura
– Se consideriamo, ad esempio, lo statement getenv(“PATH”), che ritorna
il percorso di ricerca, può ritornare una stringa arbitrariamente lunga.
Per riflettere il fatto che la stringa può essere arbitrariamente lunga,
sono generati dei vincoli del tipo getenv$return!used!max ≥ +∞ e
getenv$return!used!min ≤ 0. Allo stesso modo, una variabile intera i
inserita dall’utente genera i vincoli: i!max ≥ +∞ e i!min ≤ -∞
Analisi di alterazione

Vincoli generati, passati al modulo di analisi di alterazione
– Scopo: ottenere dei vincoli che siano successivamente utilizzabili
dal risolutore
– Solo valori finiti

2 passi
– Identificare e rimuovere variabili che danno valore infinito
– Identifica e rimuove variabili di vincolo non inizializzata

il sistema di vincoli è esaminato per vedere se
– vincoli di tipo max hanno un estremo inferiore finito
– vincoli di tipo min hanno un estremo superiore finito

Requisito non soddisfatto se
– non sono state inizializzate nel codice sorgente
– gli statement che influiscono sulle variabili di programma non sono state
catturate dal generatore di vincoli
 => non esiste modello per funzioni di librerie
Risoluzione dei vincoli…

si utilizza la programmazione lineare
– modella e manipola nuovi tipi di vincoli
– garantisce correttezza dei risultati

Un programma lineare e' un problema di
ottimizzazione:
minimizza cTx
riferito a Ax ≥ b
– A matrice m x n di costanti
– b e c vettori di costanti
– x è vettore di variabili

sistema di m disequazioni in n variabili, necessarie per
trovare i valori delle variabili che soddisfano i vincoli e
minimizzano la funzione obiettivo cTx, nel campo dei numeri
reali finiti.
…

Insieme di vincoli ottenuti dopo l'analisi è insieme di
vincoli lineari
– si può allora formulare come problema di programmazione
lineare
– Obiettivo: ottenere i valori




buf!alloc!max
buf!alloc!min
buf!used!max
buf!used!min
– che rendono la differenza tra byte allocati e byte usati dal puntatore al
buffer buf più piccola possibile, rispettando i vincoli
– se tutte le variabili max hanno minimo finito e tutte le
variabili min hanno massimo finito, vale che:

min Σbuf (buf!alloc!max - buf!alloc!min + buf!used!max - buf!used!min)
…

Trovare soluzioni intere è Programmazione
Lineare Intera: problema NP-completo ben
conosciuto
– NP = è noto un algoritmo che termina in tempo polinomiale rispetto alla
dimensione dei dati
– NP-hard = un algoritmo per risolvere uno di questi problemi può essere
convertito in un algoritmo per risolvere un qualunque problema NP
– NP-completi = sia NP che NP-hard

programma lineare ammissibile
– si trovano valori finiti per tutte le variabili (rispettando
i vincoli)

soluzione ammissibile è ottima
– se massimizza (o minimizza) il valore della funzione

programma lineare inammissibile
– se non ha soluzioni ammissibili
…


soluzione ottima per determinare i limiti del buffer
nessun programma può essere illimitato
– vincoli esaminati dall'analizzatore di alterazione




tutte le variabili max della funzione obiettivo hanno limite inferiore finito
Minimizzando le variabili max otterremo ancora valori finiti
Similmente per min
se programma lineare non ha soluzioni ammissibili
– non si può assegnare arbitrariamente dei valori finiti alle variabili per
ottenere soluzioni ammissibili
– Es: settare tutte le variabili di max a +∞ e quelle di min a -∞ => ci
sarebbero troppi falsi allarmi!

approccio in cui si rimuovere un piccolo insieme di vincoli per rendere
il sistema ammissibile
– noto come Irreducibly Inconsistent Set (IIS)

insieme minimale di vincoli: l'intero insieme dei vincoli di un IIS è
inammissibile ma ogni sottoinsieme è ammissibile
Elastic Filtering algorithm


1.
Dato un insieme di vincoli determina un IIS (se esiste) e garantisce
che nel caso ce ne siano più di uno almeno uno venga rilevato
si può iterare l'algoritmo più volte sul risultato dell'iterazione
precedente per creare il più piccolo programma lineare ammissibile
da uno non ammissibile
esamina l'insieme dei vincoli C per
determinare ammissibilità
Se è ammissibile STOP
Altrimenti -> 2
2.
3.
4.
allora esiste almeno un C’  C che
è IIS
genera dopo un certo numero di
iterazioni, C-C’ ammissibile
settare i valori delle variabili max e
min in C' rispettivamente a ∞ e -∞
5.
ci sono variabili a ∞
rimuovere i vincoli alterati da
queste variabili: C’’  C-C‘
D=C-C’-C”
Risolvere i vincoli
gerarchicamente

Elastic Filtering algorithm veloce ma
anche un’approssimazione
– può rimuovere più vincoli di quelli necessari
per avere una soluzione ammissibile
risolutore gerarchico

scomporre l’insieme dei vincoli in insiemi più
piccoli e risolverli separatamente
– grafo diretto aciclico (DAG)
 ogni insieme di vincoli è associato a un vertice del
DAG e gli archi rappresentano le dipendenze tra
essi
 modifica a ∞ un numero inferiore di variabili, risulta
più preciso e determina dei valori precisi
Individuare overrun

basata sui valori restituiti dal risolutore e dai valori
dell’analisi di alterazione
– buffer puntato da header ha 2048 byte allocati e la sua
lunghezza può variare tra 1 e 2048 byte => non si può
verificare un buffer overrun => header è segnato
come “sicuro”. La stessa cosa vale per buf.
– buffer puntato da ptr ha un numero di byte allocati
[1024..2048] mentre la lunghezza può variare
[1..2048]. ptr fa parte di due statement. Alla riga (6)
ptr punta ad un buffer lungo 2048 byte mentre alla
riga (9) punta ad un buffer lungo 1024 byte. La non
sensibilità al contesto dell’analisi significa che non
possiamo fare differenze tra i due punti del programma
e dunque possiamo derivare che al massimo ptr è di
2048 byte. In questo tipo di situazione in cui si verifica
che


ptr!alloc!min < ptr!used!max ≤ ptr!alloc!max
possiamo avere un overrun. Questo però è un falso
positivo dovuto alla non sensibilità del contesto dell’analisi
– variabile copy: copy!alloc!max < copy!used!max e c’è
un’esecuzione del programma in cui saranno scritti più
byte di quelli che possono essere ottenuti

Concludiamo dunque che in questa situazione si verifica
un overrun
Aggiungere la sensibilità
al contesto

generazione dei vincoli
– non è sensibile al contesto
– si considera ogni punto di chiamata come un
assegnamento delle variabili in input reali alle
variabili in input formali e il ritorno dalla funzione
come un assegnamento delle variabili in output
formali alle variabili in output reali

si fondono le informazioni attraverso i punti di
chiamata, ottenendo un’analisi imprecisa
2 tecniche


constraint inlining
summary information
constraint inlining…

Inlining nelle funzioni
– rimpiazza una chiamata di
funzione con un’istanza del corpo
della funzione
– gli argomenti reali sono sostituiti a
quelli formali
– eseguita a tempo di compilazione
– crea una nuova istanza dei vincoli
della funzione chiamata ad ogni
punto di chiamata

Se non sensibile al contesto
– si trattano differenti punti di
chiamata ad una funzione, in
modo identico
Ad ogni punto di chiamata di una
funzione, produciamo i vincoli per la
funzione con le variabili locali e i
parametri formali, rinominati
unicamente per quel punto di
chiamata
…


La sensibilità al contesto si può ottenere modellando ciascun punto
di chiamata alla funzione come un insieme di assegnamenti di
istanze rinominate delle variabili formali
un assegnamento per ciascun parametro in input formale rinominato
della funzione
– allevia il problema di unire le informazioni attraverso differenti chiamate
alla stessa funzione
– per il puntatore cc1 otteniamo il range [0…2047] per cc1!alloc e
[1…2048] per cc1!used
– per il puntatore cc2 otteniamo il range [0…1023] per cc2!alloc e
[1…1024] per cc2!used

miglioramento rispetto a non sensibile al contesto, ma…
– Se numero esponenziale di contesti di chiamata



il sistema di vincoli avrà un numero molto grande di variabili
ci sarà anche un grande numero di vincoli
non può funzionare con le chiamate di funzioni ricorsive
summary information…


supera i difetti del constraint inlining
si ottiene un “riassunto” per ciascuna funzione f
per “simulare” l’effetto della chiamata
– Vincoli riassuntivi



riassumono l’effetto della funzione in termini di variabili di
vincolo rappresentanti variabili globali e parametri formali
della funzione stessa
Es: i vincoli generati dalla funzione copy_buffer
Rimpiazzare parametri formali dei vincoli riassuntivi con quelli
reali corrispondenti a ciascun punto di chiamata
…

Questo approccio non causa un accrescimento del
numero di variabili di vincolo
– è più efficiente ma meno preciso del constraint inlining
– può manipolare anche le funzioni ricorsive

Es:
– i valori per cc1!used, cc1!alloc, cc2!used e cc2!alloc sono gli
stessi che si ottengono usando il constraint inlining
– i valori di copy!alloc e copy!used sono [0…2047] e [1...2048]
rispettivamente

Questo perché i valori di queste variabili ottenute dalla chiamata alla
riga (7) e alla riga (10) sono “unite”
– Il constraint inlining ritorna i valori [0...2047] e [1…2048] per
copy!alloc e copy!used, rispettivamente per la chiamata alla riga
(7)
– ritorna [0…1023] e [1…1024] per la chiamata alla riga (10)
Sperimentazione su casi
reali

Testato su
–
–
–
–
–
–


wu-ftpd (2.5.0 e 2.6.2) e sendmail (8.7.6 e 8.11.6)
Pentium 4 Xeon 3GHz, 4 GB RAM
sistema operativo Debian Linux 3.0
CodeSurfer 1.8 e compilatore gcc-3.2.1
risolutore gerarchico
Context insensitive
CodeSurfer per filtrare i warning
Testato per provare se riusciva a scoprire bug
già conosciuti e se riusciva a scoprirne altri non
conosciuti
Testing wu-ftpd 2.6.2


18mila righe di codice
tool ha segnalato 178 warning
– 14 overrun mai segnalati. Eccone uno:


•
•
•
•
potenziale overrun su buffer puntato
da accesspath in read_servers_line nel
file rdservers.c:
8192 byte possono essere copiati in un
buffer in cui sono allocati al massimo
4095 byte
La funzione fgets può copiare fino a 8192 (dimensione di BUFSIZ) byte in buffer
hcp e acp, puntano all’interno di buffer => qualcuno può far puntare hcp o acp ad un buffer lungo
fino a 8192 byte => overflow sul buffer puntato sia da accesspath sia da hostname
La procedura read_server_line è chiamata, ad es, nel main di ftprestart.c con due buffer locali,
hostaddress e configdir, per i quali sono allocati 32 byte e 4095 byte, rispettivamente
La chiamata legge il contenuto del file _PATH_FTPSERVERS, che tipicamente ha accesso
privilegiato
•
•
In alcune configurazioni questo file può essere scritto da un utente locale
Scegliendo dunque delle stringhe di input in maniera attenta, si può sfruttare l’overflow per ottenere accessi
privilegiati
Testing wu-ftpd 2.5.0


16mila righe di codice
tool ha segnalato 139 warning
– overflow su una variabile globale, mapped_path, che punta ad un
buffer nella procedura do_elem nel file ftpd.c

Uso improprio di strcat(mapped_path,dir)
– dir può derivata (indirettamente) dall’input dell’utente
– range per mapped_path!used è [0…+∞] mentre per mapped_path!alloc
è [4095…4095]

La chiamata a strcat è modellata
secondo quattro vincoli lineari
– I primi due vincoli rendono il
programma lineare non ammissibile
– risultato in dst!used!max settato a +∞
– mapped_path!used!max settato +∞
Testing sendmail

8.7.6
– 38mila righe di codice
– tool ha segnalato 295 warning


alcuni overflow di tipo off-by-one
8.11.6
– si potrebbe verificare la scrittura di n+1 byte in un buffer che ha n byte
allocati
– 68mila righe di codice
– Tool ha segnalato 453 warning

Uno coinvolge la funzione crackaddr nel file headers.c, che fa il
parsing della stringa di un indirizzo e-mail
– memorizza la stringa dell’indirizzo in un buffer statico locale, buf lungo
MAXNAME + 1 byte.
– molti controlli di limite per evitare l’overflow ma la condizione di <>
nella stringa From è imprecisa e porta all’overflow
– eseguite molte operazioni matematiche sui puntatori => bp!alloc!max
= +∞ e bp!used!max = +∞ (bp puntatore a buf)
– warning esiste anche se le condizioni di controllo sui limiti sono corrette
Performance

tempi di esecuzione impiegati per il controllo del codice
– CODESURFER: tempo impiegato dall’applicazione per l’analisi del
codice
– GENERATOR: tempo per la generazione dei vincoli
– TAINT: analisi di alterazione
– LPSOLVE: tempi per il metodo IIS
– HIERSOLVE: tempi per il metodo gerarchico
– PRE-TAINT: vincoli generati prima dell’analisi di alterazione
– POST-TAINT: vincoli generati dopo l’analisi di alterazione
sensibilità al contesto su
casi reali





Testata su wuftpd-2.6.2 usando sia l’approccio con constraint inlining sia
l’approccio summary constraint
Aggiungere la sensibilità al contesto non trova nuovi overrun ma fornisce
migliori valori dati dal risolutore dei vincoli
Test: controllo delle variabili “affinate”
senza sensibilità al contesto: valori per 7310 variabili
summary constraint: raffinamento di 72 variabili
– Il numero di variabili di vincolo è lo stesso dell’analisi senza sensibilità al contesto
– il numero di vincoli può cambiare e cresce dell’ 1%


l’approccio introduce alcuni vincoli (i “riassunti”) e rimuove altri vincoli (i vincoli degli
assegnamenti dei vecchi punti di chiamata)
Constraint inlining
– specializza l’insieme di vincoli per ciascun punto di chiamata


aumento di 5,8 volte nel numero di vincoli
aumento di 8,7 volte nel numero di variabili di vincolo
– 7310 variabili dell’analisi senza sensibilità al contesto sono specializzate in 63704
variabili basate sul contesto di chiamata
– Su 63704 variabili specializzate, 7497 variabili sono molto più precise
– Su 7310 variabili non specializzate, 406 sono più precise in almeno un contesto di
chiamata
Effetto dell’analisi dei
puntatori

ridurre i falsi negativi con l’uso dell’analisi dei
puntatori
– tool ha la capacità di gestire, in maniera arbitraria, i
livelli di dereferenziamento

confronto risultati restituiti utilizzando i puntatori
e i risultati ottenuti senza analisi dei puntatori:
– su sendmail sono generati 251 warning a differenza
dei 295 generati con l’analisi dei puntatori

tool fallisce nel generare i vincoli per alcuni
statement
Imperfezioni

flow insensitivity produce molti falsi allarmi
– Con partizionamento è possibile affrontare questo tipo di
problema




operazione manuale e dispendiosa
benefici della context sensitivity sono limitati dalla flow
insensitivity dell’analisi
modellando i vincoli in termini di puntatori ai buffer
piuttosto che buffer stessi, possiamo perdere gli
overrun, portando a falsi negativi.
algoritmi di analisi dei puntatori sono flow insensitive e
context insensitive
– generare vincoli in termini di buffer avrebbe portato come
risultato ad un maggiore numero di vincoli e
conseguentemente ad un maggiore numero di falsi allarmi
Bibliografia











http://en.wikipedia.org/wiki/Buffer_overrun
Analysis and verification: Buffer overrun detection using linear programming and
static analysis Vinod Ganapathy, Somesh Jha, David Chandler, David Melski, David
Vitek, 10th ACM conference on Computer and communications security, 2003
The twenty most critical internet security vulnerabilities www.sans.org/top20
L. O. Andersen. Program Analysis and Specialization for the C Programming
Language. PhD thesis, DIKU, Univ. Of Copenhagen, 1994
C. Cowan, S. Beattie, J. Johansen, and P.Wagle PointGuardTM: Protecting pointers
from buffer overflow vulnerabilities. In 12th USENIX Sec. Symp., 2003
V. Ganapathy, S. Jha, D. Chandler, D. Melski, and D. Vitek.
Buffer overrun detection using linear programming and static analysis. 2003. UWMadison Comp. Sci. Tech. Report 1488 ftp://ftp.cs.wisc.edu/pub/techreports/reports/2003/tr1488.ps.Z
CPLEX Optimizer. www.cplex.com
D. Wagner, J. S. Foster, E. A. Brewer, and A. Aiken. A first step towards automated
detection of buffer overrun vulnerabilities. In Network and Distributed System
Security (NDSS), 2000.
Y. Xie, A. Chou, and D. Engler. ARCHER: Using symbolic, path-sensitive analysis to
detect memory access errors. In 9° European Soft. Engg. Conf. and 11th ACM Symp.
on Foundation of Soft. Engg. (ESEC/FSE), 2003.
CodeSurfer http://www.grammatech.com/products/codesurfer
Scarica

Buffer Overrun Detection using Linear Programming and Static