Now as a software developer I feel like this is even more important because I use libraries maintained by thousands of other developers that might also use applications that have these exploits which get their systems compromised pushing malware to thousands of other developers which end up compromising even more libraries.
I believe that memory safety should be the standard for software that thousands if not millions rely on and that it shouldn't be some political issue of X is better, Y is that, Z is something else.
But then again, social engineering is the primary source of malware spread so I don't know.
Now, maybe I'm only running into these algorithms because these are the problems I'm running into, but in the Rust community I consistently got the message that linked lists were a legacy data structure. In some cases they are, but when they do apply they do so brilliantly.
I know working in Zig leaves plenty of room for memory unsafety. Perhaps that's a bad thing. But I'm implementing an interpreter, and these algorithms are essential for it to run fast. I really do need to be aware of where every allocation happens, and what instructions the computer is running. So for now I stick with Zig, even though I know I'm opening myself up to memory exploits.
If you want to go deeper, skip the language wars entirely. Tooling does what these languages can't do. I have had great success model checking C with CBMC. Not only does this prevent memory safety issues (including memory leaks), but this also prevents deadlocks. Bonus: you can write user contracts and invariants to verify that every execution path of a function fulfills these contracts and invariants.
Kani comes close with Rust. It doesn't yet have decent concurrency support, but there is nothing that prevents this from being added in the future. The ability to write custom contracts and enforce custom invariants more than makes up for its lack of concurrency support. Similar technology could be used or adapted for Zig or C++.
Use whichever language you like. Just, please look into model checking it. The technology scales just fine, once you get over the learning curve and learn how to use compositional verification.
When you allocate space with Fil-C's "malloc", some additional bounds checking data precedes the space the program gets to use. Pointers are "fat pointers", with a pointer to the beginning of the buffer and a pointer to someplace within the buffer, allowing ordinary C pointer manipulation.
There's much new verbiage around this. But it's roughly the same idea as GCC "fat pointers".[2][3] So it's not a new idea. It's one that's been tried several times, but never caught on.
There's a performance penalty. Especially if the compiler can't hoist the checks out of loops.
[1] https://fil-c.org/invisicaps
Coreutils has a good track record. Ffmpeg doesn't. They should have rewritten ffmpeg in Rust, not coreutils.
There are other approaches though like formal verification.
Not so much about language design as how easy it is to take a defensive position on something we are comfortable with when it's challanged. This prohibits growth.
When someone suggests you have been doing something the wrong way or has a new idea, it's usually better to hear them out and take what knowledge you can from them, even if they weren't actually trying to help you.
Anyways, thanks again
Fil-C is an amazing technology, and we should use it, but we should also recognize its limitations.
An as-written JIT or GC can not be compiled with Fil-C because they are inherently memory unsafe.. which means one of the biggest user CVE targets by volune, Google Chrome, not only can not be compiled by Fil-C -- it also can't consume Fil-C compiled libraries. (Chrome not only has v8 jit, but 2 different GC systems - V8's and Oilpan, plus PartitionAlloc)
Which means Fil-C is an amazing tech for securing the manual-deallocator daemons and backends, but does not secure the biggest attack vector on 2 billion consumer devices.
This is why i'm writing a maximally memory safe browser in dotnet where the only unsafe will be the FFI and RyuJIT underneath me. [1]
I also think we have gone too long with poor operating system safety tools. I want a system based on Andrew Valencia's VSTa's hierarchial capability system. In that system protection ids are hierarchial and infinitelly narrowable without any special authority. So user.david can forge user.david.browser which can forge user.david.browser.site.ycombinator and so on. The closest we have to this today is (relatively) heavy weight virutlized containers.
I'm hoping that the next release of Sculpt, Genode's user-facing OS release, will offer this, as promised in their road map for their realse 26.08.[1] I'm hopeful that we can finally drop all these stupid layers of cruft trying to patch fundamentally insecure operating systems.
Tannenbaum was right, in the end, and Linus was wrong.[2]
[1] https://genode.org/about/road-map
[2] https://en.wikipedia.org/wiki/Tanenbaum%E2%80%93Torvalds_deb...
[*] "Multi-level secure operating system" is the required magic phrase to allow finding articles on this subject through search engines.
I realized some time ago when I talk to people about rust, I don't really mention memory / thread safety in a security context, I mention them in a reliability context. Compile-time checks mean I basically never spend time debugging runtime crashes, and generally can count on the compiler to catch memory and thread safety issues before the program ever runs, and this saves me time and aggravation in production. I care about security, but I care more about reliability, and rust compile-time checks mean my software is reliable in a way that runtime-checked languages just aren't.
This applies to lots of languages. Every time I hit a runtime crash in python, or ruby, or js I die a little inside. Even though they are memory-safe because they're garbage collected, I still waste time debugging runtime errors.
That's true, though it's worth noting that they do still need unsafe for the FFI. Often this is where the memory safety issues arise in GC languages. So no language is truly "memory safe".
Like, of course you can prevent all memory safety errors by using a garbage collector. That has been known since the 90s. In fact, there was a good two decades after Java released where all the research on memory safety just stopped, because the standard answer became "use a GC". If you couldn't use GC, you were stuck with languages made prior to Java - which in practice meant just C - or the one language everyone tried to staple every new programming paradigm onto, C++.
In fact, part of why C++ became such an untameable beast of a language is because it became load-bearing for non-GC projects. Anything not in the ISO C++ standard was, in practice, something you just couldn't do in native code. Oh, of course you can't introspect structs in a compiled language, of course macros are useless and unhygenic, of course templates have to be monomorphized and bloat your binary size. And so the pressure for C++ to be an all-singing, all-dancing, all-dressed language grew.
Rust's big story is memory safety, but the more Rust I wrote, the more I realized that the memory safety is only part of the picture. Rust has a very nicely curated selection of features that allows the language to remain understandable despite the sophistication of the compiler. Memory safety is a selling point, sure, but it is also the lubricant that makes working with those features pleasant.
If you want a real complaint about Rust, it's that some features are oversimplified in ways that make certain scenarios harder and make some features way more "magic" than they should be. Have you ever tried writing Futures code without making use of the async keyword? It's nearly impossible, for several reasons; the main being that Rust's type systems cannot express self-referential borrows. This also makes returning a reference to something in an Rc or RefCell you own more difficult[0]. And there are numerous other states memory can be in that are hidden from Rust's type system. Rust can't even represent a real destructor fn. Dropping a value multiple times, or using it after it's been dropped, is explicitly forbidden; but Drop impls still can't take values out of themselves because Rust doesn't have an "owned reference" - i.e. memory you can take values from but can't deallocate.
But none of this compromises memory safety - it just makes certain things harder than they should be.
[0] Strictly speaking, there's an owning_ref crate that manages this; stdlib is also working on a "mapped mutex guard" type that would do the same thing without a dependency.
Unfortunately, I've never seen a version of this data targeting modern C++ (>=11, with smart pointers, already 15 years old).
You could even use Rust alongside Fil-C to ensure the unsafe blocks are safe. It would even be interesting to turn off some of Fil-C's expensive protections in the safe parts of the Rust code, making an average-of-both-worlds sort of solution.
What I do care about are C and C++, these two absolute garbage languages (though I do have a lot of love for C, you can both love and hate something).
Tools like Fil-C, hardened mallocs like SlimGuard, and other "make C safe" solutions all sacrifice absurd amounts of performance because these two moronic languages refuse to have a proper slice/span type.
Use-after-free and double-free are serious problems but they're a spec of dust compared to missing bounds-checks. The low-hanging fruit is right there for the picking but everyone is worried about how to reach the fruits at the top.
C++ only now in C++26 is finally adding bounds checks to the [] operator on std::span and (hilariously) is also finally adding the now mostly redundant .at() method which should have been there from day one.
Clang added -fbounds-safety which is a feature that should have existed for a long time and serves as a decent stop-gap to a proper slice type. But of course, since it's not standardized, most people won't use it.
What these two languages have taught us is that if you want to produce utter garbage you should make it an ISO standard.
Me, personally, I find this whole discussion on memory safety amusing. Missing bounds checks are by far the biggest source of vulnerabilities and they're a problem in exactly 2 languages and those 2 languages have outright refused to do anything about it for decades.
But hey, even in memory safe languages you have frameworks like log4j that had remote code execution from a format string as a feature. Is the problem memory safety specifically, or this utter cavalier attitude towards security?
I'm not even suggesting something dumb like "you just gotta get good at C and then you won't have any vulnerabilities". Humans are fallible, which is why we delegate what we can to machines. I'm just pointing out the utter lack of care. People just don't care.
At least do the bare minimum of effort like, I don't know, not make remote code execution a feature tied to format strings. Or provide a slice type in your language and string manipulation functions in your standard library that make use of it, preferably 20 years ago.
To the extent Fil-C and Rust both address these vulnerabilities, and don't include design features that in any practical way admit them, they're memory safe. What always feels like is missing from these kinds of analyses is that there are lots of memory-safe programming environments. Almost every Java, Python, Ruby, Javascript, and Go program is memory safe, in the real meaning of the term.
The competition to lock in and promote adoption of the "most" memory-safe language is a category error... unless you do what every language-war argument does, and redefine the term.
I don’t dislike Rust, and when I point out that Rust is not fully memory safe, it’s because I find the details here super interesting. It’s interesting that Rust deliberately chooses to have an unsafe subset. It’s interesting how that leaks out to the rest of the language. It’s interesting because us language designers ought to be thinking about how this could be avoided. It’s a hard problem! And that makes it fun!
If you do read what I say on twitter, you’ll find praise for Rust, many concessions about Rust being faster than Fil-C, as well as a wide range of other opinions. When I point out Rust’s unsafety, I’m just citing facts. It’s interesting how doing that really seems to upset some folks.
Pointing out a limitation in a technology does not imply dislike, and it’s best not to take it personally.
I also feel there's a second dimension to the politics, where Rust is the "woke" language and C / Zig / Odin are now the "anti-woke" languages. At least, going by their most vocal online communities.
I'm sure offline there's still engineering decisions being made (I hope).
Memory safety is a really big reason to use Rust, but it is very very far from the only reason. Even if Fil-C was magically zero-overhead (that is basically what CHERI is), I would still rather use Rust.
Rust has so many advantages over something like Fil-C it's hard to list them. I think Fil-C is a great project but it's only really relevant if you have no choice but to use C. If you can use Rust you also get:
* Compile time memory safety (Fil-C / CHERI are run-time).
* A modern functional-style type system (helps prevent logic bugs).
* Tree ownership (helps prevent logic bugs)
* Sane toolchain (helps prevent hair loss).
* Easy dependencies.
* Vastly more things checked at compile time.
Also I find it amusing how this post claimed it wasn't about Rust and then spent the whole time talking about it.
Rust is great. I don't think that really needs to be debated any more.
As an example of that, the rust compiler helps the programmer to avoid many mistakes but it's still possible to use memory incorrectly and the tool is only safe as long as you use it as intended. You could say that C is safe too as long as you use it as intended and make no mistakes. The only problem is that it's hard to use it as intended and make no mistakes.
So let's think about what memory safety could mean. My understanding is that a memory bug is when you use memory in an unintended way. An example of that is when you write to memory after you have deallocated it, which means after you have decided not to use it any more.
No compiler or tool can make sure you don't write to or read deallocated memory, because whether it's allocated depends on what your intention is, and no compiler can read your thoughts. They can only give you a tool (like functions for allocating and deallocating memory or compiler checks) and hope that you will use that tool in a way that reflects your intention. One problem is that those tools are not always sufficient to keep track of your intentions and you may not always use them as intended.
Memory allocation is relative. In a sense, no program that runs under an operating system can use deallocated memory, because when they read or write to memory that has not been mapped to the process, they crash. In a sense, even "safe" rust can use deallocated memory; let's say you have an array of 10 integers and you decide that the fifth integer should not currently be used, you have then deallocated it on a level where the available tools can not help you, but you can of course still access that memory.
So again, everything is safe if you use it as intended and nothing is safe if you use it in other ways. Safety depends on how well the code matches your intentions. Remember that programming always happens in layers and no tool can cover all of them. It's not even clear what "all layers" would mean or how many there are in a program.
One of the top two issues with Rust is its cumbersome and overbearing syntax and anything which sidesteps that is automatically attractive to anyone which is worried about memory safety and doesn’t like to encode every little detail about in the type system. That’s the majority of programmers… think about it, that’s one of the big reasons people switched to GC languages which are the most popular languages.
The classic Rust approach to addressing criticism of the syntax was a mix of downplaying, gaslighting and “you’re holding it wrong”. But if easy to use, reasonably ergonomic alternatives become available, I expect that most would prefer them to Rust.
The problem with choosing Rust over a GC language like the above (or a good one, like OCaml) isn't that it's not "fine" to use Rust, but that manual memory management is an inefficient use of developer time. That's an issue for the developer, and one he inflicts on himself, not an issue for the end-user. Both Rust and (safe) GC languages provide memory safety, after all.
Or as the poster put it
> When seeing the title of this post, I bet in some people's minds, the first thought was "Rust devs!". This connection is not unfounded.
Exactly. The Rust community has been very holier-than-thou on the memory safety front and quite absolutist ("how dare you code in non-Rust, don't you care about memory safety? You must be a bad person").
Now that it turns out that there is an alternative that is even safer, we suddenly get "well, it's a bit more complicated, memory safety isn't everything, you have to look at the broader context and requirements"
Excellent!
Glad you've come around to that point of view, dear Rust community. Now let's have civilized discussions about trade-offs.
Well I feel most of the Rust devs are there for saying shits about other languages to prove their own dumbassness.
The real issue is that we came to accept unsafe languages and are taking a really long time to put such features back.
So... Yes, Rust is less safe. Just that now the Rust apologist wants to back away from that and say that actually memory safety isn't actually the end all be all. C has a lot of security problems. Rust has less, at the cost of breaking the ecosystem. Fil-C has even less, and breaks the ecosystem. Why is the place Rust stops now suddenly good enough?