Una cattiva gestione della memoria che comporta conseguenze gravissime e serie ripercussioni sia sul sistema, che sugli utenti finali.

La programmazione, croce e delizia di ogni informatico che si rispetti, rappresenta il cuore pulsante di ogni applicazione, sistema e servizio odierno.
Miliardi di dispositivi sono connessi tra loro e operano in maniera quasi del tutto autonoma, grazie all’ottimizzazione del codice sorgente dell’applicazione stessa. Tuttavia, anche negli ambienti di sviluppo più controllati e sicuri, non si può sfuggire all’errore umano.

Questo spazia da una svista nel flusso del codice stesso o da una gestione non corretta della memoria, entrambi con conseguenze più o meno gravi.
Di certo l’errore che causa l’insorgenza delle vulnerabilità più critiche, è legata all’heap corruption. Si tratta di una svista annidata negli angoli più nascosti della gestione della memoria dinamica, invisibile fino a quando non scatena comportamenti imprevisti, come crash di sistema, o la generazione di veri e propri exploit.

heap corruption

Ma come sempre, andiamo con ordine e affrontiamo per gradi l’argomento.

  1. Cos’è l’heap corruption?
  2. Principali cause dell’heap corruption
  3. Quali rischi per la sicurezza comporta l’heap corruption?
  4. Come si può prevenire l’insorgenza di una heap corruption?

Cos’è l’heap corruption?

Prima di parlare dell’argomento cardine di questo articolo, è bene fare una breve introduzione al concetto di heap e alla sua importanza nel contesto di sviluppo.

L’heap è una porzione di memoria dinamica impiegata da un programma in esecuzione per allocare e deallocare i blocchi di dati a runtime in modo flessbile. Differentemente dallo stack, la pila di esecuzione che ha una dimensione predeterminata e utilizza una gestione dati di tipo LIFO (Last In First Out), l’heap consente ai programmi di allocare e deallocare dinamicamente i dati, senza la necessità di seguire un ordine ben preciso.
Tale meccanismo è molto comune in linguaggi di programmazione di alto livello, come il C++, le cui funzioni malloc() e free() permettono rispettivamente di allocare e liberare porzioni di memoria al programma. Tale spazio in memoria è cruciale, in quanto è qui che vengono gestite le strutture dati principali, quali:

  • Liste;
  • Alberi;
  • Tabelle di hash;
  • Insiemi;
  • Matrici;
  • Dizionari;
  • Grafi;

La principale criticità dell’heap risiede proprio qui: nella sua versatilità.
L’allocazione dinamica alla base del suo funzionamento, se gestita in maniera errata, porta all’insorgenza dell’heap corruption.

Questo altro non è che l’alterazione della sua struttra stessa, che finisce per danneggiare i metadati regolatori della sua integrità. La memoria destinata all’heap non è infatti composta solo dai blocchi di dati allocati dinamicamente, ma anche da strutture di controllo che tengono traccia del numero di blocchi liberi, di quelli occupati e delle loro rispettive dimensioni. Ad ogni blocco allocato sono associati dei metadati specifici, che servono a indicare la sua lunghezza effettiva e la sua posizione in memoria.

Nel momento in cui l’heap non viene allocato correttamente o si verificano anomalie legate alla lettura/scrittura dei dati sullo stesso, i metadati possono corrompersi, generando così una serie di ripercussioni impreviste non solo sul programma, ma anche sul sistema stesso.

heap corruption schermata

Principali cause dell’Heap Corruption

Affinché l’heap si corrompa, devono essere soddisfatti determinati requisiti, la maggior parte dei quali è strettamente legata agli errori logici del programma stesso. Di certo il più comune è il Buffer Overflow, ovvero la scrittura dei dati da parte di un programma oltre i limiti del buffer dati. Nel momento stesso in cui una porzione dell’heap viene sovrascritta, i dati successivi vengono irrimediabilmente modificati, finendo per alterare in toto la struttura dell’heap. In tal modo è possibile far insorgere gravi vulnerabilità nel sistema attraverso il programma che lo provoca, permettendo così a un attore malevolo di potervi accedere sfruttando tecniche come Arbitrary Code Execution.

Un’altra causa molto nota di heap corruption è l’UAF (Use-After-Free), ovvero il tentativo di accesso a un’area di memoria appena deallocata da parte di un programma. Ciò avviene a causa di un puntatore che punta ad una specifica porzione di memoria, senza prima effettuare un controllo sulla sua effettiva disponibilità. Così facendo un altro programma potrebbe aver già allocato i propri dati in quel punto, portando il puntatore invalidato a sovrascriverli con quelli del programma precedente, causando così la corruzione dei metadati relativi all’heap.

Un caso simile all’UAF è il WAF (Write-After-Free), solo che in questo caso il programma scrive direttamente i propri dati all’interno della porzione di memoria appena deallocata. Se la stessa è già stata allocata per scopi differenti, o per altri programmi, i dati dell’heap si corrompono direttamente.

Un altro errore che provoca la corruzione dell’heap, è il Double Free. Questo consiste nel tentativo da parte di un programma da liberare lo stesso blocco di memoria più di una volta. I sistemi di gestione della memoria monitorano costantemente le aree allocate e quelle libere, ma nel momento in cui si verificano molteplici tentativi di liberare un singolo blocco di memoria, avviene la corruzione della struttura dati stessa. Questo causa l’insorgenza delle vulnerabilità di sicurezza all’interno del programma stesso.

Quali rischi per la sicurezza comporta l’heap corruption?

In base a quanto discusso finora, si è compreso che la corruzione dell’heap rappresenta un grave problema per la sicurezza dei propri sistemi, poiché coinvolge in maniera diretta la memoria di sistema. Nel momento stesso in cui un attore malevolo riesce a ottenere l’accesso a quest’ultima, diviene letteralmente il padrone del sistema, poiché tutte le operazioni passano dalla memoria. Ciò può provocare la messa in atto di svariate tipologie di attacco, tra cui si indicano:

Arbitrary Code Execution

Di certo si tratta dell’incubo peggiore di qualsiasi utente, azienda e organizzazione.
Nel momento stesso in cui una porzione dell’heap viene corrotta da una sovrascrittura di dati, l’attaccante altera il flusso stesso di esecuzione del programma. Questo perché manipola direttamente i puntatori dell’heap, costringendo il programma ad accedere a porzioni della memoria a cui normalmente non potrebbe farlo. Ciò fa sì che questi possa eseguire software malevolo nel programma stesso. Spesso questo si traduce nell’iniezione diretta di payloads e malware nel sistema.

Escalation dei privilegi

Quando l’heap si corrompre, un attaccante può sfruttarlo a proprio vantaggio per accedere al sistema e inserirsi come amministratore. In tal modo realizza una vera e propria escalation di privilegi, ottenendo quelli di livello massimo e potendo accedere non solo al sistema vulnerabile, ma anche alla rete interna a cui questo è collegato.

Denial of Service (DoS)

Molte volte la corruzione dell’heap è sfruttata per mettere a segno attachi DoS. Poiché la memoria è responsabile del corretto funzionamento del sistema stesso, manomettendo il suo heap si possono ottenere dei comportamenti imprevedibili nello stesso. La maggior parte di questi porta inevitabilmente al crash del sistema e alla conseguente indisponibilità dei servizi ad esso associati. In un contesto che coinvolge server di grosse dimensioni e servizi essenziali, ciò rappresenta un pericolo da non sottovalutare.

Accesso diretto a dati sensibili

Spesso e volentieri l’heap conserva al suo interno i dati sensibili dell’utente. Che siano credenziali di accesso, chiavi crittografiche, o token di autenticazione, un attaccante che corrompe l’heap può facilmente impadronirsene, in quanto altera la sua struttura. In tal modo può non solo ottenere l’accesso agli account degli utenti senza che questi se ne accorgano, ma può perfino riutilizzare questi dati sensibili per commettere frodi a nome dei rispettivi proprietari.

Come si può prevenire l’insorgenza di una Heap Corruption?

Come abbiamo potuto vedere, l’impatto della corruzione dell’heap ha conseguenze a dir poco devastanti. Ecco perché bisogna adottare tutta una serie di attenutanti e strategie per prevenire l’insorgenza di tale evenienza. Di seguito sono riportati alcuni consigli in merito:

  • Impiegare linguaggi di programmazione sicuri
    Poiché linguaggi come C e C++ prevedono l’allocazione manuale dei blocchi di memoria, si dovrebbero considerare alternative in possesso di garbage collector. Linguaggi come Java, Python e Rust non solo ne sono muniti, ma consentono una gestione automatica della memoria. Ciò favorisce una minor insorgenza nel commettere errori nell’allocazione di porzioni di memoria e alle relativa corruzione dell’heap.
  • Adottare strumenti per il debugging adeguati
    Come ogni buon programmatore sa, il debugging è un processo critico nel ciclo di sviluppo. Questo perché consente di osservare direttamente il flusso di esecuzione di un software, permettendo anche di comprendere le cause legate alle sue eccezioni, o di individuare falle critiche. Strumenti più avanzati , come AddressSanitizer e Valgrind permettono perfino di rilevare accessi illegali alla memoria, buffer overflow e use-after-free durante la fase di sviluppo. In tal modo si riduce notevolmente la probabilità di far giungere il codice vulnerabile all’ambiente di produzione.
  • Fare uso di tecniche di protezione avanzate
    I moderni IDE e le attuali piattaforme di sviluppo integrano al loro interno dei meccanismi di protezione molto efficaci, come ad esempio ASLR (Address Space Layout Randomization) e DEP (Data Execution Prevention). Il primo è responsabile della randomizzazione degli indirizzi di memoria, che rende molto difficile ad un attaccante la previsione della disposizione dell’heap. Il secondo impedisce l’esecuzione di codice in aree della memoria destinate ai dati. Ciò previene l’insorgenza di un’heap corruption.
  • Implementare nel proprio codice design pattern sicuri
    Utilizzando funzioni e librerie sicure, previene l’insorgenza di errori legati al buffer overflow. Inoltre è mandatoria la gestione rigorosa dei puntatori, spesso considerati il punto più vulnerabile del codice sorgente di un programma, in quanto i veri responsabili dell’accesso alla memoria.

In conclusione, l’heap corruption rappresenta una delle problematiche più pericolose e complesse nella sicurezza informatica legata alla gestione della memoria dinamica. Data la sua insorgenza strettamente legata agli errori di programmazione, si rende necessario adottare pratiche di sviluppo sicure e di lavorare sempre su piattaforme aggiornate. In tal modo si riduce notevolmente la sua insorgenza ed è possibile salvaguardare gli utenti finali, i veri destinatari del prodotto che viene immesso sul mercato.


    Dichiaro di aver letto e compreso l'Informativa sul trattamento dei dati