Alex si occupa di sicurezza dei software da Mozilla, in particolare, di sandboxing e anti-exploitation per Firefox. In precedenza era ingegnere del software per lo United States Digital Service e faceva parte del consiglio di amministrazione di Python e Django Software Foundations.
C’è un bug che colpisce gli iPhone, un altro che colpisce Windows e un altro ancora i server che eseguono Linux. A prima vista, potrebbe sembrare che non abbiano nulla in comune, ma, in realtà, tutti e tre esistono perché il software che veniva sfruttato è stato scritto con linguaggi di programmazione che possono incorrere in una categoria di errori chiamata ”memory unsafety.” — cioè la possibilità di copiare arbitrariamente dei bit di memoria da una locazione ad un’altra. Consentendo questo tipo di vulnerabilità, linguaggi come C e C++ hanno facilitato per anni un flusso quasi infinito di vulnerabilità critiche per la sicurezza informatica.
Videos by VICE
Immaginate di avere un programma con una lista di 10 numeri. Cosa succederebbe se chiedeste al programma di restituirvi l’undicesimo elemento di quella lista? Probabilmente, si verificherebbe un errore di qualche tipo — quello che succede in un qualsiasi linguaggio di programmazione memory-safe (per esempio, Python o Java). Invece, un linguaggio di programmazione non sicuro da quel punto di vista cercherà in qualsiasi locazione di memoria l’undicesimo elemento (come se esistesse) e proverebbe ad accedervi. A volte questo può causare un crash, ma in molti casi viene restituito ciò che si trova in quella locazione di memoria, anche se questa non ha nulla a che fare con la nostra lista. Questo tipo di vulnerabilità è chiamato ”buffer-overflow,” ed è uno dei tipi di vulnerabilità memory-unsafe più comuni. HeartBleed, che ha colpito il 17% dei server web sicuri su internet, ha sfruttato il buffer-overflow, riuscendo a leggere 60 kilobyte dopo la fine di una lista, incluse le password e i dati di altri utenti.
Tuttavia, ci sono altri tipi di vulnerabilità di sicurezza della memoria che riguardano C e C++.
Altri esempi di vulnerabilità sono il ”type confusion” (confondere il tipo di valore esistente in un posto in memoria), lo ”use after free” (utilizzare un pezzo di memoria dopo aver detto al sistema operativo di averlo già utilizzato) e lo ”use of uninitialized memory” (utilizzare un pezzo di memoria prima di averci memorizzato qualcosa). Insieme, queste formano alcune delle vulnerabilità più comuni da software ampiamente usati come Firefox, Chrome, Windows, Android e iOS. Ho seguito gli avvisi di sicurezza per questi progetti per più di un anno e in quasi tutte le release di questi prodotti, più della metà delle vulnerabilità sono di memory-unsafety. L’aspetto più inquietante è che le vulnerabilità più gravi (generalmente quelle che possono portare all’esecuzione del codice a distanza, dove un aggressore può eseguire qualsiasi codice che vuole sul vostro computer — in generale, la vulnerabilità più grave di tutte) sono quasi sempre di memory-unsafety. Dalle mie ricerche sulla sicurezza delle librerie di elaborazione delle immagini open source ImageMagick e GraphicsMagic, nell’ultimo anno, ho trovato più di 400 vulnerabilità di sicurezza della memoria.
Se queste vulnerabilità sono così frequenti, se possono causare così tanti danni e se esistono già linguaggi che aggirano il problema, allora perché è ancora così comune? In primo luogo, mentre ora c’è una buona scelta per i linguaggi che prevengono le vulnerabilità di memory-unsafety, non è sempre andata così. C e C++ esistono da decenni e sono estremamente popolari, mentre linguaggi sicuri utilizzabili per la programmazione di basso livello (browser web e sistemi operativi), tipo Rust e Swift, hanno appena iniziato a diffondersi.
Un problema più importante è che, generalmente, quando gli sviluppatori pensano a quale linguaggio di programmazione usare per un nuovo progetto, lo scelgono in base ai linguaggi conosciuti dal loro team, alle prestazioni e all’ecosistema di librerie che possono essere sfruttate. La sicurezza non è quasi mai una considerazione fondamentale. Questo significa che i linguaggi che enfatizzano la sicurezza, a scapito della facilità d’uso, partono svantaggiati.
Inoltre, molti dei più importanti progetti software per la sicurezza di internet sono stati avviati anche più di dieci anni fa — per esempio Linux, OpenSSL e il webserver Apache hanno tutti più di vent’anni. Per progetti di grandi dimensioni come questi, riscrivere semplicemente tutto in un nuovo linguaggio non è un’opzione praticabile; bisogna farlo in modo incrementale. Questo significa che i progetti dovranno essere scritti in due linguaggi, invece che in uno solo, aumentando così la complessità. Può anche significare riqualificare un team enorme, il che richiede tempo e denaro.
Infine, il problema più grande è il fatto stesso che molti sviluppatori non capiscono che c’è un problema. Molti ingegneri software ritengono che il problema non è tanto che linguaggi come il C o C++ facilitino queste vulnerabilità, ma che altri ingegneri scrivono codice pieno di bug. Secondo questa visione, il fatto che cercare di ottenere l’undicesimo elemento in una lista di 10 elementi possa causare una vulnerabilità non è un problema, piuttosto è problematico che qualcuno abbia scritto codice che cerca di ottenere l’undicesimo elemento, perché non ne capisce abbastanza di programmazione. In altre parole, alcune persone pensano che il problema non sia il linguaggio di programmazione in sé, ma solo che alcuni programmatori non sanno come usarlo bene.
Uno dei criteri per scegliere un linguaggio di programmazione dovrebbe essere ”In che modo questa scelta influirà sulla sicurezza?”
Molti sviluppatori trovano questa posizione convincente, al di là di ogni evidenza — queste vulnerabilità sono onnipresenti, e colpiscono anche le aziende con i maggiori budget per la sicurezza e gli sviluppatori più talentuosi. Una cosa è discutere i compromessi e come possiamo rendere più semplice imparare i linguaggi memory-safe, ma dopo migliaia e migliaia di vulnerabilità che potrebbero essere prevenute sfruttando un linguaggio di programmazione migliore, è chiaro che “cercare di non avere bug” non è una strategia praticabile.
Tuttavia, ci sono anche delle buone notizie. Non tutti fanno finta che il problema non esista. Rust (piccolo disclaimer: il mio datore di lavoro, Mozilla, è lo sponsor principale di Rust) è un linguaggio di programmazione relativamente nuovo pensato per essere usato per ogni problema che possono causare C e C ++, pur conservando la memoria così da evitare le insidie della sicurezza. L’utilizzo di Rust si sta diffondendo, viene adottato da Mozilla, Google, Dropbox e Facebook, e credo che questo dimostri che molte persone stanno iniziando a cercare soluzioni sistematiche ai problemi di sicurezza. Inoltre, il linguaggio di programmazione Swift di Apple è anche sicuro per la memoria, mentre il suo predecessore, Objective-C, non lo era.
Ci sono una serie di cose che possiamo fare per accelerare la ricerca di una soluzione completa al problema di sicurezza in corso, rappresentato dalla memory-unsafety. In primo luogo, possiamo migliorare la quantificazione dei danni causati dalla memory-unsafety. Ad esempio, The CVE project, un database che raccoglie delle vulnerabilità conosciute in tutto il settore, potrebbe segnalare per ogni vulnerabilità se si tratta di un problema di memory-unsafety e se un linguaggio che cura questo aspetto avrebbe potuto impedirlo. Questo ci aiuterebbe a rispondere a domande come: ”Quali progetti trarrebbero i maggiori benefici da un linguaggio di programmazione memory-safe?”
In secondo luogo, dovremmo investire nella ricerca su come favorire la migrazione efficace di grandi progetti software esistenti verso linguaggi di memoria sicuri. Al momento, sembra eccessivo anche il semplice pensare di tradurre qualcosa come il kernel Linux in un altro linguaggio di programmazione. Una ricerca dedicata a capire quali strumenti potrebbe facilitare questo processo, o come progettare dei linguaggi di programmazione per facilitarlo, ridurrebbe drasticamente il costo del miglioramento di progetti più vecchi e di più ampia portata.
Infine, possiamo estendere la cultura della sicurezza all’interno dell’ingegneria del software. Quando ho imparato per la prima volta C++ al college, si dava per scontato che a volte il programma andasse in crash. Nessuno mi aveva mai detto che molti di questi crash erano anche potenziali vulnerabilità di sicurezza. La mancanza di consapevolezza fin dai primi passi della carriera di uno sviluppatore riguardo la connessione tra i bug, i crash e i problemi di sicurezza, è emblematica di come la sicurezza sia una preoccupazione secondaria nell’ingegneria del software e di come la insegniamo. Quando si creano nuovi progetti si dovrebbe accettare che uno dei criteri per la scelta di un linguaggio di programmazione sia ”Che impatto avrà sulla sicurezza questa scelta?”
Al momento, la memory-unsafety è un flagello per il nostro settore. Ma non è necessario che ogni release di Windows o Firefox corregga decine e decine di vulnerabilità di sicurezza evitabili. Dobbiamo smettere di trattare ogni vulnerabilità di memory-unsafety come un incidente isolato, e trattarle come il problema sistemico profondamente radicato che sono. Poi, dobbiamo investire nella ricerca ingegneristica su come costruire strumenti migliori per risolvere la questione. Se facciamo quel cambiamento e quell’investimento possiamo migliorare drasticamente la sicurezza informatica per tutti gli utenti, e rendere HeartBleed, WannaCry e i bug di iPhone da milioni di dollari molto meno comuni.
Questo articolo è apparso originariamente su Motherboard US.