Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Meltdown & Spectre

Meltdown & Spectre

A fairly quick explainer, heavy on the fun parts, for NashJS I think. Pitched as a lightning talk, but it wasn't really.

January 2018

8341c5bff3dcbd8ed34d9d68bd4169f2?s=128

Jason Orendorff

January 11, 2021
Tweet

Transcript

  1. Meltdown & Spectre @jorendorff Meltdown and Spectre are two new

    security attacks revealed January 4th. You may have heard about this. They allow an attacker to steal sensitive data, like passwords. These attacks are like nothing I've seen before.
  2. Inside modern CPUs To understand how they work, you need

    to understand branches, instruction pipelines, and speculative execution.
  3. Inside modern CPUs * Branches

  4. Inside modern CPUs * Branches * Instruction pipelines

  5. Inside modern CPUs * Branches * Instruction pipelines * Speculative

    execution
  6. Branches if (the light is off) { turn the light

    on; } look for keys; There's a decision point in this code. At the end of the first line, the CPU has to decide whether to continue with the next instruction, and turn the light on; or jump ahead to this part where it looks for your car keys. This kind of decision point is called a branch.
  7. Branches if (the light is off) { turn the light

    on; } look for keys;
  8. Branches while (!sink.empty) { do dishes; } switch (game.mode) {

    case EASY_MODE: //... } return; Normal code is full of branches. They're everywhere. It's not a problem, unless you're a CPU.
  9. Branches while (!sink.empty) { do dishes; } switch (game.mode) {

    case EASY_MODE: //... } return;
  10. Branches while (!sink.empty) { do dishes; } switch (game.mode) {

    case EASY_MODE: //... } return;
  11. Branches while (!sink.empty) { do dishes; } switch (game.mode) {

    case EASY_MODE: //... } return;
  12. Instruction pipeline Modern CPUs have instruction pipelines. They're massive assembly

    lines for computation. One part of the CPU is racing ahead and fetching upcoming instructions from RAM; another part is decoding the instructions that have been fetched; another part is figuring out what data we need for those instructions; another part is prefetching that data from RAM; another part is actually executing the instructions; and another part is storing the results. This is a picture of a jet engine but it’s basically the same thing. Actual Intel CPUs have a 31-stage pipeline. It's one of the engineering wonders of the world. But branches are bad for the pipeline. Why?
  13. Instruction pipeline if (the light is off) { turn the

    light on; } look for keys; oh no which way are we going? When you reach a branch, you can't keep racing ahead and working on what's next, because you don't know what's next! You have to wait here for the actual compute stage of the CPU to come along and tell you which branch to take.
  14. This is called a stall. We're not moving forward anymore.

    Execution is stalled. Well, stalls suck, so CPUs are designed to try to figure out as early as possible which way a branch will go, to avoid stalling. And if they can't figure it out in advance… they guess.
  15. Speculative execution if (the light is off) { turn the

    light on; } look for keys; The CPU will make an educated guess as to which way the branch will go, and race ahead in that direction. This is called speculative execution, because the CPU is starting to execute these instructions, and just sort of hoping they are the right ones. If so, great! We avoided a stall.
  16. Speculative execution if (the light is off) { turn the

    light on; } look for keys; whatever, just assume it’s off. go go go!
  17. Speculative execution But the guess may turn out to be

    wrong. Eventually the CPU figures out the branch should have gone the other way. Then what? Well, the parts of the CPU that have been racing ahead have done a bunch of erroneous work. The CPU has to discard all that work, go back to the branch, and start over, loading, decoding, and executing instructions along the branch that the program actually took. If that happens, it's as bad as a stall, but we were going to stall anyway. And CPUs are good at guessing, so at most branches, speculative execution prevents stalls.
  18. Speculative execution whoa, whoa… everybody back up

  19. The complete attack OK, now you know enough to understand

    Meltdown and Spectre. The two attacks are very similar. In five steps:
  20. The complete attack 1. Run evil code First, you have

    to get your target to run some code for you. Which seems like it should be hard. People should be like, no, I’m not running your evil code. Who are you and what are you doing in my living room? But, they’ll visit your web site and run your JavaScript. So you’re in.
  21. The complete attack 1. Run evil code 2. Trigger speculative

    execution Second, you have to get speculative execution to happen. You must train the CPU on your code so that the CPU expects one branch to be taken, and starts speculatively executing those instructions.
  22. if (index is in bounds) { ... } Your goal

    is to get this to happen.
  23. if (index is in bounds) { ... } whatever, just

    assume it’s in bounds. go go go!
  24. The complete attack 1. Run evil code 2. Trigger speculative

    execution 3. Read the secret Now the attack. In those speculative instructions, you load a byte of data that you're not supposed to have access to.
  25. if (index is in bounds) { let secretByte = array[index];

    ... } whatever, just assume it’s in bounds. go go go! One way to do it is to read off the end of a typed array. For now, just accept that this is allowed. Obviously it shouldn’t be. But in theory, it's OK, because we’re only speculatively executing this code. If the read turns out to be invalid, all this gets rolled back, so it’s no big deal.
  26. if (index is in bounds) { let secretByte = array[index];

    ... } whatever, just assume it’s in bounds. go go go! we’re very sure the index is in bounds
  27. The complete attack 1. Run evil code 2. Trigger speculative

    execution 3. Read the secret 4. ??? OK. So we have one byte of a secret. Now we're in a scenario from a science fiction story. You're an evil secret agent, and you've obtained the top-secret information! But the universe you're living in is a spurious universe. A mistake universe. Mere nanoseconds from now, the CPU is going to detect the mistake and roll all this back. Your temporary universe where you've got the secret is going to be rolled back out of existence. How do you get the secret out?
  28. The complete attack 1. Run evil code 2. Trigger speculative

    execution 3. Read the secret 4. Smuggle it out It turns out there are ways to do this. There are things you can tell the CPU to do that have subtle side effects that won’t get rolled back.
  29. if (index is in bounds) { let secretByte = array1[index];

    g_dontCare = array2[secretByte]; } whatever, just assume it’s in bounds. go go go! For example, do a second memory access, for a location that depends on this secret data. So if the byte you’ve read is, I dunno, 65, the next thing you do is look up element number 65 in a separate array, one that you created. I know, this looks super pointless. But this is what it looks like when you’re trying to smuggle information from one timeline to another, OK? Here’s why this works. The CPU caches every piece of memory it touches, for speed, so when you do this, the CPU grabs element number 65 out of “array2”, and that gets cached.
  30. if (index is in bounds) { let secretByte = array1[index];

    g_dontCare = array2[secretByte]; } whatever, just assume it’s in bounds. go go go! 65
  31. if (index is in bounds) { let secretByte = array1[index];

    g_dontCare = array2[secretByte]; } whatever, just assume it’s in bounds. go go go! 65 read and cache array2[65]
  32. if (index is in bounds) { let secretByte = array1[index];

    g_dontCare = array2[secretByte]; } Eventually, the CPU detects its mistake, rolls back the universe to the branch, and executes the correct path. Your precious secret information is lost. …Or is it? You've still got `array2`, and one element of `array2` is cached in the CPU. The cache was not rolled back.
  33. The complete attack 1. Run evil code 2. Trigger speculative

    execution 3. Read the secret 4. Smuggle it out 5. Recover it How can you recover the secret number that you lost? (discuss) Right. You precisely measure how long it takes to access each element of array2. Whichever element is fast, that’s the one you cached earlier! This is what's called a timing attack. You've just recovered one byte of the secret. Now repeat this for the next byte, and the next byte, until you've got the whole secret. Speaking of timing attacks, I think I’m about to be attacked, for running over my time, so let's wrap it up.
  34. Stopping Meltdown 1. Run evil code 2. Trigger speculative execution

    3. Read the secret 4. Smuggle it out 5. Recover it Meltdown attacks operating systems, using a specific CPU bug. In step 3, Meltdown reads from operating system memory that it categorically should not have access to. The CPU allows the read, speculatively, while the permission check is going on in parallel. Eventually it rolls everything back, but it's too late. …How do we fix this? Well, Intel needs to mail everyone a new CPU. But while we're waiting for that to happen, operating systems have been patched so that kernel memory isn't addressable at all in user processes. That stops Meltdown. It also makes your computer slower. So if you haven't installed updates lately, your laptop is about 5% faster than everyone else’s! Good work!
  35. Stopping Meltdown 1. Run evil code 2. Trigger speculative execution

    3. Read the secret 4. Smuggle it out 5. Recover it
  36. Stopping Spectre 1. Run evil code 2. Trigger speculative execution

    3. Read the secret 4. Smuggle it out 5. Recover it Spectre is the single-process version of this bug. It attacks browsers. With Spectre, it's easiest to block step 5. That's why all browsers are now rounding off the value of performance.now() to the nearest millisecond, and disabling something called SharedArrayBuffer. We're trying to eliminate this timing attack by eliminating timers. …It’s not good enough. We expect to see attacks that don't require precise timers, so we're going to have to somehow keep JIT code from accessing secrets even speculatively. …This makes Spectre unique and scary. We're talking about locking down code that in theory isn't even executing. That's where we are.
  37. Stopping Spectre 1. Run evil code 2. Trigger speculative execution

    3. Read the secret 4. Smuggle it out 5. Recover it
  38. Stopping Spectre 1. Run evil code 2. Trigger speculative execution

    3. Read the secret 4. Smuggle it out 5. Recover it today
  39. Stopping Spectre 1. Run evil code 2. Trigger speculative execution

    3. Read the secret 4. Smuggle it out 5. Recover it today
  40. Stopping Spectre 1. Run evil code 2. Trigger speculative execution

    3. Read the secret 4. Smuggle it out 5. Recover it today soon
  41. I'm out of time. For more, read the two papers

    at meltdownattack.com. They're 16 pages each and very good. Thank you!