Exploiting System Management Mode with a very very very very very very very long
interrupt.
It turns out that you can break SMM — the secure, ultra privileged execution
environment running invisibly in the background of every x86 CPU — with nothing
more than an obscenely long-running machine instruction.
SMM requires that all cores are either in SMM or out of SMM at the same time.
Its security model doesn’t work without this – when one thread enters SMM, it
makes all the others enter too.
To break this, all we need is someone too busy to notice they’re supposed to
join SMM.
At this point, core 1 is out of SMM while core 0 is in, letting core 1 attack
core 0. Here’s the catch: for this to work we need a very, very, verrrrry long
instruction — longer than any instruction was ever supposed to take. Most
machine instructions on a modern CPU are fast:addtakes 1 cycle. To get core
1 to give up waiting on core 0, we need an instruction on core 0 that takes
around 4,000,000,000 cycles — over 1 second of wall-clock time.
x86 firmware runs thefollowing
codewhen a CPU core enters SMM:
The code waits for all cores to enter SMM, or for up to 1 second, whichever
occurs first. To get a core to execute SMM code while another core stays
executingoutsideSMM, we need that outside core to stay uninterruptible for
theentiresecond — an SMI is taken at an instruction boundary, so any gap
between two instructions is a door the pending SMI walks through to drag the
core in. The delay therefore has to be asingleinstruction: one
uninterruptible op that outlasts the one-second rendezvous.
There are many ways to reach the forbidden 1-second instruction, and the exact
approach will vary platform-to-platform. But, roughly: find a high-latency MMIO
address, and then convince the CPU to read from it as slowly as possible — abuse
an undocumented region that answers reads at a crawl, then use the widest load
the ISA will give you to haul a ridiculous pile of bytes across it in a single
instruction, ideally while the other cores are elbowing onto the same bus so the
whole thing wheezes through under contention. One read, one instruction, and
the CPU is stuck holding it for the better part of a second.
The providedproof-of-conceptis tuned for a Zen 3 Ryzen 7
5800H, where a widexmmload from slow MMIO at0xfcc68860stalls long enough to break the all-cores rendezvous:
The PoC exploits this by pitting two cores against each other. One core is heldoutsideSMM by the long instruction — a tight loop on the very slow load:
Meanwhile another core arms the per-core SMI counters:
The counters tell the story. If they diverge, a core kept runningoutsideSMM
while the others were pulled in — it lived through SMIs the rest of them serviced
without it.