Eseguendo ESLint su un file JavaScript con una vulnerabilità di SQL injection, il test verrà superato senza errori. Eseguendo CodeQL sullo stesso codice sorgente, verrà segnalata l'iniezione con l'indicazione precisa del file, della riga e del percorso del flusso di dati. Entrambi sono strumenti di analisi statica. Entrambi esaminano lo stesso codice sorgente senza eseguirlo. La differenza tra i risultati non dipende dalla configurazione delle regole o dal supporto del linguaggio, ma dalla profondità dell'analisi di ciascuno strumento. ESLint esamina una funzione alla volta e segnala ciò che rileva all'interno di tale ambito. CodeQL, invece, segue i dati attraverso le chiamate di funzione, i moduli e l'intero grafo delle chiamate fino a raggiungere un punto critico.
Questa distinzione, tra analisi intraprocedurale e interprocedurale, è il concetto tecnico più importante per comprendere perché diversi strumenti di analisi statica rilevano problemi diversi. Spiega perché un linter può dare un giudizio positivo a una codebase, mentre uno strumento più sofisticato individua vulnerabilità di sicurezza critiche nello stesso codice. Spiega perché gli strumenti SAST per la sicurezza sono costosi da utilizzare. Spiega perché il tasso di falsi negativi degli strumenti "veloci" è strutturalmente più alto del tasso di falsi negativi di quelli "lenti". Ed è il concetto che i team aziendali devono comprendere prima di valutare qualsiasi toolchain di analisi statica.
Visualizza il grafico completo della chiamata, non solo un file.
SMART TS XL Traccia i flussi di dati attraverso COBOL, Java, Python e ogni altro linguaggio di programmazione che utilizzi.
Maggiori InformazioniChe cos'è l'analisi intraprocedurale?
L'analisi intraprocedurale esamina ciò che accade all'interno di una singola funzione, metodo o procedura, senza considerare cosa succede quando tale funzione chiama altre funzioni o viene chiamata da esse. Il confine dell'analisi coincide con il confine della funzione. Quando uno strumento di analisi intraprocedurale raggiunge una chiamata di funzione, si arresta, non segue la chiamata nella funzione chiamata e non sa cosa quest'ultima fa con i dati ricevuti.
Gli esempi canonici di analisi intraprocedurale sono il linting e il flusso di dati di base all'interno di una singola funzione:
python
def process_input(user_data):
# Intraprocedural analysis sees: user_data used here
query = "SELECT * FROM users WHERE id = " + user_data # flagged: string concat in SQL
return query
Una semplice regola che segnala la concatenazione di stringhe nelle query SQL funziona a livello intraprocedurale perché il pattern pericoloso è visibile all'interno di una singola funzione. Ma modifichiamo leggermente la struttura del codice:
python
def get_user_id(request):
return request.args.get("id") # untrusted input
def build_query(user_id):
return "SELECT * FROM users WHERE id = " + user_id # looks safe in isolation
def handle_request(request):
uid = get_user_id(request)
query = build_query(uid) # vulnerability spans three functions
db.execute(query)
Ora la fonte dei dati contaminati (request.args.get("id")) ha una funzione diversa dal pericoloso lavandino (db.execute(query)Nessuna singola funzione contiene informazioni sufficienti affinché uno strumento intraprocedurale possa rilevare la vulnerabilità. get_user_id Sembra tutto a posto, si limita a leggere da una richiesta. build_query Sembra funzionare correttamente, accetta un parametro e costruisce una stringa. handle_request Sembra tutto a posto, chiama funzioni ed esegue una query. L'iniezione SQL è reale, sfruttabile e invisibile a qualsiasi strumento che non superi i confini delle funzioni.
In cosa eccelle l'analisi intraprocedurale?
Gli strumenti intraprocedurali sono veloci. Molto veloci. Poiché analizzano ogni funzione in modo indipendente, la loro velocità aumenta linearmente con le dimensioni del codebase: analizzare un codebase di un milione di righe ha lo stesso costo proporzionale all'analisi di un codebase di diecimila righe. Questa velocità li rende ideali per:
- Hook di pre-commit che corrono in meno di un secondo
- Feedback in linea dell'IDE mentre uno sviluppatore digita
- Controlli di qualità CI/CD su ogni richiesta di pull
- Applicazione dello stile e delle convenzioni (denominazione, formattazione, metriche di complessità)
- Uso improprio delle variabili locali (variabili non utilizzate, riferimenti non definiti, errori di tipo evidenti)
- Complessità ciclomica e lunghezza del metodo, metriche puramente interne alla funzione
Gli strumenti in questa categoria includono ESLint, Pylint, Clippy (Rust), golangci-lint, Checkstyle e la maggior parte dei linter. Sono essenziali e preziosi per la tipologia di problemi che sono in grado di individuare. La loro limitazione è di natura strutturale, non dovuta a una carenza qualitativa.
Che cos'è l'analisi interprocedurale?
L'analisi interprocedurale (dell'intero programma) è in genere la più potente e quella che richiede maggiori risorse. L'analisi interprocedurale arricchisce i grafi di flusso di controllo con i collegamenti di chiamata e ritorno delle funzioni provenienti da un grafo delle chiamate. L'analisi può evitare di visitare ripetutamente le stesse funzioni creando dei riepiloghi delle funzioni stesse.
Laddove l'analisi intraprocedurale si ferma ai confini delle funzioni, l'analisi interprocedurale li oltrepassa. Costruisce un grafo delle chiamate che rappresenta quali funzioni chiamano quali altre, quindi traccia il flusso di dati, il flusso di controllo o la contaminazione lungo i bordi di tale grafo. Quando uno strumento interprocedurale incontra una chiamata di funzione, segue la chiamata fino alla funzione chiamata, oppure utilizza un riepilogo precalcolato del comportamento della funzione chiamata, e continua l'analisi dall'altro lato.
Le tre funzioni di SQL injection sopra riportate sono un semplice esempio. L'analisi interprocedurale reale gestisce:
- Flusso di dati multi-hop: input non attendibile che passa attraverso cinque funzioni prima di raggiungere un sink pericoloso
- contaminazione del valore di ritorno: una funzione restituisce dati corrotti; i chiamanti utilizzano quel valore di ritorno corrotto in operazioni non sicure
- Contaminazione sensibile al campodati non attendibili memorizzati in un campo di un oggetto, recuperati successivamente in una funzione diversa
- Propagazione condizionale: la contaminazione fluisce solo lungo determinati percorsi di esecuzione, a seconda delle condizioni di runtime che l'analisi deve modellare staticamente
Giava
// Interprocedural vulnerability: three-hop taint flow
public class UserController {
public String handleRequest(HttpServletRequest request) {
String userId = request.getParameter("id"); // source: untrusted input
User user = userService.getUser(userId); // taint flows through call
return generateReport(user); // taint reaches sink
}
}
public class UserService {
public User getUser(String id) {
// id is tainted -- passed to repository
return userRepository.findByRawId(id); // taint propagates
}
}
public class UserRepository {
public User findByRawId(String id) {
String query = "SELECT * FROM users WHERE id = " + id; // sink: SQL injection
return db.execute(query);
}
}
Uno strumento intraprocedurale che esamina UserRepository.findByRawId in isolamento vede un parametro id concatenato in una query SQL. Questo sembra potenzialmente pericoloso, ma lo strumento non ha modo di sapere dove id proviene da, potrebbe essere un valore affidabile e convalidato dal codice interno. Quindi uno strumento intraprocedurale lo segnala come un possibile problema (producendo falsi positivi su ogni query parametrizzata ma strutturata nella codebase) o lo ignora (producendo falsi negativi su vulnerabilità reali). L'analisi di contaminazione interprocedurale rileva se i dati non attendibili fluiscono attraverso le funzioni verso operazioni sensibili. Traccia i dati da request.getParameter("id") attraverso userService.getUser(), attraverso userRepository.findByRawId(), db.execute(query)producendo un risultato preciso con l'esatto percorso di flusso della contaminazione, senza falsi positivi né falsi negativi.
I tre livelli di analisi: uno spettro pratico
Gli strumenti di analisi statica nel mondo reale si collocano su uno spettro che va dall'essere puramente intraprocedurali all'essere completamente interprocedurali. Comprendere la posizione di ciascuno strumento permette di capire sia cosa rileva sia cosa non rileva.
| Livello di analisi | Obbiettivo | Ciò che vede | Cosa manca | Strumenti |
|---|---|---|---|---|
| Lessicale/sintattico | Token individuali e sintassi | Formattazione, denominazione, sintassi di base | Tutto semantico | Più carino, rustfmt, gofmt |
| Intraprocedurale | Singola funzione | Flusso di dati locale, complessità, flusso di controllo all'interno della funzione | Vulnerabilità interfunzionali, problemi architetturali | ESLint, Pylint, Clippy, golangci-lint |
| Interprocedurale superficiale | Un livello di profondità delle chiamate | Relazioni dirette chiamante-chiamato | Flussi multi-hop profondi | Alcune regole Semgrep, regole SAST di base |
| Interprocedurale completa | Analisi completa del grafico delle chiamate | Flussi di contaminazione multi-hop, analisi sensibile al contesto | Polimorfismo a tempo di esecuzione (parziale), chiamate riflessive | CodeQL, Facebook Infer, SMART TS XL |
| Programma completo / semantico | Semantica del programma completo | Proprietà formalmente dimostrabili | Scalabilità su codebase di dimensioni molto grandi | Infer, Kani (Rust), strumenti di verifica formale |
L'analisi interprocedurale presenta un'efficacia potenziale relativamente elevata e prestazioni potenziali relativamente basse. Grazie al ricco modello di programma, è anche meno soggetta a falsi negativi e falsi positivi e ha un'efficacia relativamente maggiore. L'analisi interprocedurale non è la soluzione ideale per gli ambienti CI/CD a causa delle sue limitazioni prestazionali, che potrebbero ridurre la velocità di sviluppo. Pertanto, l'analisi interprocedurale viene spesso fornita come servizio dedicato che viene eseguito su server farm o strumenti dedicati.
Come funziona l'analisi interprocedurale: tre approcci chiave
1. Analisi basata sul riepilogo
L'analisi può evitare di rivedere le stesse funzioni più volte creando dei riepiloghi delle funzioni (a volte chiamati modelli). Un riepilogo cattura il comportamento di una funzione in forma compatta: dato questo insieme di input con queste proprietà di contaminazione, la funzione produce questo insieme di output con queste proprietà di contaminazione. Quando un chiamante invoca la funzione, l'analisi crea un'istanza del riepilogo anziché rianalizzare il corpo della funzione da zero.
L'analisi basata sui riepiloghi è ciò che rende l'analisi interprocedurale gestibile su larga scala. Senza i riepiloghi, analizzare una funzione che chiama una libreria richiederebbe l'analisi ricorsiva dell'intera libreria dal codice sorgente, incluse tutte le funzioni che la libreria chiama. Con i riepiloghi, le funzioni della libreria vengono pre-analizzate una sola volta e i relativi riepiloghi vengono riutilizzati a ogni chiamata.
2. Approccio basato sulla stringa di chiamata (analisi contestuale)
L'analisi sensibile al contesto distingue tra diversi siti di chiamata della stessa funzione. Se sanitize(input) Quando si utilizzano dati affidabili in un punto e dati non affidabili in un altro, un'analisi contestuale produce risultati diversi per ogni punto di chiamata, anziché presumere in modo conservativo il caso peggiore per tutti. Ciò riduce significativamente i falsi positivi rispetto a un'analisi non contestuale.
Il framework IFDS (Interprocedural, Finite, Distributive, Subset) formalizza questo problema come un problema di raggiungibilità di un grafo. Nel framework IFDS, il flusso di taint è descritto da funzioni distributive su un dominio finito di fatti e la propagazione interprocedurale è ridotta alla raggiungibilità di un grafo. Questa formulazione rende le analisi IFDS precise ed efficienti dal punto di vista algoritmico; strumenti come FlowDroid (per Android) e diverse query CodeQL utilizzano IFDS internamente.
3. Costruzione del grafico delle chiamate
Ogni analisi interprocedurale richiede un grafo delle chiamate: il grafo orientato in cui i nodi sono funzioni e gli archi rappresentano le chiamate. La costruzione del grafo delle chiamate stesso comporta decisioni complesse:
dispaccio statico (Chiamate non virtuali Java, chiamate di funzioni C, chiamate a livello di modulo Python): facili da modellare, f() chiamate f.
Dispacciamento dinamico (metodi virtuali Java, chiamate duck-typed Python): richiede l'analisi della gerarchia delle classi (CHA) o algoritmi di costruzione del grafo delle chiamate più precisi (RTA, VTA) per determinare quali implementazioni di un metodo potrebbero effettivamente essere chiamate in un dato punto di chiamata.
Chiamate riflessive e puntatori a funzione : i casi più difficili. Gli strumenti devono o sovrastimare (presumendo che qualsiasi funzione corrispondente possa essere chiamata, producendo falsi positivi) o sottostimare (tralasciando alcuni percorsi di chiamata, producendo falsi negativi).
La precisione del grafo delle chiamate determina direttamente la precisione dell'analisi interprocedurale. Un grafo delle chiamate che include troppi collegamenti (sovra-approssimazione) produce falsi positivi; uno che omette dei collegamenti produce falsi negativi.
Cosa rileva l'analisi interprocedurale che l'analisi intraprocedurale non può rilevare
Vulnerabilità di sicurezza (analisi delle tracce)
La maggior parte delle vulnerabilità OWASP Top 10 richiede un'analisi di contaminazione interprocedurale per essere rilevata in modo affidabile:
- SQL Injection: input non attendibili fluiscono attraverso più funzioni fino a una query di database
- XSS: il flusso HTML non attendibile attraversa le funzioni di rendering verso un contesto di output
- Iniezione di comando: i dati controllati dall'utente raggiungono
exec()orsystem()tramite funzioni intermedie - Attraversamento del percorso: i percorsi dei file derivati dall'input dell'utente raggiungono le operazioni del file system
- SSRFGli URL controllati dall'utente vengono inoltrati alle chiamate del client HTTP
python
# Intraprocedural tool: sees no issue in any individual function
# Interprocedural tool: traces the full taint path and reports SSRF
def get_target_url(request):
return request.json().get("url") # source: attacker-controlled
def fetch_resource(url):
return requests.get(url) # sink: SSRF
def api_endpoint(request):
target = get_target_url(request)
data = fetch_resource(target) # full path: source -> sink
return data
Problemi relativi ai puntatori nulli e alla sicurezza della memoria.
Facebook Infer è lo strumento interprocedurale canonico per l'analisi della sicurezza dei valori nulli. Utilizza la bi-abduzione, una forma di ragionamento interprocedurale, per scoprire le precondizioni e le postcondizioni delle funzioni, quindi verifica che i chiamanti soddisfino tali precondizioni. Un dereferenziamento nullo, che richiede di sapere che una funzione può restituire null in determinate condizioni, è invisibile agli strumenti intraprocedurali.
Giava
// Intraprocedural tool: no issue in either function
// Infer: finds that processResult() can receive null from fetchData()
String fetchData(boolean condition) {
if (condition) return getData();
return null; // can return null
}
void processResult(String result) {
System.out.println(result.length()); // NPE if result is null
}
void run() {
String data = fetchData(false); // passes false: fetchData returns null
processResult(data); // null dereference
}
Uso successivo alla scadenza e perdite di risorse
In C e C++, i bug di tipo use-after-free spesso coinvolgono memoria allocata in una funzione e liberata in un'altra, con una terza funzione che mantiene un riferimento. Nessuna analisi di singola funzione è in grado di rilevare questa classe di errori. L'analisi interprocedurale analizza l'integrità del flusso di controllo: garantisce che le chiamate di funzione seguano i modelli previsti per prevenire gli exploit.
Violazioni di accoppiamento e dipendenza architettonica
Oltre alla sicurezza e alla correttezza, l'analisi interprocedurale si applica all'analisi architetturale: quali componenti chiamano quali, quali dipendenze violano i confini dei livelli definiti e quali modifiche si propagheranno a cascata attraverso il grafo delle chiamate. Questa è l'applicazione dell'analisi interprocedurale che strumenti come SMART TS XL Applicabile ai codebase aziendali, traccia come una modifica a una funzione si propaga attraverso l'intero grafo delle chiamate per determinarne la reale portata d'impatto.
Quando è necessaria l'analisi interprocedurale
Non tutti i progetti richiedono un'analisi interprocedurale completa. La decisione dipende da cosa si sta cercando di scoprire e dal costo di un eventuale mancato riscontro.
È necessaria un'analisi interprocedurale quando:
- La sicurezza è una preoccupazione primaria ed è necessario trovare vulnerabilità di injection, XSS o SSRF in modo affidabile
- Stai analizzando un codebase legacy o aziendale in cui la logica di business si estende su molte funzioni e moduli
- Prima di un refactoring è necessaria un'analisi d'impatto per comprendere cosa verrà influenzato se una funzione cambia.
- Stai conducendo un'analisi di conformità o di audit che richiede di dimostrare la completezza
- Stai migrando o modernizzando una codebase e devi comprendere le dipendenze tra i componenti.
L'analisi intraprocedurale è sufficiente quando:
- L'obiettivo è far rispettare lo stile, conformarsi alle convenzioni e individuare gli errori locali più evidenti.
- È necessario un feedback rapido integrato nel ciclo di sviluppo (pre-commit, IDE, CI per ogni pull request).
- Il codice sorgente è abbastanza piccolo da permettere agli sviluppatori di conoscere tutte le relazioni tra le chiamate.
- La sicurezza viene applicata a un livello diverso (framework validato, sistema di tipi) e il rischio di vulnerabilità tra funzioni è basso.
La risposta pratica per la maggior parte dei team è: usarli entrambi. Gli strumenti intraprocedurali (ESLint, Pylint, Clippy) vengono eseguiti a ogni commit per un feedback rapido. Gli strumenti interprocedurali (CodeQL, Infer, Semgrep con regole di taint) vengono eseguiti sulle pull request o con una pianificazione notturna per un'analisi approfondita. I due livelli sono complementari, non in competizione.
Strumenti di analisi interprocedurale: cosa copre ciascuno
| Chiavetta | Approccio di analisi | Lingue disponibili | Caso d'uso primario |
|---|---|---|---|
| CodiceQL | Interprocedurale completa, basata su IFDS | C/C++, Java, JS/TS, Python, Go, Ruby | Rilevamento delle vulnerabilità della sicurezza |
| Inferenza di Facebook | Bi-abduzione, interprocedurale | Java, C, C++, Objective-C | Sicurezza nulla, perdite di memoria, condizioni di gara |
| Semgrep (modalità di rilevamento) | Contaminazione interprocedurale superficiale | 30+ lingue | Corrispondenza dei modelli di sicurezza con il flusso di dati |
| Codice Snyk | Interprocedurale basato sull'apprendimento automatico | Java, JS/TS, Python, C# | Sicurezza con feedback intuitivo per gli sviluppatori |
| Polispazio | Interpretazione astratta | C, C++, Ada | sistemi embedded critici per la sicurezza |
| Frama-Do | Interprocedurale completa + formale | C | Verifica formale, certificazione di sicurezza |
| SMART TS XL | Interlinguistico, intersistemico | COBOL, JCL, Java, Python, RPG, SQL | Analisi dell'impatto aziendale, mappatura delle dipendenze |
Analisi interprocedurale oltre i confini linguistici
Gli strumenti interprocedurali standard operano all'interno di un singolo linguaggio. Una query CodeQL su una codebase Java traccia le anomalie attraverso le chiamate alle funzioni Java, ma si interrompe quando il codice Java chiama un programma COBOL o legge da un dataset scritto da un job batch JCL. È proprio a questo confine che gli strumenti di analisi su scala aziendale incontrano un problema che gli strumenti monolingua non sono in grado di risolvere.
SMART TS XL'S analisi statica del codice esegue un'analisi delle dipendenze in stile interprocedurale su ogni linguaggio nell'ambiente aziendale simultaneamente. Quando un servizio Java chiama un programma COBOL che legge da una tabella DB2 che un altro job JCL popola, SMART TS XL traccia quella catena e la rappresenta come un modello di dipendenza unificato. analisi d'impatto La funzionalità applica quindi questo grafico di chiamate tra linguaggi diversi per rispondere alla domanda: se questo programma COBOL cambia, quali servizi Java, quali job JCL e quali consumatori a valle ne risentiranno?
Si tratta del problema interprocedurale applicato a livello architetturale anziché a livello funzionale, lo stesso principio (seguire il grafo delle dipendenze attraverso i confini anziché fermarsi ad essi) applicato all'intero sistema aziendale anziché ai singoli programmi. Come esaminato nel contesto della visualizzazione del codice e della mappatura delle dipendenze , rendere visibile l'intera struttura delle dipendenze è il prerequisito per qualsiasi analisi che pretenda di comprendere gli effetti di una modifica.
Per le organizzazioni che conducono modernizzazione dell'eredità, il grafico delle chiamate che SMART TS XL La creazione di strutture tra lingue diverse ha lo stesso scopo di un grafo di chiamate interprocedurali all'interno di una singola lingua: è la mappa di come tutto si connette e, senza di essa, qualsiasi analisi di impatto, rischio o sequenza di migrazione si basa su ipotesi anziché su prove.
Il compromesso tra falsi positivi e falsi negativi
Il motivo per cui l'analisi interprocedurale non è utilizzata ovunque non è che nessuno ci abbia pensato, ma che precisione e prestazioni sono in conflitto. Un'analisi interprocedurale completa e contestualizzata di una codebase di un milione di righe può richiedere ore o giorni. La stessa codebase analizzata da ESLint richiede solo pochi secondi. Questa differenza di prestazioni è strutturale: l'analisi interprocedurale cresce con la complessità del grafo delle chiamate, non solo con le dimensioni della codebase.
Il vantaggio dell'investimento consiste in tassi di falsi positivi e falsi negativi sensibilmente inferiori:
- Falsi positivi (strumenti che segnalano un problema quando non è così): gli strumenti intraprocedurali producono molti più falsi positivi perché non possono determinare se un modello sospetto sia effettivamente pericoloso senza conoscere il contesto fornito dai chiamanti. Uno strumento intraprocedurale che rileva una query SQL con una stringa concatenata non può sapere se la stringa proviene dall'input dell'utente o da una fonte interna attendibile.
- Falsi negativi (strumenti che non rilevano le vulnerabilità reali): gli strumenti intraprocedurali producono molti più falsi negativi perché non sono in grado di rilevare vulnerabilità che si estendono oltre i confini delle funzioni, le quali, per le analisi rilevanti per la sicurezza, rappresentano la maggior parte delle vulnerabilità reali.
La disciplina di scegliere quando utilizzare ciascun livello di analisi e di combinarli entrambi in una toolchain a più livelli è ciò che distingue l'ingegneria della sicurezza dalla mera conformità formale. Sapere che il vostro linter fornisce risultati puliti vi dice ben poco sulla vostra postura di sicurezza. Sapere che la vostra analisi di contaminazione interprocedurale ha tracciato ogni percorso dalle fonti di input esterne ai punti sensibili della vostra applicazione e non ha trovato alcun flusso non convalidato, vi dice molto di più.


