6 ms·
> We count about 40% of our security vulnerabilities to date to have been the direct result of us using C instead of a memory-safe language alternative. This is
by kpcyrd 2y ago
> We count about 40% of our security vulnerabilities to date to have been the direct result of us using C instead of a memory-safe language alternative. This is however a much lower number than the 60-70% that are commonly repeated, originating from a few big companies and projects.
There has been discussion in an Arch Linux internal channel about the accuracy of these classifications. We noticed many advisories contain a "This bug is not considered a C mistake. It is not likely to have been avoided had we not been using C."-disclaimer, but was unclear what the agenda was and how "C mistake" is defined.
It was brought up because this disclaimer was also present in the CVE-2025-0665 advisory[0], which is essentially a double-free but on file descriptor level. The impact is extremely low (it's more "libcurl causing unsoundness in your process rather than can-be-exploited-into-RCE"), but it's a direct result of how C manages resources. This kind of bug can also occur in Python, but you're unlikely to find this kind of bug in Rust.
Could this bug have occurred with a programming language that isn't C? Yes. Could this bug have been avoided by using a programming language that isn't C? Also yes.
[0]: https://curl.se/docs/CVE-2025-0665.html https://curl.se/docs/CVE-2025-0665.html
- uecker 2y agoThe question is: Could such bugs be avoided in C using the right tools and strategies. And the answer is also often: yes. This is why a large component of the argument for switching to other languages is usually that is impossible to avoid such bugs in C even for experts. But I think this argument, while having some small amount of truth to it, also is partially deceptive. One can not simply look at number of CVEs and conclude this, one needs to compare apples to apples and then I find the reality looks differently, e.g. if a simple mitigation for the bug in C could not be used for some reason, but this reason would also prevent the use of another language in the first place, then using this as argument is misleading.
- pornel 2y ago> Could such bugs be avoided in C using the right tools and strategies "right tools and strategies" is very open-ended, almost tautological — if you didn't catch the bug, then obviously you haven't used the right tools and the right strategies! In reality, the tools and strategies have flaws and limitations that turn such problem into "yes but actually no". Static analysis of C code has fundamental limits, so there are bugs it can't find, and there are non-trivial bugs that it can't find without also finding false positives. False positives make developers needlessly tweak code that was correct, and leads to fatigue that makes them downplay and ignore the reports. The more reliable tools require catching problems at run-time, but problems like double-free often happen only in rare code paths that are hard to test for, and fuzzers can't reach all code either.
- uecker 2y agoStatic analysis of arbitrary legacy code is limited. But I do not find it difficult to structure my code in a way that I can reasonable exclude most errors. The discussion of false positives in C is interesting. In some sense, 99% of what the Rust compiler would complain about would be considered false positives in C. So if you want to have safety in C, you can not approach this from this angle. But this relates to my point. If it is acceptable to structure the code in specific ways to make the Rust compiler happy, but you do not accept that you may have to write code in specific ways to avoid false positives in C, then you are already not comparing apples to apples.
- GTP 2y ago> If it is acceptable to structure the code in specific ways to make the Rust compiler happy I think this is a misleading way of presenting the issue. In Rust, there are class of bugs that no valid Rust code can have (unless maybe when using the "unsafe" keyword), while there is valid C code that has said bugs. And here's the difference: in Rust the compiler prevents some mistakes for you, while in C it is you that have to exercise discipline to make sure every single time you structure the code in such a way to make said bugs unlikely. From this, it follows that Rust code will have less (memory-related) bugs. It is not an apples to apples comparison because Rust is designed to be a memory safe fruit, while C isn't.
- uecker 2y agoThe point was that you can not reject warnings as part of a solution for C because "it annoys programmers because it has false positives" while accepting Rust's borrow checker as a solution.
- GTP 2y agoI think the general point of my comment still stands, as you and everyone else working on the project would need to be disciplined and only release binaries that were compiled with no warnings. And even using -Werror doesn't fully solve the problem in C, as not having warnings/errors is still not enough to get memory safety.
- kpcyrd 2y agoThe blogpost claims they are already running "all the tools", can you please be more specific which one they are missing? Maybe the tool to avoid this kind of lifetime issue just happens to be rustc?
- uecker 2y agoRust is one tool which can be used to avoid life time issues. It is not the only tool. It also only works perfectly only when you exclusively limit yourself to using safe Rust and not use C libraries, unsafe Rust, or not directly use APIs that use integers (I assume in this example, Rust may have special safe wrappers, but in general the language also does not prevent this error). Resource management is something model checkers could verify in C. One could also design a safe API around it in C. Possibly GCC's analyzer could find such issues. In any case, the question is how much effort one wants to invest or not and what tradeoffs the solutions have. A small risk of missing such things may also be an entirely reasonable choice, even so Rust proponents irrationally claim otherwise. For example, curl uses C89 which is certainly not the best choice for safety. It is the best choice for portability to obscure platforms, but this requirement would also rule out Rust.
- kpcyrd 2y ago> It is not the only tool The burden of proof is on you. I asked which tool specifically would have detected CVE-2025-0665 and I don't mean to be mean, but your reply is essentially a very confident "I don't know but I'm sure somebody could build one", while also handwaving the security benefits of programming in Rust. When building a http client library, unsafe Rust and C FFI are not really a problem I'm having.
- uecker 2y agoI already mentioned model checkers one option. There are also other memory safe languages besides Rust. Whether an existing tool finds a bug in an existing C code is a different question though. I would have expected a double close to be found by GCC's analyzer, but I haven't tried for this particular bug (in simple cases it certainly find s it: https://godbolt.org/z/rzr9zT619 https://godbolt.org/z/rzr9zT619). In general, it these tools find bugs more easily in well written C code, but do not help much when code is convoluted. But not writing convoluted code is more important than the choice of programming language anyway.
- pjmlp 2y agoIt would help if since lint was created in 1979, the large majority of C developers actually used the right tools and strategies. In practice only when forced down MISRA like processes people seem to care, versus how relevant secure programming is seen in other programming language communities since 1960's. Secure programming was part of Burroughs and Multics design, so why is the answer from a systems language community designed a decade later, and after 40+ years since the Morris worm, "we don't use right tools and strategies over here"?
- uecker 2y agoI think this is probably the reason why Rust is attractive to companies. They can say, "use Rust and never use unsafe without sign-off from some senior programmer" and then can be relatively sure there is no buffer overflow. In C you would say, you need to follow these guidelines with tools X, Y, Z and then you can be relatively sure there is no buffer overflow. The problem is that this is a striking argument only when the only thing you care about are memory safety issues. As soon as you care about other forms of security / correctness, you need "guidelines with tools X, Y, Z" anyway. And when you have mixed code base, you also need this anyway.
- Galanwe 2y ago> It was brought up because this disclaimer was also present in the CVE-2025-0665 advisory[0], which is essentially a double-free but on file descriptor level I don't see how Rust would have prevented calling close() two times with the same eventfd.
- Munksgaard 2y agoThe same way Rust prevents calling close() two times on a file.
- technion 2y agoThe standard for rust is that close() gets called automatically when the file descriptor goes out of scope. I believe you could choose to do it manually but that's unusual coding.
- Munksgaard 2y agoThere isn't actually any close() function in the std::fs::File: https://doc.rust-lang.org/std/fs/struct.File.html https://doc.rust-lang.org/std/fs/struct.File.html
- ibotty 2y agoAnd if there were one (`drop` kind-of is that function though), it will consume the file handle so you cannot call it again.
- coliveira 2y agoThe problem is not as much C, but coding practices that make it seem like we're still in the 1970s. Codebases like curl use C at a very low level. But C has functions, has structures, has a lot of functionality to allow you to write at a higher level, instead of chasing pointers at each while statement. Code that handles pointers could be abstracted in the same way people will have to do in other languages.
- zxilly 2y agoWhat about performance overheads? I think the reason a lot of people write C is that they have direct control over the generated assembly to maximise performance.
- pjc50 2y ago> they have direct control over the generated assembly They do not. As seen by all the "optimizer has done weird stuff" bugs.
- aaronmdjones 2y agoEven direct control over the generated assembly (C does not provide this; only writing assembler does, and only if you don't do things like LTO) is not sufficient. Modern CPUs do all sorts of weird things. Assembly instructions can be executed out of order. Your conditional jump instruction can be speculatively executed before the condition's truth is known. Fetches from main memory can be reordered. Even more wildly, copying the contents of one register to another is often a no-op. Yes, that's right; the following code: mov edx, eax ... does next to nothing on some modern CPUs. All it does sometimes is set an internal note to the effect of "later references to edx should read/write eax instead", until that note is cleared by some other operation. You can write your assembler with the best of intentions as to how it should behave, only to discover that the CPU does things entirely differently. You still end up getting the same observable result out of it, but any timing and order of operation guarantees went out the window decades ago.
- pjmlp 2y agoBell Labs 1970s, I advise learning about what already existed elsewhere in systems programming languages.
- im3w1l 2y agoFor all it's many faults, even C++ fstreams is not vulnerable to double freeing (and as a partial reply to @Galanwe, they way they avoid issues is runtime checking).
- kllrnohj 2y agoIn C++ you can also make the FD-equivalent of std::unique_ptr, like Android does with unique_fd: https://cs.android.com/android/platform/superproject/main/+/main:system/libbase/include/android-base/unique_fd.h https://cs.android.com/android/platform/superproject/main/+/... It doesn't guarantee the issue never happens, like Rust would, but it does make it dramatically less likely to occur. Also I think in general people vastly under-appreciate how severe an issue EBADF actually is. Outside of extremely specific, single-thread-only scenarios, that error is essentially the kernel telling you that heap corruption occurred, but almost nobody treats it with that level of severity
- pjmlp 2y agoAlready in early 1990s, with C++ARM as the first standard, there were plenty of advantages using C++ instead of plain C. RAII, streams instead of stdio patters, compilers had collection classes for common types (string, array, ...) with bounds checking configuration,....