When optimizing code for "dead store removal" the optimizing compiler may remove code necessary for security. For example: // Beginning of Example Code #include <string> using std::string; #include <memory> // The specifics of this function are // not important for demonstrating this bug. const string getPasswordFromUser() const; bool isPasswordCorrect() { bool isPasswordCorrect = false; string Password("password"); if(Password == getPasswordFromUser()) { isPasswordCorrect = true; } // This line is removed from the optimized code // even though it secures the code by wiping // the password from memory. memset(Password, 0, sizeof(Password)); return isPasswordCorrect; } // End of Example Code It's considered good practice to remove sensitive data from memory when it's no long needed so that the sensitive data doesn't accidentally end up in the swap file, temp file, memory dump file, etc. However, in the above example, the optimizing compiler removes the "memset" function as part of "dead store removal" optimization. The optimizing compiler realizes that "memset" writes to "Password" but "Password" is never again read; hence, it is removed as part of the "dead store removal" optimization. A programmer could erroneously think that his code is secure, even though the securing code -- "memset" -- is removed from the compiled code. Release: GCC-3.2 Environment: i386 but most likely applicable to all environments How-To-Repeat: Any code where "memset" is the last function to affect a sensitive variable/data would be affected by this problem.
Fix: Two fixes exist from this problem: a workaround and a permanent solution. WORKAROUND: Read "Password" after it has been wiped from memory. For example, insert this line after "memset": // Beginning of Example Code *(volatile char *)Password = *(volatile char *)Password; // End of Example Code This will do nothing, but it will result in reading the "Password" variable, signaling to the optimizing compiler that "memset" should not be "dead store removal" optimized out. PERMANENT SOLUTION: The WORKAROUND uses both memory and execution time. A better solution would be to turn off optimization for that part of the code. However, to the best of my knowledge, GCC does not support altering optimization options on-the-fly though preprocessor statements. (If it does, will someone please provide me with a link to the appropriate documentation? I was unable to find any documentation showing that GCC supports this feature.) For example, altering optimization options on-the-fly through preprocessor statements could be implemented this way: // Beginning of Example Code #pragma optimize("-no-dead-code-removal") memset(Password, 0, sizeof(Password)); #pragma optimize("-dead-code-removal") // End of Example Code There's a dozen different ways to accomplish this, but to the best of my understanding and knowledge, the PERMANENT SOLUTION has to be implemented as a new feature (if such a feature doesn't already exist).
State-Changed-From-To: open->closed State-Changed-Why: This is not a bug in GCC. The call to memset() clear hasn't any externally visible effect according to the C language specification, so it can be removed by the optimizer. See the discussion around http://gcc.gnu.org/ml/gcc/2002-01/msg00518.html for additional information on a very similar topic.
From: "Joseph D. Wagner" <wagnerjd@prodigy.net> To: <fw@gcc.gnu.org>, <gcc-bugs@gcc.gnu.org>, <gcc-prs@gcc.gnu.org>, <nobody@gcc.gnu.org>, <wagnerjd@prodigy.net>, <gcc-gnats@gcc.gnu.org> Cc: Subject: RE: optimization/8537: Optimizer Removes Code Necessary for Security Date: Sun, 17 Nov 2002 08:59:53 -0600 Direct quote from: http://gcc.gnu.org/onlinedocs/gcc-3.2/gcc/Bug-Criteria.html "If the compiler produces valid assembly code that does not correctly execute the input source code, that is a compiler bug." So to all you naysayers out there who claim this is a programming error or poor coding, YES, IT IS A BUG! From: http://gcc.gnu.org/ml/gcc/2002-01/msg00518.html > The problem is the standard gives wide latitude in what the optimizer can optimize Isn't this also the solution? Can't the optimizer check to see if the function is memset(), and if so check to see if the value is 0 or NULL, and if so leave it in? The optimizer could check if memset() is zeroing out memory by checking if the fill value is 0 or NULL. If the fill value is 0 or NULL, the optimizer can reasonably presume that this instance of memset() is for security and not optimize it out. Joseph Wagner -----Original Message----- From: fw@gcc.gnu.org [mailto:fw@gcc.gnu.org] Sent: Sunday, November 17, 2002 8:28 AM To: gcc-bugs@gcc.gnu.org; gcc-prs@gcc.gnu.org; nobody@gcc.gnu.org; wagnerjd@prodigy.net Subject: Re: optimization/8537: Optimizer Removes Code Necessary for Security Synopsis: Optimizer Removes Code Necessary for Security State-Changed-From-To: open->closed State-Changed-By: fw State-Changed-When: Sun Nov 17 06:28:05 2002 State-Changed-Why: This is not a bug in GCC. The call to memset() clear hasn't any externally visible effect according to the C language specification, so it can be removed by the optimizer. See the discussion around http://gcc.gnu.org/ml/gcc/2002-01/msg00518.html for additional information on a very similar topic. http://gcc.gnu.org/cgi-bin/gnatsweb.pl?cmd=view%20audit-trail&database=g cc&pr=8537
From: Florian Weimer <fw@deneb.enyo.de> To: "Joseph D. Wagner" <wagnerjd@prodigy.net> Cc: <gcc-bugs@gcc.gnu.org>, <gcc-gnats@gcc.gnu.org> Subject: Re: optimization/8537: Optimizer Removes Code Necessary for Security Date: Sun, 17 Nov 2002 16:27:12 +0100 "Joseph D. Wagner" <wagnerjd@prodigy.net> writes: > Direct quote from: > http://gcc.gnu.org/onlinedocs/gcc-3.2/gcc/Bug-Criteria.html > > "If the compiler produces valid assembly code that does not correctly > execute the input source code, that is a compiler bug." In this case, "correctly" means "correctly according to ISO 9899 and the GCC documentation", not just "as expected". > So to all you naysayers out there who claim this is a programming error > or poor coding, YES, IT IS A BUG! It would be a bug if GCC would implement Joseph D. Wagner's Imaginative Version Of C, but the GNU C compiler implements a different programming language, I'm afraid. Just because it's unexpected to you and a few others, it's not a bug automatically. >> The problem is the standard gives wide latitude in what the optimizer >> can optimize > > Isn't this also the solution? Solution to which problem? Of course you can special-case this particular instance in the optimizer, but this isn't a good idea. There's already enough bloat in GCC. > Can't the optimizer check to see if the function is memset(), and > if so check to see if the value is 0 or NULL, and if so leave it in? This only solves one particular incarnation of the more general problem. Currently, when you have scrubbing requirements, you have to inspect the object code anyway, even if any of the changes to GCC suggested so far were made. There is no way to tell the compiler, "this data is critical, don't make any copies of it". Anyway, correct scrubbing is only a very weak form of protection and prone to race conditions in multi-tasking environments. Although one of the most widely used operating systems doesn't do any scrubbing on the operating system level, this is hardly a problem we want to and can fix in GCC.
Setting the correct resolution.
just for future reference, in >=C11 we have memset_s for this particular issue, and in >=C23 we also have memset_explicit, both of which are not allowed to be optimized out