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

Jason Orendorff

January 11, 2021
Tweet

More Decks by Jason Orendorff

Other Decks in Programming

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.

    View Slide

  2. Inside modern CPUs
    To understand how they work, you need to understand branches, instruction pipelines, and speculative execution.

    View Slide

  3. Inside modern CPUs
    * Branches

    View Slide

  4. Inside modern CPUs
    * Branches
    * Instruction pipelines

    View Slide

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

    View Slide

  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.

    View Slide

  7. Branches
    if (the light is off) {
    turn the light on;
    }
    look for keys;

    View Slide

  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.

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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?

    View Slide

  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.

    View Slide

  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.

    View Slide

  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.

    View Slide

  16. Speculative execution
    if (the light is off) {
    turn the light on;
    }
    look for keys;
    whatever,
    just assume it’s off.
    go go go!

    View Slide

  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.

    View Slide

  18. Speculative execution
    whoa, whoa…
    everybody back up

    View Slide

  19. The complete attack
    OK, now you know enough to understand Meltdown and Spectre. The two attacks are very similar. In five steps:

    View Slide

  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.

    View Slide

  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.

    View Slide

  22. if (index is in bounds) {
    ...
    }
    Your goal is to get this to happen.

    View Slide

  23. if (index is in bounds) {
    ...
    }
    whatever,
    just assume it’s in bounds.
    go go go!

    View Slide

  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.

    View Slide

  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.

    View Slide

  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

    View Slide

  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?

    View Slide

  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.

    View Slide

  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.

    View Slide

  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

    View Slide

  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]

    View Slide

  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.

    View Slide

  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.

    View Slide

  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!

    View Slide

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

    View Slide

  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.

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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!

    View Slide