NSA guidance on how to protect against software memory safety issues [pdf]
media.defense.gov
media.defense.gov
I think they're trying to combine the concepts of languages that have GCs and prevent buffer overflows into a catch-all term, but "memory safe language" isn't a good choice. Non-programmers might take the term at face value.
It's kind of like using the term "safe flame thrower", except no one would be lulled into a false sense of security with that one.
Don't get me wrong, I'd rather use a "safer flame thrower" than a "flame thrower", but I also wouldn't trust anyone selling me a "safe flame thrower".
The above is my best Dijkstra impersonation.
"Memory safe language" is a perfectly good term for the moment since most languages fall square into "completely unsafe" or "completely safe with the ability to be unsafe in special circumstances".
I don't know of any that are "fairly safe". Maybe something like Carbon will be like that.
The current set of papers for WG21 the "C++ Standards Committee" includes more than one taking this stance, but obviously the standout is P2687 from Bjarne Stroustrup and Gabriel Dos Reis. Bjarne and Gaby describe the situation (in which the US Federal Government has noticed that there's a problem) as an "Emergency"
https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2022/p26...
P2687R0 is an initial draft (even by R0 standards it looks very hastily thrown together) but it wrings its hands about the propensity of programmers to write bad code, and then it offers a list of eight kinds of (un)safety, including "delivering a result in 1.2ms to a device supposedly responding to an external event in 1ms". Alas, although C++ doesn't really help you with any of this list, other languages don't solve all of them and so why bother right?
It also takes pains to group together intractable problems (e.g. dynamic allocation but with assurance of no leaks) with solved problems (e.g. preventing use-after-free). The R0 draft doesn't really spend much time explaining how, if at all, they would solve the problem (beyond vaguely "static analysis" so it can't be critiqued on that basis.
I'm sure the committee (of which both Bjarne and Gaby are members) will look favourably on this sort of work, but nobody in government should take anything like this seriously until the actual problem gets solved, ie the programs stop being full of memory bugs.
Software can still be delivered in those languages, after all the goverments themselves have millions invested into such codebases, but it comes with an extra price tag versus other safer alternatives.
Good advice. As a Lisp developer, I have used Swift and it seems like a nicely designed language, very good tooling on macOS/iPadOS and OK on Linux. Go, Rust, and others are also good.
Off topic, but I thought it sad how once 9/11 happened with the side-tracked war on terrorism, the NSA and FBI apparently (from my private citizen perspective) stopped doing much of the previous great work on public support for computer security, going after international computer crime cartels, etc. As a US taxpayer, I would like to see them prioritize that type of work.
It doesn’t make sense to teach terrorists how to secure their systems if you need to exploit these systems to achieve your mission.
Now that Chinese, Russian and North Korean actors are putting in work and causing real economic damage in the west it looks like defence is starting to hold some value again.
If I tell you not to jump off a bridge so you do it, you're still unwise because now you're just defying my instructions without thinking.
Maybe the C++ bridge isn't on fire, but if you think you can smell smoke you wouldn't be alone.
Go was designed by (among others) the father of Unix, Ken Thompson, with an understanding of the mistakes of C and C++. Despite getting hilariously little respect here on Hacker News, the language is (for many purposes) an excellent replacement for C.
(Yes, yes, I know you disagree. Tell me more about how C++ is necessary and all the time you spend fighting it is actually a huge win! Please tell me how slow garbage collection is, all evidence to the contrary!)
Go reads from, and writes to, variable-sized blocks of memory through (pointer, length, capacity) triples, known as "slices". A Go slice "[]int" is essentially a C struct like this, passed around in 3 registers:
struct IntSlice {
int* pointer;
ssize_t length;
ssize_t capacity;
};
Bounds checking in Go is easily disabled (pass -B flag to the compiler) but almost nobody does so because the performance benefit is miniscule. With bounds checking and garbage collection, Go avoids almost all memory-related bugs.You can understand Go as C redesigned for a world where parallel computing (e.g., dozens of CPU cores) is commonplace, and the benefits of safe code outweigh the tiny costs.
We're not using 80286 chips anymore. We can afford safety.
In all seriousness though it does seem to mitigate a lot of exploitable memory corruption vulns, perhaps not memory-corruption-caused Denial-of-Service vulnerabilities as well as Rust et all maybe.
Even K from K&R uses it!
For the former go won't help. Tinygo may help some day, but for any weird new chip the first thing they make is a c compiler.
The linux kernel accepts rust, or will next year. It does not accept go. So Torvalds at least disagrees it fits there.
Go has plenty of uses, but writing microcontroller firmware and device drivers are not among them. And why else would you pick c?
I can't think of a language that comes close to any of that.
And if lack of standard specified name-mangling disqualifies C++, so it also does to C, because there is no such thing as a C ABI, only OS ABIs that happen to be written in C, and the C compilers of the platform follow the OS ABI, which you can get in C++ with extern "C".
True, but then you add translation layers everywhere because you used C++ in your function interface.
A C++ program which calls nothing but C libraries will not have the “shape” of a C++ program, but essentially the shape and structure of a regular C program. And the small pieces that remain C++-shaped might not be large enough or provide enough architectural benefit for the drawbacks of C++ to be worth it.
(Paraphrased from this thread: https://news.ycombinator.com/item?id=20849570)
C++ has few surprises? This is from Scott Meyers, author of several books on the intricacies of C++:
> I no longer plan to update my books to fix technical errors.It's not that I'm too lazy to do it. It's that in order to fix errors, I have to be able to identify them. That's something I no longer trust myself to do.
He then goes on to explain how the language is so complex, that in two years he has already forgotten enough of the gotchas to trust himself. This is far short of a language with no surprises.
[0] https://scottmeyers.blogspot.com/2018/09/the-errata-evaluati...
When the choice comes down to C vs C++, the only possible answer is C++ when safety matters.
If more languages are allowed into the decision pool, then both of them should be avoided.
Macro Assemblers and other systems languages lost to C.
Heck even using Basic, Pascal, C++ instead of C would already be an improvement, and many don't because of religion not lack of tooling, there are enough vendors.
F-Secure decided they wanted to use Go for writing firmware, and so they did, TamaGo unikernel was born and is shipping into USB security keys all over the world.
As you say, it isn't hard to design a better systems language than c, but I don't think it is hard to design one better than go either.
The key is that isn't enough to be better. The amount of inertia behind c is so much greater than any of its predecessors just by virtue of time and the growth of the industry.
ADA was better, but had bad timing and bad marketing and was encumbered by price. It still did pretty well just on its merits. But a lot of ada domains got reverted to c or c++ for developer supply and interest. Right or wrong, ada wasn't "cool".
I see rust much farther ahead on the adoption path. And while it isn't perfect or even revolutionary, it is an improvement, and the cool kids like it so management lets me use it.
I'm not defending c, and if my only choices for a new project were a weird flavor c or a weird flavor of go, I can't imagine I'd pick c. Or even c99 vs a weird flavor of go.
But I don't forsee that ever being the choice. What does tinygo offer over other llvm backended languages targeting the same archs? GC seems like that might be the answer if you don't lose it? Channels and greenthreads? Library support if it compiles? I really don't find rust too complicated after using for real work. Both nim and zig seem like better choices than go as well. Even julia if you do some nonstandard stuff. And ada would be the other obvious choice.
Edit: sorry D should probably get a mention here too with new targets having been added.
Keep in mind Go has an unsafe statement for manual memory management and pointer arithmetic. You can also import C-style malloc/calloc should you need them.
https://dgraph.io/blog/post/manual-memory-management-golang-...
That said, there are at least a couple languages that let you do manual memory management in safe code (ada and rust). While go jas nice syntax, if I'm giving up gc and the other bits missing from tinygo, what does it offer over D or zig?
C# has always supported AOT compilation via NGEN, with the caveat that it needs to be done on-device and only dynamic linking is supported.
Besides NGEN, in the 20 years of .NET history, we had CosmOS AOT compiler, Singularity (whose tech landed on Windows 8 MDIL), Midori (whose tech landed on UWP .NET Native), Mono AOT (still going at it), Unity's IL2CPP, and now Native AOT.
Go has GC and phat binaries. Frankly for a lot of stuff C# is a way better choice than Go or C++
I can see how Go's static compilation can be attractive for small cli utilitities.
Sounds like C is not your thing, and that's ok.
Go is an improved C. It's for people who like C but need C's well-understood problems fixed.
C-style pointers are not a problem. They don't need to be fixed. They're great, and that's why Go retains them.
Here is an example of C-style code making essential use of C-style pointers (especially nil pointers) but it's Go:
https://bugfix-66.com/50788214f539d50382528e86242eb3c846b03f...
You need to be able to represent addresses, and you need to be able to say "address of nothing" (i.e., nil pointer).
Once you've seen Sum Types it's obvious that you just wanted Option<T> - a sum of None and Some of your pointer / reference / whatever type T.
Thus type T must actually point at / be something, whereas Option<T> can be None, and your APIs can be design accordingly e.g. the function which always adds a Dog to this Kennel should take a Dog, not an Option<Dog> that would be silly, but the function to take the most recent addition to the Kennel back out would return Option<Dog> not Dog because if the kennel is empty there is no latest addition to return.
Said in another way... if it makes any sense (this is at the nexus of philosophy and arithmetic):
Since anything resembling emptiness or length of 0 requires a reference then 0 comes after the one (1, monad).
1 is primordial over 0.
Existence wraps emptiness.
For existence to wrap not emptiness nor 0 it must wrap nil. However no matter how you reference nil you must do so through at least one layer of indirection.
In Rust for example Option<&T> is literally the same size as &T because the &T is implemented as a non-NULL pointer, so the None value fits in a niche where NULL would go if this was a pointer (this is called the Guaranteed Niche Optimization, many other more elaborate niche optimizations are routinely performed by the compiler but are not promised, however Rust promises the optimization we need here will always happen)
The machine code ends up identical to what you'd get from unsafe languages with raw pointers if you remembered all the necessary checks, however the safe source code written by humans has stronger typing preventing them from writing code that would e.g. put a NULL Dog in a Kennel, an easy mistake in the unsafe languages.
Some languages solve just this one narrow problem by having specifically "Nullable" and "Non-nullable" types, plus some way to get the Non-nullable from a Nullable (with a branch if it's null) essentially Option but built-in to the language. But wait, as well as this Option feature you also want Result which is another Sum type. Should you add a special built-in for that too? Some languages choose to do so. OK, and how about ControlFlow? Poll? There will be others. I believe that languages should just suck it up and offer Sum types.
It is actually the other way around, having a way to represent “no pointer” is fine. What’s missing is the ability to check at compile time that a pointer is not nil.
The key is that once a value has definitely been obtained, the compiler needs to be able to track that, otherwise it is unclear whether the value needs to be checked again whenever it is used. Often those checks are not where they should be, leading to many bugs.
So if the choice is between C and Go, for userspace code, Go is the answer.
As proven by UNIX father's design of Inferno and Limbo, where C is only allowed in the kernel and DisVM implementation.
No, it's not. The runtime wouldn't even fit on many platforms. GC pauses are similarly not acceptable on soft/real-time systems, etc.
Ada/Rust get closer to a replacement.
Half joking, as a C/C++ programmer here.
* Unless you run out of memory, of course. Then you have issues.
https://mcuoneclipse.com/2022/11/06/how-to-make-sure-no-dyna...
There are bare metal Go runtimes being shipped in products today, e.g. TamaGo unikernel for firmware.
As there are real time Java implementations, there could exist Go ones if the market cared about having them as well.
Oh:
> The RTSJ addressed the critical issues by mandating a minimum specification for the threading model (and allowing other models to be plugged into the VM) and by providing for areas of memory that are not subject to garbage collection, along with threads that are not preemptable by the garbage collector. These areas are instead managed using region-based memory management. The latest specification, 2.0, supports direct device access and deterministic garbage collection as well.
https://en.wikipedia.org/wiki/Real_time_Java
Albeit 2.0 is still WIP at this time.
TIL
why not just without the part up to the comma? as in
"here are two..."
Studies suggest some of MISRA's rules if followed reduce significant bugs in software, some others increase bugs and many are neutral because they forbid things nobody who isn't entering an Obfuscated C Contest would think to do even in C.
e.g. IIRC MISRA says don't put variable declarations inside parts of a switch and then use the variables in other parts of the switch. Nobody does that, it's very silly.
Or MISRA says you must have a default switch case. So your three-way switch for the headlights enum, OFF/ DIPPED/ FULL fails because it needs a default. What's the default? Well, I guess you can assert it's never hit? But your C compiler has enum checking already, without a default it would have flagged if you forgot SPECIAL_HACK which is in the enum, but thanks to default the C compiler thinks you remembered about that and at runtime in somebody's car the SPECIAL_HACK is enabled and their car's CPU crashes.
The automotive industry wanted to write C. Or at least, the programmers it hired did, and nobody said "No. That is a terrible idea, stop it" instead they came up with MISRA C to continue excusing the inexcusable.
Interestingly though, I take the stance that Rust is much closer to C than C++.
Performance stuff was written with inline Assembly, or straight Assembly with nice macro assemblers (TASM, MASM), using C or C++ in detriment of the above selection only helped bringing UNIX stuff into MS-DOS.
EDIT: See books like "PC Intern: System Programming : The Encyclopedia of DOS Programming Know How".
EDIT2: It is available at archive.org, https://archive.org/details/pcinternsystempr0000tisc/mode/2u...
Also I think the arrays had bounds checking?
Yes, it suffers from use-after-free, lets ignore everything else that is safer than C.
Bounds checking are enabled by default and if one wants to shoot themselves on the foot there is always {$R-}.
I think that if Go didn't exist he would be considered "one of the greats".
Go is an extremely well designed and balanced language, it is extremely productive while still being easy to maintain and have pretty decent performance.
Now, Rust is the new hotness and it doesn't make a lot of sense to use other things if you care about both safety and speed.
Go 1.17 implements a new way of passing function arguments and results using registers instead of the stack. Benchmarks for a representative set of Go packages and programs show performance improvements of about 5%, and a typical reduction in binary size of about 2%. (https://go.dev/doc/go1.17)
I think the basic problem here is that you don't know the facts.
I use Go a lot today, as a former user of both C and C++, but Rust is probably better as a C/C++ replacement.
This is a quite common memory access pattern in HPC.
"A Case Study and Characterization of a Many-socket, Multi-tier NUMA HPC Platform"
https://ieeexplore.ieee.org/document/9306956
Or for something with FPGAs and other cool stuff in-between,
"The ATLAS Data Acquisition and High Level Trigger system"
https://iopscience.iop.org/article/10.1088/1748-0221/11/06/P...
Chances are you probably don't want to use memory safe languages now.