Questo sito utilizza cookie per raccogliere informazioni sull'utilizzo. Cliccando su questo banner o navigando il sito, acconsenti all'uso dei cookie. Leggi la nostra cookie policy.OK

cyber security

Progettare sistemi sicuri (software e hardware): i principi generali

Lo strumento principale per progettare sistemi sicuri, contro cyber attacchi, è il linguaggio di specifica e ancor più quello con cui programmiamo i sistemi. Prevenire è importante quanto reprimere il cyber crime. Vediamo come

14 Mag 2018

Pierpaolo Degano

Dipartimento di Informatica, Università di Pisa

Letterio Galletta

IMT Istituto di Alti Studi, Lucca


Così come avviene nel vivere civile, i comportamenti contrari alla legge e all’ordine nello spazio cibernetico possono essere contrastati prevenendoli e quando ciò si riveli insufficiente, reprimendoli.

Al momento tuttavia la repressione a posteriori è la principale arma di difesa contro questo genere di crimini. Infatti, la maggior parte degli sforzi per garantire la sicurezza e proteggere la riservatezza dei nostri dati e delle nostre comunicazioni viene indirizzata a scoprire se vi sono degli attacchi, a valutarne la loro pericolosità e a bloccarli, a volte a scapito della qualità del servizio che un’organizzazione sta offrendo o usando. Vogliamo qui insistere sulla necessità di prevenire i comportamenti criminosi e sui vantaggi che ne derivano, e su come si possano costruire sistemi di elaborazione che siano il più possibile resistenti ad azioni criminali certificandone a priori la qualità.

I passi per sistemi IT sicuri

Semplificando al massimo, ci sono almeno tre passi da fare per sviluppare sistemi software sicuri (e anche hardware) che consistono: (1) nell’individuazione di ciò che il sistema deve fare e di ciò che vogliamo proteggere, (2) nella specifica del comportamento del sistema stesso e infine (3) nella sua realizzazione o implementazione.

Prima di arrivare a una situazione soddisfacente, sarà necessario ripetere più volte questi passi intercalandoli tra loro, o a causa di modifiche che si rendano indispensabili per adeguare il sistema al mutato ambiente d’uso o per migliorarne le prestazioni o addirittura il funzionamento.

Il primo passo attiene principalmente al committente e ovviamente coinvolge anche chi realizzerà il sistema sia nella stesura dei suoi requisiti sia nella definizione delle caratteristiche e del livello di sicurezza voluti.

Gli uni riguardano la cosiddetta correttezza del manufatto e riguardano le sue proprietà funzionali, mentre le altre spesso vanno sotto il nome di proprietà non funzionali. Si noti che gli aspetti di sicurezza cui siamo qui interessati sono principalmente del secondo tipo e che essi coinvolgono non solo l’accesso e l’uso dei dati, ma anche il modo e l’ordine temporale con cui tali attività vengono sviluppate.

Il secondo passo richiede l’uso di linguaggi di specifica, una sorta di linguaggi di programmazione ad alto livello, che ci permettono di trascurare moltissimi dettagli realizzativi e di concentrarci sul comportamento del sistema in esame e sulle sue proprietà di interesse. Il significato delle specifiche è definito in termini matematico-logici, quindi senza alcuna ambiguità o incompletezza, né contraddittorietà. Un immediato vantaggio di lavorare con teorie logiche è che, espressa formalmente una specifica, se ne può certificare il buon comportamento, dimostrando che possiede le proprietà individuate al passo precedente, sia funzionali che non funzionali (evitiamo qui di considerare proprietà che non siano dimostrabili, ma torneremo brevemente su questo punto nel seguito). Inoltre, la certificazione che la specifica in esame gode delle proprietà richieste può essere sostenuta da una serie di strumenti automatici, o addirittura esser fatta in modo completamente meccanico.

Una volta certificata la specifica del sistema e dimostrate le sue proprietà, si passa a dettagliare la descrizione ad alto livello usando un linguaggio di programmazione adeguato. L’adeguatezza non si misura solo nell’efficienza dei programmi prodotti o nella facilità con cui si scrivono, ma anche nella presenza e all’efficacia di strumenti di supporto alla programmazione. Qui siamo particolarmente interessati a strumenti che verifichino il comportamento dei programmi e controllino la loro esecuzione. In particolare c’è bisogno di un supporto per garantire che il comportamento del programma a tempo di esecuzione realizzi tutte le funzionalità del sistema specificato (correttezza) e che il passo di codifica conservi le proprietà non funzionali, quali la segretezza e/o l’accessibilità di alcuni dati e la loro non modificabilità da parte di entità non autorizzate, la robustezza della crittografia e dei protocolli per lo scambio di informazione, e infine la qualità dei servizi offerti in termini di tempi di risposta e di accessibilità. Spesso questi strumenti sono parte integrante del compilatore del linguaggio di programmazione, il cui compito fondamentale è quello di tradurre i programmi da un linguaggio il più possibile vicino al programmatore in uno (quasi) immediatamente eseguibile dalla macchina, colmando le grosse differenze che vi sono.

La traduzione di un programma in uno equivalente (quasi) interpretabile dall’hardware avviene per fasi, trasformando le varie rappresentazioni in modo da avvicinarle a quella finale. In ciascuna fase il programma via via trasformato è analizzato in alcuni suoi aspetti mediante alcuni componenti del compilatore. Tra gli strumenti che aiutano nella certificazione e nella verifica del software ci sono quelli che effettuano le cosiddette analisi statiche, i generatori di codice e i controllori a tempo di esecuzione (run-time monitors). Sebbene l’area dei compilatori (e degli interpreti) e dei verificatori sia un campo affascinante su cui molto c’è da dire, ci limitiamo a elencare molto sommariamente gli strumenti di cui sopra, e omettiamo del tutto i dettagli tecnici che sarebbero necessari per una loro presentazione più accurata.

Alcuni strumenti di sostegno alla realizzazione e alla certificazione

I vari analizzatori statici esaminano il testo del programma ignorando i dati di ingresso e deducono alcune proprietà che, se dimostrate, varranno a tempo di esecuzione su tutti i possibili dati di ingresso. Questi analizzatori devono scrutinare un numero finito di istruzioni che compongono il programma e quindi operano in tempo finito, perché ignorano i dati di ingresso che sono potenzialmente infiniti. Inoltre, essi sono progettati in modo da essere particolarmente efficienti e da richiedere tempo e spazio per ottenere le risposte che sono del tutto accettabili; per giunta queste analisi si effettuano poche volte, prima di rilasciare il prodotto, e quindi non ne rallentano il funzionamento. Purtroppo, non tutte le proprietà sono dimostrabili guardando solo il testo di un programma, ma richiedono la sua esecuzione che potrebbe anche non terminare mai. Pertanto, il risultato degli analizzatori statici non può che essere approssimato, ma come si dice, sul lato buono: se l’analisi assicura che un problema non può sorgere, esso non si manifesterà per nessun dato di ingresso e in nessuna esecuzione; può tuttavia segnalare un falso positivo, ciò avvisare che ci può essere un errore che a tempo di esecuzione non accadrà mai. È quindi necessaria una valutazione dell’accuratezza dei risultati dell’analisi per non venir sommersi da falsi positivi. Nell’ultimo decennio sono stati sviluppati alcuni strumenti statici per il controllo della segretezza nello scambio di informazioni in rete, per esempio type systems e control flow analyzers; un altro approccio molto promettente è basato sull’abstract interpretation.

I generatori di codice incorporano un’enorme quantità di dettagli e di tecniche che consentono di ottenere programmi vicini alla macchina che sono particolarmente efficienti. Per far ciò utilizzano dei componenti che vanno sotto il nome (forse improprio) di ottimizzatori di codice. Purtroppo, però questi strumenti, pur conservando gli aspetti funzionali dei programmi, a volte rendono il codice più vulnerabile ad attacchi da parte di malintenzionati: non conservano cioè alcune proprietà non funzionali. Recentemente sono stati proposti dei metodi che consentono di verificare, a tempo statico, se il programma ottimizzato è altrettanto sicuro di quello originale. Altri strumenti infine agiscono sui modi con cui i dati vengono memorizzati fisicamente, e fanno sí che un eventuale attaccante non possa in alcun modo riconoscere se quello che sta fraudolentemente leggendo è un dato importante o se è solo una porzione di memoria senza alcun interesse, per esempio il rimasuglio di una computazione precedente.

Durante la generazione di codice si possono anche inserire all’interno del codice stesso chiamate a un run-time monitor, il cui compito è quello di controllare se una certa istruzione del programma, per esempio l’accesso a una risorsa, tipicamente una filza, è permessa a quell’utente in quella particolare sezione del programma, agendo come se ne fosse il guardiano. Poiché questo controllore verrà presumibilmente chiamato spesso, è opportuno che sia realizzato nel modo più efficiente possibile per non degradare eccessivamente la qualità del servizio in esecuzione. Si noti che un’accurata analisi statica potrebbe determinare quelle chiamate al run-time monitor che sono sicuramente superflue, permettendo così di eliminarle e di rendere tale controllore il meno invasivo possibile.

 

In conclusione

Come quelle di ogni prodotto che usiamo, le caratteristiche di funzionamento di un sistema software devono essere certificate e verificabili, forse in misura ancor più stringente quando si tratti di garantire la sicurezza e la riservatezza di informazioni che ci riguardano. Inoltre, per almeno due ragioni non è opportuno che i controlli di sicurezza vengano aggiunti a sistemi a posteriori, quando sono già operativi e se ne scoprono debolezze o falle. La prima ragione è che un controllo attento durante ogni passo di calcolo porterebbe a un degrado delle prestazioni del sistema in esame; la seconda è che, per quanto occhiuto il controllore sia, gli attaccanti operano al di fuori di ogni regola e a volte, o forse spesso come dimostrano i molteplici episodi di cui veniamo a conoscenza, riescono a evitare di essere intercettati e a portare a buon fine i loro attacchi. È quindi indispensabile sviluppare sistemi che siano sicuri per costruzione, o che almeno lo siano abbastanza da rendere difficile la vita a chi li attacca o ne modifica malevolmente il comportamento.

Come abbiamo accennato sopra, la tecnologia corrente offre già svariati metodi, tecniche e strumenti per progettare sistemi, per analizzarne il comportamento, per verificare la presenza e l’efficacia di opportuni dispositivi di contrasto agli attacchi e per certificare che le proprietà che vorremmo sono davvero garantite dal nostro sistema.

Lo strumento principale è il linguaggio di specifica e ancor più quello con cui programmiamo i sistemi, tra cui il compilatore e tutti gli strumenti a esso collegati. Naturalmente la costruzione di un sistema che fornisce un certo insieme di garanzie di sicurezza, la prevenzione, non ci esime dal dover impiegare, mentre il sistema è in esecuzione, ulteriori strumenti che scoprono e contrastano altri tipi di attacchi, la repressione.

L’intero processo di produzione di software sicuro, come il controllo durante il suo esercizio, richiedono persone con conoscenze, competenze e capacità non facili da trovare. La loro formazione, che richiede studi ed esperienze a livello universitario oltre che sul campo, è quindi fondamentale per affrontare e dominare i rischi che corriamo quotidianamente nello spazio cibernetico. Inoltre, la mancanza di un numero sufficiente di esperti limita la capacità di reazione dell’intero paese ad attacchi che possono colpire strutture vitali per il suo ordinato funzionamento. C’è quindi bisogno non solo di sviluppare nuove e più adeguate tecnologie e strumenti più sofisticati per difenderci da questo genere di crimini, ma anche di educare e formare persone che siano in grado di usare in modo efficace gli strumenti di difesa. In questo compito le università e gli enti di ricerca pubblici e privati danno contributi importanti ed essenziali, e devono essere messi in grado di darne di più.

Articolo 1 di 4