Hacker News new | comments | show | ask | jobs | submit login

I have no problem with Curl being written in C (I'll take battle-tested C over experimental Rust) but this point seemed odd to me:

>C is not the primary reason for our past vulnerabilities

>There. The simple fact is that most of our past vulnerabilities happened because of logical mistakes in the code. Logical mistakes that aren’t really language bound and they would not be fixed simply by changing language.

So I looked at https://curl.haxx.se/docs/security.html

#61 -> uninitialized random : libcurl's (new) internal function that returns a good 32bit random value was implemented poorly and overwrote the pointer instead of writing the value into the buffer the pointer pointed to.

#60 -> printf floating point buffer overflow

#57 -> cookie injection for other servers : The issue pertains to the function that loads cookies into memory, which reads the specified file into a fixed-size buffer in a line-by-line manner using the fgets() function. If an invocation of fgets() cannot read the whole line into the destination buffer due to it being too small, it truncates the output

This one is arguably not really a failure of C itself, but I'd argue that Rust encourages a more robust error handling through its Options and Results when C tends to abuse "-1" and NULL return types that need careful checking and can't usually be enforced by the compiler.

#55 -> OOB write via unchecked multiplication

Rust has checked multiplication enabled by default in debug builds, and regardless of that the OOB wouldn't be possible.

#54 -> Double free in curl_maprintf

#53 -> Double free in krb5 code

#52 -> glob parser write/read out of bound

And I'll stop here, so far 7 out of 11 vulnerabilities would probably have been avoided with a safer language. Looks like the vast majority of these issues wouldn't have been possible in safe Rust.




I think you are being too generous. All of those 11 vulnerabilities were caused by the language, its lack of memory safety, limited expressiveness, poor abstractions it encourages, etc.


That's not completely fair. It's true that there are some issues I've not counted that could be caused by the "limited expressiveness" of C. Notably the latest vulnerability, #62, which is caused by bogus error checking and would probably not have occurred in idiomatic Rust and could easily be blamed on C's terrible error handling ergonomics (or lack thereof).

However for others it's not immediately obvious how a safer language would've helped, for instance #59: Win CE schannel cert wildcard matches too much

This is clearly a logical error due to a badly implemented standard. There's no silvet bullet here.


The patch that they put out for that issue combines multiple topics into one.

* A preprocessor #if statement is altered with defined(USE_SCHANNEL)&&defined(_WIN32_WCE) which is OR-ed with the other conditions, so that code that was previously not compiled on WinCE is now potentially defined.

* A local buffer in the code is increased from 128 to 256 characters. A comment in the patch refers to a "buffer overread". So there is a C issue in here!

* In a call that appears to be a Microsoft API function CertGetNameString, a flag argument that was zero is now specified as CERT_NAME_DISABLE_IE4_UTF8_FLAG. Unless I don't understand something in the patch comment, it doesn't appear to be remarking on this at all.

* Code that was taking on the responsibility for doing some matching logic is replaced by something that appears to be using proper API's within curl (Curl_cert_hostcheck).

Why didn't the programmer know about the existing function? One possibility is that it didn't exist yet at the time that code was written. Other such ad hoc matching code may have been refactored to use the matching function; this wasn't found. Or maybe the function did exist, but wasn't well documented. A review process isn't in place that would allow someone to raise a red flag "this should be calling Curl_cert_hostcheck and not itself using string matching at all, let alone be checking for a * wildcard and incrementing over it."


How a safer language helps with the non-obvious errors is:

* makes code smaller with less distracting repeated boiler-plate, so things like that stand out more.

* programmers waste less time on fighting language-related ergonomic pains, and so more of their attention is available to spot these errors.

If your language is such that you shout "hooray" and pat yourself on the shoulder when it compiles and the code passes the address sanitizer and Valgrind and whatnot, then actual functional problems will slip under the rug.

Permit me, also, to indulge in some argumentum ad lingua obscura: could we spot in a Brain##### program that some certificate wildcard matches too much? :)


It's easy to see the advantages of memory safety, since it eliminates an entire class of bugs that C programs tend to suffer from, but I think talking about language "ergonomics" is much more tenuous.

One thing that C programmers tend to love about C is that it's simple. C doesn't have that many language constructs and that makes C programs easier to reason about, read and audit, which also makes it easier to find these non-obvious errors. A C programmer approaching an unfamiliar codebase can feel assured that it is made out of the same concise set of constructs as any other C program. On the other hand, modern languages like Rust, C# and Go are a lot more complicated. They have most of the language constructs of C, plus some extra, so not only do you have to understand the concepts in C, you also have to understand things like ownership, variance or pattern matching. Every new feature increases cognitive burden on the programmer. Adding something like exception handling, for example, means that control flow suddenly becomes more complex. Now, instead of only leaving at return statements, a function has a potential exit point anywhere it calls another function. "Smart" features that encourage terse code and reduce boilerplate can also result in code that is totally obtuse to anyone other than the person who wrote it.

I'm not saying that C is at a sweet-spot for language complexity (Brainfuck is probably too simple, but OTOH it only has eight symbols!) I'm just saying that it's important to understand why a lot of developers really like C, and C is often praised for its simplicity. Any language that intends to replace C needs to understand why C programmers use C.


I'd argue that talking about C's simplicity requires one big caveat... it gains that simplicity by delegating a lot of essential complexity to the underlying platform and the spec leaves a lot of details up to the compiler writers.

While I'm sure experienced C developers will be used to that, one cannot simply read The C Programming Language, set a language reference on their desk, and safely learn how C will behave through experience because of all the unspecified or counter-intuitive things which often don't even trigger compiler warnings with -Weverything.

While Rust may have more concepts to grasp and more grammar to remember, I find it far less of a mental burden to code in for the same reason that OOP proponents trumpet encapsulation... C forces me to constantly double-check that I haven't forgotten some detail of the language or GCC's implementation which is sensible if you understand the low-level implementation, but completely counter to my intuition. Rust allows me to audit the heck out of modules containing unsafe code to make sure the unsafety can't escape, then set it aside to think about another piece of the logic.

Rust also places a stronger emphasis on making it possible to reason locally, bolstered by things like hygienic macros.

Finally, simplicity does not automatically make something intuitive. (That's something I hear quite commonly among HCI people bemoaning how Apple has brilliantly used simplicity to convince iOS users that, if they get stumped by the UI, it's their own fault, not a failing on Apple's part.)


> A C programmer approaching an unfamiliar codebase can feel assured that it is made out of the same concise set of constructs as any other C program.

Plus a shit load of user defined macros to make it look like a modern language.

Every major c repo will have their own macros for foreach, cleanup on exit, logerrorandreturn, etc. Another example is extensive use of attributes like _cleanup_ from gcc to simulate raii, not plain c89 at all.


In the future, there will be no bugs because the languages will be so expressive that they cannot be expressed. The only mistakes a programmer can make will be choosingthe wrong language.


> Rust has checked multiplication enabled by default in debug builds, and regardless of that the OOB wouldn't be possible.

That bug is only triggered by an unrealistic corner case (username longer than 512MB). Run-time check in debug build won't help unless you realize the possibility before hand and put a unit test for that. I am more interested in the second part of your comment: "the OOB wouldn't be possible". How does rust protect against such integer overflow caused by multiplication? Thanks in advance.


"That bug is only triggered by an unrealistic corner case (username longer than 512MB)."

What makes you call that an "unrealistic" case? You're probably imagining some sort of world where a randomly distributed set of usernames are sent to this function drawn from the distribution of "real usernames that people use". Since 0 of them are longer than 512MB, you intuitively assume a 0 probability of exploit.

But in the security world, that's the wrong distribution. You have to assume a hostile, intelligent adversary. It isn't hard at all to construct a case of some web service allowing you to specify remote resources accessible with a username & password of your choosing (not corresponding to the username you're using to log in), an attacker specifying one of their own resources, reading the incoming headers to notice that you're using a vulnerable version of libcurl, and stuffing 512MB+exploit into the username field of your web app. If you don't add any other size restrictions between the attacker and the libcurl invocation, they may well pass it right in. (And your same intuition will lead you to not put any size restrictions there; "why would anybody have a multi-megabyte username?" You won't even have thought the question explicitly, you just won't put a length restriction on the field because it'll never even cross your mind.) By penetration standards, that doesn't even rise to the level of a challenge.


I think you missed the point of the post you are responding to. That person understands the value of the bug, and is just pointing out that Rust runs with unchecked math in production and would therefore be just as vulnerable in production. The benefit would come from it running with checked math in debug mode, but you, as the parent posted noted, would have to have a unit test or an integration test that realized this vulnerability to begin with and tried to exploit it.

The use of "unrealistic" was meant in the sense that you wouldn't think of it, not that you shouldn't guard against it once known.


> therefore be just as vulnerable in production.

I think there's two components here: yes, this might lead to a _logic bug_, but it should never lead to a _memory safety_ bug. That is, to get that CVE, you need both, and Rust will still protect you from one.


No, by default rust production builds run without memory safety turned on.

Edit: I believe I confused rust with another language.


What do you mean?


My point is that it is not an unrealistic case. In a security context, it is perfectly realistic.


I agree that the case is perfectly realistic.

> How does rust protect against such integer overflow caused by multiplication?

To me, the question was whether or not Rust would have been able to natively protect against this, considering it was a runtime issue?


To be fair, I would probably also run my security test suite against a debug binary too if I knew this kind of thing could be caught.


In Rust overflows generally will cause a panic. So "2 + 2" will return 4 or panic if you're on a 2-bit system (they provide .saturating_add() and .wrapping_add() to get code that will never panic)

Thus, you could have caused a denial of service by crashing a rust-based Curl, but the crash would have been modeled and just uncaught.


To be clear, Rust only panics from integer overflow when in debug mode. In release mode it will silently overflow just as happily as C.


To be extra extra extra clear:

1. Rust specifies that if overflow happens, it is a "program error", but it is well-defined as two's compliment overflow.

2. Rust specifies that in debug builds, overflow must be checked, and panic if it happens.

In the future, if overflow checking ever has acceptable overhead, this allows us to say that it must always be checked. But for now, you will get a well-formed result.


I'm not a Rust user (yet), but I'm a little surprised that with its emphasis on safety Rust doesn't maintain all debug safety checks in production by default. You could then do some profiling and turn off only those few (or one or none) that actually turned out to provide enough real-world, provable benefit to be worth turning off in this specific piece of code.

Since you would turn them off one at a time explicitly, rather than having a whole set of them disappear implicitly, you would probably also tend to have a policy of requiring a special test suite to really push the limits of any specific safety issue before you would allow yourself to turn that one off.

Obviously, if this occurred to me at first glance, it occurred to the designers, who decided to do it the other way after careful consideration, so I'm just asking why.


Basically, overflow doesn't lead to memory safety in isolation. That's the key of it. The worst that can happen is a logic errors, and we are not trying to stop all of those with the compiler :) Justifying a 20%-100% speed hit (that was cited in the thread) for every integer operation to save something that can't introduce memory safety is a cost we can't afford to pay.

If you want the full details, https://github.com/rust-lang/rfcs/blob/1f5d3a9512ba08390a222... is the RFC, and https://github.com/rust-lang/rfcs/pull/560 was the associated discussion, there is a lot of it.

EDIT: oh, one more thing that may have significance you may or may not have picked up: one reason why under/overflow in C and C++ is dangerous is that certain kinds are undefined behavior. It's well-defined in Rust. Just doing that alone helps, since the optimizer isn't gonna run off and do something unexpected.


Modern C compilers also allow to control the integer overflow behavior. For example, with gcc -ftrapv you can make the program to abort on overflow.


I think you can opt-in the checked arithmetic in release mode if you can stomach the performance cost.

Anyway, the buffer overflow itself is:

>If this happens, an undersized output buffer will be allocated, but the full result will be written, thus causing the memory behind the output buffer to be overwritten.

In Rust's case the output buffer would be returned into a dynamically sized type, probably a Vec<>. Attempts to put more data in the Vec than it can hold would either cause a runtime error or cause the Vec<> to reallocate (which could cause a performance issue, or maybe even a DoS if you could cause the system to allocate GBs of memory, but it wouldn't allow access to invalid memory).

So maybe something like:

    /* XXX potential multiplication overflow */
    let mut buf = Vec::with_capacity(insize * 4 / 3 + 4);

    while let Some(input) = get_input() {
        // Reallocs when capacity is exceeded
        buf.extend_from_slice(input);
    }
So even if the multiplication overflow is not caught it's by design impossible to have a buffer overrun in Rust.


By default rust doesn't allow out of bounds indexing (indexing and iteration are checked, although bounds checks are often optimized out), you have to explicitly write unsafe code to read off the end of an array or vector.


Can you elaborate on bounds checks being optimized out? Is it only in certain situations where compiler can prove that they are completely unnecessary? Otherwise, they can't be relied on, so what's the point of having them at all?


Yes, if the compiler can prove they're not needed, then it can remove the checks. But it has to prove it.



> The simple fact is that most of our past vulnerabilities happened because of logical mistakes in the code.

All bugs are logical errors at various scale.


> Looks like the vast majority of these issues wouldn't have been possible in safe Rust.

Is it not where you're supposed to point to re-implementations or equivalents written in Rust?


> Of course that leaves a share of problems that could’ve been avoided if we used another language. Buffer overflows, double frees and out of boundary reads etc, but the bulk of our security problems has not happened due to curl being written in C.

He addressed all of those points in the second short paragraph. None of those are C vulnerabilities, they were mistakes made on the part of the developers, not the language. Avoidance of problems in a safer language doesn't mean when things happen, it's the language's fault.


> None of those are C vulnerabilities, they were mistakes made on the part of the developers, not the language.

The point of type system features e.g. Option types instead of nulls, or linear/affine types avoiding use-after-free is to make programmer mistakes turn into compiler errors. Nothing more. There is no point in talking about whether or not something is the "languages fault" or not. We know C is like juggling knives. It's a tool. It has drawbacks and benefits. Being widespread and fast are the benefits. Not turning many forms of programmer errors into compiler errors is the drawback. That means the programmer can't make mistakes because they will be shipped. But programmers invariably make mistakes.

The grandparent argued that for the sample of issues he looked at, a lot would in fact be avoided by the type system of e.g. Rust - contradicting the argument in the blog post (could be because of the small sample though).

I think it's perhaps less important to focus on the number of issues of each kind, and instead look at the severity of them. If the kinds of issues avoided by better type systems are typically trivial issues, but the kind of issues coming from logic errors are severe security issues - then perhaps the case for stronger type systems isn't so strong after all. But I doubt that's the case.


You can write Standard ML in C if you so desire and only use tagged unions (which ARE the option types of C).

It is not even that laborious, it just requires some discipline.


Discipline is precisely the thing which shouldn't be required to avoid disaster, if a language is to be well-designed (this is a feature of good design in general).


> He addressed all of those points in the second short paragraph. None of those are C vulnerabilities, they were mistakes made on the part of the developers, not the language.

They are absolutely the fault of the language, given other languages would have made these bugs impossible.

Errare humanum est is hardly a new concept, blaming humans for not being computers is inane, and in fact qualifies for in errare perseverare diabolicum as out of misplaced pride you persevere in the core original error of using C.


I agree with the gist of your comment but I think it's a bit harsh to say that using C for curl was an error. If I can trust wikipedia the original release of the library was in 1997, at the time it was a perfectly reasonable choice IMO.

I doubt the library would have reached its current adoption levels if it had been written in any other language (and I presume an other C library would've taken its place).


You're speaking past each other. You're talking about "blame" in a moralistic sense, but the other posters just mean simple causation -- "would this fault have occurred if a safer language was used?"

The former sense would be useful... if you want to sue someone or something I guess? But the latter is more useful in real life, so that's what we're discussing


Maybe, maybe not: from Wikipedia the first official validation of GNAT (the Free Ada compiler) was done in 1995. So there were already safer languages available.


These are surely C vulnerabilities and contradict the statement regarding zero policy static analysis errors.

#55 -> OOB write via unchecked multiplication

#54 -> Double free in curl_maprintf

#53 -> Double free in krb5 code

#52 -> glob parser write/read out of bound


> These are surely C vulnerabilities

Yes ...

> and contradict the statement regarding zero policy static analysis errors.

Not necessarily - I've found that static analysis tools (Coverity etc.) have limitations, and I'd expect them to find less than half of these kind of bugs - serious fuzzing tends to find more.

The static analysis tool has to work with the type system of the language, and C's type system isn't particularly helpful, so the tool has to find a balance between flagging almost every pointer dereference as "potentially a problem", most of them being false positives, and flagging only the small percentage of actual problems that can be unambiguously proven to be problems with the context available inside a single function definition.

(Just stating generalities, haven't looked at the fixes for these bugs.)


Double free is not necessary a C issue, but it can be also a program logic issue - I expect to have an object, but it's already deleted. So it's one of

1. object should not exist and the second free is incorrect 2. object should exist and the first free is incorrect 3. object existance is uncertain and the second free must somehow check that

Although I can double-free only in unsafe languages, the wrong logic behind it can be the same in safe languages. It just have different consequences.


Of course it is a C issue, in the same sense that ALL logic errors are in the case you describe -- so either C issues do not exist or you did not manage to find the correct definition: for ex OOB access is caused by faulty program logic, and the consequences are dramatic in unsafe languages. That is an issue of the language, despite you being able to compute OOB indexes in any language. Same thing for double free; the language issue is that the result is catastrophic, not that you can write for ex faulty logic attempting a liberation too early, or an extra one. (Because in safe languages, the result is not as catastrophic as in unsafe languages).

That is the whole concept of safety.


Let's take some safe language, e.g. c#

1. object should not exist and the second free is incorrect

In C# I can have to variables pointing to the same object, I null only one of them. The second should be nulled too, but it's not. That's a logic error. So in C# I end up with some object that should not be there, but it is. Which is better - doube-free or undestroyed object - depends on use case.


The big difference that you are overlooking is that double free leads to memory corruption, with undefined behavior of program execution.

It can crash right away, in a few seconds, minutes, hours later, or never and just keep generating corrupt data.

Having a reference that the GC doesn't collect doesn't lead to memory corruption, just more being used than it should be.


> Having a reference that the GC doesn't collect

Using such object is/can be as dangerous.

In fact I find double-free safer because it usually crashes (and in my code I do checks so it almost certainly crashes), while in C# I can happily use such object without knowing it. But as I said, it depends on specific use case.


> In fact I find double-free safer because it usually crashes (and in my code I do checks so it almost certainly crashes)

You don't know what an undefined behavior is, do you ? You cannot be sure it crashes since the compiler is allowed to do anything with the assumption it doesn't happen. It's absolutely legit for the compiler to remove all the code you added to check a double-free didn't happen because it is assuming that's dead code.

See this post[1] from the LLVM blog which explains why you can't expect anything when you're triggering an UB.

[1]: http://blog.llvm.org/2011/05/what-every-c-programmer-should-...


I know very well what UB is and I bet there is not a single big program which does not have undefined behaviour. I even rely on UB sometimes, because with well defined set of compilers and systems, it's in reality well defined.

I was talking in general about "unsafe" languages. I use c++ in my projects and use custom allocators everywhere, so there is no problem with UB there. The custom allocators also do the checking against double-free.


What do you mean by checking against double-free? Either you pay a high runtime cost, or use unconventional (and somehow impractical in C++) means (e.g. fancy pointers everywhere with a serial number in them), or you can't check reliably. Standard allocators just don't check reliably, and thus do not provide sufficient safety.

Anyway, double-free was only an example. The point is that a language can, or not, provide safety by itself. Not just allow you to create you own enriched subset that is safer than the base language (because you often are interested in safety of 3rd party components not written in your dialect and local idioms of the language)

In the case of C and C++, they are full of UB, and in the general case UB means you are dead. I find that extremely unfortunate, but this is the reality I have to deal with, so I don't pretend it does not exist...


> What do you mean by checking against double-free?

I pay small runtime cost for the check by having guard values around every allocation. At first I wanted to enable it only in debug builds, but I am too lazy to disable it in release builds, so it's there too. Anyway the overhead is small and I do not allocate often during runtime.

> Anyway, double-free was only an example. The point is that a language can, or not, provide safety by itself.

I can write safe code in modern C++ (and probably in C) and I can write unsafe code in e.g. Rust, only difference is which mode is default for the language. On the other hand I have to be prepared to pay the performance (or other) price for safe code.

> In the case of C and C++, they are full of UB, and in the general case UB means you are dead.

I doubt there is a big C or C++ program without UB, does that mean they are all dead? I do not think so.

> I find that extremely unfortunate, but this is the reality I have to deal with, so I don't pretend it does not exist...

I do not like UB in C++ too, but mostly because it does not make sense on platforms I use. On the other hand I can understand that the language can not make such platform-specific assumptions. I can pretend UB does not exist with some restrictions. UB in reality does not mean that the compiler randomly do whatever he wants, it do whatever he wants but consistently. But as I said it twice, it depends on use case. Am I writing for SpaceX or some medical instruments? Probably not a good idead to ignore UB. Am I making writing a new Unreal Engine? Probably not a good idea to worry much about UB, since I would never finish.


> UB in reality does not mean that the compiler randomly do whatever he wants, it do whatever he wants but consistently.

There is nothing consistently consistent about UB. The exact same compiler version can one day transform one particular UB to something, the other day to something else because you changed an unrelated line of code 10 lines under or above, and the day after tomorrow if you change your compiler version or even just any compile option, you get yet another result even when your source code did not changed at all.

EDIT: and I certainly do find extremely unfortunate that compiler authors are choosing to do that to us poor programmers, and that they mostly dropped the other saner interpretation expressively allowed by the standard and practiced by "everybody" 10 years ago; that UB can also be for non portable but well-defined constructs. But, well, compiler authors did that, so let's live with it now.


> There is nothing consistently consistent about UB.

Yet, for years I am memmove-ing objects which should not be memmoved. Or using unions the way they should not be used.

> and that they mostly dropped the other saner interpretation expressively allowed by the standard and practiced by "everybody" 10 years ago

Do you have any example?

> that UB can also be for non portable but well-defined constructs.

Do you mean instead of signed integer overflow being UB it should be defined as 2 complement or something like that?


> Yet, for years I am memmove-ing objects which should not be memmoved. Or using unions the way they should not be used.

There can be two cases:

A. you rely on additional guarantee of one (or several) of the language implementation you are using (ex: gcc, clang). Each compiler usually has some. They are explicitly documented, otherwise they do not exist.

B. you rely on undocumented internal details of your compiler implementation, that are subject to change at any time, and just have happened to not have changed for several years.

> Do you have any example?

I'm not sure that compiler did "far" (not just intra-basic-block instruction scheduling) time-traveling constraint propagation on UB 10 or 15 years ago. For sure, some of them do now. This means you should better use fno-delete-null-pointer-checks and all its friends, because that might very well save you completely in practice from some technically UB but not well known by your ordinary programmer colleague - so likely to appear in lots of non-trivial code bases.

Simpler example: behavior of signed integer overflow. (Very?) old compilers simply translated to the most natural thing the target ISA did, so in practice you got 2s complement behavior in tons of cases and tons of programs started to rely on that. You just can't rely on that so widely today without special care.

More concerning is the specification of << and >> operators. On virtually all platforms they should map to shifting instructions that interpret unsigned int a << 32 as either 0 or a (and same thing for a>>0), and so regardless of the behavior (a<<b) | (a>>(32-b)) should do a ROL op. Unfortunately, mainly because some processors do one behavior and others do the other one (for a single shift), the standard specified it as UB. Now in the standard spirit, UB can be the sign something that is non-portable but perfectly well-defined. Unfortunately now that compiler authors have collectively all "lost" (or voluntarily burned) that memo, and are actively trying to trap other programmers and kill all their users, either it is already handled as all other UB in their logic (=> nasal daemons) or it is only an event waiting to happen...

Maybe a last example: out-of-bound object access was expected to reach whatever piece of memory is at the position of the intuitively computed address, in the classical C age. This is not the case anymore. Out-of-bound object access now carry the risk of nasal-daemons invocation, regardless of what you know about your hardware.

Other modern features of compilers also have an impact. People used to assume all kind of safe properties at TU boundaries. Those where never specified in the standard, and they have been dropped through the window with WPO. It is likely that some code-bases have "become" incorrect (become even in practice, given they always have been in theory with the most risky interpretations of the standard, that compiler authors are now unfortunately using)

> Do you mean instead of signed integer overflow being UB it should be defined as 2 complement or something like that?

Maybe (or at least implementation specified). I could be mistaking, but I do not expect even 50% of C/C++ programmers knowing that signed overflow is UB, and what it means precisely on modern implementations. I would even be positively surprised if 20% of them know about that.

And before anybody through them at me:

* I'm not buying the performance argument at least for C, because the original intent of UB certainly was not to be yielded this way, but merely to specify the lowest common denominator of various processors -- its insanely idiotic to not be able to express a ROL today because of that turn of events and the modern brain-fucked interpretation of compiler authors -- and more importantly because I happen to know how modern processors work, and I do not expect stronger and safer guarantees to notably slow down anything)

* I'm not buying the "specify the language you want yourself or shut up" argument either, for two at least reasons: - I also have an opinion about safety features in other aspects of my life, yet I'm not an expert in those area (e.g. seat belt). I am an expert in CS/C/C++ programming/System Programming/etc... and I'm a huge user of compilers, in some case in areas where it can have an impact on people health. Given that perspective, I think any argument to just specify my own language or write my own compiler would just be plain stupid. I expect people actually doing that for a living (or as a main voluntary contributor, etc..) to use their brain and think of the risks they impose on everybody with their idiotic interpretations, because regardless of they want it or I want it or not, C and C++ will continue to be used in critical systems. - The C spec is actually kind of fine, although now that compiler author have proven they can't be trusted with it, I admit it should be fixed at the source. But would have them be more reasonable, the C spec would have been continued to be interpreted like in the classical days, and most UB would merely have been implementation defined or "quasi-implementation defined" (in some cases by defining all kind of details like a typical linear memory map, crashing the program in case of access to unmapped are, etc...) in the sense you are thinking of (mostly deterministic -- at least way more than it unfortunately is today). The current C spec do allow that and my argument would be that doing otherwise (except if the performance price is extremely highly unbearable, but the classical implementations have proven it is not!). So I don't even need to write an other less dangerous spec, they should just stop to write dangerous compilers...


I don't want to jump on the "Rust fixes everything" train, but lifetimes, scope-based destructors and reference counted pointers seem like they help with this sort of thing beyond just preventing literal accesses of freed memory; they can make sure objects are around when you need them and that they're destroyed automatically when you don't anymore.

Of course, you don't get it all for free; you have to wrestle with mutability and lifetimes, which can get hairy.


>Avoidance of problems in a safer language doesn't mean when things happen, it's the language's fault.

Actually that's exactly what it means.

That's the only way people mean "It's because of C". That a safer language would have prevented those classes of errors"

That's regardless whether a more careful programmer would also have prevented them in C too.


Catching these kind of programmers mistakes is exactly what distinguishes a language as "safer". Be it because they are ruled out by the type system, memory system, or at least the run-time checks.


> None of those are C vulnerabilities, they were mistakes made on the part of the developers, not the language.

Would you ever declare a bug to be the language's fault, other than compiler bugs?


Personally, I certainly wouldn't.


A compiler bug isn't the fault of the language though (edit: although on rereading you may not have intended to agree with that part).


Yeah, even I only put that in there as a way to handle cases (like Rust) where there is a single implementation of the language, which makes "implementation bugs" and "language bugs" one and the same.


yeah, agreed.


If the language does not prevent these mistakes from happening where it would be possible to do so, isn't the language at fault?


But you can't have your cake and eat it too. The language does not implement those checks because perhaps they're not so trivially implementable in the language.

Sure, rust is "safer" than C, but is it a better language? that's arguable.


> Sure, rust is "safer" than C, but is it a better language? that's arguable.

Obviously there are more aspects to a language than safety, so I'll give you that, but yes Rust is a better language than C in this aspect.

Don't forget that programming languages are meant for humans, not for computers. One of the primary goals of a programming language is to prevent humans from making dumb mistakes.


> One of the primary goals of a programming language is to prevent humans from making dumb mistakes.

That clearly wasn't the case when C was designed.


I'd wager that using C prevents large classes of errors that would be common place in assembly.

That said, colloquial history seems to say that there were plenty of languages which did a better job at that than C.


> Avoidance of problems in a safer language doesn't mean when things happen, it's the language's fault.

My definition of fault is "what should be changed to prevent such error". And you are not going to change developers.


" ... a programming language designer should be responsible for the mistakes that are made by the programmers using the language. [...] It's very easy to persuade the customers of your language that everything that goes wrong is their fault and not yours. I rejected that..."

- Tony Hoar

I think this is a particularly solid point by Tony Hoare (made during his talk on 'the billion dollar mistake', null).

We find it very easy to blame developers for mistakes that really shouldn't have been possible to make ta all.


I think there needs to be a grain of salt with the billion dollar mistake thing. The really dangerous part isn't dereferencing the null so much as what happens afterwards. If you dereference a null and the program terminates, that sucks. But, you debug it and life moves on. If "something" happens that's a whole different much bigger issue. Rust doesn't have null but you can ".unwrap()" an option type. Which may terminate the program and force you to debug. If memory serves, Haskell doesn't enforce exhaustive patterns. So once again, the program terminates and you're stuck debugging. That's really just life.

Now, I do in general prefer languages "without" null as they generally have more warning about when you can actually encounter null. Though my personal experience says that in sensibly written programs dealing with null isn't that big a deal. Is it a billion dollar mistake? I would have little trouble believing that. However, that would likely make dynamic typing an order of magnitude or two larger in the mistake dept.


The quote wasn't really about null - that's just the talk he was giving. It was about checked array indexing.

I would also say there really is no comparison between null, which escapes typechecking, and unwrap, which does not. But that's not my point, nor is it Hoare's. It's that we blame people for problems that are better solved by languages.




Guidelines | FAQ | Support | API | Security | Lists | Bookmarklet | DMCA | Apply to YC | Contact

Search: