Live data from Hacker News

Zeroing buffers is insufficient

daemonology.net

31–40 of 134 posts

Re: Zeroing buffers is insufficient

#32
post #7

"As with "anonymous" temporary space allocated on the stack, there is no way to sanitize the complete CPU register set from within portable C code" I don't know enough of modern hardware, but on CPUs with register renaming, is that even possible from assembly? I am thinking of the case where the CPU, instead of clearing register X in process P, renames another register to X and clears it. After that, program Q might…

Once a rename register is garbage collected, it's flagged as "not ready" which is a state in which any instruction attempting to read it will block. They can only be scheduled once it's been written to.

Re: Zeroing buffers is insufficient

#33
post #4

It's becoming gradually more tempting to write a crypto library in assembly language, because at least then, it says exactly what it's doing. Alas, microcode, and unreadability, and the difficulty of going from a provably correct kind of implementation all the way down to bare metal by hand. The proposed compiler extension, however, makes sense to me. Let's get it added to LLVM & GCC?

That works for well-defined ISAs (like ARM), but not for those with undocumented pipelines, or instructions defined by practise (like x86 and amd64). In other words, if you write a crypto library in x86 assembler, Intel don't guarantee that they won't introduce a side channel in their next chip model or stepping.

Sadly, I know that only too well: hence my "alas, microcode" comment! A prefix or mode or something which allows code to handle secure data and it gets constant-time multipliers, for example, or true µop-level register zeroisation, would be handy, but also close to unverifiable - we just have to sort of trust it, which sucks.

Until then, we do the best we can with turtles all the way down. Software running under that same undocumented pipeline is going to find it very hard to access or leak (accidentally or otherwise) internal registers, at least.

For the other avenue of attack (cold-boot attacks), it's also notable that registers, at least, have extremely fast remanence compared to cache, or DRAM - bit-fade is a very complex process, but broadly speaking, faster memory usually fades faster.

Digression along that vein: I basically pulled off a cold-boot attack on my Atari 520 STe in the early 1990s (due to my wanting to, ahem, 'debug' a pesky piece of software that played shenanigans with the reset vector and debug interrupts), with Servisol freezer spray pointed directly at the SIMMs in my Xtra-RAM Deluxe RAM expansion (and no, cold-boot attacks are not new, GCHQ's known about them for at least 3 decades and change under the peculiarly-descriptive cover name NONSTOP, I believe?). It just seemed sensible to me: cold things move slower, and they had a particularly long (and very pretty) remanence - I was able to get plenty of data intact, including finding where I needed to jump to avoid the offending routine and continue my analysis with a saner technique (i.e. one that didn't make me worry about blowing up the power supply or electrocuting myself)! It's harder these days - faster memory - but the technique incredibly still works and was independently rediscovered as such more recently: very much a "wait, this still works on modern RAM?" moment for me. (By the way, when I accidentally pulled out the SIMMs with the internal RAM disabled - whoops - and rebooted the Atari on my first try, it actually powered up with an effect that I can only describe as "pretty rainbow noise with double-height scrolling bombs" that would not have looked out of place in a demoscreen! I don't know if that was just mine, but... the ROM probably never expected to find RAM not working, and I guess the error-plotting routine had a very pretty and unusual error in that event?)

I've never seen or heard of anyone pulling off a NONSTOP on a register in a CPU, or actually even on an L1, L2 or L3 cache (maybe an L2 or L3 might be possible, depending on design?). They're fast - ns->µs remanence? - and cooling doesn't help much. I don't know if it's possible at all, but I'd tentatively suggest that it might be beyond practical attack - unless the attacker has decapped the processor and it's already in their lab (in which case you're fucked, no matter what!). That's what suggests that approaches like TRESOR (abuse spare CPU debug registers to store encryption key; use that key to encrypt keys in RAM), despite being diabolical hacks, actually work.

If you fancy giving it a try in the wild by the way, I think a Raspberry Pi might be a good modern test subject - the RAM's exposed on top of the SoC, there are no access problems, and it's cheap so if it dies for science, it's not such a problem. (Of course, you'd want probably to want to change bootcode.bin so that it dumps the RAM after it enables it but before it clears it.) The VideoCore IV is kind of a beast - and is frustratingly close to being able to do ChaCha20 extraordinarily efficiently, if I can just figure out how to access the diagonal vectors... or if I can, or if I can fake it.

Re: Zeroing buffers is insufficient

#34
post #30

I don't think this can be a language feature. It's more a platform thing: Why is keeping key material around on a stack or in extra CPU registers a security risk? It's because someone has access to the hardware you're running on. (Note that the plain-text is just as leaky as the key material. Yike!) So stop doing that. Have a low-level system service (e.g., a hypervisor with well-defined isolation) do your crypto ope…

This is also why dedicated cryptoprocessors exist, with special features for attack resistance; I'm not completely certain about this, but I'd think the software running on those does not have to zero memory containing keys, because the whole environment that said software runs in has been secured from the outside already, and if it's possible to read any memory or run untrusted code from outside on those without being detected, then there are far bigger problems to worry about...

Re: Zeroing buffers is insufficient

#35
post #8

It very much looks like a situation in which the system has already been compromised and is running malicious programs that it shouldn't. These malicious programs could still face the hurdle of being held at bay by the permission system that prevents them from reading your key file. However, they could indeed be able to circumvent the permission system by figuring out what sensitive data your program left behind in u…

I'm not sure this is the scenario we're fighting. The problem is when your program (which handles sensitive data) has a flaw in it: for example, it might be possible to trick it into leaking uninitialized data (possibly out of bounds) over the wire. Another potential issue is core dumps (and maybe swapping, but that's a little different). You don't want sensitive data to be written on the disk.

Malicious programs running with your program's privileges are a different scenario altogether, and usually they can do a lot of damage. Want sensitive information out of another process? Try gdb.

But yes, it is trivial for the kernel to zero a page before handing it out.

Re: Zeroing buffers is insufficient

#36
For AESNI, you probably are already using some sort of assembly to call the instructions. In the same assembly, you could wipe the key and plaintext as the last step.

For the stack, if you can guess how large the function's stack allocation can be (shouldn't be too hard for most functions), you could after returning from it call a separate assembly function which allocates a larger stack frame and wipes it (don't forget about the redzone too!). IIRC, openssl tries to do that, using an horrible-looking piece of voodoo code.

For the registers, the same stack-wiping function could also zero all the ones the ABI says a called function can overwrite. The others, if used at all by the cryptographic function, have already been restored before returning to the caller.

Yes, it's not completely portable due to the tiny amount of assembly; but the usefulness of portable code comes not from it being 100% portable, but from reducing the amount of machine- and compiler-specific code to a minimum. Write one stack- and register-wipe function in assembly, one "memset and I mean it" function using either inline assembly or a separate assembly file, and the rest of your code doesn't have to change at all when porting to a new system.

Re: Zeroing buffers is insufficient

#37
post #4

Earlier quoted context omitted.

That works for well-defined ISAs (like ARM), but not for those with undocumented pipelines, or instructions defined by practise (like x86 and amd64). In other words, if you write a crypto library in x86 assembler, Intel don't guarantee that they won't introduce a side channel in their next chip model or stepping.

Sadly, I know that only too well: hence my "alas, microcode" comment! A prefix or mode or something which allows code to handle secure data and it gets constant-time multipliers, for example, or true µop-level register zeroisation, would be handy, but also close to unverifiable - we just have to sort of trust it, which sucks. Until then, we do the best we can with turtles all the way down. Software running under that…

If I wanted to read CPU registers from the outside, there's an easy way: JTAG. You should be able to halt the CPU, read (and modify!) the registers, and resume the CPU.

That should be possible even on x86, though on x86 the relevant documentation is probably hard to find. For some ARM processors, it should be as easy as installing openocd.

Of course, JTAG requires physical access to plug the debugging cable, which puts it in a different category of attack.

Re: Zeroing buffers is insufficient

#39
post #30

I don't think this can be a language feature. It's more a platform thing: Why is keeping key material around on a stack or in extra CPU registers a security risk? It's because someone has access to the hardware you're running on. (Note that the plain-text is just as leaky as the key material. Yike!) So stop doing that. Have a low-level system service (e.g., a hypervisor with well-defined isolation) do your crypto ope…

Sensitive information doesn't exist in a vacuum. What we need to protect is more than some keys that can be carefully loaded onto a crypto processor hiding in a secure bunker. Yes, users should have security too. The point of entry matters too.

So how do you get that isolated box onto everyone's computer and phone? How do you move these users' sensitive information onto that isolated box without leaving a trace on their non-isolated computer? How do you move their keys around?

When you use two systems to process sensitive information, you have at least two problems to solve...

Re: Zeroing buffers is insufficient

#40
post #23

Earlier quoted context omitted.

That depends how much the compiler can optimize (away). If the next call is free(), it's quite trivial to skip the zeroing and just take the correct branch. I am still uncertain while people want to just 'zero' it. Filling random data (just one random() call) and then using inline PRNG, then summing the result, storing it globally in volatile would reliable 'zero' the data but it's quite CPU intense.

Why shouldn't the compiler be able to figure out that you sum a series of numbers that aren't used anywhere else, thus don't need to be spilled to RAM?

You write them to a global state volatile
Post reply on HN