Aikido

Perché è meglio evitare la ricorsione senza protezione della profondità

Rischio di bug

Regola
Evitare la la ricorsione senza protezione .
Ricorsione senza adeguata profondità limitazione rischi pila
overflow e crea vulnerabilità vulnerabilità derivanti da input
. Ricorsione con restrizioni profondità limiti e adeguati
limiti verifica è accettabile.

Supporto linguistico: 45+

Introduzione

Le funzioni ricorsive senza limiti di profondità possono esaurire lo stack delle chiamate, causando arresti anomali. Input dannosi, come oggetti JSON profondamente annidati o strutture di dati cicliche, possono innescare intenzionalmente una ricorsione illimitata. Una singola richiesta appositamente costruita può causare l'arresto anomalo del servizio superando i limiti dello stack, creando una vulnerabilità di tipo denial-of-service (DoS) di facile sfruttamento.

Perché è importante

Implicazioni per la sicurezza (attacchi DoS): gli aggressori possono creare input che innescano una ricorsione profonda, causando il crash dell'applicazione. Strutture JSON, XML o dati collegati profondamente annidati sono vettori di attacco comuni. Una singola richiesta dannosa esaurisce lo stack, mettendo fuori uso l'intero servizio per tutti gli utenti.

Stabilità del sistema: gli errori di overflow dello stack causano l'arresto immediato del processo senza un degrado graduale. In ambiente di produzione, ciò comporta la perdita di richieste, l'interruzione delle transazioni e l'indisponibilità del servizio. Il ripristino richiede il riavvio dell'intera applicazione.

Esaurimento delle risorse: la ricorsione illimitata consuma la memoria dello stack in modo esponenziale. Ogni chiamata ricorsiva aggiunge un frame dello stack e le catene di ricorsione profonde possono consumare megabyte di memoria. Ciò influisce sugli altri processi presenti sullo stesso server e può causare condizioni di esaurimento della memoria.

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é è sbagliato: L'assenza di un limite di profondità consente agli autori degli attacchi di inviare oggetti profondamente annidati che superano i limiti dello stack. Input come {a: {a: {a: {...}}}} Una ricorsione annidata di 10.000 livelli provoca il crash dell'applicazione a causa di uno stack overflow. La funzione esegue la ricorsione in modo cieco, senza verificare 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é è importante: Il maxDepth Questo parametro limita la ricorsione a 100 livelli, impedendo il traboccamento dello stack. Tale limite è sufficientemente elevato per le strutture di dati annidate legittime (la maggior parte dei dati reali raramente supera i 10-20 livelli), pur essendo sufficientemente basso da bloccare gli attacchi prima che consumino una quantità significativa di memoria dello stack. Un input dannoso profondamente annidato genera un errore anziché causare il crash dell'applicazione. Il controllo della profondità avviene prima dell'elaborazione, con un'interruzione immediata in caso di superamento dei limiti.

Conclusione

Aggiungere parametri di profondità a tutte le funzioni ricorsive che elaborano dati esterni. Impostare valori massimi di profondità ragionevoli in base alla complessità prevista della struttura dei dati. Quando i limiti di profondità vengono superati, generare errori o restituire valori predefiniti, anziché causare un arresto anomalo del programma.

Domande frequenti

Hai delle domande?

Qual è una profondità massima ragionevole di ricorsione?

Dipende dalle vostre strutture dati. Per l'analisi di JSON o la traversata di alberi, un numero di livelli compreso tra 100 e 1000 è ragionevole. La maggior parte delle strutture dati standard non supera i 10-20 livelli. Stabilite dei limiti in base al vostro ambito, ma assicuratevi sempre di fissarne. Monitorate l'ambiente di produzione per verificare le profondità effettive e adeguate i limiti di conseguenza.

Come si fa a convertire la ricorsione in iterazione?

Utilizza stack o code espliciti. Sostituisci le chiamate ricorsive con un ciclo che inserisce gli elementi in uno stack, per poi prelevarli ed elaborarli. In questo modo avrai il pieno controllo sull'utilizzo della memoria e sulla profondità. Per la traversata degli alberi, l'iterazione in larghezza o in profondità con strutture dati esplicite impedisce l'overflow dello stack.

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

All'inizio, prima di qualsiasi elaborazione. In questo modo si rileva rapidamente un errore in caso di superamento dei limiti, evitando così di sprecare risorse di calcolo su dati che verranno scartati. Le clausole di controllo all'inizio della funzione rendono i controlli di profondità espliciti e facili da verificare.

Come si gestiscono i riferimenti ciclici nelle funzioni ricorsive?

Tieni traccia degli oggetti visitati in un Set o in un WeakSet. Prima di procedere con la ricorsione, verifica se l'oggetto è già stato visitato. In caso affermativo, saltalo, genera un errore oppure restituisci un segnaposto. Ciò impedisce la ricorsione infinita causata 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.