At 3:07 PM +1300 11/7/02, Peter Gutmann wrote: >> [Moderator's note: FYI: no "pragma" is needed. >> This is what C's "volatile" keyword is for. > > No it isn't. This was done to death on vuln-dev, > see the list archives for the discussion. > > [Moderator's note: I'd be curious to hear a summary -- > it appears to work fine on the compilers I've tested. > --Perry]
i include below two parts: a summary of the vuln-dev thread, and a compiler jock's explanation of why peter's #pragma is the _only_ solution that reliably will work. - don davis, boston vuln-dev thread: http://online.securityfocus.com/archive/82/298061/2002-10-28/2002-11-03/1 (thanks to tim fredenburg sending this URL to me.) summary: programmers can obstruct dead-code elimination in various ways: - use the volatile attribute (but correctly); o introduce dynamic dependency; + do the memset with an external call. punchline: the subtler or newer the obstruction, the less likely we are to see that _all_ compilers treat the obstruction correctly. the safest route is to code with obstructions that have long been known to obstruct dead-code elimination. hence, wrapping memset() in an external routine is most likely to work with various buggy compilers. synopsis: * peter posted the same message as he posted to the cryptography list, appealing for new support from the compilers; * syzop said, "didn't happen w/ gcc 2.95.4"; * michael wojcik suggested: define an external call that does memset's job, so as to defeat dead-code elimination * dan kaminsky suggested: introduce dynamic [runtime] dependencies; * dom de vitto said, "use the volatile attribute"; * kaminsky replied: compilers are more likely to reliably respect dynamic dependency, than to correctly support the volatile attribute; * pavel kankovsky replied, "volatile" is mandatory in the standard, so it's ok to trust it; * peter also replied to kaminsky: the dead-code elimination problem seems specific to gcc 3.x . the underlying problem is unreliable support for standard features and for standards compliance. * michael wojcik explains (to peter, pavel, and kaminsky) why "volatile" isn't as good as his external call: - "passing a volatile object to memset invokes undefined behavior" - "access to volatile objects may be significantly slowed" - "volatile seems like the sort of thing broken implementations may get wrong" michael also argues that more compiler support isn't necessary, since the standard provides effective features. <end of synopsis/summary> ------------------------ since i used to build compilers long ago, before i got into security work, i asked an expert friend (32 yrs of compiler development) about what he thought of this problem, and of the proposed solutions. this guy, btw, was the lead engineer for digital/compaq's fx32! runtime binary translator for the alpha workstations, & he knows a lot about optimizers. he says that of the four proposed solutions - * #pragma dont_remove_this_code_you_bastard; * use the volatile attribute (but correctly); * introduce dynamic dependency; * do the memset with an external call; - only peter's pragma can be expected to work reliably: * the c99 standard and its predecessors don't at all intend "volatile" to mean what we naively think it means. specifically, in the hands of a high-end compiler developer, the spec's statement: "any expression referring to [a volatile] object shall be evaluated strictly according to the rules of the abstract machine" is really talking about what the compiler can infer about the program's intended semantics. a c99-compliant compiler _can_ legitimately remove a volatile access, as long as the compiler can deduce that the removal won't affect the "program's result." here, "the program's result" is defined by the compiler's sense of what the "abstract machine" is: the abstract machine is mostly defined by the language features, but can also take into account whether a debugger or specialized hardware are running during compilation & or runtime execution. for example, such a savvy compiler might leave our volatile-memory memset() call in place when the debugger is running (knowing that the debug- ger might want to view the zeroed key). but then, when the debugger is turned off, the same compiler could decide to remove the "dead" memset() call, because this won't affect the program's results. * standards-compliant compilers normally distinguish between "conformant" source programs and "noncon- formant" source programs. for example, a noncon- formant program might be one that uses a deprecated feature. with nonconformant source programs, the compiler can perfectly legitimately bend various compilation rules, especially so as to get better optimization results. the idea is that the spec's strict rules and semantics only make sense for conformant programs. so, in the case of "volatile," a compiler won't necessarily be bound by the "rules of the abstract machine," unless the source program strictly conforms to the language spec's "best practice" definition of how a C/C++ program ought to look. * with the most modern dynamic compilation techniques (my friend's specialty), the compiler re-examines & re-optimizes the executable program _at_runtime_ , so external references and dynamic dependencies aren't intrinsic obstacles to code-motion during optimization, anymore. * finally, my friend gives the example of a compiler that might decide to make a copy of our key buffer at runtime, in pursuit of some optimization. the compiler might have the program zeroize one copy of the key, but not the other copy. as long as the program's end result turns out to be "correct," such a bizarre trick can still fulfill the language spec. - don davis, boston - --------------------------------------------------------------------- The Cryptography Mailing List Unsubscribe by sending "unsubscribe cryptography" to [EMAIL PROTECTED]