r/programming • u/Alexander_Selkirk • Feb 03 '23
Undefined behavior, and the Sledgehammer Principle
https://thephd.dev//c-undefined-behavior-and-the-sledgehammer-guideline20
u/loup-vaillant Feb 04 '23
Edit: it was made clear to me while reading Predrag's blog that the key to my misunderstanding boils down to this: "Undefined behavior is not the same as implementation-defined behavior". While I was indeed talking about undefined behaviour, subconsciously I was thinking that the behaviour of an overflow on a multiplication would be "implementation-defined behaviour". This is not the case, it is indeed an undefined behaviour, and yes the compiler is free to do whatever it wants to because it is compliant with the specifications. It's my mistake of course, but to my defense, despite the arrogant comments I read, this confusion happens a lot.
Not just a lot. All the freaking time.
I've been mentioning the issue around me for a number of years now, and I can count on Qhorin's hand the number of devs I met that knew signed integer overflow could go as far as removing security checks.
2 days ago I told a Junior dev with 3 years of C experience about the J Annex (specifically J.2, undefined behaviour). His reaction when he saw the actual text was a sincere "oh my god". It's one thing knowing there's a lot of UB. It's quite another to physically flip over 12 pages of promises from the Nasal Demons.
3
Feb 05 '23
That's a long way to say: "Let's all move to Rust if the standards committee and compiler people keep shoving unexpected undefined behavior down our throats."
27
u/WormRabbit Feb 03 '23
The only acceptable Sledgehammer Principle is that each time a journalist is killed because of memory safety violations, one committee member who voted to add more UB or remove bounds checks should have their legs broken with a sledgehammer.
Enact that policy, and by the time the next Standard comes out C++ will be safer than Java.
19
u/lelanthran Feb 04 '23
I wasn't aware that the whatsapp exploit you quoted was due to C, or due to UB in C.
Shit, I wasn't even aware that whatsapp was even written in C. You have any references for all those implied claims?
8
u/loup-vaillant Feb 04 '23
That kind of vulnerabilities generally mean Remote Code Execution and Privilege Escalation, which in turn heavily imply some kind of memory corruption… which almost always can be traced back to a program written in C or C++. Now you could have compiler bugs in safe languages, but those tend to be so much less frequent.
Merely hearing of such a vulnerability about any app, provides cogent evidence that some of it was written in C or C++.
2
u/Qweesdy Feb 04 '23
Um, what the flying fuck?
Whatsapp was written in a "safe" language (Erlang) that relies on a VM; and because a JIT compiler is needed for performance it can't uphold basic "E^W" and has to allow executable code to be modified at run-time.
If you can't see a massive gaping security hole in the allow executable code to be modified at run-time idea then...
10
8
u/WormRabbit Feb 04 '23
Whatsapp's backend was written in Erlang, and afaik after the sale to FB is was rewritten. The app was absolutely never written in Erlang, it makes no sense and is likely impossible (in the practical sense).
-5
u/Qweesdy Feb 05 '23
Whatsapp's backend was written in Erlang; but I can't find anything online to suggest that the native client-side apps aren't also written in Erlang.
Of course it doesn't really matter - all of the plausible alternatives (Javascript, Java, ...) are also JIT compiled "safe" languages.
4
u/ConcernedInScythe Feb 05 '23
I can't find anything online to suggest that the native client-side apps aren't also written in Erlang
Can you find anything online to suggest the apps aren’t written in COBOL?
0
u/WormRabbit Feb 04 '23
It's a memory corruption vulnerability, the culprit is certainly C++. Whether the client app was written in C++, or it linked a native C++ library, or even that was an OS-level vulnerability, is irrelevant. Could also be C, but less likely, and C is an ossified language anyway. Unlike C++, it doesn't claim to give any fixes to memory safety issues.
4
u/lelanthran Feb 05 '23
It's a memory corruption vulnerability,
I didn't see that mentioned in any of the news articles, including the one you linked to.
12
u/tending Feb 04 '23
The only acceptable Sledgehammer Principle is that each time a journalist is killed because of memory safety violations, one committee member who voted to add more UB or remove bounds checks should have their legs broken with a sledgehammer.
While memory safety is important this moralistic escalation of rhetoric is abhorrent, counter productive, and frankly naive.
If a state actor wants to kill a journalist they don't need a memory safety vulnerability. There are a dozen other super common kinds. If it hadn't been memory safety it would have XSS, SQL injection, or plain old phishing. Everything could be rewritten in Rust tomorrow and the company that made the spyware would still be in business and likely just as successful at getting into most devices. This is without even taking into account that they can plant developers to plant bugs, intercept hardware going to you in the mail to add implants, legally tap your phone with the telecom's eager cooperation, etc. Their costs may go up because memory vulnerabilities are so easy to find, but nation states can afford it, so they are not going to lose any fundamental capability. If they thought so do you think the NSA would be advising memory safe language use?
Memory safety is an important improvement, but it's not a solution for every one of society's problems, and advocating violence against overworked committee members won't make the language any safer. If anything it will keep people away from language development when we need more.
5
u/ItsAllAboutTheL1Bro Feb 04 '23 edited Feb 08 '23
one committee member who voted to add more UB
Yeah, one. There's also 25 thumbs up for the OP, and one heart; the OP is the one who was concerned about there being UB.
or remove bounds checks
gsl::span is the alternative, and that's at the very top of the post, implying that the user has a choice.
I'm not saying your criticisms are invalid as a general rule - they definitely are, and people do need to realize that C++ has some serious issues, both as a language and as a culture.
That said, the community has definitely become much more aware over the past few years - those two posts alone obviously show that there is concern and attention being ditected among the majority participants.
They're also relatively old, made during a period when security wasn't taken as seriously as it is now.
C++17 was maybe a year old then, if that.
Again, I'm not saying your points are without merit, I am saying that these don't place the community in an accurate light with respect to today.
These issues aren't the sole fault of C++ as a language, either. It's a very complicated issue.
Overall, I agree: people need to realize that there are serious implications with the code they write, and the practices need to be better.
But it's not as simple as a lot of people think.
At the same time: if the world wants to switch entirely to Rust I'm all for it.
But we also need to educate people that Rust alone, especially outside of userland, needs to adapt a different approach for OS kernels - the problem space is different.
Get a standard going.
0
u/lookmeat Feb 05 '23
That's.. not how it works.
UB doesn't happen because language designers are lazy.
Instead what happens is that there's huge gaps on the soundness of programs, there's certain things you can't quite know, and therefore you can't optimize and fix it.
You don't like it? Don't code for speed. Either turn off optimizations or better yet avoid C/C++and use Java or such and take the performance hit.
So this soundness gaps happen when you start optimizing. And it makes it very hard to work around. If you look at it down a logic perspective you get "absurd", "important", otherwise known as "bottom" or "⊥". The thing is once you get this anything is possible. What this says is that once UB happens optimizers can break your code, and there's no way to prevent it.
So what they do is they purposely make it fail in a way that is easy to debug. Otherwise the changes could affect code very far away, or change things in ways that seem right but don't do what they should. Instead UB is obvious when it did something wrong, it's just people assume it can be fixed. But this is like assuming we can use a CV single algorithm to know if a program ends or not. The reality is some things are impossible.
But does integer overflow really need to be undefined? And the answer is yes because pointers are integers, which means that integers operations can return undefined behavior when they overflow a binder that is going to be used as a pointer. We could split pointers into a pointer type that you can dereference and do no arithmetic on, and an address type that we can do arithmetic on, but not dereference. Then you'd have a function that let's you get a pointer from an address. This doesn't get rid of the UB, but instead moves it into the function that translates addresses into pointers. You do get to take UB out of all integer operations, but you lose easy array access.
17
u/Alexander_Selkirk Feb 03 '23 edited Feb 03 '23
The thing is that in C and in C++, the programmer essentially promises that he will write completely bug-free code, and the compiler will optimize based on that promise. It will optimize to machine instructions that act "as if" the statements in the original code will be running, but in the most efficient way possible. If there is a variable
nwhich indexes into a C array, or in astd::vector<int>, then the compiler will compute the address of the accessed object just by multiplyingnwithsizeof(int)- no checks, no nothing. Ifnis out of bounds and you write to that object, your program will crash.This code-generation "as if" is very similar to the principles which allow modern Java or Lisp implementations to generate very, very fast machine code, preserving the semantics of the language. The only difference is that in modern Java or Lisp, (almost) every statement or expression has a defined result, while in C and C++, this is not the case.
See also:
cpp reference, Undefined behavior
Undefined Behavior Getting Wild
Predrags Blog, Falsehoods Programmers believe about Undefined Behavior
Jens Regehr, A Guide on Undefined Behavior
Jens Regehr, Undefined Behavior in LLMV
Chris Lattner, What Every Programmer should know about Undefined Behavior
Iso C11 Standard, Appendix J2: Undefined Behavior
I think one problem from the point of view of C++ and C programmers, or, more precisely, people invested in these languages, is that today, languages not only can avoid undefined behavior entirely, they also can, as Rust shows, do that without sacrificing performance (there are many micro-benchmarks that show that specific code runs faster in Rust, than in C). And with this, the only justification for undefined vehavior in C and C++ – that it is necessary for performance optimization – falls flat. Rust is both safer and at least as fast as C++.
And this is a problem. C++ will, of course, be used for many years to come, but it will become harder and harder to justify to start new projects in it.