Aikido

Perché dovresti evitare la ricorsione senza protezione della profondità

Rischio di bug

Regola
Evita la ricorsione senza protezione
della profondità. La ricorsione senza
un'adeguata limitazione della profondità rischia il
stack overflow e crea vulnerabilità DoS
derivanti da input malesi. La ricorsione
con limiti di profondità
imposti è accettabile.

Lingue supportate: 45+

Introduzione

Le funzioni ricorsive prive di limiti di profondità possono esaurire lo stack delle chiamate, provocando arresti anomali. Input malevoli, come oggetti JSON profondamente annidati o strutture dati cicliche, possono innescare intenzionalmente una ricorsione illimitata. Una singola richiesta manipolata può mandare in crash il servizio superando i limiti dello stack, creando una vulnerabilità di tipo denial-of-service estremamente facile da sfruttare.

Perché è importante

Implicazioni sulla sicurezza (attacchi DoS): gli attaccanti possono creare input in grado di innescare una ricorsione profonda, mandando in crash l'applicazione. Oggetti JSON o XML profondamente annidati o strutture dati collegate sono vettori di attacco comuni. Una singola richiesta malevisa esaurisce lo stack, bloccando l'intero servizio per tutti gli utenti.

Stabilità del sistema: gli errori di stack overflow causano l'arresto immediato del processo senza alcuna degradazione graduale. In produzione, questo si traduce in richieste scartate, transazioni interrotte e indisponibilità del servizio. Il ripristino richiede il riavvio dell'intera applicazione.

Esaurimento delle risorse: la ricorsione illimitata consuma memoria di stack in modo esponenziale. Ogni chiamata ricorsiva aggiunge un frame allo stack e catene di ricorsione profonda possono consumare megabyte di memoria. Ciò influisce sugli altri processi presenti sullo stesso server e può scatenare condizioni di out-of-memory.

Esempi di codice

❌ 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;
}

Perché è errato: L'assenza di un limite di profondità consente agli attaccanti di inviare oggetti profondamente annidati che superano i limiti dello stack. Input come {a: {a: {a: {...}}}} annidato a una profondità di 10.000 livelli fa crashare l'applicazione con un errore di stack overflow. La funzione esegue una ricorsione cieca senza verificarne la profondità.

✅ 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;
}

Perché questo è importante: Il parametro maxDepth il parametro limita la ricorsione a 100 livelli, prevenendo il sovraccarico dello stack. Questo limite è sufficientemente elevato per strutture dati annidate legittime (la maggior parte dei dati reali raramente supera i 10-20 livelli) e al tempo stesso abbastanza basso da bloccare gli attacchi prima che venga consumata una quantità significativa di memoria dello stack. Un input malevolo e profondamente annidato genera un errore anziché mandare in crash l'applicazione. Il controllo della profondità avviene prima dell'elaborazione, fallendo rapidamente quando i limiti vengono superati.

Conclusione

Aggiungi parametri di profondità a tutte le funzioni ricorsive che elaborano dati esterni. Imposta massimi di profondità ragionevoli in base alla complessità della struttura dati prevista. Lancia errori o restituisci valori predefiniti quando i limiti di profondità vengono superati, invece di causare un crash.

FAQ

Hai domande?

Qual è una profondità di ricorsione massima ragionevole?

Dipende dalle tue strutture dati. Per il parsing JSON o l'attraversamento di alberi, 100-1000 livelli sono ragionevoli. La maggior parte delle strutture dati legittime non supera i 10-20 livelli. Imposta i limiti in base al tuo dominio, ma abbi sempre dei limiti. Monitora la produzione per verificare le profondità effettive e regola i valori di conseguenza.

Come posso convertire la ricorsione in iterazione?

Usa stack o code esplicite. Sostituisci le chiamate ricorsive con un ciclo che inserisce gli elementi in uno stack, per poi estrarli ed elaborarli. In questo modo ottieni il pieno controllo sull'utilizzo della memoria e sulla profondità. Per l'attraversamento di alberi, l'iterazione in ampiezza (breadth-first) o in profondità (depth-first) con strutture dati esplicite previene lo stack overflow.

Dovrei controllare la profondità all'inizio o alla fine delle chiamate ricorsive?

All'inizio, prima di qualsiasi elaborazione. In questo modo il sistema fallisce rapidamente se i limiti vengono superati, evitando calcoli sprecati su dati che verranno rifiutati. Le guard clause all'ingresso della funzione rendono i controlli di profondità espliciti e facili da verificare.

Come gestisco i riferimenti ciclici nelle funzioni ricorsive?

Tieni traccia degli oggetti visitati in un Set o WeakSet. Prima di eseguire la ricorsione, verifica se l'oggetto è stato già visitato. In caso affermativo, saltalo, genera un errore o restituisci un segnaposto. Questo previene la ricorsione infinita derivante da strutture dati circolari: obj.child.parent === obj.

Metti in sicurezza ora

Metti in sicurezza il tuo codice, il cloud e il runtime in un unico sistema centralizzato.
Trova e risolvi le vulnerabilità rapidamente e automaticamente.

Nessuna carta di credito richiesta | Risultati della scansione in 32 secondi.