Déjà vu-lnerability: A Year in Review of 0-days Exploited In-The-Wild in 2020 is a recent post on the Project Zero blog that illustrates my recurring complaint about Project Zero. It states:
Memory corruption is still the name of the game and how the vast majority of detected 0-days are getting in.
That’s a good start, but it continues with this:
But what may be the most notable fact is that 25% of the 0-days detected in 2020 are closely related to previously publicly disclosed vulnerabilities. In other words, 1 out of every 4 detected 0-day exploits could potentially have been avoided if a more thorough investigation and patching effort were explored.
Don’t get me wrong: it would be great if people investigated bugs thoroughly and built better patches. Of course we all want that!
But if that’s all they have to suggest—and that is all they have to suggest—then they have missed the mark, again.
Their advice (patch better) is unsatisfactory in two ways. First, it doesn’t correctly identify and address the root cause of the problem. The root cause is not memory corruption! The root cause is programming in C/C++, which causes memory corruption bugs. Whenever you have a memory corruption bug in a program, it is almost always the case that the program was written in an unsafe language, namely C or C++. So, memory corruption is a symptom, not a cause. Their root cause analysis traced the problem to the ground, but stopped there.
The second reason follows inevitably from the first: because they do not address the root cause, their solution is only addressing symptoms. In fact, their advice is just another spin on the tired, old, false myth of how to prevent memory corruption in C programs:
Just program better! Good programmers don’t make those kinds of mistakes. My programs don’t have buffer overflows. Don’t blame C, blame bad programmers.
Again, this is false. No human is perfect, bugs are inevitable, and in C, some bugs are going to lead to memory corruption. Decades of experience and data have shown this over and over again.
The P0 spin on this myth is: if you’ve made a bug, make sure you don’t make another bug when you fix the first bug.
That would be nice, but, really? Programmers are supposed to take their usual process, which produced the bug in the first place, and turn on INFALLIBLE MODE so that there is no bug in the patch? How is likely is that?
If you think about the big picture, the insanity of the P0 approach becomes clear. The P0 approach starts with a bug, and hopefully ends with a complete and correct patch. In other words, they are fixing one bug at a time—note in particular that what needs to be done to fix that one bug likely has nothing to do with fixing the next bug.
You simply can’t make headway in an ocean of bugs by fixing one bug at a time.
This is why we must take another approach, one that addresses an entire class of bugs at once. Namely, we should shift, as quickly as possible, from unsafe languages like C and C++ to safe programming languages, which in principal eliminate memory corruption bugs.
I am not saying that this would solve all of our security problems. In particular, there are still going to be bugs in safe languages, and there will still be a need to patch bugs completely and correctly. That is no easier in a safe language compared to an unsafe language.
The difference is that the bug in the safe language does not lead to memory corruption; instead it leads to a runtime fault that typically causes the program to halt. This can be a very bad bug (leading to denial of service, etc.), but it is much, much better than memory corruption leading to remote code evaluation.