Il disservizio CrowdStrike derivava da una discrepanza di 21 contro 20 campi. Kestrel controlla il codice prima del riavvio.
CrowdStrikeSicurezza degli endpointResilienza IT

Il crash di CrowdStrike dipendeva da un conteggio: 21 campi dove il kernel ne attendeva 20. Nessun livello indipendente verificava.

Ashutosh SinghalAshutosh Singhal20 luglio 202611 min

Il 19 luglio 2024, un singolo aggiornamento di un fornitore ha mandato in crash milioni di macchine Windows in meno di 90 minuti, e la causa è stata un semplice numero. Un file di canale «Rapid Response Content» di CrowdStrike dichiarava 21 campi dove l'interprete kernel distribuito se ne aspettava 20. Quel campo aggiuntivo ha prodotto una lettura fuori dai limiti (out-of-bounds read), una schermata blu istantanea e, poiché il crash si è verificato così presto durante la fase di avvio, l'agente in blocco non è mai riuscito a ripartire per ricevere un comando di rollback. Il ripristino ha richiesto di raggiungere fisicamente ogni singola macchina e ripararla manualmente in modalità provvisoria (Safe Mode).

Ho letto l'analisi delle cause primarie (root-cause analysis) di CrowdStrike, pubblicata in agosto, più di una volta prima che emergesse ciò che realmente mi turbava. Non si trattava di un attacco informatico. Non era un modello imperfetto. Era un fatto aritmetico decidibile, 21 contro 20, inserito in un payload che nessun livello indipendente aveva mai verificato prima che raggiungesse la produzione. Il validatore del fornitore lo ha approvato. Le aziende che si sono ritrovate paralizzate non possedevano quel validatore. Ne hanno subito le conseguenze.

Ho trascorso l'ultimo periodo a costruire una demo attorno a questo divario: una console che ho chiamato Kestrel, che si posiziona tra un fornitore di software e una flotta di produzione e decide, tramite codice, cosa il fornitore sia autorizzato a distribuire. Potete vederne il funzionamento su veriprajna.com/demos/software-update-integrity. Ciò che mi ha sorpreso durante la costruzione è stato il punto esatto in cui risiedeva la soluzione. Ero partito con la convinzione che avrei avuto bisogno di un modello più intelligente, e poche righe di semplice Python hanno intercettato il crash prima di qualunque altra cosa.

Ho ricostruito il crash e poi ho lasciato che fosse il codice a decidere

Ho ricostruito la sequenza di fallimento del 19 luglio sotto forma di fixture e vi ho indirizzato contro il mio stesso sistema, aspettandomi quasi di restare deluso dal mio stesso replay. Il pacchetto è C-00000291, un file di canale Rapid Response Content proveniente da un fornitore fittizio che ho chiamato SentinelEdge, inviato a una flotta sintetica di 8.500 endpoint denominata Acme Financial. Nessuna di queste è una società reale. La firma del guasto è invece autentica: uno schema dichiarato di 20 campi che sale a 21, distribuito al 100% della flotta in un'unica ondata, senza alcun piano di rilascio graduale (canary).

Il policy gate scatta simultaneamente su quattro controlli, e ciascuno di essi è pura aritmetica o una ricerca diretta, mai una valutazione soggettiva. Il diff dello schema individua 21 campi dove l'interprete ne attende 20 e segnala la lettura fuori dai limiti. Una sandbox simulata, che è un modello deterministico di esito per profilo e non una farm di macchine virtuali Windows reali, manda in boot-loop 5 profili su 6 della flotta attraverso cicli di riavvio. Ricava tale risultato da un segnale di compatibilità dei driver indipendente dal controllo dello schema, cosicché i due riscontri si corroborano a vicenda anziché ripetersi a eco. Il rilevatore di agenti inerti contrassegna il ciclo di rollback come vero, perché l'agente che va in crash è esso stesso il componente che dovrebbe ricevere il rollback, ed è inerte prima del completamento dell'avvio. Il raggio di impatto è del 100% a fronte di una policy canary del 5%. Verdetto: BLOCK. Sullo schermo si legge: bloccato prima che qualunque endpoint di produzione venisse riavviato.

La console Kestrel che mostra il pannello Block Rollout per il pacchetto C-00000291 del fornitore SentinelEdge, con le quattro prove deterministiche, una flotta di 8.500 endpoint e una stima di 5.000.000 $ di tempo di inattività evitato.
C-00000291, la firma del 19 luglio riprodotta. Il gate attiva tutti e quattro i controlli: discrepanza nel conteggio dei campi dello schema (20 attesi, 21 forniti), boot-loop in sandbox su 5/6 profili, ciclo di rollback con agente inerte e raggio di impatto del 100% contro la policy canary del 5%. Verdetto BLOCK, tempo di inattività evitato stimato in 5.000.000 $.

La stima del tempo di inattività evitato su quel singolo aggiornamento indica 5.000.000 $, e desidero essere preciso su cosa rappresenti tale cifra. Si tratta del modello interno della demo: la quota interessata moltiplicata per un parametro di 5 milioni di dollari l'ora e per un limite minimo di ripristino di un'ora, con la formula stampata a schermo. Non è denaro che un cliente ha concretamente risparmiato. Il vero ripristino del 19 luglio ha richiesto giorni, non un'ora, quindi la soglia minima è deliberatamente prudente.

Il caso verde mi ha spaventato più di quello rosso

Ero più preoccupato per il caso verde che per quello rosso, perché un livello di governance che blocca l'aggiornamento pericoloso ma soffoca anche quello sicuro non è altro che un disservizio programmato da noi stessi. Lo stesso fornitore fittizio rilascia RRC-7741, un aggiornamento benigno di firme di rilevamento, con schema dichiarato da 20 a 20 e un piano canary scaglionato all'1,2%. Il team di agenti si avvia, lo schema coincide, 5 profili su 6 superano i loro cicli di riavvio, il ciclo di agente inerte è falso, il raggio di impatto rientra nella policy. Verdetto: APPROVE ROLLOUT, distribuito a un anello canary di 102 endpoint. Verde, rapido, lineare.

La console Kestrel che mostra il pannello verde Approve Rollout per RRC-7741 rilasciato a un anello canary dell'1,2%, con la traccia di valutazione 7/7 e il registro delle prove.
L'aggiornamento benigno dello stesso fornitore, RRC-7741. Lo schema corrisponde, 5/6 profili superano i cicli di riavvio, ciclo di agente inerte falso, raggio di impatto dell'1,2% conforme alla policy. Verdetto APPROVE ROLLOUT, rilasciato a un canary di 102 endpoint, registro delle prove sha256:798431b4c96612a9.

Tra i sei aggiornamenti benigni del set, il gate ha prodotto zero falsi blocchi. Lo affermo specificando il denominatore, perché sei è sei, e non consentirò che venga arrotondato in una promessa per la vostra flotta. Il valore del caso ALLOW è più circoscritto e più determinante di una percentuale. Un gate è credibile solo se risulta invisibile sul normale traffico e irremovibile nell'unico evento in grado di abbattere la vostra infrastruttura.

Perché ho tolto il verdetto dal modello

Ho intrapreso questo sviluppo partendo dal presupposto che la parte complessa risiedesse nel ragionamento e che un modello più affilato o un critico più accorto sarebbe stato l'elemento capace di cogliere l'aggiornamento errato. Mi sbagliavo, e ho impiegato del tempo ad ammetterlo. C'è un gruppo di LLM all'interno di Kestrel: un normalizzatore, un interprete di sandbox e due critici contrapposti, uno che sostiene che l'aggiornamento sia sicuro da distribuire e uno che sostiene che causerà un crash. La coppia avversaria guadagna il suo posto perché sottopone a red-teaming il verdetto da entrambe le direzioni prima che qualunque cosa venga decisa. Ma nessuno di questi agenti stabilisce il verdetto.

Il verdetto viene stabilito da due semplici file Python, verifier.py e gate.py, che risiedono interamente all'esterno del framework degli agenti. Il gruppo opera su Pydantic AI con il modello predefinito claude-opus-4-8, e l'intero sistema funziona anche offline senza alcuna chiave API tramite un fallback consultivo deterministico. In ciascuna di queste modalità il gate è identico e restituisce la medesima decisione, poiché la decisione è aritmetica, non inferenza. Gli agenti consigliano, il codice decide. Un agente consultivo orientato su «consenti» non può annullare un riscontro deterministico critico, e questa non è una questione di preferenze.

Un livello creato per controllare il fornitore non può fidarsi della parola del fornitore per quanto riguarda la sicurezza. Non può nemmeno fidarsi della parola del proprio modello.

Questa frase è il motivo per cui l'architettura si presenta in questo modo. La fiducia in un prodotto il cui unico compito è governare ciò che un fornitore distribuisce non deve mai passare attraverso un componente che possa essere convinto a dire sì.

Cosa consegnerei a un revisore

Ho tenuto il Cyber Resilience Act dell'UE aperto su un secondo monitor mentre costruivo il registro delle prove, perché quel registro è l'artefatto che dovrei effettivamente difendere. Ogni decisione esporta un file HTML immutabile e un file JSON firmato contenente un hash di contenuto SHA-256, il verdetto, le prove deterministiche, i risultati della sandbox per ciascun profilo, i verdetti degli agenti consultivi con l'identificativo del loro modello, le regole di policy attivate e una traccia di valutazione passo per passo in cui ogni fase riporta la propria latenza.

La vista della decisione di Kestrel per il pacchetto C-00000291 bloccato, che mostra il registro delle prove con sha256:0f4f71b2bd7d1753 e i pulsanti per aprire il record HTML e il JSON firmato.
Il registro delle prove esportato per l'aggiornamento bloccato. Un hash di contenuto SHA-256, un record HTML e un file JSON firmato, generati direttamente all'atto della decisione.

La traccia è la componente che ho sottovalutato finché non ho fatto clic su un singolo passaggio. Un evento recita: «Normalizzazione del manifesto del fornitore firmato, completata in 184 ms», e viene conservato con l'output della decisione per la verifica di conformità. Ogni passaggio è ricalcolabile. Un regolatore non deve fidarsi ciecamente della mia dashboard. Può ripetere l'aritmetica e ottenere il medesimo risultato.

Una finestra modale della traccia di valutazione di Kestrel che riporta «Normalizzazione del manifesto del fornitore firmato, completata in 184 ms», con una nota in cui si specifica che l'evento è conservato per l'audit.
Un passaggio della traccia di valutazione, aperto. Normalizzazione del manifesto del fornitore firmato, completata in 184 ms, conservata con l'output della decisione per la revisione di conformità.

Sono meticoloso su cosa sia e cosa non sia la firma. Si tratta di un SHA-256 locale, non di una PKI aziendale. Il feed degli aggiornamenti del fornitore e i ticket ITSM sottostanti sono stub di test, non connettori in tempo reale. Il registro è progettato per allinearsi alle esigenze di rendicontazione: la notifica tempestiva degli incidenti prevista dal CRA, l'obbligo di divulgazione entro quattro giorni lavorativi di un incidente di cybersicurezza rilevante imposto dalla SEC e le questioni di responsabilità del fornitore emerse nella causa Delta v. CrowdStrike nella contea di Fulton nel 2025. Progettato per allinearsi. Non rilascia certificazioni a nessuno, non costituisce consulenza legale e chiunque vi venda un registro di controllo sostenendo che vi renda conformi cerca solo di vendervi qualcosa.

C'è un'ulteriore decisione di cui sono orgoglioso, ed è un rifiuto. Il test fixture XX-0000 è un payload di contenuti proprietari crittografati che il gate non può analizzare, quindi non formula ipotesi. Restituisce ABSTAIN e lo inoltra a un revisore umano, perché un gate che dà il via libera a ciò che non sa interpretare è peggiore di nessun gate. Gli host legacy che la sandbox non è in grado di modellare vengono contrassegnati ed esclusi, mai considerati sicuri per presunzione. Il vocabolario conta quattro parole: ALLOW, HOLD, BLOCK, ABSTAIN, e quest'ultima è quella che difenderei con maggior vigore.

Cosa ha il diritto di significare 12 su 12

Qui devo rallentare, perché è esattamente il momento in cui un fondatore inizia ad arrotondare per eccesso. E ho chiamato l'azienda Veriprajna, «vera saggezza», quindi l'arrotondamento è fuori discussione. Su un set fisso ed etichettato di dodici aggiornamenti, il gate restituisce la decisione corretta su tutti e dodici. Sei di essi sono innocui e non ne blocca nessuno. Uno corrisponde all'onesto ABSTAIN. Il quadro mostra 12/12 decisioni verificate, 0/6 falsi blocchi e 13,3 milioni di dollari di tempo di inattività evitato stimato sull'intero set, di cui 5 milioni derivano dal solo blocco della classe CrowdStrike.

Il pannello benchmark di Kestrel che riporta 12/12 decisioni verificate, 0/6 falsi blocchi e 13,3 M$ di esposizione evitata sull'insieme di test di rilascio etichettati.
Il quadro di valore sul set di test etichettati. 12/12 decisioni verificate, 0/6 falsi blocchi sugli aggiornamenti innocui, 13,3 M$ di tempo di inattività evitato stimato sull'insieme. Denominatori piccoli, dichiarati di proposito.

Ora la parte che mi rifiuto di abbreviare. Questi sono risultati ottenuti su dodici elementi etichettati, non una promessa sul prossimo aggiornamento che giungerà sulla vostra flotta. Sei elementi innocui sono sei. Questo non significa che «blocca il 100% degli aggiornamenti difettosi», non lo sarà mai, e se mai doveste cogliermi a scrivere questa frase dovreste smettere di leggermi. Il numero che sostengo è di diversa natura: medesimo input, medesima decisione, a ogni esecuzione, poiché il verdetto non comporta alcuna temperatura di modello. Eseguite nuovamente il set di test domani e restituirà risultati identici byte per byte, il che permette a un livello deterministico di essere verificato come un livello probabilistico non potrà mai essere.

La domanda che mi rimane

Ciò che mi rimane impresso di questo progetto è la totale ordinarietà del guasto. Ventuno campi dove ne erano attesi venti. Un numero che qualunque verificatore indipendente avrebbe potuto intercettare per via aritmetica prima ancora che una sola macchina si riavviasse, se solo ci fosse stato un verificatore indipendente tra il fornitore e la flotta. Non ce n'era nessuno. Ancora oggi, per lo più, non ce n'è alcuno.

Ogni azienda esegue da otto a dodici agenti dotati di privilegi di kernel provenienti da fornitori che non controlla, e ciascuno di essi può spingere un file direttamente nel ring 0. Gli strumenti SBOM controllano le dipendenze open source. La gestione delle identità controlla gli accessi. Nessuno legge l'aggiornamento proprietario del fornitore in entrata per dimostrarne l'innocuità. Kestrel non è un EDR e non tocca mai il kernel. Si colloca al di sopra di questi agenti e governa ciò che hanno la facoltà di distribuire. È questo il livello che ho cercato di costruire, e la spiegazione approfondita si trova su veriprajna.com/demos/software-update-integrity.

E se preferite vederlo in azione anziché leggermi mentre lo descrivo, ecco l'intero sistema in funzione da cima a fondo.

Ecco dunque ciò che ora domando a proposito di ogni flotta che incontro: quando arriverà il prossimo aggiornamento di un fornitore, cosa si interporrà tra quel file e la produzione, e sarà in grado di dimostrare il proprio lavoro? Se la risposta è un comitato di gestione dei cambiamenti (CAB) che si fida ciecamente del fornitore, allora l'aritmetica che ha paralizzato milioni di macchine continua a funzionare indisturbata. Non si annuncerà. Apparirà esattamente identica a ogni aggiornamento precedente, fino all'istante esatto del riavvio.

Ricerca correlata

Pubblicato anche su

Costruisci la tua IA con fiducia.

Collabora con un team che vanta una profonda esperienza nella creazione della prossima generazione di IA aziendale. Lascia che ti aiutiamo a progettare, sviluppare e implementare una strategia di IA di cui ti puoi fidare.

Veriprajna società di consulenza Deep Tech è specializzata nella creazione di sistemi di IA safety-critical per i settori sanitario, finanziario e regolamentato. Le nostre architetture sono validate rispetto a protocolli consolidati con una documentazione di conformità completa.