85 ms·
How to zero a buffer
- angersock 12y agoThis sort of thing would be exactly what should go into the "Friendly C" dialect being chatted about the other day--for things like zeroing memory, it's very unexpected that a compiler would be like "nah, not feeling it...nobody will notice anyways".
- cperciva 12y agoIf you're not worried about writing in a language which is widely supported, just use memset_s and tell people to find a C11 compiler.
- JoshTriplett 12y agoOr have your build system add memset_s.c to their compile on systems that don't have it.
- deleted 12y ago[deleted]
- tedunangst 12y agoA C11-unaware compiler will not be guaranteed to provide the always zero semantics, any more than your own secure_memzero could be optimized away.
- deleted 12y ago[deleted]
- stephencanon 12y agomemset_s is an optional feature of C11. Implementations are not required to provide it unless they define __STDC_LIB_EXT1__.
- deleted 12y ago[deleted]
- ars 12y agoNo, you are looking at it backward. The compiler tries to optimize the program so that it will run fast. If you force it to execute this code it only benefits the very rare security program, yet every single program will run slower. That's a bad tradeoff. Better to make the security program jump through hoops and let everyone else run fast.
- CJefferson 12y agoIf your compiler wrote every variable into memory, every time you changed it, your code would run at least 3 times slower. Compilers generally do not worry about making sure the value of unallocated blocks of memory are what you expect, as there is no way to observe them within your program.
- Someone1234 12y agoCan someone explain this line: static void * (* const volatile memset_ptr)(void *, int, size_t) = memset; I've written some C but that is utter gibberish to me.
- pavlov 12y agoIt declares a function pointer variable named "memset_ptr", and assigns the value of "memset" to it. The "void *" before the first parenthesis is the return type of the function. The stuff in the first parenthesis applies to the function pointer variable. The second parenthesis lists the types of the arguments to memset.
- cperciva 12y agomemset_ptr is a const (we can't change its value) volatile (its value might change on its own) pointer to a function which taking three arguments (void * , int, size_t) and returning void *; and the memset_ptr symbol does not have external linkage and is initialized to "memset".
- Gracana 12y agocdecl is really handy for stuff like this. Try it out: http://cdecl.ridiculousfish.com/?q=static+void+*+%28*+const+volatile+memset_ptr%29%28void+*%2C+int%2C+size_t%29 http://cdecl.ridiculousfish.com/?q=static+void+*+%28*+const+...
- Hello71 12y agohttp://cdecl.org/ http://cdecl.org/
- blinkingled 12y agoIt's declaring a function pointer which is just an alias for memset and additionally qualifying it as volatile so the compiler doesn't optimize the call out.
- adultSwim 12y agohttp://ieng9.ucsd.edu/~cs30x/rt_lt.rule.html http://ieng9.ucsd.edu/~cs30x/rt_lt.rule.html Learning the right-left rule helps here. You'll still need to know what the keywords mean.
- tiffanyh 12y agoFor those of you unaware, Colin Percival (author of the blog) was for many years the FreeBSD Security Officer and he's highly recognized in the field for his expertise. He also runs http://www.tarsnap.com/ http://www.tarsnap.com/ which is arguably the most secure (and cost effective) back up solution in the market. (I'm in no way affiliated with Colin and/or Tarsnap. Just a fan of his work and humble attitude.)
- cperciva 12y agohumble attitude I'm guessing you haven't seen the "comeback of all time" thread...
- tiffanyh 12y agoI haven't. But I think it's super funny and cool that you (yourself) are pointing it out. All the best with you. Edit: just read the "comeback of all time". That was really funny. Nice nod from PG as well. For those of you unaware like me: https://news.ycombinator.com/item?id=35083 https://news.ycombinator.com/item?id=35083 Colin is our resident mathematical genius :)
- bentcorner 12y agoThanks for linking that. It's always interesting to see posts from several years ago and reflect on how things turned out (if you go up a few parents you can see discussion about tarsnap and dropbox makes a brief appearance).
- tiffanyh 12y agoColin, if you don't me asking (and this is extremely off topic), do you know of any resources to read up on the differences between FreeBSD and DragonflyBSD now 11 years after the split. I'm not looking for you to take sides on the matter. I just enjoy reading history and would like to read a recent review of the two BSD now 11 years later and how they compare. I've looked and looked for the past few months and can't seen to find a good solid length article on it ... which is why I ask.
- drivingmenuts 12y agoWhy would the compiler be allowed optimize away a call to a perfectly valid function? This seems like it's allowing to compiler to make judgement calls on whether or not your code is worthy of being executed.
- dietrichepp 12y agoBecause the C standard permits such optimizations. It is really just a case of inlining followed by dead store optimization, two fairly common optimization passes which are normally desired. The subtlety is that while the compiler considers it a dead store, you don't, because you're going behind the compilers back to examine memory afterwards.
- rohit89 12y agoHow about compilers having a way for us to tell it that a particular function must not be removed? It seems silly that we have to come up with hacks to work around the compiler.
- ygra 12y agoThey do. Every function they don't know about is considered important, potentially having side effects and the call will remain. Just call a function that exists in a library somewhere else (but use a static library and link-time optimization and that goes wrong again, because the optimizer knows the function then).
- TheLoneWolfling 12y agoOr, for that matter, use a dynamic library and it can still go wrong! (hypothetical future JITter or somesuch)
- DSMan195276 12y agoIt's because memset is a standard function and has a defined standard way of acting, with the most important part being that it doesn't produce any side-effects. It's also worth noting that accessing memory that's no longer in the current scope is undefined-behavior, so the compiler can assume it doesn't happen. Thus the memset has absolutely zero effect on the actual program and isn't necessary. In general this isn't a big deal. It's only a big deal here because we're working on the assumption that you might have an issue in your code that invokes undefined-behavior and access contents of memory that you're not supposed to be looking at anymore, and the compiler's just assuming that your program won't ever allow that.
- defen 12y agoNice teaser at the end there. Does it have something to do with the fact that the OS may have paged the memory containing the sensitive data to disk?
- cperciva 12y agoPart 2 should be up tomorrow. ;-) But no, I'm not talking about VM paging.
- jacquesm 12y agoCoredumps?
- schoen 12y agoOS/language runtine should provide cryptographic key management routines that are correct in the OS/device context?
- jacquesm 12y agoSure, but programs can crash (or you can attempt to make them crash) and then your keys are sitting there unprotected in a coredump. Anyway, I'm curious if that's the situation Colin was thinking about or not. That would seem to be quite hard to protect against. The example Colin gave was one that is definitely not at the OS / Library level. Though maybe you're right and that's where the solution to all this lies.
- dsjoerg 12y agoI figured the article would be about how you have to write random data to the buffer to truly "zero" it, otherwise the ghost of the data can still be read using some trick.
- Dylan16807 12y agoMain memory decays in milliseconds.
- tedunangst 12y agoWhen this still doesn't work: JIT compiled C. The compiler can check for memset and elide it. (Or hell, one can envision the hypothetical Antagonizer9000 compiler including a version of memset which peeks up the stack to see what it's clearing and stops short.)
- rchowe 12y agoWould it be possible to read a value from the array and do something with it (e.g. send it to /dev/null)? Even a JIT shouldn't be able to optimize the value out if you're actually using the value, and as long as you're not zeroing secure memory all the time, the performance hit shouldn't be that large. This might cause side effects if /dev/null does not exist or is not the null device.
- pcwalton 12y agoA compiler could just forward the zero from the memset directly to your write syscall and delete the memset (and I would certainly implement this optimization in a compiler if I found it helped real code).
- davidtgoldblatt 12y agoTo clarify: the strategy in the post doesn't actually work (or at least, is not guaranteed to work in every conforming implementation): the "volatile" only applies to the read of the function pointer, not to the execution of the function in question. You don't even need to assume some sort of crazy evil compiler to have to worry about this - speculative inlining of function pointers guarded by a safety check is something that FDO builds will actually do.
- BrandonM 12y agoThe first comment there (by Anonymous) claims that the final technique can also be optimized: (memset_ptr)(p, 0, len); > can be replaced by: if (memset_ptr == memset) { memset(p, 0, len); } else { memset_ptr(p, 0, len); } > Which in turn can be optimized using the other tricks noticed above into: if (memset_ptr != memset) { memset_ptr(p, 0, len); } I'm no expert, but this seems like a believable defeat of the technique in the post.
- scott_s 12y agoColin, you're missing end-parenthesis in your memset calls.
- cperciva 12y agoFixed, thanks.
- Genmutant 12y agoWhy wouldn't you make key volatile? Shouldn't that solve all the problems? Or is it because it would be to slow because the compiler can't do that many optimizations in the rest of the function any more?
- cperciva 12y agoYes, making key volatile would force the zeroing to happen; and yes, you don't want to do that because it would absolutely kill your code performance.
- theseoafs 12y agoCan't you just cast it to a `volatile uint8_t *` at some later point when you need to ensure that we've zeroed the memory?
- mikeash 12y agoThat's discussed in the article. Volatile ultimately applies to the storage, so a sufficiently smart compiler may be able to deduce that you're lying to it with the cast and elide the write.
- jbert 12y agoCan you play the game the other way and "fail safe"? i.e. declare the storage volatile but running your crypto code on a non-volatile ptr to it (obtained via cast) to get your performance back? If the compiler then generates enough smarts to work out that the non-volatile ptr you've passed into your crypto code is referring to volatile storage, then you keep security but get a (noticeable in testing?) performance hit. I guess that's not as good as your solution though.
- jhallenworld 12y agoSlightly OT since it has little to do with security, but fighting the optimizer is something FPGA Verilog and VHDL designers must also master. If you don't use an the result of some logic it will be optimized out. One way to prevent this is to route it to a pin. If logic is fed by a constant, it will be optimized out right up to the point where the result of the logic is mixed with some external input. (early tools could not use the dedicated reset net due to this- reset for each flip-flop had to be routed to a pin or the reset net was optimized out which means the initial state of your flip-flop is lost). If you have identical logic, one copy is optimized out due to aggressive CSE. This is often bad for performance (routing in an FPGA is as slow as logic, so it's better to regenerate identical results in multiple places), so you add "syn_maxfan" constraints to prevent the "optimization". On the other hand, an input flip flop will be duplicated if the fanout limit is exceeded- but this prevents the use of the dedicated I/O cell flip flop which then causes external timing to be messed up. So you use syn_maxfan=infinite for this case.
- legulere 12y agoWhy would you want your FPGA to have circuits that aren't used? And why don't you want constant expressions to be pre-calculated by the optimizer?
- exDM69 12y agoI have the exact same question. I presume this is for some incremental development work. Like testing and seeing the number of gates/pins used for a design but you need to feed the logic with constant placeholders.
- smegel 12y agoWhen I allocate the key on the heap, the memset is carried (heavily optimized and inlined). When I allocate key on the stack, it disappears. Using gcc -03: #include <string.h> void doSecure(void) { /*char key[32];*/ char *key = (char*) malloc(sizeof(char)*32); memset(key,sizeof(char),32); } int main(void) { doSecure(); return 0; } -- key on stack main: .LFB13: .cfi_startproc xorl %eax, %eax ret .cfi_endproc -- key on heap main: .LFB13: .cfi_startproc subq $8, %rsp .cfi_def_cfa_offset 16 movl $32, %edi call malloc movabsq $72340172838076673, %rdx movq %rdx, (%rax) movq %rdx, 8(%rax) movq %rdx, 16(%rax) movq %rdx, 24(%rax) xorl %eax, %eax addq $8, %rsp .cfi_def_cfa_offset 8 ret .cfi_endproc
- rwg 12y agoThat's not a behavior you can count on — clang 3.4 will optimize doSecure() down to "ret" and main() down to "xorl %eax, %eax" + "ret" in both cases, for example. Also, gcc not optimizing out the malloc + memset in the heap case seems like a missed optimization that the gcc devs might fix in the future.
- tedunangst 12y agoA more realistic idiom is memset followed by free. The free provides a solid hint to the compiler that the object is dead without relying on escape analysis.
- smegel 12y agoNaturally, however adding free made no difference in this case. I guess if your dealing with a raw pointer rather than an array type, gcc cant be sure what memory you intend to erase.
- pjmlp 12y agoThat is compiler specific behaviour. You have zero guarantees it will work in another compiler or even between releases of the same one. Never take a compiler behaviour for the standard. That's the beauty of standards.
- denim_chicken 12y agoIn GNU C one can add the statement asm ("" : : "m" (&key)); just before or after the memset, effectively telling the compiler that the address of "key" escapes the scope of the function.
- fafner 12y agoGCC also has an `optimize' function attribute which might make sense to use here. This can set optimizations for the function to -O0. But I haven't tried it.
- cesarb 12y agoThat's not enough, since even -O0 applies a few optimizations, and which optimizations it applies could change in the future. The best answer is really GCC's __asm__("" : : "m" (&key)), or perhaps something like __asm__("" : : "r" (key) : "memory"), after the memset. It generates no extra code, just ensures that the memset won't be removed. For other compilers (in practice only MSVC, since clang is gcc-compatible), you could pass the pointer to a dummy assembly function instead of using inline assembly; even link-time optimization can't know what happens within a function written in assembly. Or, for better performance, create in assembly a "safer_memset" which is a single instruction: a jump to the real memset function.
- syncsynchalt 12y agoThat's not portable, whereas the "volatile" solution is.
- userbinator 12y agoWhile this completely subverts our intention, it is perfectly legal: The observable behaviour of the program is unchanged by the optimization. This begs the question of what is "observable behaviour" - execution time, which is definitely "observable" and the basis of timing-based attacks, can certainly change depending on what the optimiser decides to do. I think this and similar cases of "fighting the optimiser" should really be solved with per-function (or even per-statement) optimisation settings; both GCC and MSVC support #pragma's to do this, although it's nonstandard.
- cperciva 12y agoThe term "observable behaviour" is defined in the standard: Essentially, I/O to files and interactive devices, plus accesses to volatile objects.
- schoen 12y agoPerhaps in retrospect this was an inappropriate choice of definition, at least for cryptographic operations.
- bunderbunder 12y agoI'm gonna go ahead and say it: Perhaps in retrospect C is an inappropriate choice of language for these kinds of applications. This "Performance at all costs, including safety and predictability" thing may be appropriate in video games, but for security-critical applications that philosophy is downright negligent.
- tedunangst 12y agoI'm not aware of any language that would be better. Most languages don't even let you touch memory to try to zero it.
- swift 12y agoMany languages zero every allocation unless they can prove that you immediately write over that memory without reading it.
- e12e 12y agoIt is a little mind boggling that support for proper handling of this didn't arrive until c11. For a symmetric cipher without a demanding setup/init phase - would it make sense to just do a few rounds on a buffer using the zeroed key? Obviously quite a few more cycles, but should at least be a predictable (constant) overhead?
- maxlybbert 12y agoWhat do you mean? The solution that Percival presents compiles fine on my C89 compiler.
- e12e 12y agoI didn't mean to imply that the solution as presented didn't work, I was just wondering if it would also work simply running the cipher with the zeroed key in order to avoid zeroing the key being optimized away. Obviously that'd be a lot more cycles; I'm just curious if it would be a viable solution ;-)
- maxlybbert 12y agoI was referring to the first sentence ("It is a little mind boggling that support for proper handling of this didn't arrive until c11."). I don't see anything C11-specific in the code Percival posted. I don't know enough to say anything useful about the rest of your comment.
- e12e 12y agoMy point was that this dance around "observed behaviour" isn't needed in C11, as per: "(...) on C11 (are there any fully C11-compliant platforms yet?) you can use the memset_s function. (...) [which is] guaranteed (or at least specified) to write the provided buffer and to not be optimized away." On another note, searching for memset_s and openbsd yielded this hit from 2012: https://mail-index.netbsd.org/tech-security/2012/07/22/msg000540.html https://mail-index.netbsd.org/tech-security/2012/07/22/msg00... Which seems to point back to: https://mail-index.netbsd.org/tech-userlevel/2012/02/25/msg006157.html https://mail-index.netbsd.org/tech-userlevel/2012/02/25/msg0... So I guess the "trick" outlined in the (very lucid) post has been known for a while.
- jimmaswell 12y agoCouldn't you just compile these functions with optimization turned off, in a separate binary or something?
- amalcon 12y agoYou would also need to link with optimizations turned off (or link dynamically, making the behavior undetermined at link time) to be sure. Linker optimizations have been a thing for a few years now.
- kazinator 12y agoThe article the completely obvious: /* implemented in another translation unit */ void zero_for_sure(void *data, size_t size); void func(void) { char securedata[42]; /* ... */ zero_for_sure(securedata, sizeof securedata); } The key here is that our zero_for_sure is an external function in a separately translated file. In the absence of a stunningly advanced global optimization that peeks into other previously compiled units, the compiler has no idea what zero_for_sure does, and so it has to earnestly pass it the given piece of memory. In turn, zero_for_sure is just this: void zero_for_sure(void *ptr, size_t size) { memset(ptr, 0, size); } The compiler has no idea where ptr might come from since this is an external function, and so it cannot optimize away the memset. Only if the compiler could consider the whole program together could it still optimize this. In fact, you don't even need this function, just a dummy external function: void zero_for_sure(void *ptr, size_t size) { char securedata[42]; /* ... */ memset(securedata, 0, sizeof securedata); commit(securedata); } Of course, commit is a noop which just returns. But the compiler doesn't know that because commit is in another translation unit. The only optimization card that the compiler could pull here is since securedata is going away (so that it is illegal for commit to stash a pointer to it), it's okay to call commit with a pointer to some other block which contains zeros, and not actually securedata. With any trick like this, you should inspect the object code to make sure it's doing what you think it's doing. Oh, and sizeof doesn't require parentheses when the operand is an expression; they are required when a type name is used as an operand.
- DenisM 12y ago>In the absence of a stunningly advanced global optimization that peeks into other previously compiled units You'll be surprised, but this stuff exists since late last century, known as Link-Time Code Generation (LTCG): http://msdn.microsoft.com/en-us/magazine/cc301698.aspx http://msdn.microsoft.com/en-us/magazine/cc301698.aspx
- kazinator 12y agoI've just been digging through the C99 standard (official) and the N1570 (final C11 draft) and have come to the conclusion that these optimizations break the language. This is probably not something you should be compiling your OpenSSL shared library or SSH with. A C program consists of translation units which may be preserved in translation form. That happens in translation phases 1 through 7. Multiple translation units may be linked, which is translation phase 8. Phase 8 only consists of resolving references; the last semantic analysis takes place in phase 7. An example under 5.1.2.3 gives the range of adherence between actual semantics and abstract semantics. Though it is just an example, and not normative, it is very clear from the wording that the locus of valid optimizations is the translation unit. Selected citations: 5.1.1.1 Program Structure A C program need not all be translated at the same time. [...] After preprocessing, a preprocessing translation unit is called a translation unit. Previously translated translation units may be preserved individually or in libraries. [...] Translation units may be separately translated and then later linked to produce an executable program. 5.1.1.2 Translation Phases [...] 7. White-space characters separating tokens are no longer significant. Each preprocessing token is converted into a token. The resulting tokens are syntactically and semantically analyzed and translated as a translation unit. 8. All external object and function references are resolved. Library components are linked to satisfy external references to functions and objects not defined in the current translation. All such translator output is collected into a program image which contains information needed for execution in its execution environment. 5.1.2.3 Program Execution 8. EXAMPLE 1 An implementation might define a one-to-one correspondence between abstract and actual semantics: at every sequence point, the values of the actual objects would agree with those specified by the abstract semantics. The keyword volatile would then be redundant. 9. Alternatively, an implementation might perform various optimizations within each translation unit, such that the actual semantics would agree with the abstract semantics only when making function calls across translation unit boundaries. In such an implementation, at the time of each function entry and function return where the calling function and the called function are in different translation units, the values of all externally linked objects and of all objects accessible via pointers therein would agree with the abstract semantics. Furthermore, at the time of each such function entry the values of the parameters of the called function and of all objects accessible via pointers therein would agree with the abstract semantics. In this type of implementation, objects referred to by interrupt service routines activated by the signal function would require explicit specification of volatile storage, as well as other implementation-defined restrictions.
- jasonme 12y agoWell, if the buffer is so critical and yet small, why not just free it and re-allocate the whole thing next time we use it.
- davidcuddeback 12y agoBecause freeing memory doesn't erase its contents.
- foobarqux 12y agoYou should have test cases to verify the zeroing behavior in the object code. Even if the standard says a compiler must do something does not mean that it does.
- maxlybbert 12y agoThe difficult thing is that any way to verify the zeroing behavior would change the compiler's decision about whether it could elide the call to memset. So it's possible (well, almost guaranteed) that the test would succeed even though the memory wouldn't actually be zeroed in production.
- pepve 12y agoRemember that we're trying to mitigate exploits. The test code could just be an exploit. Either that or just dump and analyze the memory.
- paulasmuth 12y agoIf you set up your tests up correctly they should test the exact binary or shared library that is deployed to production and not some test-specific build.
- jbert 12y agoI was thinking about that. I think the way to do the test would be to put in a known key value, then call the platform-specific equivalent of "abort and write a core file". The test would then grovel through the core looking for the known key sequence.
- jstanek 12y agoDoes GCC include any flags to prevent this sort of detrimental optimization?
- prof_hobart 12y agoThere's a bunch listed (https://gcc.gnu.org/onlinedocs/gcc-3.1/gcc/Optimize-Options.html https://gcc.gnu.org/onlinedocs/gcc-3.1/gcc/Optimize-Options....), including -O0 // Do Not Optimise This sounds like it should do the trick (but I've not done C coding for quite some time, so I don't know if there's a nuance as to why it wouldn't), but would also presumably kill any other optimisation. A better option may be to combine that with pragmas (https://gcc.gnu.org/onlinedocs/gcc/Function-Specific-Option-Pragmas.html https://gcc.gnu.org/onlinedocs/gcc/Function-Specific-Option-...) to switch optimisation levels within the code. Anyone who's played with GCC recently know whether this would work or not?
- zokier 12y agoYou mean something like friendly C: http://blog.regehr.org/archives/1180 http://blog.regehr.org/archives/1180 (discussed on HN 8 days ago: https://news.ycombinator.com/item?id=8233484 https://news.ycombinator.com/item?id=8233484)
- apaprocki 12y agoAt least in LLVM 3.4, this seems to do the trick too: static void secure_memset(void *, int, size_t) __attribute__((weakref("memset")));
- haberman 12y agoInteresting. This appears to solve a more general problem, which is: how to create a barrier against inter-procedural optimization and dead code elimination. I wonder if this trick could also be used to solve the double-checked locking problem. From the quintessential DCLP paper (http://www.aristeia.com/Papers/DDJ_Jul_Aug_2004_revised.pdf http://www.aristeia.com/Papers/DDJ_Jul_Aug_2004_revised.pdf): Consider again the line that initializes pInstance: pInstance = new Singleton; This statement causes three things to happen: Step 1: Allocate memory to hold a Singleton object. Step 2: Construct a Singleton object in the allocated memory. Step 3: Make pInstance point to the allocated memory. [...] DCLP will work only if steps 1 and 2 are completed before step 3 is performed, but *there is no way to express this constraint in C or C++*. But Colin's pattern here seems to be a way of indeed guaranteeing this. The volatile function pointer is a barrier against inter-procedural optimization: if the function must be called, then step 3 cannot possibly be performed before steps 1 and 2. (There might still be necessary hardware barriers that are missing, and the lack of a memory model for pre-C11/C++11 probably makes it all technically undefined behavior anyway. But the key sequential ordering constraint that was claimed inexpressible in C and C++ appears to indeed be expressible with this trick, if indeed the trick works for guaranteeing a call to memset).
- nitrogen 12y agoif the function must be called, then step 3 cannot possibly be performed before steps 1 and 2. So just to clarify, there's no way the compiler could do "1, 3, 2" instead of "1, 2, 3"? It seems a naive implementation of a compiler could store the pointer to the allocated memory in the pInstance variable before calling the constructor, rather than using a temporary location for the pointer (e.g. a register). Does C++11 and later specify otherwise?
- haberman 12y agoI should have been more specific. To use Colin's trick with this pattern, you would need to write a separate function (like InitializeSingleton()) that calls the constructor and returns the pointer. If InitializeSingleton() is impossible to inline/optimize, which is the goal of Colin's trick, then 1 and 2 must happen before 3, because 3 cannot happen until the function has been called and returns, and the function does steps 1 and 2.
- xroche 12y agoWhy would you want to zero a buffer ? Because it may contain sensitive information, I presume. If you don't have additional properties w.r.t allocated memory, what prevent a system with high load to temporarily put the given memory block on swap, leaking the information on disk ? Security is hard...
- nitrogen 12y agowhat prevent a system with high load to temporarily put the given memory block on swap mlock() or mlockall() is useful in this case, but those are POSIX functions, not C.
- bostik 12y agoThere is mlock(2), which is supposed to prevent the memory from being swapped. The problem with that is that the call requires either root or CAP_IPC_LOCK. If your user lacks the capability, you have to run the program setuid root. Allocate the sensitive buffers at the start, call mlock() on them and only then drop the privileges. There's also the 10kg fine-tuning hammer, mlockall(2). That makes ALL the memory for the calling process to become unswappable. As it can lock either the "currently held" memory, or "all the memory to be allocated during process lifetime", it can provide for some additional amusement under memory pressure.
- leni536 12y agoI assume this privilege problem can be solved now with UID namespaces in linux. However it's really ugly, depends on running multiple child processes and linux specific.
- ibisum 12y ago>Why would you want to zero a buffer ? Because it may contain sensitive information, I presume. You don't zero sensitive buffers. You randomize them, then free() them.
- clarry 12y agoWhy do you randomize them?
- deleted 12y ago[deleted]
- bakhy 12y agoI had no idea that such things are possible in C. The things about I've read recently (the "friendly" C suggestion) and this seem like violations of the spirit of the language. And for what, really? The language looses its signature predictability, which to me seemed like a great feature of C. If you write crappy code and expect the compiler to fix it for you, you should maybe consider another language. I can only imagine how hard it is to write reliable system software in a language that does these things.
- pjmlp 12y agoActually back in the old K&R days it was much much worse, as no real standard was in place, compilers were just kind of compatible with endless little surprises. Specially fun when trying at home your UNIX homework and vice-versa.
- pjc50 12y agoOther way round: the C language after optimisation is unpredictable, which makes it hard to write secure system software. You don't expect to write sound-looking code and have it broken for you by the optimiser.
- PhasmaFelis 12y ago> If you write crappy code and expect the compiler to fix it for you, you should maybe consider another language. This seems rather to be a case of writing good code and having the compiler break it for you.
- bakhy 12y agoYes it is. You misunderstood my point, which was about the purpose of existence for such optimizations - they are meant to improve code, which presumably needs improvement. But if your code needs improvement, then why not go for a higher level language? BTW, I gave you an upvote by accident :D
- clarry 12y agoThey are meant to make the code run faster. If your code needs optimization, going for higher level languages is seldom a good idea. You could try do low level optimizations in your C or assembly, but for most programs this will eventually backfire. So letting the compiler do its job is actually a good thing.
- Perseids 12y ago> on C11 […] you can use the memset_s function How is the case for modern C++? Are there `vector` or smart pointer alternatives that reliably zero the memory in the destructor?
- sgentle 12y agoThis all seems kind of silly. Why doesn't C have a type qualifier like called "secure" to inform the compiler that it should avoid security-compromising optimisations and maybe even automatically zero the memory when it falls out of scope?
- ovi256 12y agoThat sounds a lot like automatic memory management! To a C dev, that's the same as communism to a US Republican.
- dllthomas 12y agoAs a C dev, no. First, stack allocation is a type of "automatic memory management", and in most situations we C devs are perfectly comfortable with it. Second, in terms of how the memory is allocated/deallocated, the above doesn't sound any different than stack allocation. The difference is more like "volatile", telling the compiler "this memory is special, treat it carefully", and it mostly doesn't seem unreasonable. Note that C compilers frequently have extensions providing a way of naming destructors for particular variables. It probably would still be possible to skip it with a non-local jump (longjmp or computed goto) but avoiding those in security conscious code is probably already standard - it basically is in most code I've encountered.
- ygra 12y agoThe problem is that C doesn't concern itself with security at all. It's a language with its semantics being defined by an abstract machine and observable behaviour on that machine. A compiler is only obliged to emit an executable that has the same behaviour as the original program would have on the abstract machine, again, only regarding observable behaviour. You can still side-step the problem by calling a function that's not defined in the standard (so it cannot be inlined by the compiler), usually something like SecureZeroMemory on Windows and its equivalent on other OSes.
- annnnd 12y agoIs the proposed solution really the best approach? It seems complicated to me and relies on obscure parts of the language. Maybe the problem (compiler optimizes away function call because the result is no longer needed) could be solved like this: memset(key, 0, sizeof(key)); if (key[0]) // we are using key, so you can't skip memset() dropDead(); Unless the compilers "understand" memset and still optimize away the last two lines? I would hope not... Does anyone know how aggressive the C optimizers are these days?
- Genmutant 12y agoThe compiler understands memset, so that would probably not work.
- anon4 12y ago> Unless the compilers "understand" memset and still optimize away the last two lines They do. That's the whole "problem" - the compiler knows what memset is and what it does, since it's specified in the standard.
- DSMan195276 12y agoIt's not even that it 'understands' memset, more likely that the memset call is almost immediately inlined into your code making it obvious what's going on.
- anon4 12y agoJust put it in a shared library and don't worry about it. Why all these compiler-specific brittle solutions when simply putting a function in a .so will ensure it's being called and will prevent any link-time optimisations.
- TheLoneWolfling 12y agoLast time I checked, there was nothing specifically preventing an implementation of C from doing whole-program optimization at runtime, even to the point of dynamic library calls. So: this is not something you can rely on always working. Yes, it works currently, but it is not guaranteed to always do so.
- pjungwir 12y agoDoes anyone have any advice on articles about C compiler optimizations in general (especially gcc)? I'm doing my first serious C work in ten years, and I keep wondering if I should fuss with things like this or let the compiler handle it all: foo->bar->baz[i].oof = foo->bar->baz[i].durb + meep; vs what *tmp = foo->bar->baz[i]; tmp->oof = tmp->durb + meep; EDIT: I'm not asking for a link to this: https://gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html https://gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html I'm asking if there is advice about it. Any overviews with common pitfalls, advice on when to use -O1 vs -O2, specific optimizations to turn on/off, etc.
- exDM69 12y agofoo->bar->baz[i].oof = foo->bar->baz[i].durb + meep; This is fine, no need to "optimize" anything. This kind of common subexpression elimination should be done by any modern compiler (for any language!) and the algorithm behind it is taught in university classes too. Most of the time it's safe to use -O3. If you're doing numerical code with floating points -ffast-math is also pretty safe if your code is correct (ie. no NaN/Inf bugs). Almost the only reason to turn off optimization (-O0) is when higher optimizations make using a debugger harder. Here's a pretty nice article with some specific optimizations that GCC can (or can't) do. It's pretty old, though, the examples were done with GCC 4.2.1, current version is around 4.9. http://ridiculousfish.com/blog/posts/will-it-optimize.html http://ridiculousfish.com/blog/posts/will-it-optimize.html These days Clang can be as good or better than GCC most of the time. The exceptions are in more exotic code like kernel space stuff or micro controller programming. There's no room for guesswork if you actually want to optimize code, so spend some time reading the assembler output from your compiler as well as benchmarking the results. I usually use objdump -d objfile.o to look at assembly output.
- sjolsen 12y ago>I usually use objdump -d objfile.o to look at assembly output You can also compile to assembly with -S. I think it's clearer that way.
- 12y ago
- im3w1l 12y agoA sufficiently malicious compiler could keep around a copy of the key in non-volatile memory.
- api 12y agoIf your goal is just to "burn" the memory, why not write your own loop that copies some arbitrary piece of data that the compiler can't optimize out over the memory's contents? Do something like fill the buffer with its own pointer address.
- clarry 12y agoIt's been stated here already. You can write whatever you want into it. It doesn't matter. What matters is that the compiler realizes there's a dead store going into it, i.e. that the data is thrown away after it's written. So it can optimize out any write, since no conforming program can read that data after it's thrown away.
- dllthomas 12y agoIt seems like ideally what we need is a language designed for as-fast-as-secure computation, JIT for the specific architecture it is going to run on to ensure no differences in timing, energy use, or anything (within whatever bounds are achievable) even in the face of different cache layouts, CPU optimizations and similar and which makes it a point to clean up everything that is not meant to be returned.
- oso2k 12y agoWouldn't returning the passed memory block through the return fix the optimization issue?
- MichaelMoser123 12y agoYou can have a function that wrapps memset with zero argument this wrapper should be in a different shared library, this way the compiler will not follow it; wait, that's exactly what memset_s is,
- dllthomas 12y agoNot exactly. There's no reason memset_s can't be understood by the compiler, inlined, and optimized, so long as those bits still get zeroed. That's not the case for any of the other approaches.
- MichaelMoser123 12y agothe compiler should not make any assumption about what memset_s is doing (same goes for user defined memset wrapper function in different shared library - the implementation of that function is not known at compile or link time); if it can't make such assumption then it can't optimize the call out.
- dllthomas 12y agoI think you misunderstand me. There is no guarantee that there will be no optimization of or around memset_s (at least, not provided by the standard), and we don't want one. What the standard has done is assured us that it will not be optimized away - that the effect of zeroing that memory will be treated as visible even if the memory is otherwise dead. Allowing the compiler to optimize without changing semantics is desirable, and is permitted by the standard but prevented by the attempts to erect artificial walls through linking (and also by the volatile function pointer in the article), so memset_s - in addition to being clearer - is a technically superior solution.