Lancez ESLint sur un fichier JavaScript présentant une vulnérabilité d'injection SQL et il réussira l'analyse sans problème. En revanche, exécutez CodeQL sur le même code source et il signalera l'injection en précisant le fichier, la ligne et le chemin d'accès exacts aux données. Ces deux outils d'analyse statique examinent le même code source sans l'exécuter. La différence entre leurs résultats ne tient pas à la configuration des règles ou à la prise en charge des langages, mais à une différence fondamentale dans la profondeur d'analyse de chaque outil. ESLint examine une fonction à la fois et signale ce qu'il observe à l'intérieur de ce périmètre. CodeQL, quant à lui, suit les données à travers les appels de fonctions, les modules et l'intégralité du graphe d'appels jusqu'à ce qu'il rencontre une vulnérabilité.
Cette distinction entre analyse intraprocédurale et interprocédurale est le concept technique fondamental pour comprendre pourquoi différents outils d'analyse statique détectent des problèmes différents. Elle explique pourquoi un linter peut déclarer un code source sain tandis qu'un outil plus sophistiqué y décèle des failles de sécurité critiques. Elle explique également pourquoi les outils SAST de sécurité sont coûteux à exécuter. Elle explique pourquoi le taux de faux négatifs des outils « rapides » est structurellement plus élevé que celui des outils « lents ». Enfin, c'est un concept que les équipes d'entreprise doivent maîtriser avant d'évaluer toute chaîne d'outils d'analyse statique.
Consultez le graphique d'appel complet, et non un seul fichier.
SMART TS XL trace les flux de données à travers COBOL, Java, Python et tous les langages que vous déployez.
En savoir plusQu’est-ce que l’analyse peropératoire ?
L'analyse intraprocédurale examine ce qui se passe à l'intérieur d'une fonction, d'une méthode ou d'une procédure, sans tenir compte des interactions entre cette fonction et d'autres fonctions. La limite de l'analyse est la limite de la fonction. Lorsqu'un outil d'analyse intraprocédurale atteint un appel de fonction, il s'arrête ; il ne poursuit pas l'exécution de l'appel vers la fonction appelée et ignore le traitement des données par cette dernière.
Les exemples canoniques d'analyse intraprocédurale sont le linting et le flux de données de base au sein d'une seule fonction :
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
Une règle simple qui signale la concaténation de chaînes dans les requêtes SQL fonctionne au sein d'une même procédure car le schéma dangereux est visible dans une seule fonction. Mais modifiez légèrement la structure du code :
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)
La source des données corrompues (request.args.get("id")) a une fonction différente de celle du puits dangereux (db.execute(query)Aucune fonction ne contient à elle seule suffisamment d'informations pour qu'un outil intraprocédural puisse détecter la vulnérabilité. get_user_id Ça a l'air correct, ça lit juste une requête. build_query Ça a l'air bien, ça prend un paramètre et construit une chaîne de caractères. handle_request Tout semble correct : le programme appelle des fonctions et exécute une requête. L’injection SQL est bien réelle, exploitable et invisible pour tout outil ne franchissant pas les limites des fonctions.
Quels sont les avantages de l'analyse peropératoire ?
Les outils intraprocéduraux sont rapides. Très rapides. Comme ils analysent chaque fonction indépendamment, leur coût est proportionnel à la taille du code : analyser un code d’un million de lignes coûte proportionnellement autant qu’analyser un code de dix mille lignes. Cette rapidité les rend idéaux pour :
- Crochets de pré-validation cette course en moins d'une seconde
- Retour d'information intégré à l'IDE pendant qu'un développeur tape
- portes de qualité CI/CD à chaque demande de fusion
- Application des styles et des conventions (nomenclature, formatage, indicateurs de complexité)
- Utilisation abusive des variables locales (variables inutilisées, références non définies, erreurs de type évidentes)
- Complexité cyclomatique et longueur de la méthode, des mesures purement intra-fonctionnelles
Les outils de cette catégorie comprennent ESLint, Pylint, Clippy (Rust), golangci-lint, Checkstyle et la plupart des linters. Ils sont essentiels et précieux pour le type de problèmes qu'ils détectent. Leur limitation est structurelle et non liée à un défaut de qualité.
Qu’est-ce que l’analyse interprocédurale ?
L'analyse interprocédurale (analyse de programme complet) est généralement la plus puissante et la plus gourmande en ressources. Elle enrichit les graphes de flux de contrôle avec les arêtes d'appel et de retour des fonctions issues d'un graphe d'appels. Grâce à la création de résumés de fonctions, l'analyse peut éviter de revisiter plusieurs fois les mêmes fonctions.
Alors que l'analyse intraprocédurale s'arrête aux limites des fonctions, l'analyse interprocédurale les franchit. Elle construit un graphe d'appels représentant les fonctions qui en appellent d'autres, puis suit le flux de données, le flux de contrôle ou la contamination le long des arêtes de ce graphe. Lorsqu'un outil interprocédural rencontre un appel de fonction, il suit cet appel jusqu'à la fonction appelée, ou bien il utilise un résumé précalculé du comportement de cette dernière et poursuit l'analyse de l'autre côté.
Les trois fonctions d'injection SQL ci-dessus ne sont qu'un exemple simple. Les véritables mécanismes d'analyse interprocédurale gèrent :
- Flux de données multi-sauts: entrée non fiable qui passe par cinq fonctions avant d'atteindre un point de terminaison dangereux
- Valeur de retour altérée: une fonction renvoie des données corrompues ; les appelants utilisent cette valeur de retour corrompue dans des opérations non sécurisées
- contamination sensible au champ: données non fiables stockées dans un champ d'objet, récupérées ultérieurement dans une autre fonction
- Propagation conditionnelle: la contamination ne se propage que le long de certains chemins d'exécution, en fonction des conditions d'exécution que l'analyse doit modéliser statiquement
Java
// 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);
}
}
Un outil intraprocédural examinant UserRepository.findByRawId isolément voit un paramètre id concaténées dans une requête SQL. Cela semble potentiellement dangereux, mais l'outil n'a aucun moyen de savoir où id Cette valeur peut provenir d'une source fiable et validée issue du code interne. Un outil intraprocédural la signale alors comme un problème potentiel (générant des faux positifs pour chaque requête paramétrée mais structurée du code) ou l'ignore (produisant des faux négatifs sur de véritables vulnérabilités). L'analyse de contamination interprocédurale détecte si des données non fiables transitent par des fonctions pour atteindre des opérations sensibles. Elle suit les données depuis request.getParameter("id") à travers userService.getUser(), par userRepository.findByRawId(), to db.execute(query), permettant d'obtenir un résultat précis avec le trajet exact du flux de contamination, sans faux positif ni faux négatif.
Les trois niveaux d'analyse : un spectre pratique
Les outils d'analyse statique utilisés en pratique se situent sur un continuum allant d'une analyse purement intraprocédurale à une analyse entièrement interprocédurale. Comprendre la position de chaque outil permet de saisir ce qu'il détecte et ce qu'il ne détecte pas.
| Niveau d'analyse | Domaine | Ce qu'il voit | Ce qui manque | Outils |
|---|---|---|---|---|
| Lexical / syntaxique | Jetons individuels et syntaxe | Mise en forme, nommage, syntaxe de base | Tout est sémantique | Plus joli, rustfmt, gofmt |
| En cours d'intervention | Fonction unique | Flux de données local, complexité, flux de contrôle au sein de la fonction | Vulnérabilités interfonctionnelles, problèmes d'architecture | ESLint, Pylint, Clippy, golangci-lint |
| Interprocédural superficiel | Un niveau de profondeur d'appel | Relations directes appelant-appelé | Flux multi-sauts profonds | Quelques règles Semgrep, règles SAST de base |
| interprocédural complet | Parcours complet du graphe d'appels | Flux de contamination multi-sauts, analyse contextuelle | Polymorphisme d'exécution (partiel), appels réflexifs | CodeQL, Facebook Infer, SMART TS XL |
| Programme complet / sémantique | Sémantique complète du programme | propriétés formellement démontrables | Évolutivité sur des bases de code très volumineuses | Infer, Kani (Rust), outils de vérification formelle |
L'analyse interprocédurale présente un potentiel d'efficacité relativement élevé, mais des performances potentielles relativement faibles. Grâce à la richesse de son modèle de programme, elle est également moins sujette aux faux négatifs et aux faux positifs, et son efficacité est globalement supérieure. Cependant, en raison de ses limitations de performance susceptibles de ralentir le développement, l'analyse interprocédurale n'est pas optimale pour les environnements CI/CD. C'est pourquoi elle est souvent proposée comme un service dédié, exécuté sur des fermes de serveurs ou via des outils spécifiques.
Comment fonctionne l'analyse interprocédurale : trois approches clés
1. Analyse basée sur un résumé
L'analyse peut éviter de réexaminer plusieurs fois les mêmes fonctions en créant des résumés de fonctions (parfois appelés modèles). Un résumé décrit le comportement d'une fonction de manière concise : étant donné cet ensemble d'entrées avec ces propriétés de contamination, la fonction produit cet ensemble de sorties avec ces propriétés de contamination. Lorsqu'un appelant appelle la fonction, l'analyse instancie le résumé au lieu de réanalyser le corps de la fonction depuis le début.
L'analyse basée sur des résumés permet de réaliser des analyses interprocédurales à grande échelle. Sans résumés, analyser une fonction appelant une bibliothèque exigerait d'analyser la bibliothèque entière à partir du code source, y compris chaque fonction appelée, et ce de manière récursive. Grâce aux résumés, les fonctions de la bibliothèque sont pré-analysées une seule fois et leurs résumés sont réutilisés à chaque appel.
2. Approche par chaîne d'appel (analyse contextuelle)
L'analyse contextuelle permet de distinguer les différents sites d'appel d'une même fonction. sanitize(input) Lorsqu'une requête est effectuée avec des données fiables à un endroit et des données non fiables à un autre, une analyse contextuelle produit des résultats différents pour chaque point d'appel, au lieu de présumer systématiquement le pire scénario. Cela réduit considérablement les faux positifs par rapport à une analyse insensible au contexte.
Le cadre IFDS (Interprocédural, Fin, Distributif, Sous-ensemble) formalise ce problème comme un problème d'accessibilité de graphe. Dans ce cadre, le flux de contaminations est décrit par des fonctions distributives sur un domaine fini de faits, et la propagation interprocédurale est réduite à l'accessibilité de graphe. Cette formulation rend les analyses IFDS à la fois précises et algorithmiquement efficaces ; des outils comme FlowDroid (pour Android) et plusieurs requêtes CodeQL utilisent IFDS en interne.
3. Construction du graphe d'appels
Toute analyse interprocédurale nécessite un graphe d'appels : un graphe orienté dont les nœuds représentent des fonctions et les arêtes, des appels. La construction de ce graphe implique des choix délicats :
Expédition statique (Appels non virtuels Java, appels de fonctions C, appels au niveau des modules Python) : modélisation simple, f() en cours f.
Dispatch dynamique (méthodes virtuelles Java, appels typés en canard Python) : nécessite une analyse de la hiérarchie des classes (CHA) ou des algorithmes de construction de graphes d'appels plus précis (RTA, VTA) pour déterminer quelles implémentations d'une méthode pourraient être réellement appelées à un site d'appel donné.
Appels réflexifs et pointeurs de fonction : les cas les plus complexes. Les outils doivent soit sur-approximer (en supposant que toute fonction correspondante puisse être appelée, ce qui génère des faux positifs), soit sous-approximer (en omettant certains chemins d’appel, ce qui génère des faux négatifs).
La précision du graphe d'appels détermine directement la précision de l'analyse interprocédurale. Un graphe d'appels comportant trop d'arêtes (sur-approximation) génère des faux positifs ; un graphe incomplet génère des faux négatifs.
Ce que l'analyse interprocédurale révèle et que l'analyse intraprocédurale ne peut pas révéler
Vulnérabilités de sécurité (analyse de contamination)
La plupart des vulnérabilités figurant parmi les 10 principales vulnérabilités de l'OWASP nécessitent une analyse de contamination interprocédurale pour être détectées de manière fiable :
- Injection SQL: les données d'entrée non fiables transitent par plusieurs fonctions avant d'être soumises à une requête de base de données
- XSS: du code HTML non fiable transite par les fonctions de rendu vers le contexte de sortie
- Injection de commande: les données contrôlées par l'utilisateur atteignent
exec()orsystem()par le biais de fonctions intermédiaires - Parcours de chemin: les chemins de fichiers dérivés de la saisie de l'utilisateur atteignent les opérations du système de fichiers
- SSRF: les URL contrôlées par l'utilisateur sont acheminées vers les appels clients 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
Problèmes liés aux pointeurs nuls et à la sécurité de la mémoire
Facebook Infer est l'outil interprocédural de référence pour l'analyse de la sécurité des valeurs nulles. Il utilise la bi-abduction, une forme de raisonnement interprocédural, pour identifier les préconditions et postconditions des fonctions, puis vérifie que les appelants respectent ces préconditions. Un déréférencement de valeur nulle, qui nécessite de savoir qu'une fonction peut renvoyer `null` sous certaines conditions, est invisible pour les outils intraprocéduraux.
Java
// 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
}
Utilisation après libération et fuites de ressources
En C et C++, les erreurs d'utilisation après libération impliquent fréquemment de la mémoire allouée dans une fonction et libérée dans une autre, une troisième fonction conservant une référence. Aucune analyse univoque ne permet de détecter ce type d'erreur. L'analyse interprocédurale vérifie l'intégrité du flux de contrôle : elle s'assure que les appels de fonction suivent les schémas attendus afin de prévenir les exploitations.
Couplage architectural et violations de dépendance
Au-delà de la sécurité et de l'exactitude, l'analyse interprocédurale s'applique à l'analyse architecturale : quels composants appellent quels autres, quelles dépendances violent les limites des couches définies et quelles modifications se propageront dans le graphe d'appels. C'est à cette application de l'analyse interprocédurale que s'appliquent des outils comme SMART TS XL s'applique aux bases de code d'entreprise, en traçant la façon dont une modification apportée à une fonction se propage à travers le graphe d'appels complet afin de déterminer la véritable portée de l'impact.
Quand une analyse interprocédurale est nécessaire
Tous les projets ne nécessitent pas une analyse interprocédurale complète. La décision dépend de l'objectif recherché et du coût d'une absence d'information.
Une analyse interprocédurale est nécessaire lorsque :
- La sécurité est une préoccupation primordiale et vous devez détecter de manière fiable les vulnérabilités d'injection, XSS ou SSRF.
- Vous analysez un code source existant ou d'entreprise où la logique métier s'étend sur de nombreuses fonctions et modules.
- Une analyse d'impact est nécessaire avant toute refactorisation, afin de comprendre les conséquences d'une modification de fonction.
- Vous effectuez une analyse de conformité ou d'audit qui exige de faire preuve de rigueur.
- Vous migrez ou modernisez une base de code et devez comprendre les dépendances entre les composants.
L'analyse peropératoire est suffisante lorsque :
- L'objectif est de faire respecter le style, les conventions et de corriger les erreurs locales évidentes.
- Vous avez besoin d'un retour d'information rapide intégré au cycle de développement (avant validation, IDE, CI par PR).
- Le code source est suffisamment petit pour que les développeurs connaissent toutes les relations d'appel.
- La sécurité est assurée à un niveau différent (cadre validé, système de types) et le risque de vulnérabilités interfonctionnelles est faible.
Pour la plupart des équipes, la solution pratique consiste à utiliser les deux. Les outils intraprocéduraux (ESLint, Pylint, Clippy) s'exécutent à chaque commit pour un retour d'information rapide. Les outils interprocéduraux (CodeQL, Infer, Semgrep avec règles de contamination) s'exécutent lors des pull requests ou de façon régulière chaque nuit pour une analyse approfondie. Ces deux couches sont complémentaires, et non concurrentes.
Outils d'analyse interprocédurale : Contenu de chaque outil
| Outil | Approche d'analyse | Langues | Cas d'utilisation principal |
|---|---|---|---|
| CodeQL | Interprocédural complet, basé sur l'IFDS | C/C++, Java, JS/TS, Python, Go, Ruby | Détection des vulnérabilités de sécurité |
| Facebook Infer | Bi-abduction, interprocédural | Java, C, C++, Objective-C | Sécurité nulle, fuites de mémoire, conditions de concurrence |
| Semgrep (mode de contamination) | contamination interprocédurale superficielle | Plus de 30 langues | Correspondance des modèles de sécurité avec le flux de données |
| Code Snyk | interprocédural basé sur l'apprentissage automatique | Java, JS/TS, Python, C# | Sécurité avec des retours d'information adaptés aux développeurs |
| Polyspace | Interprétation abstraite | C, C++, Ada | Systèmes embarqués critiques pour la sécurité |
| Frama-C | Interprocédural complet + formel | C | vérification formelle, certification de sécurité |
| SMART TS XL | Interlingue, intersystème | COBOL, JCL, Java, Python, RPG, SQL | Analyse d'impact sur l'entreprise, cartographie des dépendances |
Analyse interprocédurale au-delà des frontières linguistiques
Les outils interprocéduraux standard fonctionnent au sein d'un seul langage. Une requête CodeQL sur un code source Java détecte la contamination via les appels de fonctions Java, mais s'arrête lorsque le code Java appelle un programme COBOL ou lit un jeu de données écrit par un traitement par lots JCL. C'est à cette limite que les outils d'analyse à grande échelle rencontrent un problème que les outils monolangages ne peuvent résoudre.
SMART TS XL's analyse de code statique effectue une analyse des dépendances interprocédurales simultanément sur tous les langages de l'environnement d'entreprise. Lorsqu'un service Java appelle un programme COBOL qui lit une table DB2 alimentée par un autre travail JCL, SMART TS XL Elle retrace cette chaîne et la représente sous la forme d'un modèle de dépendance unifié. analyse d’impact Cette capacité applique ensuite ce graphe d'appels interlangages pour répondre à la question suivante : si ce programme COBOL change, quels services Java, quels travaux JCL et quels consommateurs en aval sont affectés ?
Il s'agit du problème interprocédural appliqué à l'échelle architecturale plutôt qu'à l'échelle fonctionnelle ; le même principe (suivre le graphe de dépendances au-delà des frontières sans s'y arrêter) est appliqué à l'ensemble du système d'entreprise plutôt qu'aux programmes individuels. Comme l'illustrent la visualisation du code et la cartographie des dépendances , rendre visible l'intégralité de la structure des dépendances est indispensable à toute analyse visant à comprendre l'impact d'une modification.
Pour les organisations menant modernisation de l'héritage, le graphe d'appels qui SMART TS XL La construction de liens entre les langages remplit la même fonction qu'un graphe d'appels interprocédural au sein d'un seul langage : c'est la carte de la façon dont tout est connecté, et sans elle, toute analyse d'impact, de risque ou de séquence de migration est basée sur des hypothèses plutôt que sur des preuves.
Le compromis entre faux positifs et faux négatifs
Si l'analyse interprocédurale n'est pas systématiquement utilisée, ce n'est pas par manque d'idées, mais parce qu'il existe un compromis entre précision et performance. Une analyse interprocédurale complète et contextuelle d'un code source d'un million de lignes peut prendre des heures, voire des jours. L'analyse du même code par ESLint ne prend que quelques secondes. Cet écart de performance est structurel : la complexité de l'analyse interprocédurale augmente avec celle du graphe d'appels, et non seulement avec la taille du code source.
L'avantage de cet investissement réside dans la réduction mesurable des taux de faux positifs et de faux négatifs :
- Faux positifs (Outils signalant un problème alors qu'il n'y en a pas) : les outils intraprocéduraux génèrent beaucoup plus de faux positifs car ils ne peuvent déterminer si un schéma suspect est réellement dangereux sans connaître le contexte fourni par les appelants. Un outil intraprocédural qui détecte une requête SQL contenant une chaîne de caractères concaténée ne peut pas savoir si cette chaîne provient d'une saisie utilisateur ou d'une source interne fiable.
- Faux négatifs (outils ne détectent pas les vulnérabilités réelles) : les outils intraprocéduraux produisent beaucoup plus de faux négatifs car ils ne peuvent pas détecter les vulnérabilités qui dépassent les limites des fonctions, ce qui, pour une analyse pertinente en matière de sécurité, représente la majorité des vulnérabilités réelles.
La capacité à choisir judicieusement chaque niveau d'analyse et à les combiner au sein d'une chaîne d'outils structurée distingue l'ingénierie de la sécurité d'une simple conformité formelle. Savoir que votre analyseur de code (linter) fournit des résultats corrects ne vous renseigne que très peu sur votre niveau de sécurité. En revanche, savoir que votre analyse de contamination interprocédurale a exploré chaque chemin depuis les sources d'entrée externes jusqu'aux cibles sensibles de votre application, sans détecter aucun flux non validé, vous apporte des informations bien plus précieuses.


