Skip to main content
  1. Concetti/

Race Condition e TOCTOU - Vulnerabilita' di Timing e Atomicita'

·5 mins
Alessio Barnini
Author
Alessio Barnini
Table of Contents

Cosa fa
#

Una race condition e' una vulnerabilita' in cui il comportamento del sistema dipende dal timing relativo tra due o piu' operazioni concorrenti. TOCTOU e' la variante piu' nota: la finestra tra il controllo di una risorsa (check) e il suo utilizzo (use) puo' essere sfruttata dall'attaccante.

TL;DR
#

  1. Race condition: risultato dipende da chi arriva primo tra due operazioni concorrenti.
  2. TOCTOU: il sistema verifica una risorsa (check), poi la usa (use). L'attaccante agisce nella finestra tra i due passi.
  3. Esempio filesystem: check permessi su /tmp/file, poi write. Nella finestra, l'attaccante sostituisce il file con un symlink a /etc/passwd.
  4. Esempio web: doppio click su "invia pagamento" prima che il primo venga committed. Saldo scalato due volte.
  5. Fix: operazioni atomiche (transazioni DB con lock, mutex, semaforo, O_NOFOLLOW per symlink).
  6. Sinonimi operativi: "state attack", "concurrency bug", "TOCTOU". Fix: operazioni atomiche, mutex, SELECT FOR UPDATE.

Race Condition
#

Una race condition emerge quando due o piu' operazioni concorrenti accedono alla stessa risorsa condivisa e il risultato finale dipende dal timing relativo tra le operazioni. In sicurezza, questo crea una finestra di opportunita' per l'attaccante.


TOCTOU - Time of Check to Time of Use
#

TOCTOU e' la variante security-critica delle race condition. Il sistema esegue due operazioni separate nel tempo: prima verifica (check) poi usa (use). La finestra tra le due operazioni e' il punto di attacco.

flowchart TD
    A[Time of Check] --> |"verifica: risorsa OK"| B{Finestra TOCTOU}
    B --> |"attaccante modifica la risorsa qui"| C[Time of Use]
    C --> |"usa: ma la risorsa non e' piu' quella verificata"| D["Risultato inatteso"]

    style B fill:#a72626,stroke:#c0392b,color:#fff
    style D fill:#a72626,stroke:#c0392b,color:#fff

Esempio classico filesystem (in C):

/* VULNERABILE - TOCTOU */
if (access("/tmp/file", W_OK) == 0) {
    /* FINESTRA: attaccante esegue: ln -sf /etc/passwd /tmp/file */
    int fd = open("/tmp/file", O_WRONLY);
    write(fd, data, len);  /* scrive su /etc/passwd! */
    close(fd);
}

/* CORRETTO - Nessuna finestra */
int fd = open("/tmp/file", O_WRONLY | O_NOFOLLOW);  /* O_NOFOLLOW: rifiuta symlink */
if (fd >= 0) {
    /* verifica permessi sul fd stesso, non sul path */
    if (fstat(fd, &st) == 0 && (st.st_mode & S_IWUSR)) {
        write(fd, data, len);
    }
    close(fd);
}

Esempio database (Node.js / TypeORM):

/* VULNERABILE - TOCTOU */
async function transferFunds(fromId: number, amount: number) {
    const account = await Account.findOne(fromId);  // CHECK: legge saldo
    // FINESTRA: altra request fa la stessa cosa qui
    if (account.balance >= amount) {
        await Account.update(fromId, { balance: account.balance - amount });  // USE
    }
}

/* CORRETTO - Transazione atomica con pessimistic lock */
async function transferFunds(fromId: number, amount: number) {
    await dataSource.transaction(async (manager) => {
        const account = await manager.findOne(Account, {
            where: { id: fromId },
            lock: { mode: 'pessimistic_write' }  // lock acquisito al CHECK
        });
        if (account.balance >= amount) {
            account.balance -= amount;
            await manager.save(account);
        }
    });
}

Primitive di Sincronizzazione
#

Le primitive di sincronizzazione chiudono la finestra TOCTOU rendendo la sequenza check-use atomica o serializzata.

flowchart LR
    subgraph MUTEX ["Mutex (Mutual Exclusion)"]
        direction TB
        T1["Thread 1"] --> L1["Acquisisci Lock"]
        L1 --> USE1["Usa Risorsa"]
        USE1 --> R1["Rilascia Lock"]
        T2["Thread 2"] --> WAIT["Attendi Lock disponibile"]
        WAIT --> USE2["Usa Risorsa (dopo Thread 1)"]
    end
MeccanismoDescrizioneQuando usarlo
Mutex (Mutual Exclusion)Lock binario: un solo thread alla voltaAccesso esclusivo a una risorsa condivisa
SemaphoreLock con contatore N: al massimo N thread simultaneiPool di connessioni, risorse limitate non esclusive
Atomic OperationsOperazioni indivisibili a livello hardwareContatori, flag, operazioni semplici senza lock overhead
Database TransactionAtomicita' garantita dal motore DBQualsiasi operazione che modifica dati relazionali
Tip

Keyword esame: "Prevents race condition by allowing only one thread" → Mutex. "Limits concurrent access to N threads" → Semaphore. "Indivisible operation at hardware level" → Atomic. Tutti e tre prevengono race condition ma con granularita' diversa.


Casi Reali
#

Mars Rover Spirit (2004): una race condition nel filesystem del rover causava un safety reboot al riavvio. Il check del filesystem e il trigger del reboot non erano atomici. Il rover eseguiva in loop infinito di reboot sulla superficie di Marte. I tecnici NASA inviarono un comando di bypass via radio per rompere il ciclo.

Tesla Model 3 - Pwn2Own Vancouver 2023: ricercatori sfruttano una TOCTOU via Bluetooth per compromettere l'infotainment, poi escalation a root. Premio: $100.000 + il Tesla. Tesla ha patchato la vulnerabilita' prima della divulgazione pubblica.

Dirty COW (CVE-2016-5180): race condition nel kernel Linux nel meccanismo Copy-On-Write. Un processo senza privilegi otteneva accesso in scrittura a file read-only mappati in memoria. Presente nel kernel dal 2007, scoperta nel 2016. Permetteva privilege escalation a root modificando /etc/passwd.


Scenario Reale
#

Un SOC analyst analizza un ticket di supporto anomalo: un utente e-commerce segnala che il saldo del suo wallet e' diventato negativo dopo aver cliccato rapidamente due volte il pulsante "Usa crediti". I log mostrano due POST a /api/wallet/debit nello stesso millisecondo con la stessa session.

Root cause: assenza di transazione atomica nell'handler del debit. Entrambe le request leggono il saldo nello stesso momento (100 crediti), entrambe calcolano 100 - 50 = 50, entrambe scrivono 50. Il saldo finale e' 50 invece di 0, ma nessuna delle due request "sa" dell'altra.

Fix immediato: SELECT FOR UPDATE nella transazione. Tutti i debit vengono serializzati: la seconda request attende che la prima completi prima di leggere il saldo aggiornato.

Warning

Il TOCTOU non richiede un attaccante esterno. Bug di concorrenza emergono naturalmente con traffico normale su sistemi non progettati per la concorrenza. La differenza tra un bug e una vulnerabilita' e' che l'attaccante puo' innescare il timing deliberatamente (es. flood di request simultanee).

Dev parallel: in Node.js asincrono, due request che arrivano contemporaneamente e modificano lo stesso campo nel DB senza locking portano a questo esatto problema. La soluzione e' SELECT FOR UPDATE in TypeORM/Prisma, o optimistic locking con versione field. Il problema non e' il linguaggio (Node e' single-thread nel event loop) ma il database: le query girano in parallelo.

Collegato a
#

Related