XNU kernel heap overflow due to bad bounds checking in MPTCP
bugs.chromium.org
bugs.chromium.org
From a safety perspective you want to either box these derived types or copy the headers into properly typed objects so you don't end up with the complex if-else ladders that plague IP stacks. But the cycles to do this--to recursively construct simple boxed types or copy headers--can quickly add up to more than the cost of copying the entire packet as packets move through the pipeline. Engineers [unfortunately] bend over backwards attempting to achieve zero-copy.
We should be using more type safe techniques, and certainly D and Rust make such techniques more ergonomic. But it's fanciful to think that engineers won't recapitulate the same sins[1]. If it were true we'd all be using seL4 and it wouldn't much matter if a language was "safe".
[1] You can this see kind of recapitulation regularly in "high performance" Java and Rust applications where the same type punning patterns are used, replacing pointers with indices into buffer arenas. Po-tay-to, po-tah-to.
And particularly in a kernel environment even @safe code can do things that violate or exacerbate assumptions elsewhere in the code (e.g. complex RCU protocols), thus Linus' preference to avoid languages with a lot of implicit machinery or non-obvious execution flow. So it's not like @safe code can simply be removed from the equation.
size_t zfoo = ...;
/* todo: better typecheck of malloc */
int[zfoo]* foo = malloc(zfoo * sizeof *foo);
[zfoo] ifoo = ...; /* allowable values 0,1,...,zfoo-1 */
/* zero-length arrays cause problems here */
return foo[ifoo]; /* no runtime check; type guarantees in-bounds access */
There are several problems with this, but it sure would be nice.0: Stupid dependent type system tricks: have x % y typed as [y], have x < y typed as Maybe([y]), make for([n] i=0; i<n ;i++) do the useful thing.
int foo(int len, int (*arr)[len])
{
printf("number of elements in *arr: %zu\n", sizeof *arr / sizeof (*arr)[0]);
}
int main()
{
int a[6];
int b[10];
foo(6, &a);
foo(10, &b);
return 0;
}
If you do this, the compiler clearly has the information to do at least dynamic bounds checks in 'foo', which it may also be able to statically eliminate (eg if there is a loop from 0 to len - 1).It might be nice if there was some syntactic sugar for this that made the 'len' parameter implicit and unnamed, and didn't need the explicit & / * on the array pointer.
The compiler should be able to diagnose incorrect array length passed at the call site, though?
int length(int len) { return len; }
It would also mean no slices. [n] i = ...;
int i1 = length(i); /* fine, but we dont know what i1 is */
[n] i2 = length(i); /* error, we assume length can return any int */
[n] i3 = length(i) < n else panic("");
/* also fine, and maybe we can optimize out the check, but we don't have to */len needs to be explicit unless it's packaged as eg arr.len. Also (* arr)[i] is just as unacceptable as (* p).item, but you can't do arr[i] unless you abandon the ability to do indexing on raw pointers, which would break essentially all of C.
Maybe you could do something like arr->[i] as sugar for (*arr)[i]?
Yes, that's what I mean. I point this out and I think people often assume I'm talking about replacing c style arrays instead of what I really mean, adding a bounded array reference type.
And yes it's true that you wouldn't be able to pass it to string functions and API's that take standard array/pointers. But still would be really really nice to have that in the tool box. because a lot of times getting things correct with minimal hassle and 'brittalty' is more important then speed or code size.
I would lead with the stronger demand, because that's what would IMO trigger the more interesting discussion.
As a former C dev (ran away to webdev years ago from embedded due to flaccid job market) I'm open to an idea of on-demand boundary checks.
To me it will just be a matter of having the compiler insert them for me or me writing them myself.
Or: use Rust.
Ultimately Rust's automatic bounds checks rely on higher level facilities in the language for dealing with slices that C just doesn't have.
(Another interesting use of fat pointers is in Rust's "trait objects", where the first word of a "&Trait" is a pointer to the object, and the second word is a pointer to the vtable. In C++, the pointer to the vtable is part of the object; in Rust, it's part of the reference.)
Fortran added array descriptors in 1990, when it made arrays first class objects. That was a quite visible language change.
So this is a well-studied area with lots of implementations you can look at.
Rust heavily encourages the use of iterators and functional-programming both of which prevent buffer overflows by making code that could overflow less idiomatic.
I’m not trying to be snarky, but the thread was about Rust as an improvement to C and you just threw C++ into the mix... which isn’t as safe.
[1] shameless plug: https://github.com/duneroadrunner/SaferCPlusPlus/blob/master...
[2] https://github.com/duneroadrunner/SaferCPlusPlus-AutoTransla...
One could go to almost safe C++ because C code compiles with a minimum amount of changes and turning on compiler warnings, using vector, string and smart pointers already gives a massive saftety improvement, without a complete rewrite in a language that's quite different from C, like Rust is.
Smaller costs and still significant benefits in other words.
I was referring to the thread that started a conversation about pointers and safety in Rust, not to the general post.
You are correct that C and Rust require some more bridging between the languages, but C++ also requires this when going from objects to C datatypes and vice versa. Mostly you save a little bit on the header definitions, but even there, the headers are generally #ifdef'ed to almost illegibility. With Rust's bindgen tool, getting C headers into Rust is actually quite easy: https://rust-lang-nursery.github.io/rust-bindgen/
The bindgen tool allows integrating C code with Rust code, which means than an existing C code base can be replaced with Rust step by step.
A C code base can be compiled with some modifications as C++ and then it can be slowly refactored to use safer C++ idioms. In this case the safety improvements will be available much faster, even if the resulting code base won't be as safe as a Rust code base. The culture shock will also not be as large for the C programmers.
These are non-negligible benefits, and a big reason why C code bases are moving to C++.
Which recently passed a milestone of having less C than Rust: https://www.reddit.com/r/rust/comments/8ofa0w/librsvg_has_no...
At this point I personally find it much easier to move C to Rust than C++, and end up with a much safer codebase.
However that doesn't prevent copy-paste C code from team mates, only languages that aren't C compatible do provide such additional safety.
Not claiming to be an expert in C, but I paid my share of blood and tears with it, and all I can say is that it's worth the effort to learn Rust for the huge guarantees the language gives you in the same contexts as C.
If you're interested in baremetal, there are a number of tutorials out there, all focused on different things. I really love the TockOS tutorials, they are really awesome for getting across some of the different benefits at different levels: https://github.com/tock/tock/tree/master/doc/courses/rustcon...
And then more raw OS stuff: https://os.phil-opp.com/
For GUI software or GPGPU development, it still needs to mature a bit more.
I mostly think this is a gap in common libraries at the moment and decent coding practices. We’ll see what happens with all of that.
Personally I’m more focused on web-backends, micro-services, and DNS, so GUIs don’t generally present problems for me.
I do agree though that it might need still some ergonomic improvements to earn the hearts of other developers that don't share this opinion.
Many of the Rust users have all gone through similar learning hurdles, and are very helpful when asked. Often there are tricks that newcomers are unaware of for working around some of the Rust idiosyncrasies. I encourage you to reach out.
The official community page has a lot of resources: https://www.rust-lang.org/en-US/community.html
Rust's lifetimes record when values are valid and when they're not that exist in the code statically (essentially the same ones that exist in C++ code), in a way that allows the compiler to check that they're being used correctly, that a valid (i.e. "can be used") value never points to or uses an invalid one.
> ...but is indicative of untested code
That's not encouraging.
Kind of surprising that other kinds of testing didn't reveal that bad log line, since it will always KP...
Only proof-based methods can prove code works under every case.
If you're interested, see Coq [1] (including the first guaranteed-correct C compiler) or Isabelle.
Ada, for example, has discriminants, which allows binding of a specific view of a byte buffer to a specific constant.
There are also other solutions, for example making values to be types.
""" The trigger is here: https://bugs.chromium.org/p/project-zero/issues/detail?id=15... … If you're in to iOS exploit dev take a go at it and blog about it! I'll publish what I have soon, hopefully this week. """
Kind of depressing :(