> Using a stricter language helps with reducing some classes of bugs, at the cost of reduced flexibility in expressing a solution and increased effort creating the software.

First of all, those languages do not "help" "reducing" some classes of bugs. They often entirely remove them.

Then, even assuming that any safe language with unsafe regions (Rust, C#, etc) would not give you comparable flexibility at a fraction of the risk... if your flexible, effortless solution contains entire classes of bugs, then there is no point in comparing "effort". You should at least take into account the effort in providing a software with a high confidence that those bugs are not there.

No amount of chest-thumping about how good of a programmer you are and telling everyone else to, "get good," has had any effect on the rate of CVE's cause by memory safety bugs that are trivial to introduce in a C program.

There are good reasons to use C. It's best to approach it with a clear mind and a practical understanding of its limitations. Be prepared to mitigate those short comings. It's no small task!

I am not sure the number of CVEs measures anything meaningful. The price for zero-days for important targets goes into the millions.

While I am sure there can not be enough security, I am not at all sure the extreme focus on memory safety is worth it, and I am also not sure the added complexity of Rust is really worth it. I would prefer to simplify the stack and make C safer.

If that's your preference you're going about it all wrong. Rust's safety is about culture and you're looking at the technology, it's not that Rust doesn't have technology but the technology isn't where you start.

This was the only notable failing of Sean's (abandoned) "Safe C++" - it delivers all the technology a safe C++ culture would have needed, but there is no safe C++ culture so it was waved away as unimportant.

The guy whose mine loses fifty miners in a roof collapse doesn't need better mining technology, inadequate technology isn't why those miners died, culture is. His mine didn't have safety culture, probably because he didn't give shit about safety, and his workers either shared this dismissal or had no choice in the matter.

Also "extreme focus" is a misinterpretation. It's not an extreme focus, it's just mandatory, it's like if you said humans have an "extreme focus" on breathing air, they really don't - they barely even think about breathing air - it was just mandatory so if you don't do it then I guess that stands out.

Let's turn it around: Do you think the mining guy that does not care about safety will start caring about a safety culture because there is a new safety tool? And if it is mandated by government, will it be implemented in a meaningful way, or just on paper?

So there's a funny thing about mouthing the words, the way the human mind works the easiest way to explain to ourselves why we're mouthing the words is that we agree with them. And so in that sense what seems like a useless paper exercise can be effective.

Also, relevantly here, nobody actually wants these terrible bugs. This is not A or B, Red or Blue, this is very much Cake or Death and like, there just aren't any people queueing up for Death, there are people who don't particularly want Cake but that's not the same thing at all.

It will certainly be implemented in a meaningful way, if the consequences for the mining guy are hard enough that there won't be a second failure done by the same person.

Hence why I am so into cybersecurity laws, and if this is the only way to make C and C++ communities embrace a safety culture, instead of downplaying it as straitjacket programming like in the C vs Pascal/Modula-2 Usenet discussion days, then so be it.

At some point, in order to make C safer, you're going to have to introduce some way of writing a more formal specification of the stack, heap and the lifetime of references into the language.

Maybe that could be through a type system. Maybe that could be through a more capable run-time system. We've tried these avenues through other languages, through experimental compilers, etc.

Without introducing anything new to the language we have a plethora of tools at our disposal:

- Coq + Iris, or some other proof automation framework with separation logic.

- TLA+, Alloy, or some form of model checking where proofs are too burdensome/unnecessary

- AFL, Valgrind and other testing and static analysis tools

- Compcert: formally verified compilers

- MISRA and other coding guidelines

... and all of this to be used in tandem in order to really say that for the parts specified and tested, we're confident there are no use-after-free memory errors or leaks. That is a lot of effort in order to make that statement. The vast, vast majority of software out there won't even use most of these tools. Most software developers argue that they'll never use formal methods in industry because it's just too hard. Maybe they'll use Valgrind if you're lucky.

Or -- you could add something to the language in order to prevent at least some of the errors by definition.

I'm not a big Rust user. Maybe it's not great and is too difficult to use, I don't know. And I do like C. I just think people need to be aware that writing safe C is really expensive and time consuming, difficult and nothing is guaranteed. It might be worth the effort to learn Rust or use another language and at least get some guarantees; it's probably not as hard as writing safe C.

(Maybe not as safe as using Rust + formal methods, but at least you'll be forced to think about your specification up front before your code goes into production... and where you do have unsafe code, hopefully it will be small and not too hard to verify for correctness)

Update: fixed markup

The problem is not tools don't exist, lint was created in 1979 at Bell Labs after all.

It is the lack of culture to use them unless there is a goverment mandate to impose them, like in high critical computing.

I agree.

Definitely, but the idea is that its unique feature set is worth it.

Yeah, there are still good reasons to use it.

So use Rust, fine by me.

I might too some day, who knows.

If the language has unsafe regions, it doesn't entirely remove classes of bugs, since they can still occur in unsafe regions.

(Predictable response: "But they can only occur in unsafe regions which you can grep for" and my response to that: "so?")

I suppose the better response is that it removes those classes of bugs where they are absolutely unnecessary. Tricky code will always be tricky, but in the straightforward 80% (or more) of your code such bugs can be completely eliminated.

It's unfortunate that C has so many truly unnecessary bugs which are only caused by stupid overly "clever" exploitation of undefined behaviour by compilers.

Unfortunate, yes.

But what bugs? Suboptimal choices maybe; but any backwards compatible, popular language is going to have its share of those.

The ones GP is referring to all go away when you use -O0. They're completely artificially constructed by compiler writers language-lawyering the language. They were unforeseeable to the people who actually wrote the language, who expected interpretations like "dereferencing null crashes the program" or "dereferencing null accesses the interrupt vector table" and absolutely were not expecting "dereferencing null deletes the previous three lines of code"

Which I would definitely recommend as a strong default.

No matter whether you are using C for "freedom" or "flexibility" of "power", 95% of the time you only need that in a very small portion of your codebase. You almost definitely do _not_ need any of that in, say, the logic to parse CLI arguments or config files, which however is a prime example of a place where vulnerabilities are known to happen.

Which is in the past I would reach out to something like Perl on its heyday, given its coverage of UNIX API as part of the standard library, for anything manipulating CLI tools or config files.

Nowadays pick your scripting language, and if C is really needed, cleanly placing it in a loadable module with all security invariants into that scripting, or managed language, instead of 100% pure C source.

My solution since early 2000's.

Agreed, there's a lot to win from gluing C to a more protected language, I'm a fan of embedding a scripting language.

> Predictable response: "But they can only occur in unsafe regions which you can grep for" and my response to that: "so?"

The situation is both worse than this and better than this. Consider the .set_len() method on Rust's Vec. It's unsafe, because you could just .set_len(1_000_000) and then the Vec would happily let you try to read the nonexistent millionth element and segfault. However, if you could edit the standard library sources, you could add this new method to Vec without touching any unsafe code:

    pub fn set_len_totally_safe_i_promise(&mut self, new_len: usize) {
        self.len = new_len;
    }
This is exactly the same as the real set_len, except it's a "fn" instead of an "unsafe fn". Now the Vec API is totally broken, and safe callers can corrupt memory. Also critically, we didn't write any unsafe code in "set_len_totally_safe_i_promise". The key detail is that this new method has access to the private self.len field of Vec that unsafe blocks in the same module rely on.

In other words, grepping for all the unsafe blocks isn't sufficient for saying that a program is UB-free. You also have to make sure that none of the safe code ever violates an invariant that the unsafe blocks rely on. Read the comments, think really hard, etc.

So...what's the point of all this? The point is that it lets us define a notion of "soundness", such that if I only write safe code, and I only use libraries that are "sound", we can guarantee that my program is UB-free. In other words, any UB in my program would necessarily point to a bug in one of my dependencies, in the stdlib, or in the compiler. (Or you know, in the hardware, or in mathematics itself.) In other other words, instead of auditing my entire gigantic (safe) program for UB, we can reduce the problem to auditing my dependencies for soundness. Crucially, this decouples the difficulty of the problem from the size of my program. This wouldn't be very interesting if "safe code" was some impoverished subset, like "unsigned integer arithmetic only". But in fact safe code can use pointers, tagged unions, pointers into tagged unions, heap allocation/freeing, and multithreading. Lots of large, complicated, useful, real-world programs are written in 100% safe code. Here the version of this story with all the caveats and footnotes: https://jacko.io/safety_and_soundness.html

You still need to audit the safe part for other bugs...

But yes, this is nice and we should (and probably will) have a safe mode in C too.

Usually they can also happen outside, if you did something wrong in the unsafe region.

edit: I'm sorry that my captain obvious moment is turning out to be some truth bomb for some. Please keep downvoting as a way to regain your inner peace.

> if you did something wrong in the unsafe region.

*you or anyone else in your chain of dependencies that use unsafe