TAOSSA on Novel C++ Bug Class: delete/delete[]
From the Law Offices of Dowd, McDonald, and Schuh: a fantastic post on a C++ bug-class: misuse of delete and delete[].
The problem: the C++ runtime needs to invoke the destructors for every element of an array when it’s released. But given a bare pointer, the runtime doesn’t know whether it’s looking at an array of objects or a single object. You have to tell it. You do this by invoking “delete foo” for a single object, and “delete [] foo” for an array.
This leads to two problems:
If you screw up and invoke “delete []” on a single object, the runtime will interpret an offset from that pointer as a count variable. If an attacker controls the value at that offset, she can trick the runtime into invoking destructors on invalid memory locations.
If you screw up and involve bare “delete” on an array —- a mistake I made all the time on my last C++ project —- the runtime will call “free” on an invalid pointer; depending on how much control you have over the array, this devolves into a simple malloc attack.
An observation, and a generalization of it:
C++ is considered “safer” than C (primarily because it offers a standard dynamic string class, though most developers still just use char-stars). But it has a drastically more complicated runtime than C’s crt0.c, and minimal language-level protections. I’m ambivalent about C++, and terrified of the STL (how many hidden bug classes are there in iterators alone?).
Higher-level languages are considered “safer” than C. At some threshold of complexity, such as is crossed by Tcl, Ruby, Perl, and Python, they probably are. But below that threshold, most language features offer as many footholds for attackers as they do obstacles. How carefully are people considering C++ and Objective C?
12 Comments so far
Leave a reply
Would I be insane for considering C++ in a new project where Java would suffice for 95% of it (part of it has to be in C)?
A few weeks ago it would have been C or C++ without a question. But with the prospect of hiring some new developers, I have to think about their knowledge as well. I can write safe C and C++ code, but can they?
While C++ allows you to code in such a way that you make this mistake, it’s powerful enough to support a much safer coding paradigm:
AutoPtr foo = Create();
AutoArray fooArray = CreateArray(12);
When you’re done with the pointers, these objects will release your allocations in the appropriate manner.
Using C++ without RAII resource management has always struck me as only driving your Porsche in first gear. You can get places, but it’s a crying shame to see.
RAII only works in situations where the lifetime of an object can be tied directly to its scope. You have a stronger argument for “wrap arrays in objects that can issue delete[] in the dtor”, but then you can’t create arrays of THOSE objects.
And yes, you’d be insane to consider C++ for a new project in 2007.
Writing about delete/delete[] in 2007 is just plain pathetic, because there are dozens of C++ books out there explaining how to use RAII and similiar approaches. And if someone has not read them yet, he just needs a good pointer to the bookshelf.
And about “arrays of THOSE objects” — it’s very easy, especially using STL vectors. Need an array? Here it is: vector array; Need an array of arrays? Nothing is easier: vector > array_of_arrays.
First, you’re responding to a post that’s 7 months old, so nobody is going to read your comment.
Second, you’ve misunderstood the whole point of the post. It’s not that anyone’s “discovered” delete/delete[]. You’re right, it’s a textbook example of a C++ coding mistake, and one everyone learns when they blunder into it in their first year of writing C++.
The point is, like a growing portion of C/C++ coding flaws, we are discovering ways to weaponize the flaw, turning it from a simple bug to an exploitable security flaw.
Also: what on earth does RAII have to do with delete[]? Maybe you meant to say, “there are dozens of books on how to use vector instead of arrays”.
Thanks, I did not really expected anyone to read this but you =)
What I actually wanted to say is, you are right, “delete/[]” are harmful. And the rule is very simple — don’t use “delete/[]”. Every time when starting to assess another project, start with grep’ing on “delete” and then “blame” in your favourite version control system. A good project should have no “delete” constructs anywhere, except maybe one or two well tested and documented places.
And if the project does not have any “delete” left, there shouldn’t be any problem with them anymore?
After we have answered the first part of the question, “what to do?”, there’s a second part which sounds “how?”, and “vectors and RAII” is the answer.
maybe i should have replied on this page, which discusses same issues, but it looks already a bit too crowded…
I buy that you can evict delete[] with vectors.
I don’t buy that you can evict delete with RAII.
In any nontrivial project, a sizeable portion of all your objects won’t be block-scoped.
well, let’s pitch again RAII and delete =)
there are, generally speaking, three types of objects:
1. small objects which are block scoped
2. not-so-small objects which are block scoped
3. other long-living objects
Objects of the first kind could be easily created on stack without new/delete hassle and even without any RAII — they will be automatically destroyed when the block ends.
Objects of the second kind usually cannot be allocated on stack because of their size, so we need some kind of RAII wrapper, which frees the memory when object goes out of scope.
Third kind of objects is the most interesting, but still very simple to deal with. Object definition/declaration should be wrapped in auto_ptr (or shared_ptr or any ptr wrapper of your choice), and delete is either simply removed or replaced with trivial sink() construct if you prefer to release memory immediately and don’t want to wait until the program termination.
Actually, I sometimes do a lot of bugfixing in someone else’s code. And beforementioned steps are the first I do to deal with incorrect exception handling and/or memory leaks. Works like charm =)
Types (1) and (2) are the same thing. The contents of objects are not part of their API; that’s the point of encapsulation. So there are only 2 types of objects in considerations:
1. Block scoped objects
2. Not block scoped objects
The “not block scoped” case is the majority of all objects in nontrivial code.
You’ve now introduced shared_ptr as a means of eradicating delete calls for that case. But shared_ptr has its own dangers. C++ libraries, including some of the standard libraries, aren’t
designed to retain shared_ptrs with type intact. The moment you alias a shared_ptr to a naked pointer, you’ve introduced another, scarier class of bugs.
The whole point of using shared_ptr is not making any naked pointers at all. What kind of C++ libraries are you talking about, that require pointers and are critical to the project survival?