The hard part is taking that unsafe C/Rust and turning it into checked/safe code. For this part, Rust has many benefits over C:
- As a language it is easier to refactor. This is due to a more hygenic macro system, a lack of header files, etc.
- The safe subset of Rust is much larger and more expressive than the subset of checked C. This provides more options for the unsafe -> safe translation effort.
- The safe subset of Rust is more powerful than the subset of checked C. This means that it's more likely that a piece of code can be translated 1:1 into safe Rust, compared to a more complex transformation. For example, code which returns a pointer into one of its arguments is not uncommon in C, and this can be directly translated into Rust lifetime annotations.
This makes no sense; extending C code with Checked-C would still be easier as with Checked-C you wouldn't do any translation at all; it's a superset of C, in the same way that TypeScript is to JavaScript.
Second, you can't just add checked C to a codebase, because you have to actually interop with the unchecked C code, which uses different pointer types.
My argument is that it may be easier to do an automatic migration to unsafe Rust, and then incrementally convert to safe Rust, than it would be to leave the codebase as C and try to incrementally convert it to checked C, because the "incremental conversion to checked C" is extremely difficult.
Then add the fact that exist transformations that turn temporal errors into spatial errors, and you'll see it's not necessarily much of a limitation even then:
> Memory deallocation can be modeled as an assignment. For example, the statement free(p) can be represented by the statement p=invalid, where invalid is a special untyped pointer to a temporally ‘invalid’ range of memory.
Absolutely not. That works for memory accesses through 'p'. It doesn't help you at all for memory accesses through other pointer aliases to the same memory block.
I mean, it's trivial to replace "free(p)" with a macro that also nulls out 'p', but no-one claims that that solves UAF bugs.
If you think the paper is right, just explain how it detects use-after-free in the following code fragment:
int* p = (int*)malloc(sizeof(int));
int* q = p;
free(p);
*q = 1;> After this assignment, the base and bound of p would be updated to be equal to that of the invalid pointer, and any pointer derived from or aliased with p would inherit this metadata as well (see Section 3.5).
Looking at section 3.5, it sounds like what it's actually doing is keeping a global table of every valid base pointer and its maximum offset, and when you free a pointer, it updates that table accordingly, not just the local variable containing the pointer as it previously said.