On Friday, July 19, 2024, a large number of Windows machines around the world crashed and then kept crashing on every boot. Airlines, hospitals, banks and retailers were affected. Our own exposure was modest: about 140 office laptops and 22 Windows build agents in our CI fleet ran the affected sensor, and getting them back took most of a day, largely because of BitLocker recovery keys.
CrowdStrike published a preliminary Post Incident Review within days and a full external root cause analysis (RCA) on August 6, 2024. This post summarises what those official documents say, in our own words, and then, separately, what we changed in our own release and recovery practices. We are not assigning blame; the RCA is unusually detailed, and its findings apply to any team that ships configuration to a large fleet.
What happened, per CrowdStrike's official reports
The timeline
According to CrowdStrike, a Rapid Response Content update for the Falcon sensor on Windows was released at 04:09 UTC on July 19, 2024 and reverted at 05:27 UTC the same day. Windows hosts running sensor version 7.11 or later that were online and received the update during that window could crash. Systems that came online after 05:27 UTC were not affected. CrowdStrike stated that macOS and Linux hosts do not use Channel File 291 and were not impacted, and that the event was not a cyberattack. Microsoft separately estimated that about 8.5 million Windows devices were affected, under one percent of all Windows machines.
Sensor content versus Rapid Response Content
The RCA distinguishes two kinds of updates. Sensor Content ships inside sensor releases and includes Template Types, compiled into the sensor, which define what fields of system activity a detection rule can match on. Rapid Response Content is configuration data delivered separately through "channel files" so that detection engineers can react quickly without a new sensor release. Each channel file contains Template Instances (essentially regex-based matching criteria) for a specific Template Type, and the sensor's Content Interpreter evaluates them.
The root cause
In February 2024, sensor 7.11 introduced a new IPC Template Type to detect attacks abusing named pipes and other inter-process communication. Its definition declared 21 input fields, but the sensor code that invoked the Content Interpreter for this type supplied only 20 input values.
That mismatch went unnoticed for months because every Template Instance in testing and in the first production deployments used a wildcard for the 21st field, so the 21st value was never actually read. On July 19, two new IPC Template Instances were deployed, and one of them used a non-wildcard criterion on the 21st field. The Content Validator, which checks content before release, assumed 21 inputs would be available, so it passed the new content.
When the next IPC notification arrived on an affected host, the Content Interpreter tried to read the
21st entry of a 20-entry input array. That out-of-bounds memory read happened inside csagent.sys, a
kernel driver loaded early in boot, so the result was a system crash (the RCA shows a
PAGE_FAULT_IN_NONPAGED_AREA bugcheck). Because the driver loads early and the bad channel file was
still on disk, many machines crashed again on restart.
The RCA summarises it as a confluence of issues: the 21 versus 20 mismatch, the missing runtime bounds check in the Content Interpreter, and the lack of a test with non-wildcard criteria in the 21st field. CrowdStrike states that this was an out-of-bounds read, not an arbitrary memory write, and that this conclusion was independently reviewed.
Findings and mitigations listed in the RCA
| Finding | Mitigation described |
|---|---|
| Field count not validated at sensor compile time | Sensor Content Compiler validates input counts (in production July 27, 2024) |
| No runtime array bounds check for Channel File 291 inputs | Bounds checks added July 25, 2024; hotfix for sensor 7.11+ by August 9, 2024 |
| Template Type testing used narrow matching criteria | Automated tests with non-wildcard criteria for every field, for all Template Types |
| Content Validator logic error | New Validator checks; 21st field restricted to wildcards for this channel |
| Template Instance testing too limited | Every new Template Instance tested before production |
| No staged deployment for Template Instances | Canary and ring-based rollout with bake time; customer control over content deployment |
CrowdStrike also engaged two independent third-party software security vendors to review the sensor code and its end-to-end quality process.
Recovery
CrowdStrike's remediation guidance for affected hosts was to boot into Safe Mode or the Windows
Recovery Environment and remove the problematic channel file (C-00000291*.sys under the
CrowdStrike drivers directory), then reboot. On encrypted machines that meant entering a BitLocker
recovery key for each one, which is what turned a 78-minute content window into days of hands-on work
for many organisations.
Lessons for our own systems
The lessons below are ours. We do not ship kernel drivers, but we do ship configuration that our software trusts at runtime: feature flags, routing rules, pricing tables, ML model files. That is the same shape of risk.
Treat configuration as code, because it executes like code
The RCA's core distinction, content versus code, is one we had too: application releases went through canaries, but feature flag and rules changes went to 100% instantly. We now route every runtime configuration change through the same staged rollout as binaries: 1% of hosts or tenants, then 10%, then everyone, with automatic halts on error rate or crash metrics.
# rollout policy applied to config bundles, not only builds
stages:
- name: canary
percent: 1
bake: 30m
- name: early
percent: 10
bake: 2h
- name: full
percent: 100
halt_on:
- metric: process_crash_rate
above: 0.1%
- metric: http_5xx_rate
above: 2x_baseline
Validate the contract on both sides
The template said 21, the code supplied 20, and each side's tests only checked itself. Our equivalent is a schema shared between a config producer and its consumer. We generate the consumer's parser and the producer's validator from the same schema file, and a contract test loads real config bundles into the real consumer in CI.
Test the edges your production data will use
All tests used wildcards on the field that later failed. Fuzzing and property-based tests that vary every field, not just the ones the current rules use, would surface this class of bug. We added property-based tests for our rules engine that generate random but schema-valid rules, including values for optional fields no current rule uses.
from hypothesis import given, strategies as st
@given(rule=rule_strategy(all_fields_non_wildcard=True))
def test_engine_never_crashes_on_valid_rules(rule):
engine = RulesEngine.load([rule])
engine.evaluate(sample_event()) # must not raise
Fail safe at runtime
A bounds check would have turned a crash into a skipped rule. In our services, a config entry that fails validation at load time is now rejected and logged, and the previous known-good bundle stays active, instead of the process crashing or running with half-loaded state.
Have a recovery runbook for machines that cannot boot
Our biggest delay was BitLocker keys: some were in our device management system, some only in a legacy directory, and two were nowhere. Since then we have:
- verified that every managed device's recovery key is escrowed and retrievable by the help desk;
- kept bootable recovery media and a printed procedure in each office;
- tested the procedure on a sample of laptops and build agents once a quarter;
- made our CI build agents disposable images we can replace rather than repair.
Give customers control, and use the control you are given
The RCA's last mitigation is customer control over when content updates land. Where our vendors offer staged or delayed update channels, we now put a small canary group on the earliest channel and the rest on a later one, accepting slightly older content in exchange for a buffer.
Key takeaways
- Per CrowdStrike's RCA, a Template Type declaring 21 inputs met sensor code supplying 20, and the first non-wildcard rule on the 21st field caused an out-of-bounds read in a kernel driver.
- Configuration that changes runtime behaviour deserves the same staged rollout as code.
- Validate shared contracts on both producer and consumer sides, from one source of truth.
- Fuzz all fields, not only the ones current content uses.
- Prefer rejecting bad input over crashing, and keep last-known-good configuration.
- Rehearse recovery for machines that cannot boot, including encryption keys.
References
- CrowdStrike: External Technical Root Cause Analysis, Channel File 291 (PDF) — https://www.crowdstrike.com/wp-content/uploads/2024/08/Channel-File-291-Incident-Root-Cause-Analysis-08.06.2024.pdf
- CrowdStrike: Channel File 291 Incident, Root Cause Analysis is Available — https://www.crowdstrike.com/en-us/blog/channel-file-291-rca-available/
- CrowdStrike: Falcon Content Update Preliminary Post Incident Report — https://www.crowdstrike.com/en-us/blog/falcon-content-update-preliminary-post-incident-report/
- CrowdStrike: Technical Details, Falcon Content Update for Windows Hosts — https://www.crowdstrike.com/en-us/blog/falcon-update-for-windows-hosts-technical-details/
- Microsoft: Helping our customers through the CrowdStrike outage — https://blogs.microsoft.com/blog/2024/07/20/helping-our-customers-through-the-crowdstrike-outage/
Comments
0No comments yet. Be the first to comment!
Sign In to post a comment.