Aikido

Pourquoi il faut éviter la récursion sans protection de profondeur

Risque de bug

Règle
Éviter la récursivité sans profondeur protection.
La récursivité sans propre limitation de profondeur limitant risque pile
débordement et crée vulnérabilités vulnérabilités à partir de malveillante
entrées. Récursivité avec profondeur limites de profondeur limites et limites
limites et est acceptable.

Prise en charge linguistique : 45+

Introduction

Les fonctions récursives sans limites de profondeur peuvent épuiser la pile d'appels (call stack), provoquant des plantages. Des entrées malveillantes, comme des objets JSON profondément imbriqués ou des structures de données cycliques, peuvent déclencher intentionnellement une récursion illimitée. Une seule requête spécialement conçue peut faire planter votre service en dépassant les limites de la pile, créant ainsi une vulnérabilité de déni de service triviale à exploiter.

Pourquoi c'est important

Implications de sécurité (attaques DoS) : Les attaquants peuvent élaborer des entrées qui déclenchent une récursion profonde, provoquant le crash de votre application. Les structures de données JSON, XML ou liées profondément imbriquées sont des vecteurs d'attaque courants. Une seule requête malveillante épuise la pile, rendant l'ensemble du service indisponible pour tous les utilisateurs.

Stabilité du système : Les erreurs de débordement de pile (stack overflow) font planter le processus immédiatement sans dégradation progressive. En production, cela signifie des requêtes abandonnées, des transactions interrompues et une indisponibilité du service. La récupération nécessite le redémarrage de toute l'application.

Épuisement des ressources : Une récursion non bornée consomme la mémoire de la pile de manière exponentielle. Chaque appel récursif ajoute une trame de pile, et les chaînes de récursion profondes peuvent consommer des mégaoctets de mémoire. Cela affecte d'autres processus sur le même serveur et peut déclencher des conditions de manque de mémoire.

Exemples de code

❌ Non conforme :

function processNestedData(obj) {
    if (typeof obj !== 'object' || obj === null) {
        return obj;
    }

    const result = {};
    for (const key in obj) {
        result[key] = processNestedData(obj[key]);
    }
    return result;
}

Pourquoi c'est incorrect : L'absence de limite de profondeur permet aux attaquants d'envoyer des objets profondément imbriqués qui dépassent les limites de la pile. Une entrée comme {a: {a: {a: {...}}}} imbriquée sur 10 000 niveaux de profondeur fait planter l'application avec un débordement de pile (stack overflow). La fonction récurse aveuglément sans vérifier la profondeur.

✅ Conforme :

function processNestedData(obj, depth = 0, maxDepth = 100) {
    if (depth > maxDepth) {
        throw new Error('Maximum nesting depth exceeded');
    }

    if (typeof obj !== 'object' || obj === null) {
        return obj;
    }

    const result = {};
    for (const key in obj) {
        result[key] = processNestedData(obj[key], depth + 1, maxDepth);
    }
    return result;
}

Pourquoi c'est important : Le maxDepth le paramètre limite la récursion à 100 niveaux, prévenant ainsi le débordement de pile (stack overflow). Cette limite est suffisamment élevée pour les structures de données imbriquées légitimes (la plupart des données réelles dépassent rarement 10 à 20 niveaux) tout en étant suffisamment basse pour arrêter les attaques avant qu'elles ne consomment une mémoire de pile significative. Une entrée malveillante profondément imbriquée génère une erreur au lieu de faire planter l'application. La vérification de la profondeur a lieu avant le traitement, ce qui permet une défaillance rapide lorsque les limites sont dépassées.

Conclusion

Ajoutez des paramètres de profondeur à toutes les fonctions récursives qui traitent des données externes. Définissez des profondeurs maximales raisonnables basées sur la complexité attendue de la structure de données. Lancez des erreurs ou renvoyez des valeurs par défaut lorsque les limites de profondeur sont dépassées, au lieu de provoquer un crash.

FAQ

Des questions ?

Quelle est une profondeur de récursion maximale raisonnable ?

Dépend de vos structures de données. Pour l'analyse JSON ou la traversée d'arbres, 100 à 1000 niveaux sont raisonnables. La plupart des structures de données légitimes ne dépassent pas 10 à 20 niveaux. Définissez des limites en fonction de votre domaine, mais ayez toujours des limites. Surveillez la production pour voir les profondeurs réelles et ajustez en conséquence.

Comment convertir la récursion en itération ?

Utilisez des piles ou des files d'attente explicites. Remplacez les appels récursifs par une boucle qui pousse les éléments sur une pile, puis les dépile et les traite. Cela vous donne un contrôle total sur l'utilisation de la mémoire et la profondeur. Pour le parcours d'arbres, l'itération en largeur d'abord ou en profondeur d'abord avec des structures de données explicites prévient le débordement de pile.

Dois-je vérifier la profondeur au début ou à la fin des appels récursifs ?

Dès le début, avant tout traitement. Cela permet une défaillance rapide lorsque les limites sont dépassées, évitant ainsi un gaspillage de calcul sur des données qui seraient rejetées. Les clauses de garde à l'entrée des fonctions rendent les vérifications de profondeur explicites et faciles à auditer.

Comment gérer les références cycliques dans les fonctions récursives ?

Suivez les objets visités dans un Set ou un WeakSet. Avant de récurser, vérifiez si l'objet a déjà été visité. Si oui, ignorez-le, levez une erreur ou renvoyez un substitut. Ceci évite la récursion infinie due aux structures de données circulaires : obj.child.parent === obj.

Sécurisez-vous maintenant.

Sécuriser votre code, votre cloud et votre runtime dans un système centralisé unique.
Détectez et corrigez les vulnérabilités rapidement et automatiquement.

Pas de carte de crédit requise | Résultats du scan en 32 secondes.