Attacks that steal secrets by watching a victim program's use of shared hardware work far better when the attacker can stop the victim after just one or two instructions and measure in between, but doing that from an ordinary unprivileged program used to need dozens of helper threads or administrator rights; this work from Berkeley and collaborators shows that a single normal thread can pause a victim hundreds of times in a row by abusing the fairness rules the operating system scheduler uses to stay responsive, giving near-instruction-level spying with no special privilege, and it works on both of Linux's modern schedulers.
The argument in one glance
-
Problem
Side-channel attacks are far stronger with fine timing
A microarchitectural side channel steals secrets by watching how a victim program uses shared hardware, like the processor cache. The attack works much better if the attacker can stop the victim after only one or two instructions and measure in between. This fine timing, called temporal resolution, can turn an attack that needs a thousand victim runs into one that needs a handful.
-
What people did
Get fine timing with root access or with many threads
One line of work reaches near-perfect timing but needs administrator privileges, for example to attack a secure enclave. A second line works from an ordinary unprivileged program, but needs dozens of helper threads: each sleeps to build up scheduling priority, wakes to interrupt the victim once, then sleeps again to recover.
-
Why it fell short
Both are unrealistic or clumsy from userspace
Requiring administrator rights rules out most real settings. Requiring dozens of threads is loud, easy for a system to block by limiting thread counts, and hard to coordinate, which itself makes the timing coarser. Prior work also quietly assumed the attacker and victim already run on the same processor core, which normally needs privilege to arrange.
-
This paper's idea
Abuse the scheduler fairness rules with one thread
Controlled Preemption lets a single unprivileged thread stop a victim hundreds of times in a row. It exploits the fact that thread schedulers, to stay responsive, let a well-rested thread preempt a running one immediately, and let it repeat until a fairness limit is reached. That limit leaves a large budget the attacker spends on repeated preemptions. A separate trick places the attacker on the victim core without privilege, and the method works on both of Linux's modern schedulers.
Why this problem is worth solving
The strength of these attacks hinges on one measurable property. A microarchitectural side-channel attack does not read a victim’s memory directly. Instead, the attacker and victim share hardware — most often the processor cache — and the attacker watches how its own timing changes as the victim uses that hardware. From those timing changes the attacker infers what the victim did, and from that, secrets like encryption keys. The single most important property of such an attack is its temporal resolution: how many victim instructions run between one attacker measurement and the next. The paper gives a sharp example. In a classic attack on the AES encryption algorithm, if the attacker could measure with perfect resolution — stopping the victim right after the first few table lookups — it could recover key information after the victim encrypts just one time. With coarse resolution, where the attacker can only measure once after the whole encryption finishes, the same attack needs hundreds to thousands of victim runs to fight through the accumulated noise. So higher resolution does not just make an attack faster; it can be the difference between an attack that works and one that does not. The goal of this line of research is to reach the highest resolution: nearly single-stepping the victim, one instruction at a time, and measuring in between.
Reaching that resolution from a normal program was the stubborn part. To measure between tiny slices of victim execution, the attacker must repeatedly interrupt the victim: let it run a few instructions, take over the core, measure, then let it run a few more. Interrupting another thread means convincing the operating system scheduler to switch the core away from the victim and onto the attacker. One family of attacks does this cleanly but only against secure enclaves and only with administrator privileges, which most attackers do not have. The other family works from an ordinary unprivileged program, but pays a heavy price. Each attacker thread can force only one preemption: it sleeps to raise its scheduling priority, wakes and preempts the victim once, and then must sleep again to rebuild priority before it can preempt again. To preempt the victim many times in a row, the attacker therefore needs many threads — dozens, in reported work as many as forty, taking turns. That is easy for a defender to notice, can be blocked by a system limit on the number of threads, and is hard to synchronize precisely, which blurs the timing back toward coarse. And all of this assumed the attacker and victim were already sharing one processor core, which normally requires the privilege to pin threads. A truly practical unprivileged attack needed a way for one thread to preempt repeatedly, and a way to share the core without privilege.
What people did before, and where it broke
- Privileged single-stepping (SGX-Step and similar). Use administrator control of timer interrupts to stop a secure-enclave victim after each instruction. Where it broke. It gives excellent resolution but requires supervisor privilege, so it does not model an ordinary unprivileged attacker sharing a machine.
- Unprivileged multi-thread preemption (the Cache Games line). From userspace, spawn many threads that each sleep to gain priority and wake to preempt the victim once. Where it broke. One preemption per thread means dozens of threads for a real attack. That is loud, can hit a system thread limit, and is hard to coordinate, so the achieved resolution is coarser than the privileged approach.
- Assuming the attacker and victim already share a core. Prior userspace work either used a single-core machine or assumed both threads were pinned to the same core. Where it broke. Real machines have many cores, and pinning another user’s thread to a specific core needs privilege, so this assumption quietly hid a hard requirement.
- The gap. No unprivileged attack could make a single thread preempt a victim hundreds of times at fine grain, and none had shown how to share a core with the victim without privilege, on today’s Linux schedulers. Doing both, by turning the scheduler’s own fairness and responsiveness rules against it, is what Controlled Preemption provides.
How Controlled Preemption works
From a shared-hardware attack to near-instruction-level spying
-
-
new
-
new
-
new
-
new
-
new
The idea
Turn the scheduler’s responsiveness rule into an attack primitive. The core of the attack is not a bug but a feature working as designed. To feel responsive, a scheduler lets a thread that just woke from sleep immediately take the processor from a running thread, even before that thread has finished its guaranteed minimum time slice — because a thread that just woke, say to handle an arriving network packet, should run promptly. Controlled Preemption rides exactly this rule: the attacker sleeps for a tiny interval and is allowed to cut in on the victim the instant it wakes. Taking a mechanism built for a legitimate, benign purpose and pointing it at a harmful one is the same shape as the leaky-instruction work covered elsewhere on this site, where hardware shortcuts added purely to make common operations faster end up leaking secrets precisely because they behave differently depending on the data they touch.
Bank a large scheduling credit once, then spend it on hundreds of cheap preemptions. A single wake-up buys only one preemption in older attacks, which is why they needed dozens of threads. Here the attacker first hibernates, sleeping long enough that the scheduler grants it a big deficit in accumulated run time. Fairness rules then permit it to keep preempting the victim until that banked deficit is used up — and the budget is far larger than any one preemption costs, so a single thread preempts hundreds of times before fairness steps in. Paying an expensive setup once and then drawing on it cheaply many times is the same amortization the shared-solid-state-drive work covered elsewhere on this site relies on, building a file mapping once at open time so that the many reads and writes afterward each run fast.
Sharpen a coarse signal into a near single-step by slowing the victim. Even with repeated preemptions, the victim may run several instructions before the attacker regains the core, blurring the measurement. The attack adds a second lever: it flushes the small cache that holds the victim’s next instruction address, so the victim stalls the moment it resumes and advances only one instruction before the attacker preempts it again. Forcing behavior into a sharp, uniform, controlled pattern rather than accepting a noisy one is the mirror image of the sensor-message-padding defense covered elsewhere on this site, which pads every message to one fixed length so that timing reveals nothing — here the attacker instead forces uniform single-instruction steps so that timing reveals everything.
Following the AES key-recovery attack
Stealing AES key bits one preemption at a time
Tracing the classic table-lookup attack shows how fine timing recovers key nibbles.
Step 1 of 5
Colocate on the victim core
The attacker spawns fifteen dummy threads to occupy the other cores. The scheduler's load balancer then places the victim thread onto the attacker's core, so the two share the cache the attack will watch.
No privileged pinning is used; the placement is coaxed out of ordinary load balancing.
Why it works
- Resolution decides attack strength. Because a side channel leaks more when fewer victim instructions run between measurements → stopping the victim after one or two instructions instead of after a whole routine → so an attack that would have needed a thousand victim runs can succeed in a handful.
- Schedulers reward well-rested threads to stay responsive. Because a thread that just woke is granted a run-time deficit and may preempt a running thread before its minimum time slice ends → the attacker sleeps briefly and is then allowed to cut in at once → so it interrupts the victim exactly when it chooses.
- Fairness leaves a large spendable budget. Because the scheduler only halts the preemptions once the attacker’s banked deficit is used up, and that budget is far bigger than one preemption costs → a single thread can preempt hundreds of times before fairness intervenes → so no crowd of recharging threads is needed.
- Colocation without privilege completes the attack. Because pinning a victim to a core needs administrator rights, but the load balancer will place threads onto free cores by itself → filling the other cores with dummy threads pushes the victim onto the attacker’s core → so an unprivileged attacker meets the same-core requirement earlier work simply assumed.
- Slowing the victim sharpens every step. Because flushing the cache that holds the victim’s next instruction address makes that instruction stall → most preemptions then catch the victim after exactly one instruction → so the measured trace approaches perfect single-stepping.
What it costs (trade-offs and scope)
- The budget is finite before re-hibernating. The banked deficit allows a bounded run of preemptions — enough to complete the demonstrated attacks, but a single hibernation does not last forever. Extracting a very long victim trace needs re-hibernating, or borrowing the old multi-thread trick to extend the budget.
- Steps are not perfectly uniform. The attacker cannot guarantee exactly one victim instruction per preemption. Some preemptions catch zero instructions (wasting budget) and some catch several (blurring the measurement), which is why the instruction-cache-flush trick and extra traces are needed to clean up the signal.
- It still assumes a colocated, unprivileged foothold. The attacker must run code on the same machine and be able to invoke the victim. The colocation trick needs at least one idle core to steer the victim, and the whole method targets the case without simultaneous multithreading.
- It is scheduler-specific, though broadly applicable. The exact mechanism depends on Linux scheduler parameters and logic. The paper shows it on both the older Completely Fair Scheduler and the newer Earliest Eligible Virtual Deadline First scheduler, but a very different scheduler could change the picture.
- It is a measurement primitive, not a full break by itself. Controlled Preemption sharpens observation; turning fine traces into a recovered key still relies on the underlying side channel and known cryptanalysis, and inherits their noise and assumptions.
- Defenders have a direct mitigation. The Linux kernel team confirmed the finding and rated it low priority, and an existing option that stops a waking thread from immediately preempting a running one closes the main lever, at the cost of some system responsiveness.
The evaluation
Three ways to preempt a victim for fine-grained side-channel measurement
| Property | SGX-Step (privileged) | Userspace, many threads | Controlled Preemption |
|---|---|---|---|
| Privilege required | Unprivileged | ||
| Attacker threads needed | One | ||
| Temporal resolution | Near single-step | ||
| Solves same-core colocation | By privilege | ||
| Works on modern Linux schedulers | Not applicable | Older scheduler |
Check yourself
Check your understanding
1. What is "temporal resolution" in a side-channel attack, and why does it matter so much?
2. Why did earlier unprivileged attacks need dozens of threads, and how does Controlled Preemption avoid that?
3. What scheduler behavior is the core of the attack, and why is it there in the first place?
4. A defender enables the Linux option that stops a waking thread from immediately preempting a running thread. What does the paper suggest happens?
Sources
The paper is “Controlled Preemption: Amplifying Side-Channel Attacks from Userspace” by Yongye Zhu, Boru Chen, Zirui Neil Zhao, and Christopher W. Fletcher, published at ASPLOS 2025, DOI 10.1145/3676641.3715985. The authors are at the University of California, Berkeley, and NVIDIA and the University of Texas at Austin. The authors open-sourced an implementation and disclosed their findings to the Linux kernel security team.
This explainer is written from the full text of the paper, obtained from co-author Zirui Neil Zhao’s academic website, which matches the ASPLOS 2025 proceedings and is distributed under the ACM open-access terms. Everything above comes from the paper’s own sections: the introduction and its temporal-resolution argument (with the AES first-round example and the contrast between one victim run at perfect resolution and hundreds to thousands at coarse resolution); the background on the Linux Completely Fair Scheduler (virtual runtime, the fair-scheduling invariant, the minimum time slice, the wake-up vruntime adjustment, and the wake-up preemption threshold, with the paper’s system values of a twenty-four millisecond bound, a three millisecond minimum slice, a twelve millisecond wake-up slack, and a four millisecond preemption threshold); the threat model (an unprivileged colocated attacker using standard system calls, victims inside or outside an Intel SGX enclave, no simultaneous multithreading assumed); the Controlled Preemption mechanism (the preemption budget from the slack exceeding the preemption threshold, hibernating and napping, the number of consecutive preemptions, the performance-degradation trick using instruction-cache and translation flushing to approach single-stepping, the colocation technique using dummy threads and the load balancer, and the extension to the newer EEVDF scheduler with a reported median of two hundred nineteen consecutive preemptions); the three proof-of-concept attacks (the AES table-lookup attack via Flush-and-Reload recovering key nibbles from about five encryptions at roughly ninety-nine percent accuracy with one thread instead of forty, the first userspace enclave-single-stepping attack using last-level-cache Prime-and-Probe toward RSA key recovery, and a branch-predictor control-flow attack); and the responsible disclosure and the recommended mitigation of disabling wake-up preemption.