Wednesday, October 04, 2006 9:28 AM
LarryOsterman
Sometimes people don't really get the point of defensive programming for security...
I was reading this months issue of Dr. Dobbs Journal, and I ran into the column "Illusions of Safety" by Pete Becker. Pete writes about enhancements to the C language, and I usually really enjoy his columns.
This month was an exception. Pete basically spent the entire column explaining why you don't have to worry about the "unsafe" C runtime library functions like gets() and strcpy() as long as you design your application correctly.
I'm just fine until he gets to the 2nd page of the article, "Prevention by Brute Force". First off, he presents a "safe" version of strcpy called safe_strcpy which isn't actually a safe version of strcpy. It's actually a replacement for strncpy, and preserves one of the critical bugs in strncpy - strncpy isn't guaranteed to null terminate the output string, and neither is his "safe" replacement. He also describes testing for success on the strcpy as "tedious". Yeah, I guess that ensuring that you handle the failure of API's is "tedious". I'd also describe it as "correct".
But it's when we get to the 3rd page "Prevention by Design" that Pete totally loses me.
You see, on Page 3, Pete decides that it's OK to use strcpy. You just need to make sure that you put an assert() to make sure it doesn't overflow. And you need to make sure that the inputs to your functions are checked upon entry to the system.
But that's just plain wrong. First off, the assert() won't be there in non debug code - assert()'s disappear in production code. So your production code won't have the protection of the assert. And it completely ignores the REAL cause of the problem - what happens when the vulnerable function is called from an unchecked code path. If (when) that happens, you've got a security hole. And the bad guys ARE going to find it. Michael Howard gives LOTS of examples where developers added checks to a vulnerable code path at the entry point of a function without realizing that there was another code path to the vulnerable function. You also don't know what will happen four or five years in the future - it may be that a future maintainer of your code won't realize that your low level routine has such a constraint and calls it improperly.
It's far better to replace the strcpy() call with a strcpy_s() call and make the caller pass in the size of the target buffer. That way you don't rely on others to protect your vulnerability. There is at least one known vulnerability that was caused by someone taking Pete's advice. A development organization had a vulnerability reported to them, and they fixed it by adding a check up-front to cut off the vulnerability. Since they'd removed the vulnerability, they released a patch containing the fix. Unfortunately, they didn't realize that there was another path to the vulnerable function in their code, and they had to re-release the patch. This time they did what they should have done in the first place and not only did they add the up-front test, they also removed the vulnerable call to strcpy - that way they'll never be caught by that vulnerable call again.
On page 4, he claims that it IS possible to use gets() correctly. It's ok to have programs that read from the console as long as those programs are only called from other, known programs. But of course, this totally ignores the reality of how the bad guys work. Security vulnerabilities are rarely exposed by someone using an API or protocol in the way it was intended. They almost always occur because someone DIDN'T use a program in the way it was intended. For instance, I know there was at least one *nix vulnerability that was explicitly caused by a setuid application calling gets. Someone fed a bogus command line string to that application and presto! they had root access to the machine.
Here's another way of thinking about it (oh no, not another stupid automotive analogy). If you drive safely, you don't need seat belts (or airbags). But I wouldn't even DREAM of riding in a car without at least seat belts. Why? Seatbelts and airbags are a form of defense in depth. Your primary defense is safe driving. But just in case, your seat belts (or airbags or both) may save your life.
Bottom line: A good architecture is no substitute for Defense in Depth. Sure, apply the architectural changes that Pete described. They're great. But don't believe that they are adequate to ensure safety.