The Case for Memory Safe Roadmaps
nsa.gov
nsa.gov
- continuing accumulation of documentation and utility libraries from Khronos
- excellent learning materials from the community
- tons of legacy code
all using C++.
https://github.com/KhronosGroup/Vulkan-Utility-Libraries
https://github.com/cg-tuwien/VulkanLaunchpad
https://cescg.org/our-services/an-introduction-to-vulkan/
Until I see professionals get funding to build, maintain, document, and create training materials for Rust bindings, I'm going to continue to assume there will not be movement in that sector. I don't think industry is going to throw away the C++ ecosystem and build greenfield projects on a foundation of hobby projects.
After using Rust for a while, I actually decided to stay with to c/c++ for the rest of my career.
Care to expand?
Right now Rust is technically safer language but it doesn’t have the same amount of job postings and career opportunities as C++. That’s just an inertia problem.
The conundrum is if you jump in early and risk the language becoming yet another language or do you wait and see.
I haven’t coded C++ in 4 years, but my understanding is that the language is evolving to answer the threat Rust poses to it.
There are still COBOL and Fortran developers, C and C++ won't go away tomorrow, even if the goverment says so, but it will be harder to use them in security clearance contexts, similar to hazardous goods.
The residue of holes in C++ code is about the same as JS, Python, etc. So just eliminating C and C-like constructs gets you to the same level of security as the other languages.
> Importantly, if you look at lists of "C/C++" security holes, you find it is almost all C security holes. So when they write "C/C++" they are hoping you won't notice they are lying to you.
As these "C-isms" are part of the base language, and you cant 'opt out', this means the base language is still included in the lists. Most C++ applications still linked (and used symbols from) the system libc runtime.
I do not write C++ with any regularity, because I like my C footguns and dont want additional layers of footguns in my code. last time I tried writing C++, it allowed you to use after free, access raw memory pointers with no validation, iterate off the end of arrays and integer overflow and underflow, all without using "C" style constructs.
Maybe it has changed, maybe C++ is now not as insane as it used to be and smart auto magic pointers solve everything. I'd love to read about how modern C++ solves these issues, do you have any resources ?
It is no accident that Khronos has security critical certified subsets of OpenGL and Vulkan.
Right now the only C++ wannabe replacement that is as far ahead as Rust is Circle, and it is a big political issue with the community, unfortunately.
Forget about Carbon, Hylo or Cpp2.
My only interactions with compiled languages all have legacy constraints, or involve someone else's project.
ESP32 seems to have lots of Rust support work happening, so I can very easily imagine switching in a few years though.
If I had a reason to write a new performance-critical non-embedded app I'd probably use Rust.
C#, Go, Java, Python, Rust & Swift
My impression of historical Python is that is an old, partially arcane language that due to D.S/AI is now popular; I would initially think it would be no better than those other interpreted. Python does these things well... mainly due to pandas/dataframes/polars.
<btw, Perl? ;) >
They should've instead just said Python and languages alike?
None of the alternatives except Go and Swift is new. Ruby has turned into a robust boring technology, it does not mean it’s unworthy.
The Ruby team is still pushing the languages features and speeding up the runtime. Take a look at Ruby Ractors for example.
I do have to agree on that Ruby is more of a niche language now thanks to Rails but I’d say Ruby works very well for scripting and gluing together different systems.
If it weren't for the behemoth of legacy code we'd really have this problem more-or-less licked. Unfortunately, that behemoth is still rampaging across the landscape.
"Rewrite it in Rust" gets a bit of pushback, perhaps even justified, but at this point in time I'll take anything that just reduces that behemoth in size. The journey of a thousand miles begins with a single step, an elephant is eaten one bite at a time, etc. Rust is just one of the easier and more effective options for a legacy codebase, with the unusual advantage of being able to slip in incrementally. Almost every other language requires a true rewrite.
Ada, Fortran, assembly?
http://www.ada-auth.org/standards/rm12_w_tc1/html/RM-13-9-1....
https://old.reddit.com/r/ada/comments/mme3jk/is_ada_memory_s...
In other words, if you go out of your way to use unsafe features, and don't use the features that compensate, Ada is memory unsafe. This has become the goto dismissal of Ada, apparently more popular than "eww...a BEGIN..END language" and "designed by committee/government tainted".
No pointer direct arithmetic, though. If you allocate the memory via allocate, you can inquire if it's been deallocated.
Hasn't it always been? I'm no expert but always assumed that it was used basically military and avionics, and perhaps other safety critical equipment.
Assembly is in use, yes, but in 2023 I feel there is generally an understanding of the risks and I haven't seen the "write everything in assembly" crew in about 15 years. The problem is that there's still too many programmers blithely using C and C++ without realizing the risks and thinking they can cowboy through the problems. For every line of vulnerable, dangerous assembly I bet there's thousands of lines of C or C++.
There is also the problem that there have been some big bugs that got through even static analysis and fuzz testing, but I'd still be at least reasonably satisfied if all the critical software in C and C++ would be supported by those tools. Interpreters and compilers have had non-zero error rates too.
At the scale I'm talking about, Ada is a non-entity as well. It isn't used. "But it is! I'm a professional Ada programmer!" says someone reading this to themselves. In which case I would say, you darned well know what I mean and don't pretend otherwise just to try to score useless internet points. Ada is not a relevant force on the programming world. That may be sad, but it's true.
Duplicating the DO-178B certification that it has obtained in an endorsed language will be an incredible burden for any who attempt it.
(My reading is that the GP points to the exemplary achievement that SQLite has reached a close to (security) bugfree, at what I consider a nearly superhuman effort)
Plus everything that needs to directly interface with the above languages. So many Python libraries that are one "funny integer" away from a nightmare debugging session.
C++ at least has tools to make life significantly more safe. I can write a buffer overflow in any language, and on the scale of difficulty, ASM-C-C++-Rust-Python covers my experience (from easiest to fuck up to hardest).
Yet nobody is calling for us to rewrite everything in python. Why is the line drawn at Rust? It's perfectly simple to trash memory in Rust.
C++ is unsafe by default.
Of course it’s just as easy to write bugs in unsafe Rust as it is in C++ (actually, it’s probably even easier), but defaults matter.
At that point, if we have to re-wrap everything in rust to hide the unsafety of the interfaces to the system (sockets, shared mem, etc etc), then why not just write safe cpp wrappers?
Yes, people are writing memory overflows in their own code, but I'd argue 99% of the critical security bugs are actually in the unsafe interfaces. And we don't really need a new language to fix that. We just need new interfaces.
I love Rust, but using it for anything nontrivial makes the "safe" patina really fade. You're quickly writing what feels like C, with MaybeUninit<X> all over.
I'd contend that using Rust for anything nontrivial results in MaybeUninit & co being common.
It’s quite rare to have to make syscalls directly in Rust, just like it is in c++. Most code in any large enough system is related to the internal logic of the system, not to its interface with the outside world. And when you _do_ need to interface with the outside world, you can use a wrapper (lots of the standard library is basically wrappers around syscalls; this is true in any language). And no, in Rust unsafety doesn’t typically “leak through” interfaces, unless those interfaces are buggy.
> why not just write safe cpp wrappers?
There’s no such thing. It’s not possible to write a safe interface to c++ code in the sense that that term is used by the Rust community. In Rust, “safe interface” means: assuming there are no bugs in the underlying code, and the client code never invokes `unsafe`, using the interface cannot cause undefined behavior. This is impossible to guarantee in c++.
> I love Rust, but using it for anything nontrivial makes the "safe" patina really fade. You're quickly writing what feels like C, with MaybeUninit<X> all over.
This is not true at all in my experience. I work on Materialize, surely one of the more non-trivial Rust programs that exists. We use very little unsafe/MaybeUninit/C-like code. Do you have an example of a codebase you’re thinking of that does this?
To answer your question, I'm referring to much of the networking code in socket2 / socket, which uses MaybeUninit when doing non-standard stuff like forming your own packets. (RAW)
I'm not too familiar with `socket2` but normally in Rust to construct a buffer with arbitrary bytes in safe code you would first zero it out and then write it. Using `MaybeUninit` there is presumably just a micro-optimization to avoid having to memset things to zero.
Because that's what "safe" in Rust means. No memory safety errors, no undefined behaviour.
Not in safe Rust.
As I said, you won't write a buffer overflow in rust, but unpacking why can be interesting and it doesn't end at "bounds checks".
Rust (and Swift) are viable languages for solving most problems that people usually reach for C or C++ to solve today and both make it considerably more difficult, by default, to introduce the most common class of serious security vulnerabilities in the modern world.
In C++ vec[999] is a buffer overflow and you can index any pointer even if it isn't supposed to be an array. There are so many easy mistakes that can be made and aren't obvious to a reviewer. Maybe with a very strong linter you can consider C++ very distinct from C, but by default I don't think it is that different.
> I can write a buffer overflow in any language
Try doing it in JavaScript? If so the Mozilla security team would appreciate a private disclosure. Of course it is possible in any non-sandboxed Turing complete language, but there is a huge difference between the default accessor of the most used container type allowing it vs needing to use functions in the `sun.misc.Unsafe` package or wrapping your code in an `unsafe` block. Making code that may cause a buffer overflow explicit is a night and day difference. It means that you can't do it via a typo in the vast majority of your code, and it will grab the attention of your reviewer very quickly. Isolating the part of the code that can cause buffer overflows to a small part greatly raises the attention that is given to those areas, and greatly reduces the chance of them occurring.
I don't think that Java or Rust prevent all buffer overflows, but I also don't think that it is possible to write C or C++ without them. Sure, it is possible to be careful and avoid most of the buffer overflows most of the time, but we and our reviewers are just human so we will never prevent all of the buffer overflows all of the time.
I don't think that this recommendation is under the impression that "memory safe languages" will prevent all buffer overflows, but the idea is that they will greatly reduce the number. In many situations, I would guess the majority of them, this is a good tradeoff.
Could you elaborate on this? Rust doesn't have a runtime (beyond what C has), and am having trouble understanding what you meant to say about stdlibs.
I don't think I've ever seen anyone reference "C with bounds checks enabled" as "having a runtime". Does having stack probes also imply having a runtime? I guess I'd be less surprised if it had been worded as "some mitigations/features have a runtime cost".
> If you want features like async (standard in many language runtimes) you're also going to have to pull in some kind of external runtime dependency.
Yes, you can add a runtime to your application (if you need to use async/await). It has an additional cost over not doing that, but the "promise" is that it is "zero (additional) cost (over what you'd end up with if you wrote the functionality by hand)".
The runtime isn't all that large but every OS has one. On UNIX it's spread over libc, libpthread, libgcc, libm and so on.
On Linux stack probes usually have some support code in libgcc and/or glibc, if I recall correctly.
So if it's idiosyncratic really depends on what audience you're talking to.
It may feel like C doesn't have a runtime if you're only familiar with UNIX, because the C runtime is guaranteed to come with the OS there whereas other language runtimes are optional and thus more visible. But every language has a runtime.
I always thought that Rust was the only memory-safe language that doesn't need a runtime (beyond the libc that every language links to when running on Unix-like OSes). Maybe you could define what you mean by runtime.
Will somebody please tell me why everyone seems obsessed with optimizing for programmers going from zero to minimally productive?
I have been using Ruby for twenty years, Rust for eight, golang for nine, and C for twenty-six. Most programmers will use a language for dramatically longer than a year, so why are the first three months such a singular point of focus?
The code I wrote in the first three months of using every one of these languages was bug-ridden, unidiomatic, unnecessarily difficult to maintain, and generally terrible. Ironically, Ruby was probably the least bad in this regard. Go and Rust were probably about the same, but I’d frankly give Rust the slight edge here. C was the inarguably the worst, but it was also my first language.
Subjectively and retroactively comparing things a year in, I’d wager my Rust was of the best quality (readability, ease of maintenance, speed of development, bugs per “unit of functionality”), followed by Go, Ruby, and then C. At five years, the quality of my Rust code blows everything else out of the water. My C was still terrible (partly because it was C, partly because it was still my first language). But I’d say Ruby edged out Go at this point for me.
Obviously this is not only anecdata but wildly guesstimated looking back and comparing learning curves on languages at completely different points in my experience as a programmer. I’ll happily admit that Rust pulling so far ahead so quickly is as likely to do with it building off the knowledge of prior decades of professional software engineering. And that my personal experience with any of these languages is of course unique to me and my circumstances.
But it just seems wild to me that people seem to focus on “getting a new person up to speed as fast as possible” to the exclusion of apparently everything else.
Go being easy to pick up and learn is certainly a virtuous cycle insofar as it helps bootstrap a large community. And that’s absolutely happened!
But that is—in my mind—more of an explanation for why Go has become so popular so quickly more than it is a compelling argument for the language itself. Haskell having conversion friction might explain its lack of adoption, and that’s certainly a great argument in a discussion about why or why not to adopt it for yourself or your team! But it seems like an overvalued axis on which people seem to evaluate languages on their own.
As a counterexample: PHP classically had a reputation as being a language that was very easy for beginners to pick up. And it’s even memory safe! But it also had a reputation for having poor long-term prospects for projects written it as well as being a limiting factor in the growth of engineers using it (note: I make no claims as to the fairness of this reputation, nor to its applicability on “modern” PHP).
PHP is arguably even easier to learn than Go. So why is it that virtually nobody jumps in these discussions trumpeting that?
> What I don't understand is the excitement for using Rust vs. using garbage collected languages like Golang… since they don't need to climb the Rust borrow-checking learning curve.
Because, in my experience, climbing that learning curve has made me a better programmer more than nearly any other change in my long career. And that benefit has extended to code in every language I write.
The borrow checker isn’t just some hurdle to get in your way; it’s trying to tell you (awkwardly at times and perhaps less helpfully than one would wish) something fundamental about the way you think about and design programs. Internalizing that lesson can bring significant benefits on designing systems with clean boundaries that are easier to test, easier to reason about, and easier to compose.
Besides that, Rust greatly assists you (through features other than the borrow checker) in building software that is correct. This means it will tell you in a much wider variety of scenarios when future code invalidates previous assumptions. This is invaluable for projects that we expect to survive for a long time since the time a project is maintained will dwarf the time it’s under active development. And it will almost certainly be maintained by someone without the full context of the original developer(s). This is true even if the maintainer is the same person who wrote it in the first place, since our mental model of a program bitrots far faster than the program itself.
In practice, this aligns with my personal experience. Go projects end up with a lot of implicit assumptions that are silently violated by future work and expose bugs. They crash on nil pointer derefs. They accrue a multitude of linting tools that usually paper over some of the language’s shortcomings, but only in common cases. And they become painful to maintain as the original developers move on to other projects, with new changes grafted haphazardly into dozens of touch points instead of cleanly in one or two places. Yes, you can “easily” follow what any particular function does, but to do so you have to parse out and mentally model every minute detail, rather than being able to reason at a high level.
Conjunction is not equality. They are both memory unsafe. Then you can argue from that over how memory unsafe they are in practice (using the right practices, using the right language subset).
What makes you think so? Most Rust programmers and programmers from other languages, agree that this is not possible. I might be missing something, but can you give an example of such simple methods to trash memory in Rust, asking from a curiosity standpoint?
There’s always unsafe. I can make a pointer to anywhere by hand and write to it. That would involve some very intentional work, but I could do it if I wanted to.
But I did disclaim that it had to be somewhat intentional.
[1]: https://github.com/search?q=repo%3Aoxidecomputer%2Fhubris+un...
[2]: https://github.com/search?q=repo%3Aoxidecomputer%2Fhubris++l...
(At least, without further support. I consider "C/C++ with high quality static analysis" to be de facto distinct languages, and while I would favor something else even so, high-quality use of a high-quality static analyzer is enough to calm me down. Things have still crept through that level of care, but then, interpreters and compilers for safe languages have had safety errors in them before too.)
This is particularly true because it's just C and C++ that are memory unsafe. If we still in 1980, we could be arguing about the gradients of unsafety, but in 2023, we don't need to. Unsafety is not necessary at scale.
As for why people aren't asking to rewrite in Python, I partially answered that in my post. You can actually incrementally rewrite in Rust. You can't incrementally rewrite software in Python. There is also plenty of software that can be written in C, but simply can't be written in Python because it would be too slow. (Rewriting it in Python but oh no wait I'll just write the slow bits in C is a no-op, practically.)
As for trashing memory in Rust, by perfectly reasonable convention we generally understand that unsafe is unsafe, and that while languages can't avoid having it, having it does not necessarily make the entire rest of the language just as bad as C. I can crash Haskell with a straight-up, genuine null pointer exception with the Unsafe module in a single line of code. We do not thereby call Haskell an "unsafe" language where it is trivial to trash memory. Stock Rust is far safer than C++, to the point of being not only a qualitative change, but I'd contend, multiple such qualitative changes.
Once you separate C/C++ into safe and unsafe cateogries, and admit that Rust has unsafe uses that are "just so much harder to use", we're clearly defining a gradient:
C/C++, safer C/C++ subset or maybe Unsafe Rust, safer Rust, ...Not really. See for example desktop Linux (i.e. Gnome).
Go is also opinionated with concurrency. So that's an issue too.
It is not only or even mostly legacy. I'm a systems programmer (in classical sense, not "but my web service is soooo highly loaded and scalable that I will call it systems programming!") and from what I see on the job people start new projects in C and C++ all the time.
C programmers are often more experienced people who are used to "simple" language that gets out of the way. They don't want to invest time into learning tricky language like Rust with all the intricacies of its type system, borrow checker, etc. Something simpler like Zig might work for them, but it is not on the table at the moment.
C++ programmers tend to be people who spent hundreds if not thousands of hours learning its ugly corner cases, reading Meyers and Alexandrescu books, that kind of thing. Sunken cost is immense, they whole careers are built on being "C++ experts" and they dread to abandon it and have to learn another very complex language from scratch.
And managers often don't see value in investing time into switching projects to new language. From their PoV it is more like programmers just want to play with a new toy instead of doing "real work".
On that note, try valgrind on existing javascript engines, you might be "entertained". (I certainly was, but that was some years back.
AFAIK the only competitive JS engines written in memory safe languages are GraalJS and other JS-on-the-JVM runtimes. GraalJS has the advantage of being fully up to date, not having any memory unsafe code in it (the JIT compiler that makes it fast is a separate module, also written in a memory safe language, and the JS impl does not have low level code in it). And you can run it on SubstrateVM which is a virtual machine also written in a memory safe language, although of course small parts like the GC need to use unsafe features.
It also has other useful features like sandboxing and the ability to interop with other languages like Python or Java. Plus, it can actually sandbox native code as well because the "languages" that you can run on GraalVM include both wasm and more usefully LLVM bitcode, in which each individual C/C++ allocation becomes GC-managed and bounds checked.
So in terms of memory safety the Graal team are way ahead there.
(disclosure: I recently started part time work with the GraalVM team, but was a long term supporter before that)
As for the second - good to know! Seriously appreciate knowing that - not sure if/when I'll need it myself, but it's good to hear, and good that it's visible here!
"SQL/PSM (SQL/Persistent Stored Modules) is an ISO standard mainly defining an extension of SQL with a procedural language for use in stored procedures... SQL/PSM is derived, seemingly directly, from Oracle's PL/SQL. Oracle developed PL/SQL and released it in 1991, basing the language on the US Department of Defense's Ada programming language. However, Oracle has maintained a distance from the standard in its documentation. IBM's SQL PL (used in DB2) and Mimer SQL's PSM were the first two products officially implementing SQL/PSM. It is commonly thought that these two languages, and perhaps also MySQL/MariaDB's procedural language, are closest to the SQL/PSM standard. However, a PostgreSQL addon implements SQL/PSM (alongside its other procedural languages like the PL/SQL-derived plpgsql), although it is not part of the core product."
So depends on your definition of "dead"
https://nap.nationalacademies.org/read/5463/chapter/3
tldr; seems to be that the software development world has changed from the days that DoD was the "dominant" software developer, and Ada in the interim did not get adopted by the commercial sector (with safety critical exceptions in aerospace, etc. noted).
Go isn't memory safe when using goroutines. See: Golang data races to break memory safety: https://blog.stalkr.net/2015/04/golang-data-races-to-break-m...
* many GC'd languages like Go, C#, Java make it harder to leak memory, while languages where reference counting is more prevalent (Python, Rust) it can be easier to leak memory due to circular references.
* Languages with VMs like C#/Java/Python may be easier to sandbox or execute securely, but since native code is often called into it breaks the sandboxing nature.
* Formally-verified C code (like what aerospace manufacturers write) is safer than e.g. Rust.
* For maximum safety, sandboxing becomes important - so WASM begins to look appealing for non-safety-critical systems (like aerospace) as it allows for applying memory/CPU constraints too in addition to restricting all system access.
Do you have a single example in the last 14 years of memory safety exploit using the Go runtime? I'm talking about public and known exploit not ctf and the like.
> Is it possible to achieve arbitrary code execution on any Go version, even with PIE, and with no package import at all, just builtins? Yes!
Whether it's capture the flag is irrelevant, IMO, because anything that's allowed by the compiler will emerge given enough complexity.
1: https://blog.stalkr.net/2022/01/universal-go-exploit-using-d...
I mean if in 14 years there was nothing it's a proof that it's not an issue.
Even the attacker ack that it's not a threat.
"As said before, while a fun exercise it's pretty useless in the current Go threat mode"
Or bash before shellshock
I'm also surprised that a fix has been theorized at least as far back as 2010[1], but not implemented. Is adding one layer of internal pointer redirection for interfaces, slices, and strings really that much of a performance concern?
The reason is obvious. There's a high cost to this type of safety. Rust is hard to use and learn and many times it's safety forces users to awkwardly organize code.
And there's still the potential for race conditions even though the memory is safe, you don't have full safety.
> It's closer to a NullReferenceException than it is to reading from a null pointer in C.
No, it's exactly the same as a null pointer dereference in C, because it is literally reading from a null pointer in Go as well. In Java, the compiler inserts null checks before every single dereference and throws an exception for null references.
> There's no memory exploitation you can pull off from this bug being in a Go program, but you could in a C program
Provided the OS sends a SEGV signal for null pointer dereferences, I don't see there being a difference in security between C and Golang in this respect. It's a bigger problem when you're running without an operating system.
Doesn't OpenJDK install a SIGSEGV handler, and generate the exception from that on a null dereference?
(AFAIK, a lot of runtimes for GC'd languages that support thread-based parallelism do so anyway, because they can use mprotect to do a write barrier in hardware.)
I thought I had read that they explicitly don't do that, but I can't find it anymore. You may be right. I should have checked before saying that.
> (AFAIK, a lot of runtimes for GC'd languages that support thread-based parallelism do so anyway, because they can use mprotect to do a write barrier in hardware.)
That's true. I guess those implementations must do something more advanced than "throw a NullPointerException if the program segfaults," given their garbage collector runtimes also rely on that signal.
This is exactly what they do: https://shipilev.net/jvm/anatomy-quarks/25-implicit-null-che...
When this happens it'll cause a deoptimization and recompilation of the code to include the null check rather than rely on the signal handler repeatedly.
;)
Java it's a pest fest for exploits.
From those, I'd choose Go, C# (and not totally sure because of AOT/JIT's) and Rust.
Sure, if you haven't used it since the nineties and pay zero attention to new development.
I’ve never seen an explanation for why this mechanism was added or what it was supposed to enable — besides enabling new exploits.
Every time I’ve seen Java used for a safety critical application the justification has been entirely based on the fact that it has cryptographic libraries that are widely certified for safety by enterprises. The security people on our side were… resigned.
When it comes to unsigneds, there's no such problem (and this is in fact the real reason why anything that can be unsigned in C/C++ should be unsigned).
Trapping is nonsense - in production anyway. You might use it in development to catch errors. But in production there is no way to "fix" an overflow if it happens and is detected, so you'd be looking to crash on trap which in some code is preferable to silent data corruption.
I do lots of fixed-point math for embedded motor control. Representing rotor angle as a signed 16bit number is deeply engrained in me these days because taking differences to get a signed delta just works, and I never have to worry about rollover because it behaves exactly the way I want. ;-) While this is technically undefined behavior, I've never run across a compiler that worked differently.
Undefined behaviour can cause miscompiles which can break the expected logic of a program. In the worse case, maybe the compiler optimises away your password validation because it has decided that the failure branch is undefined behaviour.
Non determinism can lead to logic bugs, but it can't magically introduce new logic into the program like UB can as part of optimising
There's also explicit arithmetic functions that let you choose your overflow behavior:
* Checked: return None on overflow
* Wrapping: two's compliment wrapped overflow (the "overflowing" variant gives you a carry bit)
* Saturating: return maximum value on overflow
It can not, because "compiler optimization passes" is not the root cause of this behavior: it is that it is undefined behavior in the language itself. This is what gives the compiler license to make those transformations. In Rust, it is not undefined behavior, and therefore the compiler does not have the right to make those transformations for Rust code.
Just because Rust uses LLVM does not mean that suddenly it inherits C's semantics. This has actually happened (well, more specifically, C++'s semantics, C was accidentally inheriting them as well IIRC) at least one time before, but that is a bug in LLVM that was fixed. Now each language has the proper semantics here, and it all works just fine. (I am referring to the behavior with regards to infinite loops with no side effects.)
int would_increment_overflow(int a) {
if (a + 1 < a) {
return -1;
return 0;
}
This gets turned to a function that always returns 0. 0000000000000000 <would_increment_overflow>:
0: f3 0f 1e fa endbr64
4: 31 c0 xor %eax,%eax
6: c3 retq
Using unsigned int gives the wrapping semantics. int8_t abs8(int8_t n) {
return (n >= 0) ? n : -n;
}
Even with `-ftrapv`, evaluating abs8(-128) will produce -128, because 127 is the maximum value for a signed 8-bit integer (so trying to get 128 wraps around to -128), but this isn't caught. However, both Rust and Ada do catch this. This article highlights this difference between C and Ada:https://borretti.me/article/signed-integers-asymmetrical
I know Rust catches it because after I read that article, I tested it with Rust in debug mode.
It is easy enough to crank out custom integer types in C++ these days which are arbitrarily safe that there isn’t much excuse for not doing it, particularly since generics and metaprogramming does most of the work. Outside of interfacing with syscalls, there isn’t much use for C primitives beyond size_t.
I didn't know this. I can imagine you can create your own integer types as wrappers over existing primitives, and define them so that, e.g., on signed overflow, the program aborts or wraps (and your signed integers are actually a wrapper class over unsigned integers, but with operator overloading so that they act like signed integers). Is that what you mean? If not, could you show me what you mean?
This did not work well in older versions of C++ because the limited type inference meant that many common cases around different type interactions required explicit handling that made it clear you were not using native types. Around C++17 it became possible to define integer types that were almost entirely indistinguishable from the native types in ordinary C++ code.
C++17 was important because it meant a lot of safety in the code base could become automagic via the type system, especially for primitive types. The code looks the same whether you are using it or not. That was a huge capability change. C++20 then generalized it to arbitrarily complex types. It is difficult to overstate how much this improves the conciseness and safety of non-trivial code.
There are likely multiple necessary changes for it to be fast. One part is implementing the check quickly, this could be hardware assisted. Another is addressing all the optimizations the compiler does by asuming that there is no overflow, and figuring out alternative ways to get the compiler to emit fast code.
The Python Software Foundation noted this in our response to the US Government RFI on open source security. There are efforts to make using Rust easier as a systems language for Python packages and some security-critical packages like 'cryptography' have migrated to Rust.
One wolf that wants to help ensure that American computers are secure
And one wolf that wants to hoard as many 0-days as possible
Absolutely not in the memory-safe subset of Java though.
I think the uncoolness of them is due to OOP methodology... programmers today seem to want to be easier to get moving, but then spend more time debugging OR the debugging has been "placed onto" DevOps / use of Cheap fast hardware. IMHO
Their podcast titled "Signals and Threads" is quite interesting.
Actually, I have no idea if FPGAs in HFT are still a thing.
https://www.absint.com/astree/index.htm
In theory, it is possible to write completely memory safe code in C/C++ using it. As long as its analysis generates no complaints, there are no memory safety issues in the analyzed code.
Frama-C (https://www.frama-c.com) is an open source framework that has, among its analyzers, one based on abstract interpretation (https://www.frama-c.com/fc-plugins/eva.html) that is very similar in spirit to Astree.
MOPSA (https://mopsa.lip6.fr) is another open-source project (albeit more recent, and in a more "academic" stage) that also provides abstract interpretation to analyze C programs for flaws.
NASA also released IKOS (https://github.com/NASA-SW-VnV/ikos), on the same vein.
Of course they lack the polish of a product which costs tens of thousands of euros per license, but they are open source, and their purpose is the same: to ensure code safety via formal methods, in particular abstract interpretation.
It is possible to get these tools to analyze some code and generate no complaints, which ensures absence of several kinds of problems, such as memory safety issues.
Then again, it's hard to know exactly how much they differ from Astree, since you need a license to compare them, and I don't even know if you are allowed to publish such comparisons.
I have no idea how this is going to work at the OS level.
I struggle to understand the comment. Do these two sentences relate to one another or is the second one a non-sequitur?
There will always need to be unsafe ops in highest performance, low level coding.
But in the vast majority of use cases, there only need to be a few.
So huge security win being able to isolate those vs "somewhere in the entire codebase."
I do wonder what this landscape could have looked like if all of the effort had gone behind a Mach-based microkernel. I read all of the back and forth between Tanenbaum and Torvalds. At the time, I was just so excited to see "free and open" winning.
I should specify that the XNU Kernel from Darwin/OSX took a lot of my attention when it first premiered.
Had the servers been running microkernels instead, updating services without full restarts would have been trivial in almost all cases.
It's just a better approach overall. I curse the path dependency that led to the current situation.
[0]: https://github.com/search?q=repo%3Atorvalds%2Flinux++languag...
If no friendlier safe high speed programming language appears, I rather use C/C++ and trade safety for friendliness.
Maybe the risk is mitigated in other ways. Your software runs as a cli and not a service. It doesn't process outside input. It is run in an ironclad sandbox.
But honestly, if you think the way C++ with RAII want's you to then you should already be following the rules that Rust want's you to follow for the most part.
https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2022/p26...
Rust is generally perceived as having many of the same developer kindnesses as C++, while having same-class performance. Learning it has a bit of a curve, but I found that it eventually clicked (I have a C and C++ professional background).
E.g. Whenever handing out the raw pointer stored in a unique_ptr, the code receiving that raw pointer can delete it, which will very likely lead to a use after free and certainly to a double free issue.
```
class Foo;
Foo get_foo() { std::cout << "whoops, I forgot to return\n"; }
int main() { const auto foo = get_foo(); }
```
You don't have permission to access "http://www.nsa.gov/Press-Room/Press-Releases-Statements/Pres..." on this server.
Judging from the 409 comments right now it seems I am the only one have this problem?
Obviously, making certain concessions would be a deal-breaker for some, but it might be viable for legacy codebases. If you were to try to make C++ memory-safe, where does it begin to break down?
There might be a better path for memory-safe language interoperable with C++ (Carbon?) but this will require significant effort.
I'm in no way an expert in any aspect that relates to this problem, but it's reasonable to believe that it would be but a mildly challenging task if companies like Google, meta, Microsoft and the likes joined forces.
A year of concentrated efforts might be sufficient to rid us with this problem once and for all.
Btw I don’t know how this works in most languages, but circular references can be still a problem even with these restrictions. (If I remember well dangling references can be more or less handled, but correct me if I’m wrong. I remember that JavaScript definitely had problem with those)
Getting these features in the C++ language itself will be absolutely impossible, both because of massive performance implications and well as nightmarishly large ABI breaks on everything.
There are various efforts to do what you are describing. I think that they are often a good idea, but they absolutely will not solve the problem conclusively.
Because memory safety is built on top of memory unsafety, it is generally understood that when discussing things at this sort of level, that we are speaking about the Python-only subset. (Same as any other memory safe language.)
I was hopeful we'd be transitioning by now.... it's at least another decade out.
I'm in no way an expert in any aspect that relates to this problem, but it's reasonable to believe that it would be but a mildly challenging task if companies like Google, meta, Microsoft and the likes joined forces.
A year of concentrated efforts might be sufficient to rid us with this problem once and for all. Billions of lines of codes would gain (some) safety instantly.
But it's not going to be "drop-in", lots of stuff will have to be refactored to work with Rust's memory model.
See, memory management is a hard problem and the complexity involved in solving it has to live somewhere.
In C and C++, that complexity lives in the programmer's head.
In Java, Python, Go, etc. that complexity lives in the program's runtime in the form of garbage collection.
In Rust, that complexity lives in the compiler - with some caveats, Rust code is safe as long as it compiles and passes the borrow checker.
However, the ownership-based memory model of Rust is not always trivial to port other code to. Modern, idiomatic C++ written with RAII and smart pointers should be relatively easy to port to Rust, but most of the critical systems code out there is not like that at all. Most of the critical systems code out there is written in C or old-style C++ with C-style strings & arrays and raw pointers to heap memory all over the place.
So regardless of what your C/C++ language replacement looks like, it will take some serious effort to replace that kind of code.
You can't do "drop-in replacement" and "memory safety" at the same time.
https://www-users.york.ac.uk/~ss44/joke/c.htm
#joke #hilarious
In the PDF the closest things they talk about to sanitizers are SAST/DAST tooling, which isn't exactly the same thing, and compiler mitigations, which aren't exactly the same thing, but I also don't believe this is intended to be fully comprehensive at this level of detail.
As a C++ programmer, I really want to find a spare week or two to learn Rust.
Promoting cyber security in the civilian workforce is also part of the DoD's 2023 "Cyber Strategy".
The Department will take action to foster a culture of cybersecurity and cyber awareness. We will establish an expectation that senior military and civilian leaders possess a baseline fluency in cybersecurity issues. The Department will develop, fund, and implement technical curricula across different levels of professional military and civilian education, emphasizing General Officer and Senior Executive Service leadership courses.
https://media.defense.gov/2023/Sep/12/2003299076/-1/-1/1/202...
They're also trying to promote cyber security in open source software: https://www.nsa.gov/Press-Room/Press-Releases-Statements/Pre...
That's not to say you're wrong about the NSA probably having backdoors in critical software like the Rust compiler.
Now, nothing, and you if you want to train a new person, you do it on your own time.
no wonder so many bugs here and there.
Meta have a phenomenally good training program, called Bootcamp for basically everyone in engineering, so they're probably not the example you're looking for.
Unless it is a “hello world” commit to confirm tooling is operational, I have strong doubts.
When I started at FB in 2013, I had all the laptop/permissions things done by end of the first day.
At the time, the presumption was that you'd push a really small change by the end of the week, but all of the tasks available tended to be pretty easy (note I wasn't in eng then, so all my info is second hand here).
Back in the day people with SVN/CVS commit access would push directly to the main branch after testing locally, and testing would be done as part of preparing a release, so it was more necessary to have a "safety" period where new people could get used to the new codebase.
That doesn't seem like a contradiction at all. As you say, they want to hire trained workers rather than providing training themselves. Also, complaining is free while hiring good people is not...
A: What if you don't and they stay?
"I'd rather use a threading library written by one great programmer than by ten average programmers."
I see plenty of php logic which is far more likely to be publicly exposed and generally of a terrible quality.
The pushback I've received is something I'd categorize as "cart before the horse" - executives want to know exactly what will be achieved with such effort before approving any expenditure.
The teams I've been involved with - training has generally come organically and grass roots.
Do training on the things that can cause trouble longer term (inconsistent tooling taking over, security issues, company-specific things you cannot find online) and just hope people pick up the rest as they go. Good workers will usually manage. The people deciding how to train new workers maybe don't even know what kinds of "main" knowledge a given worker would need. Rather than assigning an expensive senior worker to training them, they might hope someone good willing will do it in their spare time.
I just watched the most recent Last Week Tonight, where they went in depth on Freight Trains [1]. Specifically they talked about 3 mile long trains that currently only have two operators - one conductor and one engineer - and the industry is trying to cut it down to one.
That's just an analogy for software development. But I've definitely seen people be over-worked and under-supported.
(I actually don't know what the norm was. My dad was an EE for the defense industry, and IIRC he worked for 3 different employers over his career.)
But I've really got to ask - at that point do you really want to program in C++ anymore or is it just better to use a safer language that removes pointers entirely?
How do you manage heap allocations without ever touching a pointer?
And while there are some kinds of applications where never performing heap allocations is viable, this design is simply not an option for a huge number of applications.
Any C++ program that does not do heap allocations either uses arrays as a substitute for the same thing or isn't a general purpose application.
I use safer languages when appropriate but there are cases where they would introduce more bugs than they address.
Such as? Genuinely curious.
If you implement something like a database kernel almost entirely in user space, which is what you want to do if you care about performance, you have a new set of problems that languages like Rust were not designed to solve. Ownership, mutability, and lifetimes are not resolvable for most objects at compile-time, they can only be resolved dynamically at runtime. With C++, you can make these look like ordinary objects with ordinary behaviors, with metaprogramming abstractions under the hood arbitrating the chaos. Things that look unsafe are actually guaranteed safe by the underlying abstractions. In this type of software, almost your entire address space is “unsafe” in some way, so it is enormously useful to be able to overlay a safety model that does not rely on the compiler, since the compiler cannot solve these cases.
I use both C++ and Rust in production environments. There are cases where C++ used well is legitimately safer, theoretically and empirically, which is why I still use C++ for some things. Rust may become stronger at dealing with these cases but it still has many years to go. (Also, Rust async story is a hot mess, and you really need async for scalable, performant systems software.)
I have seen that too over the years and even been to a couple when I went to other companies, 99% of the time these classes are worthless and consists of marketing drivel. Half the class contains statements like "You can do it the hard way, or the easy way if you purchase ....".
One instructor even stated "You do not need to worry about running out of memory, allocate as much as you want". That class was 20+ years ago. After that I told my manager I will not go to any more vendor classes.
The training I am referring to is by experienced peers and goes on for many months where your work is reviewed in detail, 1 line at a time with you there. That use to happen decades ago, now no more.
Edit.
Idk why downvotes
Chrome and Windows code bases had 70% of cves due to mem issues and their engineers arent some unqualified and lacking of training opportunities ppl
Even in highly regulated, controlled environments memory safety is still an issue. I have seen bugs in these environments where I know for a fact that the developer has had 40-80 hours of training on memory safety and they still pop up.
If these people have occasional issues, the rest of the world has no effin chance.
I'm afraid your approach would mean hiring someone and spending time and money on them before you're sure they'll work out in your organization. Few want to train contractors and firing FTEs after 3 months and change is a dim prospect as well.
> Memory safety vulnerabilities are coding errors affecting software’s memory management code in which memory can be accessed, written, allocated, or deallocated in unintended ways.
How can you do any of this without software running in the local machine?
Honestly asking.
1. You have a service that takes some user input
2. The service allocates a buffer on the stack for 20 bytes (the max input allowed)
3. Service doesn't actually validate that the user input is < 20 bytes
4. It writes the input into the buffer, but keeps writing past the end of the buffer.
5. It eventually overwrites the return pointer so now the user input can control where the execution jumps to on return
6. In the user input, the malicious user includes some shell code to do any arbitrary thing they want.
7. The overwrite the return pointer to jump back into the shell code and now they are executing arbitrary code in the server process.
This sort of thing was embarrassingly easy to do on old linux kernels before ASLR was implemented. Now it is dramatically harder because there are all sorts of countermeasures in place to prevent it (ASLR, stack canaries, executable vs non-executable memory, etc).
To get a sense of what a more modern real world exploit looks like, give https://googleprojectzero.blogspot.com/2021/12/a-deep-dive-i... a read.