Just now had a chance to take a look at the presentation I referenced last post. It's fairly long and detailed, but worth a thorough reading. You can grab it here:
Someone commented on my last post that this stuff should be obvious to a good C++ developer, which is largely true, but here's their response (from the slides):
Many of these problems will be old-hat to experienced C++ developers
- Not too much exposure to C++ problems in the security community
Ptacek's Second Postulate of Vulnerability Sociodynamics
- Obviousness isn't a good predictor of impact
- sprintf() is obvious – still cost a few billion dollars*
"Only bad programmers would do that"
- True, but good programming is the exception
- People make mistakes (also, not all programmers are good)
- People have ridiculous timelines
- Programming is hard – complexity grows
Legacy code bases
- C++ has evolved considerably over 15 years
I think they're completely correct about all of this. I've seen plenty of bad code, some of it written by really good programmers who tend to write a lot of good code, but like the rest of us, they're human.
OK, so say you've got all of Scott Meyers' books memorized, along with the C++ standard, and you write fewer bugs than Knuth. Even so, this presentation is worth a read. One of the problems we all have is that occasionally we find other people's flaws, and we need to prioritize the fix. Here's an example that's obvious once it's been pointed out, but if I ever see it in practice, I'll consider it exploitable.
We all know, or should know, the difference between new, new, delete and delete. These need to be paired up, and when you call delete on something, it knows to call the destructors, if one is present. Out of the 2 possible error cases, calling delete on something allocated with new is more common, and tends to be less dangerous – maybe it's a scalar, and doesn't really matter. Or you just leak memory – not good, but usually won't get you hacked (though it could – go read their presentation). However, if you call delete on something allocated with new, the DWORD preceding the pointer will be interpreted as the count, and depending on how the compiler does things, it could go calling some arbitrary number of destructors far past the allocation. This is the definition of exploitable. The Microsoft compiler is harder to attack – it uses the destructor referenced for element 0, but then it will call it using a bunch of objects that aren't going to be initialized correctly. If this is attacker-controlled, you're in trouble.
Hope you enjoy their presentation – I'll have to speak with Jeff Moss about his streak of scheduling me at the same time as presentations I want to see…