13 ms·
C and C++ code CAN be secure, but most of it is not. It is too easy to write or update C / C++ code so that it is no longer secure or has unexpected and unsafe
by thesuperbigfrog 6y ago
C and C++ code CAN be secure, but most of it is not. It is too easy to write or update C / C++ code so that it is no longer secure or has unexpected and unsafe results.
https://blog.regehr.org/archives/213 https://blog.regehr.org/archives/213 gives great insights into how undefined behavior in C and C++ can be difficult to reason about and cause problems.
The lack of bulletproof memory safety and easy-to-stray-into undefined behavior of C and C++ make it easy to create code that is difficult to fully grasp how it will behave, especially when optimizing compilers are used. The C / C++ code runs really fast, but there are hidden dangers lurking.
I don't doubt that C and C++ will be with us for a long time to come, but the growing use of Rust, Zig, Ada, and others show that better alternatives exist and that they will replace the use of C and C++ for many domains and use cases.
Edit: Downvotes? Did you read my whole comment? I am saying that C / C++ are not secure for real-world use cases.
- coliveira 6y agoUndefined behavior is not the problem that people make it to be. First of all, undefined behavior is well understood by compilers, in fact it is exactly exploited by compilers to make code run faster. The only thing you need to solve UB is to ask compilers to stop exploiting it (which normally can be done by reducing optimization). And of course you can rewrite your code to stop relying on UB. Despite all the complains, I have never seen code suffering from UB that couldn't be fixed.
- thesuperbigfrog 6y ago>> undefined behavior is well understood by compilers The security implications of undefined behavior are poorly understood by software developers: https://arc.aiaa.org/doi/pdf/10.2514/1.I010699 https://arc.aiaa.org/doi/pdf/10.2514/1.I010699
- MaxBarraclough 6y ago> Undefined behavior is not the problem that people make it to be No, undefined behaviour is a serious problem, especially regarding security. There's a long history of serious security problems in C and C++ codebases due to unintended invocation of undefined behaviour. These issues continue to arise even in well-resourced C/C++ projects with highly skilled developers, such as the Linux kernel and Chromium.
- jfoutz 6y agoIt’s pretty sweet for optimization though. Doing Nothing is almost always faster than the alternatives.
- MaxBarraclough 6y agoThe reasons for undefined behaviour being in the C/C++ language standards are performance and the ability to naturally support all sorts of different platforms, but I'm not sure the performance argument really holds up with modern optimising compilers. I admit I don't have hard numbers to hand, and sadly it's rather rare to see decent rigorous comparisons, but I don't think C and C++ have that much of a performance advantage over, say, Ada or Safe Rust. My inner pedant feels it's necessary to note that Ada is still an unsafe language, but still, it's much less unsafe than C. It also has excellent support for optionally disabling all sorts of runtime safety checks, whereas the design of C and C++ make it extremely difficult to implement such checks as 'opt-in' features in a compiler.
- astrange 6y agoIt actually is important, but it's mostly important when your program gets deployed across architectures. If you're only writing for one architecture (say x86) then you can write the C to just work well there. One example is that undefined signed overflow is important when the compiler might need to rearrange a loop index - PPC prefers to count down, x86 doesn't care. Anyway, I think undefined behavior + ubsan is better than defining all behavior. If something's undefined you know it's a bug every time you see it. If it's defined, how do you know it's wrong?
- MaxBarraclough 6y ago> If you're only writing for one architecture (say x86) then you can write the C to just work well there. > One example is that undefined signed overflow is important when the compiler might need to rearrange a loop index - PPC prefers to count down, x86 doesn't care. Signed overflow is undefined behaviour regardless of the target hardware architecture. The compiler is permitted to assume the absence of signed overflow and to optimise accordingly. Unless the compiler documentation specifically says its ok, you still have an undefined behaviour problem. It might happen to work fine, sure, but if you're serious about writing correct programs you should be aiming to deliver a program which is correct-by-definition rather than correct-by-coincidence. > I think undefined behavior + ubsan is better than defining all behavior It isn't. That's why Rust makes such a big deal of its Safe Rust subset. It's also a major selling point of SPARK Ada for safety-critical software. It's tremendously valuable to be able to categorically close the door on a whole family of potentially serious and difficult to detect bugs. > If something's undefined you know it's a bug every time you see it. No, you absolutely don't. I already mentioned that high-profile projects like Chromium and the Linux kernel continue to face security vulnerabilities arising from unintended invocation of undefined behaviour. Section 7 of the paper discusses undefined behaviour but doesn't really explore its full consequences, so instead I suggest reading [0] and [1]. Undefined behaviour means exactly that: if undefined behaviour has been invoked at runtime, the behaviour of the program is not constrained by the C/C++ standard. The program is not required to explode loudly, it can do anything. It isn't required to behave the same way each time. Hopefully it will explode loudly, but it's possible everything will seem to be fine. In the worst case the undefined behaviour leads to a serious safety issue or security vulnerability. Undefined behaviour is even permitted to 'time travel'. [0][2] > If it's defined, how do you know it's wrong? You use exceptions or some other well-defined means of detecting and handling runtime errors. For example, Java's NullPointerException and Ada's Constraint_Error. [0] https://blog.regehr.org/archives/213 https://blog.regehr.org/archives/213 [1] https://blog.llvm.org/2011/05/what-every-c-programmer-should-know.html https://blog.llvm.org/2011/05/what-every-c-programmer-should... [2] https://devblogs.microsoft.com/oldnewthing/20140627-00/?p=633 https://devblogs.microsoft.com/oldnewthing/20140627-00/?p=63...
- citrin_ru 6y ago1. Fixing/avoiding UBs requires discipline (and time) not all programmers have 2. Many programmers pointed to UB in their code would argue that it is not a problem at all - especially if code works for them and users don't complain about bugs caused by UB. It would be interesting to make a following experiment: * find and patch UBs in multiple opensource projects which are relatively popular (at least used not only by an author) * send pull requests wich UB fixes and see which fraction will be closed as "won't fix". It requires a lot of time, but may show that many developers don't care about UB.
- MaxBarraclough 6y ago> 1. Fixing/avoiding UBs requires discipline (and time) not all programmers have 2. Many programmers pointed to UB in their code would argue that it is not a problem at all These points aren't wrong, but it understates how pernicious undefined behaviour can be. As I stated earlier, even well-resourced high-profile codebases like Chromium and the Linux kernel play cat-and-mouse with security issues originating in undefined behaviour. It's not just a matter of hiring diligent coders and using the usual quality-control processes (testing, code reviews, static analysis). It's possible for C/C++ code to be entirely free of undefined behaviour, but in practice this only seems to happen when formal methods are used, or when people use code-generators/transpilers to avoid writing C/C++ by hand.
- bawolff 6y ago> C and C++ code CAN be secure Anything can be secure (and conversely anything can be insecure). The theoretical potential doesn't matter because real life is never the theoretical best case. What matters is the overall risk (is liklihood * how bad < benefit?)
- thesuperbigfrog 6y ago>> real life is never the theoretical best case Exactly; real life C and C++ code that does real work tends to be insecure.
- jerf 6y agoThis is one of those rare cases where I can say "It's 2021, and we know that's not true now." C and C++ can not be secure at scale without unreasonable amounts of effort. It can't be secure at any non-trivial scale through sheer discipline alone. 40 years ago, the case could be made. But C and C++ are not new languages, and the fact that just barely shy of no one can demonstrate the existence of secure C or C++ code bases without staggering levels of effort put into that process is data now, not just anecdote. (And let me emphasize the effort as my yardstick. Writing truly secure code is arguably something nobody has ever done at scale in any language... but C and C++ are certainly unique in the sheer level of effort it takes poured in to them to even match what a number of other languages come with out of the box, let alone exceed them. If you aren't using some very high quality and fairly expensive tools like Coverity on a routine basis, you aren't even close.)
- thesuperbigfrog 6y ago>> C and C++ can not be secure at scale without unreasonable amounts of effort. It can't be secure at any non-trivial scale through sheer discipline alone. Agreed. That is why C and C++ should be replaced with more reliable alternatives.
- pjmlp 6y agoNot even 40 years ago, because exactly in 1981, C.A. Hoare stated on his Turing award speech, "Many years later we asked our customers whether they wished us to provide an option to switch off these checks in the interests of efficiency on production runs. Unanimously, they urged us not to--they already knew how frequently subscript errors occur on production runs where failure to detect them could be disastrous. I note with fear and horror that even in 1980, language designers and users have not learned this lesson. In any respectable branch of engineering, failure to observe such elementary precautions would have long been against the law."
- GeorgeTirebiter 6y agoI am astonished that HARDWARE does not help us much. When 'everybody' decided going like a bat-out-of-hell was more important that security and safety and easy-to-not-screw-up languages w/garbage collection and capabilities and hw-enforced typing and and ... we end up where we are: a mess that runs fast. And, specifically, I think that RISC-V rather missed the boat here, by not taking the security/safety banner and leading to a new way of building computer systems. Instead, we got a different (open) ARMish thing. Progress...
- icandoit 6y agoIf you want to do something about it look into UBSan. Turn vague concerns into bugs, and then into commits :). https://clang.llvm.org/docs/UndefinedBehaviorSanitizer.html https://clang.llvm.org/docs/UndefinedBehaviorSanitizer.html UndefinedBehaviorSanitizer (UBSan) is a fast undefined behavior detector. UBSan modifies the program at compile-time to catch various kinds of undefined behavior during program execution, for example: - Using misaligned or null pointer - Signed integer overflow - Conversion to, from, or between floating-point types which would overflow the destination GCC has similar features.
- cozzyd 6y agoUnfortunately, ubsan is not implemented on all targets, as I just found out a few seconds ago trying to enable it on my arm-none-eabi project. (I suppose technically I just need to implement all of the stubs to hard fault or something... why doesn't gcc do that for me?).
- adrianN 6y agoUBSan is awesome, but you need an awesome test suite to really unlock its power.
- rurban 6y agoIf you are too lazy to write tests, either formally verify it (cbmc or satabs) or fuzz it. Libfuzzer works great finding ubsan and asan bugs fast.
- pjmlp 6y ago> UBSan modifies the program at compile-time to catch various kinds of undefined behavior during program execution Which makes it unusable with binary libraries.
- saagarjha 6y agoIt’s not intended to be something you ship.
- kzhukov 6y agoSecurity is not a feature of the language or tool. Neither Rust nor C++ are fully secured even though the former could find more memory safety problems at compile time (but not all of them). Security is the process. It contains continuous risk assessment, penetration testing, fuzzing and using various other tools throughout the product development to eliminate attack vectors. Only then you could build a secured product. Just rewriting everything in Rust won't make it.
- thesuperbigfrog 6y ago>> Security is the process. Yes, but do the programming languages / tools we choose make the security process easier? There is no perfect solution, but some programming languages / tools are better than others for preventing unexpected behavior that can lead to insecure programs.
- jjav 6y agoVery concisely and correctly put, agreed. I sometimes work on the infosec side of the house. It's easy to point at vulnerabilities due to endless memory access problems in C code and fixate on that. And it's true, so it feels satisfying. Just rewrite everything in not-C and this is fixed! And that's true as well. But remeber here the word "this" refers to the memory access bugs. It doesn't refer to vulnerabilities in that sentence. Plenty of systems without a single line of C/C++ code and we have no shortage of ways to break in anyway. So in one sense, everything changed. But wearing the black hat, nothing really changed since I compromised the system anyway. A new language without all the engineering process parent post describes, won't magically get there. For an existing system "just rewrite everything" will guarantee way more bugs for years to come simply because the old system has been battle-tested and reinforced for years. In the long haul the rewrite will converge to a better state but that haul is long (and assumes budget will remain in place long enough, which might not happen so you end up with a half-baked rewrite). For new systems starting from scratch that may in any way ever be run in a security relevant context, sure, starting with not-C is a good idea today. Unfortunately Rust seems to be the only alternative for use cases where C was actually needed (if it could be written in Java or Go or Python or ... then it didn't really need to be in C in the first place). And sadly rust is fairly user-hostile language so my guess is plenty of new projects will start with C for a long time due to lack of friendly alternatives. And tooling.
- Koshkin 6y agoI have a suspicion that “safe” and “secure” are two different things.
- liquidify 6y agoI think they can easily be made secure. You just have to be willing to strongly type everything. It doesn't mean it would be conventional or easy, but it would eliminate most type safety issues that crop up during refactoring.
- Voloskaya 6y ago> I think they can easily be made secure [...] It doesn't mean it would be conventional or easy So you can easily make it secure by doing something not easy? Something that requires discipline, willingness and unconventional practice to be secure is by definition not secure. A secure language is the opposite: The default behavior and conventions are mostly secure and you need to go out of your way to make it unsecure. Otherwise, with enough time and enough people you are guaranteed to be doomed.
- jandrewrogers 6y agoC and C++ are not the same case. Modern C++ supports fairly strict and exhaustive type safety that can robustly evaluate many types of safety at compile-time, if you choose to use it to its full capability. C doesn't even have these facilities. Anecdotally, I haven't seen an open source C++ code base that is uses type safety to that extent but it is certainly possible.
- SAI_Peregrinus 6y agoC++'s problem is that it still silently supports non-Modern style code, so the safety is opt-in. And you have to check everywhere for it. It's not as bad as C where the safety just doesn't exist, but it's not much better. As you noticed yourself, it's not actually used safely!
- cogburnd02 6y ago> C / C++ are not secure for real-world use cases. Is the kernel Linux not a real-world use case?
- rmdashrfstar 6y agohttps://www.cvedetails.com/vulnerability-list/vendor_id-33/product_id-47/opmemc-1/Linux-Linux-Kernel.html https://www.cvedetails.com/vulnerability-list/vendor_id-33/p...
- nineteen999 6y agoSure, there have been plenty of CVE's for the Linux kernel, and will continue to be. Now show us the list of replacement kernels written in Rust, Ada, Lisp, or whatever that are: 1) free software (or open source as a second best) 2) as widely distributed and as portable as Linux 3) supports as much hardware as Linux 4) supports the wide range of applications that Linux does 5) free of memory safety related CVE's as well There is always a series of tradeoffs - Linux has adopted a classic but unsafe programming language as its basis which has in turn allowed it to cover an enormous amount of ground in a (relatively) short time frame, support massive amounts of new employment and accelerate the development of the web. It is not clear that an alternative design or implementation in another (safer) language would have moved the world as far forward as Linux has in the same time frame, or whether its even possible, as nobody has demonstrated this.
- adrianN 6y agoThat Linux has won is probably more due to lucky historical circumstances than to technical superiority.
- nineteen999 6y agoIt's not apparent that Linux has won anything, the race is still open. Where are the real contenders is my question? Apparently they are still stuck at the starting line, discussing endlessly how perfect their system will be when they finally choose the right language to implement it in.
- Arch-TK 6y agoRust, Zig and Ada code CAN be secure, but most of it is not, it is too easy to write or update Rust, Zig or Ada code so that it is no longer secure or has unexpected and unsafe results. The lack of formal verification features and easy-to-stray-into logic errors in Rust, Zig and Ada make it easy to create code that is difficult to fully grasp how it will behave, especially when large projects are concerned. The Rust, Zig and Ada code runs really fast and is usually memory safe but there are hidden dangers lurking. I don't doubt that Rust, Zig and Ada will become more popular in time to come, but formally verifiable languages such as Verifiable C or Spark are actually "safe" in some meaningful sense of the term instead of giving everyone a false sense of "safety" in the form of memory safety features as if memory safety errors were somehow the only class of security critical error.
- nec4b 6y agoOf course they are secure. What language do you think was used for developing software for airplanes, cars, medical equipment and so on. C can be formally verified while rust currently can't be.