Analisi dinamica dell'applicazione
L'analisi dinamica di sicurezza delle applicazioni è una metodologia che richiede il test del software durante la sua esecuzione per rilevare bug o comportamenti anomali che potrebbero non essere evidenti dalla semplice analisi del codice sorgente. Inoltre, potremmo trovarci nello scenario in cui non si ha accesso al codice sorgente, quindi l'analisi statica viene automaticamente esclusa. Questo tipo di analisi viene tipicamente effettuato in un ambiente controllato e monitorato, spesso utilizzando strumenti come debugger, profiler e monitor di runtime. Gli strumenti di analisi dinamica possono anche eseguire test di penetrazione automatizzati per simulare attacchi reali e valutare come l'applicazione risponde a tali minacce.
L'analisi dinamica complementa l'analisi statica perché può rilevare vulnerabilità che emergono solo durante l'esecuzione dell'applicazione, o comunque sono più semplici da individuare dinamicamente. Ad esempio, problemi come race condition possono essere identificati facilmente solo quando il software è in esecuzione. Una analisi statica per rilevare problemi di concorrenza non è impossibile, ma è molto difficoltosa e spesso è difficile formalizzare il problema adeguatamente.
Esempio di analisi dinamica
Consideriamo il seguente codice scritto in c.
#include <stdio.h>
#include <stdlib.h>
int main() {
int* v = malloc(20);
malloc(256);
for(int i=0; i<20; ++i) {
v[i] = i;
}
for(int i=19; i>=0; i--) {
printf("%d -> %d\n", i, v[i]);
}
return 0;
}
Eseguendo il programma otteniamo il risultato che ci aspettiamo.
19 -> 19
18 -> 18
17 -> 17
16 -> 16
15 -> 15
14 -> 14
13 -> 13
12 -> 12
11 -> 11
10 -> 10
9 -> 9
8 -> 8
7 -> 7
6 -> 6
5 -> 5
4 -> 4
3 -> 3
2 -> 2
1 -> 1
0 -> 0
Questo programma, però, contiene un errore, ovvero la chiamata malloc
alloca 20 bytes, ma l'array è in realtà grande 20 * sizeof(int)
bytes, quindi nel primo ciclo for
stiamo scrivendo fuori dai limiti del nostro blocco allocato.
Possiamo accorgerci di questo utilizzando uno strumento chiamato valgrind
, che permette di identificare a tempo di esecuzione errori come scritture e letture in memoria non valide, allocazioni non rilasciate e molto altro.
Eseguendo valgrind
sul programma abbiamo il seguente risultato.
$ valgrind ./main
==277855== Memcheck, a memory error detector
==277855== Copyright (C) 2002-2024, and GNU GPL'd, by Julian Seward et al.
==277855== Using Valgrind-3.23.0 and LibVEX; rerun with -h for copyright info
==277855== Command: ./main
==277855==
==277855== Invalid write of size 4
==277855== at 0x109189: main (in /tmp/main)
==277855== Address 0x4a79054 is 0 bytes after a block of size 20 alloc'd
==277855== at 0x4842788: malloc (vg_replace_malloc.c:446)
==277855== by 0x10915A: main (in /tmp/main)
==277855==
==277855== Invalid read of size 4
==277855== at 0x1091B2: main (in /tmp/main)
==277855== Address 0x4a7908c is 20 bytes before a block of size 256 alloc'd
==277855== at 0x4842788: malloc (vg_replace_malloc.c:446)
==277855== by 0x109168: main (in /tmp/main)
==277855==
19 -> 19
18 -> 18
17 -> 17
16 -> 16
15 -> 15
14 -> 14
13 -> 13
12 -> 12
11 -> 11
10 -> 10
9 -> 9
8 -> 8
7 -> 7
6 -> 6
5 -> 5
4 -> 4
3 -> 3
2 -> 2
1 -> 1
0 -> 0
==277855==
==277855== HEAP SUMMARY:
==277855== in use at exit: 276 bytes in 2 blocks
==277855== total heap usage: 3 allocs, 1 frees, 1,300 bytes allocated
==277855==
==277855== LEAK SUMMARY:
==277855== definitely lost: 276 bytes in 2 blocks
==277855== indirectly lost: 0 bytes in 0 blocks
==277855== possibly lost: 0 bytes in 0 blocks
==277855== still reachable: 0 bytes in 0 blocks
==277855== suppressed: 0 bytes in 0 blocks
==277855== Rerun with --leak-check=full to see details of leaked memory
==277855==
==277855== For lists of detected and suppressed errors, rerun with: -s
==277855== ERROR SUMMARY: 30 errors from 2 contexts (suppressed: 0 from 0)
Le due cose da notare sono le scritture e letture invalide di dimensione quattro (un intero), e il fatto che all'uscita non abbiamo chiamato free
su entrambi i blocchi, quindi abbiamo ancora in uso due blocchi.