Catching integer overflows with template metaprogramming (2015)
capnproto.org
capnproto.org
Some language already did beat him to it: Ada
http://www.ada-auth.org/standards/12rat/html/Rat12-2-4.html
Ada's rules for type invariants check them in all the places you would expect, and are straightforward to use.
It's about as correct as you can get without it being impossible to compute :)
You should be able to do, at the very least, what you are doing, in a much simpler way.
Plus, it has procedure level pre and post conditions that are checked
In any case, the tools ADA has allows it to go further than what you want, actually. https://en.wikipedia.org/wiki/SPARK_(programming_language)
There is a formally verifiable language that is just a subset of ada (IE compilable as normal ada) but can formally verify what you want here even if static predicates can't.
(Now all of the above said, i'm not sure i'd go this route, but yeah)
http://www.adacore.com/uploads_gems/Ada_Safe_and_Secure_Book...
Note: See table of contents especially.
http://cowlark.com/2014-04-27-ada/index.html
Note: This "random walk through Ada" has a nice summary of all kinds of features it has from perspective of an outsider instead of an Ada developer.
https://en.wikipedia.org/wiki/SPARK_(programming_language)
http://www.adacore.com/uploads/technical-papers/SPARKSkein_S...
Note: Link to SPARK and an example by Rod Chapman on doing Skein in it. Just converting it from C to SPARK found an error in original code. Because of course it did haha...
Why don’t programming languages do this?
I worked on a contribution to clang-analyser a few months ago - one of the things I learned was (1) No-one likes code analysers that raise false alarms and (2) it's really difficult not to raise false alarms. For example, consider checking this program for divide-by-zero bugs: for (int i=-99999 ; i<99999 ; i+=2) {
printf("%d\n", 100/i);
}
for (int i=-100000 ; i<100000 ; i+=2) {
printf("%d\n", 100/i);
}
A programmer can clearly see that in the first loop there is no divide-by-zero as i will jump directly from -1 to +1 - whereas in the second loop the counter will hit 0 and trigger a divide-by-zero.But for a static analyser, your options are:
1. Model i as a single value, and simulate all ~400,000 passes through the loops. Slow - you basically have to run the program at compile time.
2. Same but truncate the simulation after, say, 20 loops on the assumption that most bugs would have been found on the first few passes. This misses the bug in the second loop.
3. Model i as "Integer in the range -99,999 to +99,999" and generate a false alarm on the first loop.
4. Support arbitrarily complex symbolic values. Difficult - as the value of a variable might depend on complicated business logic.
I guess the benefit of starting a new programming language is you can choose option 3 and say "Working code is illegal, deal with it" from the start.
In regards to 4 (valgrind) perhaps utilizing other sanitizers would be a good idea.
Awesome that they tested whether those methods would actually have caught this bug - and multiple did. That's a smart metric.
And a writeup about TMP and compile time safety to boot.
I'm struggling to come up with anything more that I'd want in a disclosure.
The sad part of the story is that Cap'n Proto actually hasn't had a release since then, so (so far) I've effectively gotten out of doing any of those release-oriented things I promised. Many changes have gone into git master, but Sandstorm used git master directly, meaning there wasn't sufficient incentive to do the work of a release. (And as an understaffed startup we could never quite justify spending the time.)
The good news is that I joined Cloudflare two weeks ago, who is a big Cap'n Proto user, and it looks like I'll have more time and justification to do releases there. (And yes, I do intend to fulfill all the promises in doing so.)
That's great news, thanks for the disclaimer/ info.
1. Undefined behavior sanitizer, which checks integer overflows at run-time, like INT_MAX + 1. Can optionally check other invariants; see https://gcc.gnu.org/onlinedocs/gcc/Instrumentation-Options.h... and https://clang.llvm.org/docs/UndefinedBehaviorSanitizer.html.
2. Abstract interpretation - using a lot of constraint solver cycles and enough math to choke a dinosaur, you can check some integer bounds at compile time "relationally", like knowing that x < y. See, for example, "Pentagons: A Weakly Relational Abstract Domain for the Efficient Validation of Array Accesses": https://www.microsoft.com/en-us/research/publication/pentago.... I'm not sure, but that code might be available in https://github.com/Microsoft/CodeContracts.
Consider the code:
int f(int x) {
return x + 1;
}
This code could overflow, but surely the compiler won't complain about it -- it would instead assume that you don't intend to pass a value for x that is too large. In order for the compiler to have enough information to complain, it would need you to specify explicitly what the range of x is -- so that it can detect violations both within the function definition and at call sites. There is no built-in way to specify this.But, this is exactly what my template hacking adds -- a way to specify the actual range of each variable.
I haven't looked closely at your #2. Presumably it also requires some constraint annotations to be added to your code. But if they've provided tools that can process those annotations and detect violations, that's pretty cool.
https://www.di.ens.fr/~cousot/publications.www/CousotEtAl-ES...
My idea was to subset as many components as possible to static-ish components it could handle then focus more on integration testing. Then one could reap the benefits in a larger project. Similar idea for SPARK, which is probably cheaper.
The traditional problem, though, was that some tools were good for specifying full correctness of high-level models while others were good for verifying properties of code itself. They rarely did both well. Still true with SPARK. Before DeepSpec, I was already pushing for mixing them up a lot with different tools on different components with some sort of unified logic. You might find it interesting that one of those was with SPARK and the successful Event-B method. I'll give you a paper show how hard it is to use Event-B by itself down to low-level code followed by how a combo improves things.
http://eb2all.loria.fr/html_files/files/landingsystem.pdf
http://journal.ub.tu-berlin.de/eceasst/article/view/785/782
One last thing before I head out is that the landing system paper is great for illustrating difficulty of correctness. It shows a small number of straight-forward requirements and design specs. Then, verification conditions are derived down to the low level code that must all be maintained true for total correctness. As in, it makes explicit all the stuff a developer must get right in their head to correctly solve this simple problem. I find it's a nice reality check for people even if they don't know the notation on intrinsic complexity of software & how that affects verification. Especially why you want to use simpler, automated methods than hope to test your way out of that level of complexity w/ corner cases. ;)
It's indeed a game-changer to be able to mix Ada2012, Spark, and C in an industry-grade programming environment. You can gradually convert a legacy codebase. I've been replacing some crufty C parts with Ada-with-Spark-restrictions then full-Spark-with-AoRTE-proof and even some functional proof with great success and can now stop worrying about crashes in security-sensitive attack-surface parts. Network/file interfaces, etc. Most of my experiments are very encouraging ! Coupled with CodePeer and/or afl-fuzz for Ada code, almost-bug-free code becomes somewhat more accessible.
Rod Chapman is also a great showman with great success stories, also very funny... and Yannick Moy at AdaCore is an amazing teacher and doing great work leading Spark further, including on small embedded targets.
I think you're doing great work around here reminding everyone once in a while about Ada & Spark, especially in articles that devolve into 'Rust-would-have-caught-it' :-).
Note: There's also work right now on a verified front-end for SPARK to integrate it into CompCert. Then, the ASM is verified as well. Exciting times. :)
EDIT to add: Yeah, that Rust would've caught it was a bit annoying. Worst was them saying it was first, safe, systems language. pjmpl and I demolished that one, though. Now we just get the other one which is at least accurate if it's memory bugs. Progress. (Shrugs)
Also, I heard Rod Chapman (at the last AdaCore tech day) say they had great success using 'the' Ada-to-C compiler some years ago. Looks like AdaCore still have the code for it, not doing much with it. You might get the code if you ask nicely (maybe T.Taft directly now that he works for AdaCore) :-D.
If you get any new idea on some combination of Spark, Ada and other tech, I'd send yannick moy an email :-D. Always helpful and insightful.
If you plan on doing your borrow checker as some kind of new static analysis pass for Ada, I'd also look into libadalang, there's some great (open source !) progress there. I also played a lot with gnat2xml but I don't recommend : it works but it was painful...
I also wondered some time ago about a Spark-to-rust compiler. But I'm waiting for my rust skills to improve first.
Anyway, it's very strange to see rust every so often mentionned for embedded software. I get the interest for 'normal' unconstrained software. But after 10 years of large-scale real-time embedded-systems programming in Ada and I maybe faced once a memory safety bug that the borrow checker would have prevented. Mostly the bugs you get are constraint_errors (overflow, underflow, array bounds,...) most already caught by the type system at compile or static-analysis time. The rest are mostly 'real' bugs : logic or temporal. You never doubt of the state of the program when reading the code, such a lower cognitive load... Going back to C from time to time is so painful, especially, when you think of all the help you're missing.
When in need of pointer go for not null access types, when in need for dynamic allocation go for controlled types (scoped deallocation) and storage pools... Everyone (except haskellers...) is missing on so much, it feels like living in the future where the compiler works (a lot !) for you. Sorry for the rant...
By the way I found one of the ways to catch temporal errors (other than uninitialized variables that are caught) in Spark is to use an enumerated state variable and check it all the time (post/pre or in invariants). It's almost always a good idea anyway to have an explicit state instead of some 'nah none will call this code in such sequence...'.
Also interesting : like functional programming or the borrow checker, or strong typing, Spark changes the way you think about your code. You think about how 'provable' your design and code is and it makes code more readable, simple. You can also just start with data flow annotations (also great for safety and security-critical code), makes reading code easier.
I'll be looking out for your next dive back into Ada then !
UBSan is a set of run-time checks and has low compile-time cost. The example function you give, f, would be complained about if you passed it INT_MAX, but that complaint would come at run-time and it would never be triggered if you never passed it INT_MAX.
I also agree that it is not possible to enforce non-INT_MAX limits, like x < 10.
Two language that do it: Whiley (http://whiley.org/) and Dafny (http://rise4fun.com/dafny).
I expect (but not sure) that the proving power is about the same as what can be achieved by template meta-programming.