> "The glibc DNS client side resolver is vulnerable to a stack-based buffer overflow when the getaddrinfo() library function is used. Software using this function may be exploited with attacker-controlled domain names, attacker-controlled DNS servers, or through a man-in-the-middle attack."
> "The vectors to trigger this buffer overflow are very common and can include ssh, sudo, and curl. We are confident that the exploitation vectors are diverse and widespread; we have not attempted to enumerate these vectors further."
> "Remote code execution is possible, but not straightforward. It requires bypassing the security mitigations present on the system, such as ASLR."
It is time for the bimonthly Internet security meltdown. Again. When they say that "exploitation vectors are diverse and widespread", they really mean it. Patch ASAP. This is a race; it is only a matter of time before criminals start automatically and systematically scanning every server on the internet for this, and you really really want to be patched before that happens.
Thinking a bit more long term, it's pretty clear at this point that we need to expunge all C language networking code from the world, replacing it with Rust or pretty much anything else. That's not sufficient by itself, but it is necessary, or else the periodic Internet security meltdowns won't ever stop.
> Thinking a bit more long term, it's pretty clear at this point that we need to expunge all C language networking code from the world, replacing it with Rust or pretty much anything else. That's not sufficient by itself, but it is necessary, or else the periodic Internet security meltdowns won't ever stop.
It was already clear in 1961 when Burroughs used Algol and C wasn't even born.
It was clear when Hoare stated on his speech in 1980:
"Many years later we asked our customers whether they wished us to provide an option to switch off these checks in the interests of efficiency on production runs. Unanimously, they urged us not to - they already knew how frequently subscript errors occur on production runs where failure to detect them could be disastrous. I note with fear and horror that even in 1980, language designers and users have not learned this lesson. In any respectable branch of engineering, failure to observe such elementary precautions would have long been against the law."
I can quote lots of other security research papers since computers exist.
But then UNIX workstations took over the computing world, bringing C with them.
I think the other thing that changed is that branch predictors are roughly infinitely times better than they were in 1980. The is-this-subscript-ok check should be easily predictable and I wonder if these checks even cost anything in a hot loop on a modern CPU.
After Z80 and Sinclair Basic, my next languages were GW-Basic, Turbo Basic and Turbo Pascal, followed by many others.
The Borland languages like many other that allowed for system programming in those days, allowed for disabling bounds checking, as Borland customers weren't like Hoare ones.
I never felt the need to disable them.
Yes they did had a few ms to the program execution, but nothing that would impact the user experience of the application.
This is one of the biggest problem in our industry, specially among C and its derived languages (C++, Objective-C), to micro-optimize each code line without any sort of measurement how it impacts the final application.
Of course there are cases where it really matters, like HPC, games and such, but those are a tiny portion of the software that gets written every day.
They have a measurable cost, and performance-obsessed developers/users hate the cost. But it's a lot smaller than it used to be, and modern compiler/JIT research is yielding ways to safely eliminate/hoist a lot of the checks.
All this "Let's replace everything with Rust" is extremely childish.
Be wary of making programming languages a religion. The Java zealots did it, and you can see the results where full Java projects are used in places where a 20 line Python script would have been easier.
If you're serious, one of your pet projects should be trying to implement BSD sockets with Rust. Then try compiling some C apps against your implementation. Then do some performance tests showing memory and time benchmarks of your Rust vs libc implementation. Do a blog post on it. Then we'll take you seriously.
The parent actually said "Rust or pretty much anything else", which means any memory safe language.
It just happens that Rust came to his/her mind as the first choice probably because it's been talked a lot about lately on HN and memory safety is its explicit focus.
I see no "programming languages a religion zealotry" in parents post, but I see a lot of bitterness in yours. Perhaps it's time to reflect.
There are tons of vulnerabilities discovered every month. Most of the them are in Java, PHP, javascript, etc. software because that's what the majortiy fo new code is written in. And those languages have features that can lead to security holes, which C doesn't have. For example, PHP supports eval(), JS has cross-site scripting attacks, Java has classloaders, etc. etc. But somehow only the vulnerabilities in C language software get programming language zealots exited. So maybe you should reflect.
Oracle JVM/OpenJDK and .net CLR are deployed in production at basically every profitable company on Earth.
PHP... Is deployed because it's perceived to be easier and friendlier than other religions, and so wins with that popularity war (as MySQL did). Facebook HHVM is another approach. Folks know the likely warts and mitigate to shrink the attack surface by defending deeply from front layers down to backend services.
Postgres was harder to use in the 2000's despite having a clean codebase but got mucb easier to use, in large part due to MySQL, while leading on features inspired by Oracle DBMS and more recently NoSQLs with hstore.
Attacking popularity for what gets the job done is moot because defense is never ending vigilance for anything real.
Perhaps the focus should be on starting to formally-verifying core libs like zlib, OpenSSL, OpenSSH (portable), glibc, etc. for correctness and resilience against side-effects and ABI promises.
Well you kindof sound like a performance zealot. Burn some cycles, stop running unmanaged code, just take the hit. It should be justifiable if a Rust implementation of a library is 5-10% slower than its C equivalent. How can choosing to build and deploy unsafe code in today's legal/political/financial environment be defensible? Is it going to take public shaming and frogmarching developers to jail for our industry to make a change?
We need to discredit performance zealots and gently herd them to a profitable careers in videogame development where they can't do much harm.
And, there's plenty to dog on Java about, but that language and VM very intentionally makes it an ordeal to load a 3rd party binary blob. Modern development systems/VM's seem to have waved off concerns about unmanaged code and security; Node.JS compiles and loads binary blobs from dependencies all willy-nilly. Java took and takes security seriously.
Depending on what you're doing, it might even be faster, not 5-10% slower. We're currently sometimes faster, sometimes slower, but we should never be slower by a huuuuuge magnitude, though of course, bugs do exist.
I've been excitedly watching this over time; I think in a few years Rust could just be straight-up faster than C the majority of the time, depending.
I think you're right with all the new opportunities for optimization in Rust.
I wanted to encourage developers to be open to compiled Rust being nowhere-near an order of magnitude slower than equivalent compiled C and still have it be an acceptable architecture choice for systems-programming.
Absolutely. I think trading a bit of speed for a bit of safety is the right choice 99.99% of the time. I've just been excited to watch the benchmarks improve :)
It doesn't have to be a big rewrite of "everything" to Rust. It would at the very least be a slow successive rewrite whenever a major change is needed.
For this particular piece of code one would hope that the fix we'll see now would also include around 100 new lines of comments, splitting of hundred-line functions into a dozen smaller ones etc. I suspect that just by doing that someone would notice another flaw of this magnitude. Also, the updated code would be easier to maintain or rewrite in the future. The whole idea that all changes are kept as small as possible because of concerns that refactoring might break something says something about how brittle it is and how much of the testing is just battle hardening and not proper tests.
> It doesn't have to be a big rewrite of "everything" to Rust. It would at the very least be a slow successive rewrite whenever a major change is needed.
That's why the suggestion to implement BSD sockets first is an interesting one. It's a non-trivial interface (which is important to be a useful proof of concept), but it's widely used, relatively well-defined, and could potentially be plugged into applications without replacing other pieces (though that may turn out to be tricky). If it works, you could start implementing more native interfaces that way. If it doesn't, maybe we'll understand better why C remains so dominant for system libraries. (Personally, I don't think it's just inertia or the cost of rewriting all that software, but that's why I'd like to see a project like the one suggested.)
I think you're misreading what he says. "...replacing it with Rust /or pretty much anything else/" (emphasis own). The point is we should be replacing C with /pretty much anything else/. Rust looks like the most obvious (but not necessary correct) candidate.
Also performance is not such a big deal as dictate security considerations. One's fancy medical web app is worthless if it leaves the potential for theft of medical data.
But why is it religion to look at all the problems caused by C code and say, let's try something that doesn't have these problems? Why isn't it religion to look at that history and say no, it's okay, let's keep using C?
The overall goal of reducing deployed LOC, complexity and low-level boilerplate is laudable and doable so by using a C isomorphic linking language (can call into and can call from) to gradually, experimenting by converting one module at a time and increasing tempo of semver API refactorings for also reducing code and features to the essentials. "The journey of a thousand miles begins..."
Also, we need more unit testing, fuzzing and formally proven correct libraries (a-la seL4). Most C development is really bad about unit testing and fuzzing because it's incredibly laborious and verbose, even when it's known to be a good idea.
You are missing the forest for the trees. Grandparent is saying "replace all C with Rust" and you replied with "but look, Rust programs rely on glibc! It's unsafe!"
Yes, Rust programs can be affected by this. But only because they call into C at a lower level, for the libc layer. If glibc was in Rust it would provide security to all application, including ssh or curl which aren't in Rust.
Also, extern "C" is for C FFI exports not calling C APIs. The world unanimously decided C FFI is the least common denominator, so its not like Rust can provide a different API with better safety hints.
Or perhaps he made a more subtle point: you can't replace all C with Rust. Even if you rewrite the libc (you'll need some assembly), even if you rewrite the kernel (you'll need a lot of assembly and "unsafe"), there will still be lots of closed-source C running on undocumented components of your computer as firmware. C is just representative of computers as they really are today, for a bunch of reasons (mostly pragmatism) unrelated to the fact that some people still like C.
Just as an ironic aside - C enthusiasts may even have switched to musl-libc already, and are not affected by bugs in glibc, while rust-loving people are vulnerable!
Also interesting, Go has for a long time supported using its own dns resolver instead of libc, but only recently in go 1.5 uses the go dns resolver by default on unix systems.
Are you arguing that since we can't replace everything with safe code, we shouldn't replace most of it? Security is not a binary state. Even taking firmware into account, if you compare the number of CVEs affecting "normal code" and firmware, the choice is trivial.
> Are you arguing that since we can't replace everything with safe code, we shouldn't replace most of it?
Well, yes, the effort to replace even a small part of current code base with something perceived as "safe code" is huge. The re-factoring effort is so great that there is no doubt a significant number of new seurity problems wouldn't be created.
They would of course be different kind of problems. Not indexing errors, perhaps, but something we don't even know about. And justifiying this very large cost is not so easy.
The analogy is wrong because most all languages are Turing complete, and therefore equivalent despite paradigm and feature differences. The main reason seL4 was written in Haskell and again in C was to cross-verify behavior. C might be legacy ubiquitous, however it doesn't have to be the last word in shared library development by any means.
Any language which has a toolchain which produce flat binaries or use link scripts to specify sections can potentially be used for kernel and os dev. Bonus points for a toolchain which can compile itself.
There are kernels written entirely in Rust with some assembly glue. Others are writing production embedded domain-specific OSes in Haskell, OCaml, etc. for real, mission-critical systems right now and have been doing so for the last 5-10 years.
C inherently or inadvertently leads to giant codebases and subtle bugs when used too liberally, as opposed to higher-level, statically-compiled languages like Haskell and others.
The main issue with legacy code is how we choose to either keep investing effort in putting fingers into the levees or try something new in an incremental fashion.
PS: I would like to see Go get with it and support non-usermode, self-hosted and embedded development without including a giant, hard-to-port runtime in everything.
Do some low level Rust programming and then do some low level C programming. It's hardly any safer, because it can't be safe at such a low level without a performance penalty. In the capitalist reality we live in, that is a cost no one will pay.
You're talking about the forrest - while we're talking about how making trees from scratch is difficult and messed up no matter how you go about it.
At some point, you have to talk to the computer directly (aka "unsafely"). Things like bounds checking, garbage collection, and other runtime utilities sound like great ideas until they translate to significant monetary costs each month.
Not everything is as trivial as writing GUI glue code in a nice comfy language with a magical framework. Although, most of it is.
>
Do some low level Rust programming and then do some low level C programming. It's hardly any safer, because it can't be safe at such a low level without a performance penalty. In the capitalist reality we live in, that is a cost no one will pay.
I do a lot of low-level Rust programming and a lot of low-level C programming. I don't know what the performance penalty you're claiming exists comes from. Can you elaborate as to the specific Rust features that you claim are slow?
(Incidentally, the project I'm working on right now in Rust beats all of the existing C competitors in performance by a lot.) :)
A little defensive, maybe? I never claimed Rust was slower than C or that it had a performance penalty (because you should use unsafe). Actually, I pretty much said the opposite. I said they were the same.
I was trying to highlight the fact that unsafe Rust and C are not that different when you're doing bitwise operations on bare metal.
I was wanted to drive the point that additional abstractions are undesirable at that low level unless they can be enforced at compile time. I'm concerned by some of the magical thinking being espoused in this thread about what's actually reasonable in terms of the trade offs you can make.
In other words I was talking about safety, not speed.
I'm glad to hear Rust is out performing C though. I just wish I had a better IDE than atom...
Didn't you once mention Xcode support for Rust?
It's not defensive to ask for evidence of a claim. If your statements are too general to be backed up without narrowing the scope or clarifying your intent, it's hardly the other person's fault for requesting clarification or evidence.
pcwalton is talking about safe rust. Unsafe rust is not really any faster in a lot of cases, and even when it is the goal is to compartmentalize the unsafe code into very small, distinct, easily testable segments.
Again, I'm not talking about performance. I'm talking about low level safety and the difficulties in achieving that in any meaningful way. I wasn't talking about just Rust when I said abstractions cost you speed. It was a (perhaps unclear) general statement about programming. As an aside, unsafe rust is faster than safe rust for a lot of cases.
I re-read what I wrote and I fail to see where I attacked or even (God forbid) criticized Rust.
So far IME, the rust community is extremely defensive and antagonistic to anyone they perceive to have made the slightest criticism. It's immature and irritating. It's the only thing that really turns me off from fully embracing it.
I can't participate in a community that's fundamentally hostile to skepticism.
But previously you said "because it can't be safe at such a low level without a performance penalty" which clearly implies performance is part of your argument. This is what I was talking about in my other reply, which mentioned "without narrowing the scope or clarifying your intent".
> I re-read what I wrote and I fail to see where I attacked or even (God forbid) criticized Rust.
This is more about backing up claims with concrete examples. pcwalton is a Rust developer, I'm sure he would be very happy to hear a specific shortcoming he wasn't aware of, or was undervaluing. That's how improvements are made.
> So far IME, the rust community is extremely defensive and antagonistic to anyone they perceive to have made the slightest criticism. It's immature and irritating. It's the only thing that really turns me off from fully embracing it.
There may be some of that, but I also see a lot of rust community members trying to explain where people's assumptions are wrong, and it being taken as being antagonistic. The fact that Rust has some compile time checking that could actually make some of the low-level code faster is what I think is being alluded to by pcwalton. Of course there will be things that rust won't catch, and will need to be done in both C and rust, and there's things that other languages may catch that rust doesn't and vice-versa.
> I can't participate in a community that's fundamentally hostile to skepticism.
That's an interesting way to put it. You've made assertions, and when asked for clarification or evidence, began your response by calling the other commentor's motives into question (is it a reasoned response, or was it overly emotional because they were "defensive"). Asking for evidence of assertions is skepticism, and to balk in face of requests to do so is actually what I would consider being defensive.
To be absolutely clear, I don't think you would have been downvoted if you didn't start your reply with "A little defensive, maybe?". I didn't downvote you, but my initial assumption was that the downvoting I saw was because of that.
I re-read this and I see where I went wrong. I thought I had made a general statement about the cost of memory safety abstractions in programming languages that was misinterpreted as a specific statement about Rust and safety in general. This was because I was not clear in my phrasing.
I guess I felt I was being attacked for a point that I wasn't even trying to make. That is, I was responding to you under the impression that I had clearly said something differently than what was interpreted. All without taking the time to read over my own post carefully. Then I got overly emotional which resulted in us talking past each other unproductively and rude remarks on my part.
So I'm sorry for being a melodramatic jerk yesterday. I am actively working on my self control and civility, but I definitely made a mistake here. Hope you have a good day.
> Notice how you're defending criticisms I never made.
I'm not defending anything, You didn't supply clarification of your point. You were asked for clarification, and provided someone else's experience (anecdotal) counteracting claims (which weren't even anecdotal, they were unsupported assertions), and you started out as classifying that response as defensive.
> notice how you assume I don't know the difference between static analysis and compile-time safety.
I said "I also see a lot of rust community members trying to explain where people's assumptions are wrong" immediately prior to that. I thought it was obvious I was talking in the general sense. I don't know what you do and don't know, but I'm not sure why you think it's valid to be upset because someone assumes this one way or the other (which to be clear, didn't happen here) when you've shown little or no evidence yet as to what you do or don't know about it. It may be the rust community stereotyping others, but without stats as to whether the people the stereotype has been applied to have it applied correctly it's hard to know whether it's useful or harmful, regardless of how it applies to you in particular.
> Notice how you assume that I must be generally uninformed or less intelligent because complete faith in Rust's importance is a self-evident truth.
Not knowing something doesn't make someone less intelligent. If you are conversing under the assumption that everyone will assume you know at least the same amount as them about every topic that is brought up, else you have right to be offended, I'm not sure how we can continue. That's not an environment I want to have a discussion in.
> Well, now that's it's happening to me personally vs just reading it. Fuck this community. It's collectively a socially inept nightmare. I'm done with caring about Rust. I was really enjoying it. After today and Reddit, I can't take anymore of this smug bullshit.
All that's happened here is you made statements and refused to back them up while at the same time you called someone else's motives into question for asking for clarification. I think it's obvious what is going on, but I'm not in your shoes. I don't think people aren't trying to be assholes you you (at least I'm not, or not trying to be one), and while you may be upset, I hope you don't leave for good. however you've interpreted my comments, please be aware that not a single one was written with the intent to upset you, or call into question your character or intelligence in any way. I can only point towards my own experience, here and elsewhere, where my state of mind greatly influenced how I read a statement and any replies to me with regard to that conversation, and think that possibly the same thing is happening here with you. That itself may come across as a smug asshole-ish thing to say depending on how you interpret it, or even my assumption that my observations apply to you. All I can do is state that my desire to be helpful after your prior statement is sincere.
> ...it can't be safe at such a low level without a performance penalty. In the capitalist reality we live in, that is a cost no one will pay.
A bit higher up:
It was clear when Hoare stated on his speech in 1980:
"Many years later we asked our customers whether they wished us to provide an option to switch off these checks in the interests of efficiency on production runs. Unanimously, they urged us not to - they already knew how frequently subscript errors occur on production runs where failure to detect them could be disastrous. I note with fear and horror that even in 1980, language designers and users have not learned this lesson. In any respectable branch of engineering, failure to observe such elementary precautions would have long been against the law."
Rust sacrificed the majority of its runtime and adopted C's memory model late in development so people wouldn't have to pay a performance cost or depend on confusing heap allocation semantics. That's why it has a really good chance at disrupting the embedded space.
The enterprise software world didn't leap off C++ and onto Java because it was faster. They did it because it didn't segfault all the time. They then leapt to Perl and Python and Ruby because they didn't segfault all the time and seemed a lot faster to develop in.
To the vast majority of people and organisations writing software, performance of the software, to within an order of magnitude, just isn't a big deal.
We aren't talking about enterprise software. It hasn't been mentioned once. You can't change the subject and then say I'm wrong. Obviously, higher level applications can and often should be implemented using garbage collected languages.
Are they? We have no way to know as no modern language has the reach and widespread usage of something like C.
As much as I'd like to think Rust is a nice option for the future, everything leads me to believe that if it ever gets as widespread as C you will have the same caveats, because the main issue is not the language but the programmers using it.
No amount of hand holding and safety will prevent bad practices from being used when they prove efficient in one way or another (laziness is a real killer).
> We have no way to know as no modern language has the reach and widespread usage of something like C.
None needs to have that widespread usage for us to see this. Many bugs in C code causing big security issues are impossible to make in more modern programming languages. Sure, you can make other security issues, of course you can. But the net result is less security issues.
> the main issue is not the language but the programmers using it.
All bugs are caused by programmers making mistakes, but that doesn't mean the language isn't an issue. A language which requires more effort to write correct and secure programs is an inferior language. A language with a long list of situations where the compiler will silently invoke unpredictable behaviour, a list any programmer will sometimes fail to fully account for, is an inferior language.
If targeting home computers, the so called mini-computers and home micros, yes. C was just yet another "managed language".
If targeting real computers, what is nowadays left as mainframes, then no.
Extended Algol was already being used by Burroughs in 1961 for writing Assembly free OSes.
There are lots of other examples, which C advocates tend to ignore, so that they can sell the story that before C there was only Assembly to those that don't bother with history of computing.
No, you're not getting it :). There was a whole world besides Assembly and C, now mostly lost to us. E.g. Lisp machines, which even had hardware support for Lisp-specific things like tagged pointers.
The initial port of Genera to an Alpha matched the performance of Symbolics fastest workstations:
http://pt.withy.org/publications/VLM.html
"Version 1.0 of the emulator exceeded our initial performance goals, achieving nearly the performance of our high-end custom workstation."
An initial port to a foreign architecture (in-order execution, 64-bit memory space which is going to overload caches, inefficient 8-bit transactions) matched 15+ years of hardware development on the native architecture.
That wasn't lack of money or development. That was a stunning rebuke that Lisp Machines had an inferior architecture.
> C compiler performance in the 80's for home micros was so bad, that functions were basically naked entry points to full inline Assembly.
For good reason: memory and disk were ferociously expensive until about 1996-1997.
Things like unchecked bounds make sense when you can barely get your computer to do what you want. Designing a VLSI chip through about most of the 1990's was HARD. Everything was an exercise in how to fit things on disk, fir things in RAM, run enough polygons, or run enough instructions on the emulator to actually boot.
Now: we can emulate a 6502 in Javascript while animating it. We can emulate entire PowerPC chips at real-time speed. So, yeah, NOW allocating a couple of cycles to bounds checking and garbage collection makes perfect sense.
We should have started the conversion away from C in 2000. The problem is that 1) sunk cost keeps design decisions made in vastly different environments around for far longer than is reasonable and 2) we didn't really have good replacement options for C until starting about 2005.
And then the cellular phone revolution hit and put us back to programming like its 1995. Finally, now that cell phones even have an overabundance of CPU, storage, and memory--we now can start to care about security.
You already have the freedom to run your webservers as mirageos unikernels (with a networking stack written in ocaml). You already have the freedom to write and run everything you want in such an environment.
But what you really want, is to force other people to write code for you, but in a language you prefer (and for it to be easy and convenient for you). Good luck with that.
> it's pretty clear at this point that we need to expunge all C language networking code from the world
Will you bell the cat? I'd pay a lot of money to see Linus' reaction to your massive network-stack replacement kernel patch written in Rust.
More seriously though, who is prepared to fund this? Is the risk worth that much effort? Because I see a lot of people being vocal about it on HN, but no real attempt is made to come up with a safe Rust-based alternatives
It likely won't help. I excluded glibc from using ASAN to let this work.
It is theoretically possible to use ASAN on glibc as well, but it's complicated and certainly not ready for any kind of production use.
Also although this is my work, it's far from clear to me whether using ASAN in production really is a useful thing. Practically I'd rather suggest looking at the safestack and CFI features of clang for production use instead.
But this is hard for them to 'scan' for. As they need to get vulnerable systems to query their dns server.
But it's not hard to get a system to do a dns query. Sometimes even connecting to it will cause your ip to appear in the logs which are then sometimes reversed ip. But one could easily just create a webpage with img.src pointing to the host.
DNS being distributed could halt the attack upstream by patching their servers to stop the queries so any vulnerable clients won't get compromised even if they make the query.
> DNS being distributed could halt the attack upstream by patching their servers to stop the queries so any vulnerable clients won't get compromised even if they make the query.
Anybody connecting to a public network that pushes network settings may still be affected. For wifis with a capture portal (hotel wifi etc) you usually have to use the network-provided dns server.
I don't think a reverse query will tickle this bug, because this bug requires simultaneous A and AAAA lookups to be triggered whereas a reverse query uses a single PTR lookup.
The fact that djbdns doesn't use BIND code is not really relevant. The problem at hand is in the BIND DNS client library that is compiled and linked into applications. It's triggered by sending responses larger than 2048 bytes to those applications. The only way that djbdns factors into it is whether an attacker, running a content DNS server out on Internet, can manage to get such a DNS response through a DNS proxy server such as dnscache, to that BIND DNS client library code running in the application.
Now one might think that the discussion at https://news.ycombinator.com/item?id=11110646 already covers this. All that the content DNS server has to do is set the TC flag in the DNS/UDP response, and it can quite happily get the proxy DNS server to re-try via DNS/TCP. The interesting point is that in fact that discussion does not cover this.
This is because the idea that one can exploit this with a malicious content DNS server has as its unstated premise that DNS/UDP and DNS/TCP responses are passed straight through caching DNS proxies. This is the case for some DNS proxy softwares, where if the data have just come in from the back end and form a complete answer, they are passed through as the front-end answer. It isn't the case for dnscache. dnscache always answers from cache, even if the data have just arrived from a back-end transaction.
So those "crafted DNS responses" beginning with 2048 bytes of valid protocol, that the malicious content DNS server has to generate, have to survive being unpacked, stuffed into dnscache's cache, extracted back out of dnscache's cache, and turned into an answer response by dnscache's from-scratch answer construction code. And this without being able to use the authority or additional sections, without being able to use more than one (non-CNAME) RRset in the answer section, and without any guarantee of any given RRset ordering. This is ... somewhat difficult.
The devil in the details of this problem lies in one sentence of the analysis that accompanies the patch:
> Overflowing bytes are entirely under the control of the attacker and are the result of a crafted DNS response.
If you've done as the people at https://news.ycombinator.com/item?id=11112527 and https://www.reddit.com/r/netsec/comments/462xx0/glibc_getadd... did, you might have convinced yourself how simple it is to exploit this. Just set up a malicious content DNS server that does what this does, and Bob's Your Uncle. But in fact those demonstrations are not how DNS lookups work. People do not point the BIND DNS client library at (malicious) content DNS servers. The DNS client library is pointed at the front end of a proxy DNS server, which performs back-end lookups to the (malicious) content DNS servers.
So one has to take into account what the proxy DNS server does in between its back end and its front end. The response returned to the BIND DNS client library from the front end is not "entirely under the control of the attacker". As in the case of dnscache, where front end responses are constructed from cache and from scratch, it's actually quite difficult for the attacker to control it at all, let alone cause dnscache to send a valid-flawed-valid response sequence. It's not just dnscache where this is a difficulty for an attacker, either.
Which is why the analysis talks of man-in-the-middle attacks. To exploit this with far greater chance of success, one gets in between the BIND DNS client library and the proxy DNS server, or just plain supplants/subverts the proxy DNS server. Which is yet another reason why it's a good idea to run a proxy DNS server locally, on each machine or at least on the LAN, and make sure that "local" traffic is blocked at relevant machine/LAN borders. An attacker who can field DNS traffic to [::1]:53 or 127.0.0.1:53 in order to be that man in the middle has already had to overcome some quite tricky obstacles. (-:
"The fact that djbdns doesn't use BIND code is not really relevant."
It is to me.
It's true that in this case it's not what mitigates this vulnerability. Although it has certainly mitigated many others over the years and, sadly, probably will do so a few more times in the future. There's just no getting rid of the BIND legacy.
Correct me if I am wrong, but using a local dnscache and the fact that dnscache does not implement ENDS0 should be enough to mitigate this one.
I have been running a local tinydns root.zone and dnscache for many years. Really like the software.
What exactly do you patch? Say you have a TCP server, written in Python or in Go. Do you have to update Python and Go ASAP then and recompile the Go server? Custom compiled Apache, Nginx? Recompile those? That's a lot of work..
The past four years of modern development practices have run screaming from the concept. Learning why the systems used to work they way they did was, like, super hard, so they reinvented the wheel and are running into the same problems that caused us to make shared libraries/COW/etc in the first place.
You've pretty fundamentally misunderstood them if you think immutable practices in anyway damage the ability to deploy fixes. Years of shared library hell is what causes things like this to go unfixed.
Immutability in software development & deployment is, for the vast majority of use cases, completely unnecessary. The only reason people talk about it now is everyone has a fetish over using simple software rather than designing software that works well.
And 'immutable practices' don't damage anything - they simply make things take longer, duplicate effort, make inefficient use of resources, and make everything generally more inflexible. The only benefit seems to be high level abstraction at the cost of everything else.
Nope, the pendulum is at it's other extreme. Go is going all in on static builds, docker containers freeze ALL THE THINGS \@/ and "immutable" package managers like Nix which in effect is like static linking without the speed benefits.
I updated this morning and nothing got restarted. Shouldn't pretty much every network service I have gotten a restart? sshd, postfix, apache, icecast2, dovecot
None of them restarted. So I restarted them all myself...
For Go, depending on your compile settings, you don't need to recompile. Go 1.5+ uses a pure Go DNS resolver [1], you have to force Go to use glibc. And even if you use glibc, it is not statically linked.
If you have dynamically linked libc (which you should), you only need to upgrade libc and restart all network processes. [Does anyone know if systemd in pid1 calls getaddrinfo? If so, this is another good reason for a minimal pid1, as you will need to reboot]
daemon-reexec
Reexecute the systemd manager. This will serialize the manager
state, reexecute the process and deserialize the state again. This
command is of little use except for debugging and package upgrades.
Sometimes, it might be helpful as a heavy-weight daemon-reload.
While the daemon is being reexecuted, all sockets systemd listening
on behalf of user configuration will stay accessible.
... etc. And yes, it can be a lot of work. It's easier to only use your distro's official packages and update the patched ones they release. Luckily most software devs are smart and compile dynamically.
There are a few interesting points in this discussion that all of the C/Rust/C++/ObjectiveC/Go/Java discussion has swamped. For those who would like to find the discussion that is not argument about computer programming languages:
* What happens if one's DNS proxy server doesn't itself support sending >2048 byte answer messages to the BIND DNS client library in the first place? Sub-thread at https://news.ycombinator.com/item?id=11111617 .
I'm not sure, but I suspect that major browsers may have their own DNS client implementations, or be calling getaddrinfo from within sandboxed contexts.
"The problem is that Chromium is issuing too many DNS queries in a short period of time, which may overload upstream devices (typically cheap NAT routers) ... which will then ignore DNS queries for a period of time."
While it is only necessary to ensure that all processes are not using
the old glibc anymore, it is recommended to reboot the machines after
applying the security upgrade.
On Debian there is also the tool checkrestart available, it is part of the debian-goodies package. This might be useful if a reboot is (currently) not possible.
apt-get install debian-goodies
Then you can run:
checkrestart
And it will list services which require a restart. For example:
service sudo restart
service ssh restart
service cron restart
service ...
On Debian 8 (Jessie) i had to restart the systemd services as well:
Just out of interest: How is this classified medium by Ubuntu? If someone manages to bypass ASLR this is a remote exploit on every SSH server running glibc out there? Or did I misunderstood something?
Out of the box SSH seems to use getaddrinfo:
reverse mapping checking getaddrinfo for <host> [<ip>] failed - POSSIBLE BREAK-IN ATTEMPT!
Does "DNE" mean there's no patch coming at all, or none yet? What about pending?
Also, why is all this important and useful information scattered all over the place? Ubuntu people, if you're reading, on this page: http://packages.ubuntu.com/trusty/libc6 I think you should have a history of updates to the package, and which security vulnerabilities it covers, with a link back to the above linked page. Looking at this page, looking at my system, looking at the news, I kind of have no idea what's going on.
I was going to complain that this people.canonical.com CVE page should also be linked from some sort of Ubuntu security bulletin, but I googled first and found that that's exactly where it's linked from. Still, I somehow reckon I would have never thought of that if not for this conversation. It should perhaps be linked from the packages page, though I'm not a UX guy, maybe that would just add confusion. But, this CVE page, with the domain "people.canonical.com", linking to launchpad pages, it doesn't look like it's made for users. You could make some improvements to make it more user friendly like the packages page. You could make it explicit: "Here's the packages you need to upgrade. here's the command to run to upgrade your whole system. Here's the command to upgrade just this package." As it stands, this doesn't even tell you what binary packages to get (libc6, I think? I'm still not even sure!), this just lists the source package (glibc). And, change "DNE" and "pending" to indicators that are more clear to us. Or, make a new page if this one is not meant for users.
For security stuff (not just Ubuntu by any means), all sorts of useful information is scattered all over the place. To name a few questions that I never determined clear answers to:
* What about this Android MMS attack that was supposed to be catastrophic and so easy to mount, that supposedly like half of all Android users are still vulnerable to? We've not heard of a single actual exploit in the wild. Where's some sort of official source downgrading its severity?
* If the libpng thing was such a fiasco, why were there no updates in Ubuntu for so long? Did it turn out not to be such a serious attack vector? Where's the documentation on that determination?
* For heartbleed: I recall having to poke around looking at the "built on" date for my openssl packages to see whether I was upgraded or not. That's not an acceptable state of affairs.
I'm not looking for answers to these particular questions now, I'm just wondering, why do we all have to rely on rumors and advice from random people in discussion forums for this stuff? There's a recent trend of understanding that improved UX can improve security (Signal, etc). Here's a good place to make some improvements, IMHO.
Sorry if I've come off as entitled here, it's just a source of irritation to me.
No, they were talking about marking glibc-2.22 as stable i.e., removing the tilde from the arches in the KEYWORDS of the ebuild.
glibc-2.22-r2 was released at the same time as glibc-2.21-r2 and it contains the fix for this issue. The Changelog merely says "misc upstream fixes" but I verified the relevant changes are there.
Can we agree that it's urgently necessary to rewrite most of the core Linux/OSS stack in memory safe languages? Exploits like this come up all the time, and we know how to completely eliminate them. I don't care if it's Rust or D or Go or Haskell or OCaml or anything else, as long as it's not C. The sooner we do this, the better.
"memory safe" languages aren't a silver bullet and urgently rewriting core libraries in a new language can be more dangerous than our current process of analyzing and fixing the existing vulnerabilities in our current libraries.
However I'm all for experimental research projects developing alternative solutions.
Indeed. But my point was there's lots and lots of ways code can have bugs. Vulnerabilities like these are serious, but rushing through a reimplementation in another language is bound to fail in a multitude of other ways.
What we need is not to have knee-jerk reactions akin to angry mops carrying flaming torches and sharpened pitchforks. What we need is to praise the excellent work researchers are doing and to offer our time and/or money into:
a) more security research,
b) development of experimental alternatives written in memory safe languages,
c) yet more security research except this time on the experimental counterparts,
d) thorough testing of the experimental counterparts to ensure functional compatibility.
Maybe a few years down the line we will have something usable in a few bleeding edge Linux distributions, but it will be even longer still before it will have the backing of Debian, RHEL and SLES.
Apparently until the UNIX culture that worships C exist, it won't happen.
C only became a thing in computing after UNIX workstations became popular.
Before it, there were quite a few safer systems programming languages to choose from, that got steam rolled by C adoption in OS SDKs.
Due to its relation with UNIX, it will most likely happen with Apple (Swift), Microsoft (.NET Native, System C#, C++/GSL), Google (Java FTW on Android, Go) than any other OS vendor.
But I am also looking forward any of them pick D, Rust, <whatever safe lang> in the process.
I also feel the pains that Android teams imposes on the NDK users to use it as little as possible.
If you bothered to read the contents of the link that you pushed.
"Notably, using native code on Android generally does not result in a noticable performance improvement, but it always increases your app complexity. In general, you should only use the NDK if it is essential to your app—never because you simply prefer to program in C/C++. When examining whether or not you should develop in native code, think about your requirements and see if the Android framework APIs provide the functionality that you need"
Also Android Java is nowadays AOT compiled to native code. So whatever C and C++ dependencies there are, could certainly eventually be replaced if there were willingness to do so.
Besides, have you ever tried to implement libc in ANSI C, without language extensions and a sprinkle of Assembly?
Nonetheless, it's something that is available to you as a developer. I am firmly in the "right tool for the job" camp, and I can see situations where C is the better tool. I don't see why mobile app development would benefit from it, but I almost never have the opportunity to work with Android.
I have never attempted to put together a full libc in ANSI C, no. My experience there is limited to putting together parts of it for various microcontrollers, where I was more or less bound to ANSI C and chose to use no assembly.
That's a long way of saying, what's your point? And why do you feel the need to get personal?
Excuse me if it felt personal it wasn't my intention, just the way I write.
Non native speaker here.
As I mentioned in the reply and in another thread, I do use the NDK, but I wouldn't if there were better alternatives.
30 years ago those microcontrollers would probably be programmable in Assembly and Forth, Pascal, Basic and C dialects. At least when I think about Z80 like ones.
Just because it's not perfect doesn't mean it's not better. Yes Android runs x000 lines of C and that's x000 lines too many, but it's still a substantial improvement over desktop systems.
What Apple OS was written in Object Pascal? It looks like they had an app framework written in that language, but the OS never was. I believe the Mac OS at the time was predominantly written in assembly, possibly with some (non-object) Pascal parts.
The initial versions were pure assembly. There was no room for any high-level code. As time went on and more storage was available, that became a possibility, but much of the OS remained assembly. This was a big part of why so much of the OS ran under emulation when the PowerPC Macs were introduced.
Nothing in that link says that the OS was written in Pascal. Yes, Pascal was a popular language for Mac apps, but of course nothing says that the OS has to be written in the same language used for writing programs that run on it.
I don't understand your last comment. I am not saying that the OS doesn't qualify as "written in Pascal" because it contains assembly. I am saying it doesn't qualify because it didn't contain Pascal. The early versions were subject to absurd space constraints that required everything to be written in assembly. My understanding is that high level languages started to be used for parts of the OS later on, but it remained predominantly assembly until well after the PowerPC transition.
I wouldn't say that UNIX workstations became truly popular until after Mac OS X took off sometime in the 2000s. C and C++ were enormously popular in the 90s, if not earlier. They were really common for Mac and Windows programming, and I don't think that had anything to do with the UNIX connection.
The only reason to get a C compiler for CP/M, MS-DOS or Amiga was to be able to bring work home from those expensive workstations at work or university.
CP/M, MS-DOS, and Amiga sound like the 80s, not the 90s. I recall that C was not super popular at that time (I arbitrarily chose to start learning Pascal around 1988, given a choice between C and Pascal, probably the wrong choice in hindsight) but by the early/mid 90s it was pretty huge.
I was there too.
C was a common language for developing from-scratch DOS applications in the 1980's. Sometimes portability was a consideration, often not. Mark Williams and Microsoft C were two popular compilers.
I never said directly I was referring to Borland C, actually it was all about Turbo Basic and Turbo Pascal.
I and a few others did use Turbo C, but we jumped right into Turbo C++ the year thereafter, as we could not envision using plain C after having done systems programming in the other Borland languages.
C was left for programming classes, usually trying to convince the teacher to use C++ instead for projects, sadly it only worked occasionally.
So are you personally volunteering to rewrite all of Linux and glibc in Rust? Are you or your employer volunteering to fund such efforts? There is a reason it hasn't happened yet - because doing so would be exceptionally time consuming and expensive.
I think OP isn't naively assuming it would be easy, or any one person would do it. More the point is there probably isn't enough of an effort or emphasis on the Linux/OSS communities part to even attempt it.
Right now, companies that run linux on their servers don't have to pay huge fines when security meltdowns happen.
I imagine if they would, they would put more money into the problem: either fund linux development or created more of a market for commercial server operating systems (which can still be open source, at least in some interpretations of the term), for example.
No. Strict liability for third party software failure is not a good idea. Software gains much much more from the freedom to innovate than it loses from exploits. Strict liability would be a terrible blow against basically everything HN represents.
Well, there are areas where freedom to innovate is more important, and then there are areas where liability is more important.
May be US (as de-facto main government-level actor in IT and internet) could create voluntary certification that opened up companies to such liabilities? Small startup operating from a garage — no certification necessary, but users are aware that no one is guaranteeing anything. IBM? Level A certification which they advertise everywhere, with billions of dollars in potential fines.
If it was voluntary, I doubt many of those companies would see it as worth the risk. The problem is that these errors happen because so many people are working on interdependent parts. Many times, it's not even your error that can make your software vulnerable. I dislike making anything so risky, because we'll see much, much less good software at the end of the day.
"It'll be slower than C", they'll say. However, I would gladly sacrifice performance for memory safety, at least in an alternate, opt-in, network stack.
Unfortunately, I doubt this will ever happen while Linus is around. He seems to be quite fond of C. I assume he would argue this is a matter of shitty developers, not a shitty language.
Rust also has nearly no runtime and no garbage collector. Lack of GC makes it possible to integrate a Rust library into scripting languages etc. This is an easier upgrade path from C, which D, Go, Nim, etc cannot provide.
(For my projects I prefer D, but Rust has a point here)
At what point will people consider Rust mature enough though? If we're going on number of years in existence, then C will always be older than Rust and the "well it's more 'battle-tested'" argument will never be false (for any language that is currently younger than C).
Neither is Rust, which comes with a huge RTS that itself likely full of such bugs. The library and the compiler too may have bugs that compromise the "guaranteed" memory safety of a libc written in Rust.
There is no silver bullet here. If one takes into account all the sources of bugs, C may very well be the best choice given its maturity and its ecosystem. Our best bet is likely to put more resources towards glibc development and especially fuzz testing. I bet there are only 1-2 developers who work on glibc full-time.
Rust does not really have an RTS per se. libstd is just a standard library.
Also, even if rustc has soundness bugs, you won't find them if you don't look for them, and even then, just trying to make your program fit the types will prevent most bugs.
By the way, glibc's code is too horrible to be read - musl is a more modern alternative.
Has there been any work on a safe C? Not a C-ish language that is safe, but an actual implementation of the C language itself.
The C standard doesn't require the compiler to check the validity of array indexing and pointer dereferences and such, but it also doesn't require the compiler not to. Can they be added without destroying performance and compatibility?
Some of this already exists, with stuff like stack canaries and address sanitizer. But can it go beyond these ad hoc solutions that catch common mishaps, and become something truly reliable?
It should be possible to just flip a switch on the compiler and have it so that, for example, if you alloca() X bytes and then use the resulting pointer to access byte X+1, that is reliably caught at runtime. And not just as some single patch that specifically looks out for alloca() abuse, but as part of a wider system that understands and catches all out of bounds access.
"CCured is a program transformation system that adds memory safety guarantees to C programs by verifying statically that memory errors cannot occur and by inserting run-time checks where static verification is insufficient."
I haven't heard of it before, but that's the sort of thing I'm thinking of, and that looks pretty cool. Looks like the resulting programs still perform reasonably well too.
Cyclone looks cool, but it's a pretty significant departure from normal C. I'm wondering how far you can take safety without changing the actual language at all. Or rather, how far you can take safety without changing the language and without completely killing performance. Memory safety shouldn't be hard: track all valid regions and pointers to them and don't allow mixing stuff up. But doing it fast is tough.
That doesn't mean sticking with the status-quo is the solution.
Rust certainly looks like a better starting point for modern, safe low-level programming than C.
C served us quite well for over 40 years, but perhaps it's time to move on...
Your comment was inevitable, but can you also include the options of rewriting in any reasonable form? I'd settle for c++ or even readable c. The problem with this code isn't necessarily the language, it's that it is an unreadable, unreviewable mess.
It's not urgently necessary since all modern systems have some form of simple address space randomization at prelink or exec.
Considering the time and effort it would take to safely and completely replace glibc with a new implementation, and the limited scope of its effect on Internet-wide security, it would be more productive to require the kernel itself to support patches like PaX, Exec Shield, or grsec in general, so that all applications (not just ones made with a specific language) would have stack-smashing protection.
None of those things is reliable. They make it harder to exploit this kind of vulnerability, but not impossible; usually they just add one extra required step to the exploitation.
Yes. It's called a mitigation, and it's what we do to make systems more secure. But they certainly can be reliable.
The Internet has been using DNS resolvers known to be vulnerable to cache poisoning birthday attacks for at least 14 years. We add basic defenses that prevent the majority of attacks, and trudge on, rather than fix the protocol itself, which is a big complicated mess. The smaller, less effective fixes reach a broader number of use cases with less effort, and so we get along okay.
Likewise, we know that no matter what language we use, there will be at some point a problem with regard to accessing memory, and so we put protections in place to limit the scope of those attacks in a broad sense. It's a more effective mitigation for a greater number of use cases. It is not a panacea.
I find that a lot of security vulnerabilities result from confusion about what side of a security boundary something is on. Treating a parameter as sorta-untrusted can be actively harmful if it leads someone to believe that parameter was supposed to be untrusted. I think it's much more effective to concentrate on strict, designed security boundaries that stop 100% of attacks than to create a bunch of ad-hoc 80% barriers.
I'm still unsure why people start projects with C, or C++ (or other unsafe languages), that do not need it. I totally understand that there are use cases where C++ and C make sense.
In my mind, these use cases are quite limited though - integration into 3rd party libraries, legacy codebases, and working in embedded environments. I also agree that performance can be a reason, but see Knuth's opinion on premature optimization.
I also think that when writing C, and C++ it makes a lot of sense to restrict oneself as much as possible a la the kinds of standards in the Google Style Guide, or NASA's style guide.
Can people tell me why they start projects in broad C++, and C still? I may be completely missing some piece of the puzzle.
C is still the lingua franca; it's the only toolchain you can rely on a target system having, and the only real usable interface for calling between code in different languages.
So my theory is that it's not so much that people are especially inclined towards starting projects in C, but that projects in C have fewer barriers to distribution and thus adoption.
By natural selection, C code spreads and becomes standard; programs in Java, JS, Ruby, Haskell, Ocaml, etc. don't tend to leave their native ecosystem.
This is exactly my reason for starting a couple of recent side-projects in C. You can pretty much count on the target system having an ANSI C compiler (even rather old systems), which can't be said for any other language. Have an ANSI C compiler? Great! You don't need anything else to build and install this program. You don't need to install a hefty runtime like the JVM and you don't need to know what a gem is.
I don't think I'm immune to the hazards inherent in the language. I try my best be aware of them and to write safe code, and still find myself introducing a segfault now and then. Luckily, tools like valgrind and Coverity can really help to catch these kinds of issues early (of course, they can't catch everything, but I've been amazed by just how much they do catch).
> Can people tell me why they start projects in broad C++, and C still? I may be completely missing some piece of the puzzle.
Sure.
---
Compelling features:
- Unrivaled portability (yes, contrary to folklore C - and to a lesser extent C++ - is more portable than Java)
- Excellent libraries
- Excellent documentation and learning resources
- Unrivaled tooling
- perfect mapping to how the computer works
- Unrivaled performance (other than Rust)
- exceptional, engaging, and intelligent community (specifically referring to the cpp community)
---
Why start a C++ project today?
- you want to write a web browser
- you want a platform agnostc GUI
- you want to learn OpenGL or Vulkan
- you want to use Unreal and you're not a visual learner
- you're want to write the business logic for a cross platform application and share as much code as possible
- you're implementing you're own programming language
- your implementing an algorithm or data structure you've been studying
- you're bored
- you like programming in C++
- you want to learn template meta-programming
- you want to learn functional programming
- you want to learn how to write a device driver
- you need the performance
- you are not writing mission critical software (aka 99% of all side projects)
- you're a curious person
- you're a contrarian and everyone you know uses Java
- you want to become a more well-rounded engineer
- you want to learn about the ins and outs of language design and features
- you want make a lot of money
- you only know C++
Those are a few reasons. Hope that helps you solve your puzzle.
PS: Do the anti-C people realize that a kernel in Rust/Nim/whatever would be under a big unsafe block anyways?
Yeah, you have to program "unsafe" code eventually. Those nice high level abstractions are not omnipresent, you know. They need something to abstract.
Just like a kernel written in C is a big block of assembly code, right?
You can isolate any unsafe blocks into small functions, with the smallest scope needed. Same as you would do for asm blocks in C. This is good because it calls attention to what you are doing, when reviewing any code with an unsafe block, you remind yourself to check for these kinds of bugs very carefully, unlike when you are reviewing generic C code where almost every line can have a memory bug.
Definitely. The ability to encapsulate code that is less strictly checked by the compiler is what makes Rust such a compelling alternative to C. I definitely think Rust is the ideal choice for a hobby kernel from scratch in 2016.
My point is that unsafety is unavoidable. You can encapsulate unsafe Rust or wrap plain old C, it's, for all practical purposes, the same. At that level language features matter a lot less. I agree we should minimize and encapsulate loosely checked code as best we can.
For example, in the mobile OS space if you want to write native apps, without rewriting 100% of the application code per platform and avoid toolchain debugging headaches, C and C++ are the only languages available in iOS, Android and WP SDKs.
In WP case, there are APIs like DirectX that aren't fully exposed via WinRT, so you need to write a C++ wrapper for those type of APIs.
There are other options, but they are either commercial (e.g. Xamarin), or require lots of DYI hacking with the SDKs toolchains.
So if you want to remain productive and not pay for the commercial solutions, there aren't any other alternatives.
Which means, even I with my safety rants, end up using them for my mobile OS hobby coding.
However if I would be doing this commercially, Xamarin would be my first option.
People seem to forget that Objective C is a superset of C. No slapping on required. You're already writing it. Avoid brackets and a few other things and it's C.
You don't even have to rename .m to .c, but you can.
> I'm still unsure why people start projects with C, or C++ (or other unsafe languages), that do not need it. I totally understand that there are use cases where C++ and C make sense.
On Linux it's easy: It is (or was, until very recently) the least sucky alternative. All others have (or had) issues with OS support (C#), distribution (Java: OpenJDK vs. Oracle JDK; Python: 2.5 to 3.4 parallel in use by different distributions; Vala: Every compiler update breaks the language; etc. pp.), availability of libraries (Ruby, PHP, Perl… for anything that's not their speciality) or general obscurity (OCaml, …).
Go and Rust (and JS, C# and Vala recently) seem to be changing this, thankfully. But those too will depend on the availability of high-quality bindings for all the C(++) libraries you have to use to get anything substantial done.
Certain language own certain domains, for better or worse, this means that most developers interested in low level programming will know C/C++, and that the will be a substantial amount of tooling already in place.
No I think he meant that if you adhere to certain language constructs the language is just as memory safe as Rust/others.
Eg.
Don't use C++ as C with vectors, use C++ with the more modern language features:
It's the difference between:
// where f is of type fstream of a file containging "this is a string\r\n"No I think he meant that if you adhere to certain language constructs the language is just as memory safe as Rust/others.
Eg.
Don't use C++ as C with vectors, use C++ with the more modern language features:
In the first one you're 'protected' by the stl code, which is arguably the same level of protection as afforded by any memory safe language’s library code.
In the first one you're 'protected' by the stl code, which is arguably the same level of protection as afforded by any memory safe language’s library code.
> is risky: what if the loop body happens to somehow call `vector.push_back(x)`?
Some people like to say that these bugs are trivial to avoid ("who would write code like that?!"), but they miss the insidious (and common) case: when code several layers down the call stack (in some arbitrary callback assigned at runtime, say) grows the vector/map from under you.
It's still easy to avoid though. Just take a const reference to the vector and use that in the loop and pass that to the callback. It just requires discipline, and in fact less discipline than writing C.
I don't subscribe to the "properly written Modern C++ is safe" meme, but still it must be recognized that there are degrees of unsafety [1] between languages.
[1] not talking about memory safety, but as a general "probability of having a defect".
I really dislike the "safe"/"unsafe" terminology, there isn't a commonly-agreed upon definition of "safe". But I think it's safe to say that it's a combination of type and memory safety, not "low probability of having a defect".
Because you can't release a single file statically compiled native binary that relies only on established system calls.
People typically can't install packages anymore.
They can grab an exe.
> Can people tell me why they start projects in ... C still?
Okay. Based on my current project (Porting AVS out of shit-tier Microsoft C++). I want a region of memory in which I can treat as a bitmap and set pixels at given location to given colours. I know of no other language that gives me this block of memory to draw into. That's probably because I don't know enough. Most languages seem to tout how immutable things are. That's a big word for many people and it just means "does not change".
If other languages want my attention then maybe they need more than a "Hello, World!" and some string processing. Show me your malloc equivalent and see if I can go from there.
Lines 952 ... 1389 [=~450 lines of code], with more than a dozen of variables holding random state. Think about the complexity you have with all the conditionals and loops, often copying and pasting similar conditions with (xx1 && xx2) variants.
While discusions about the relative merits of Rust, C, Ocaml, Intercal are fun, with enough dedication you can write unauditable/unreviewable code in any language. Even though you might avoid memory corruption, you still can't prove that such kind of code does anything correct.
Gah! As a medical device programmer, that code is a horrors how! Ifdefs, gotos, 400 lines long, scarce comments, so many arguments passed, single letter variable name, stack declarations all over the place, etc. I could go on about the helper functions in that file, lack of argument descriptions, lack of references to standards. It all reeks of a lack of structure. I'm surprised to see the Internet runs on that.
Thank you for sharing that. It's really nothing to do with C, I agree. Just a drastic lack of coding standards. Don't people refactor key code after functionality is proven and tests developed?!
You sure can prove that any correct code does something correct, but you can never prove something about a program outside of a machine-checked formal proof.
The problem is that it's far more difficult to prove what code doesn't do than what it does. I can write a function to do X, Y and Z, and I can show, through observation and aggressive testing, that it does indeed do X,Y and Z....but its a lot harder to prove that it only does X,Y and Z and nothing else. Memory safety is just one expression of this problem.
In the typical framework (refinement), if you prove your function does X, Y, and Z, then it does exactly X, Y, and Z. Please don't take my comment out of the context of formal, machine-checked proofs. It is hard, and that's my point.
Criticism of C aside, when you use a language that isn't very expressive and where it's easy to shoot yourself in the foot, you need to keep it very neat.
It's better. The musl version is slightly smaller and tidier but also doesn't handle all the same proticols so it's not a fair comparison. At least it doesn't have a lot of conditional compilation and macros.
But the musl code too has bloody ZERO comments for a nearly 100 line long method of dense/opaque C code! Why? Is commenting and splitting code into functions frowned upon in "traditional" systems programming?
Who is it that writes code like this? Why isn't code like this rejected by a maintainer with "split this into at least smaller 5 functions and comment anything non-obvious"?
Edit: looking again it's actually not that bad, it does have two lookups already broken out and a lot of the verbosity is just field assignment. Would be happy with a dozen lines of comments and a split into 2-3 functions.
I feel like comments are extremely valuable in API documentation, but not so much in the implementation.
When you're reading an API you want to understand how it works without cracking open the source. When you're looking at an implementation it is your duty to load the source into your comprehension, at which point comments become irrelevant, or worse, misleading.
As long as the function is doing one thing conceptually, I don't see the benefit of breaking it up into multiple routines just because it's too long (for some arbitrary length). Not only do you have the cognitive overhead of trying to come up with a name [1], but passing in all the required parameters to the function. And for this function, I personally don't see any good "breaking points".
At work, I have to deal with a code base where code is split up like that, and it's a royal pain to follow, and the resulting functions aren't usable by anything else.
[1] The two hardest problems in computer science is cache invalidation, naming things, and off by one errors.
It's true that a function in an expressive language can be 10 lines, which in C is 70 lines just because of verbosity (repeated assigns instead of constructors and so on). So while the threshold is certainly higher in C, this is still too large. It does 3-4 things conceptually but 2 (lookups) are already broken out. I can see at least one opportunity more for extracting a function. It's important not least because of testing.
Maybe it's just my aversion to C (and procedural) in general but I think assigning 3 fields of the same struct in succession as a sign that you are making an inline constructor that should be explicit. The chunk I meant looked like a possibility though was the hint bit.
The point of smaller code is testability. It's worth even making it somewhat harder to read. If a test makes three things in succession that in their own functions would have required 3 tests each, the aggregated do-everything function requires much more than the 9 test cases. So dividing the method in two, even if arbitrary and bad for readability can have a positive impact since the total code volume is reduced a lot.
All of this ^ only applies if the code actually has tests. If not - then don't divide arbitrarily if it reduces readability at all. But then again without tests you have bigger problems than readability...
It's your aversion to C, because the hint thing does not do what you think it does. It doesn't assign three fields of a structure, it pulls three fields out of a structure (and in my opinion, making a function just to do that is silly---it's not an opaque object (as it's documented in man pages as to the fields it has), hides the intent and further more, clutters up the code.
If you make this "accessor" function static [1], then you have to include the test code in the same C file (cluttering that file with code only used for testing [2]) or you clutter the C global function namespace with a function that's quite possibly only used on one location (and thus, can't be inlined at all due to C semantics---just some of the realities of attempting TDD in C).
[1] Functions and variable marked as "static" are only visible in the file.
[2] And you end up with pretty much dead code in a release version. You could exclude such code from a release build, but then you can't actually test the release build of each function.
I used to think C was bad without even considering the lacking modules/namespaces etc and what it means for testing. It does encourage few and large hard-to-test public functions. Am I right in suspecting that the test suites of libs like these are pretty small?
I think it really depends upon the codebase. Libraries are easier to test than applications in general, since you have a published API to throw at testing. One approach (and it seems to work for LuaJIT, a mid-sized C based project) is to just collect test cases as bugs arise (create the smallest input possible to trigger the bug in question) and as changes are made, rerun all those cases as a type of ever-growing regression test.
This is why I enabled automatic security updates on all the machines I control. I'd rather get a monitoring alert that something is broken, than to find out much later that someone rooted my server.
The Debian glibc package update that fixes this vulnerability is dated February 11. But the patch wasn't posted on the glibc mailing list until today. So did Debian get the patch even before it was made public upstream? If so, then why didn't Ubuntu get it early as well?
Sufficiently "bad" issues are disclosed to multiple Linux vendors at the same time, via the vendor-sec mailing list. If you're a member of a security team for a distribution this is generally where you hear about issues.
From there coordinated release dates will be agreed, and barring big surprises like one distro pushing out an update too soon everybody _should_ release at the same time.
Sometimes people get sick, take a holiday, or drop the ball for other reasons. I'd expect Ubuntu did get notified, but haven't yet tested/built their fixed packages to push out.
From what I'm reading this should only affect systems that use a compromised DNS server or in a MitM attack scenario. Which is serious but not so easily exploitable (I think).
It's worse than that. According to https://sourceware.org/ml/libc-alpha/2016-02/msg00416.html any system that performs a DNS lookup may be hit. And it's not hard to cause DNS lookups to happen (think reverse DNS lookups when logging login attempts, hovering a link in an email or webpage, etc):
- A back of the envelope analysis
shows that it should be possible
to write correctly formed DNS
responses with attacker controlled
payloads that will penetrate a
DNS cache hierarchy and therefore
allow attackers to exploit machines
behind such caches.
So even if you trust your local ISP and DNS servers, any random domain on the internet may be resolving to an exploit.
Also, this vulnerability has apparently been around since 2008, and sitting in public view on the bugtracker for many months. Who knows who else has been sitting quietly on this for however long? :-/
If you request a network connection to an attacker-controlled host, your network software may try to resolve the attacker's host name. The DNS NS record of their domain may then specify your resolver directly look up the record using the attacker's own name server, meaning you are directly doing DNS queries against the attacker's NS.
So in theory, all you need to be exploited is to connect to a compromised host and resolve its hostname.
It could be even worse than that. If the attacker tries to connect to you, your server may try to reverse their IP for logging, and the attacker can control the PTR record. Or the attacker could send you an email that's guaranteed to bounce, and they control the return path that your mailer has to resolve.
> Which is serious but not so easily exploitable (I think).
Oh it is. All you need is an open wifi with a router only dealing IPv4 addresses... start up radvd and serve your machine as authoritative IPv6 DNS server, and profit.
Looking for a quick mitigation technique before patches start rolling out... Would it be wise to limit responses to 512 bytes so the payload cannot be loaded?
Configuring BIND to use a specific buffer size (only for BIND 9.3.2 and newer):
Add the following line to the "options" section of your named.conf file:
edns-udp-size: n
Configuring Unbound to use a specific buffer size:
Add the following line to the "server" section of your unbound.conf file:
I haven't tested it, but I'd expect not. Given a limited EDNS0 buffer size, UDP responses would either come back with a truncated flag or not at all. Either case would trigger a TCP retry, and the bug can be triggered that way.
If this was originally filed on an open bug tracker in July 2015, what were the glibc team doing in the mean time? The Google post indicates they were "working on it" when Google got in touch. How much work was going on, exactly? How did this languish for so long?
If alloca could be used to get arbitrary-sized buffer the bug would not exist. Another Y story https://fosdem.org/2016/schedule/event/ada_memory/ points out that Ada does not have this limitation. There it is a job of the compiler to allocate bug chunks outside of the stack not to cause stack overflows. C really needs such API.
"There are two things I am sure of after all these years: there is a growing societal need for high assurance software, and market forces are never going to provide it."
Can someone please explain the fix in practice? Is it as simple as upgrading glibc (and eglibc?) on all servers? Or is there a network change I should immediately change?
Thanks for this. It'll do for my router until a new firmware is out. I tried the above but --connbyte takes a range as input not a single value. Like, "512:8192" or "512:" for "greater than 512". In practice "512:" is interpreted as "512:4294967295". So it ends up looking like,
iptables -t filter -A INPUT -p udp --sport 53 -m connbytes --connbytes 512: --connbytes-dir reply --connbytes-mode bytes -j DROP
iptables -t filter -A INPUT -p tcp --sport 53 -m connbytes --connbytes 512: --connbytes-dir reply --connbytes-mode bytes -j DROP
Can someone give me technical reasons why this world isn't possible:
Parts of the linux kernel or glibc or any other critical C code gets replaced by rust code little parts at a time, which is also calleable from C (https://doc.rust-lang.org/book/ffi.html)? That way these libraries could be made safer in a controlled and incremental manner.
And to reiterate I'm asking for technical limitations, not political or dogmatic.
The biggest technical limitation is that there's currently only one Rust compiler, and it targets LLVM, which means that it can currently compile for only a subset of all platforms that C can compile to.
For some projects this isn't a problem. For example, Firefox is replacing bits of itself little by little with Rust, which is fine because Rust targets all platforms that Firefox is built for (though it did take some wrangling to get Rust working on Windows XP). However, a project like glibc is used across a much greater variety of platforms, and many of those platforms may have nothing but C compilers. And those C compilers may very well be buggy and nonstandard, so even transpiling to C won't help unless you're willing to hand-tweak the generated code.
I thought about this before, since it seemed like a good idea to me too. It's not that simple though, there's tons of problems you'd hit and the result wouldn't be what you want. The bottom line is that Rust is simply not a drop-in replacement for C:
You can't just pass a Vec<> to some C code and expect it to work. The reality is that you're going to end-up writing a lot of C-like Rust - Which either acts on C types directly or converts them to Rust types and then does things to them and converts them back. Either way, the safety is largely lost due to this because C types are not going to be safe.
You also can't use any inline C functions or preprocessor macros in your Rust code, meaning that the interop between C and Rust isn't really that good - FFI lets you call functions, but functions only make-up a part the C API. The rest would have to be duplicated in Rust and kept up-to-date, which is a huge development burden and very error prone.
And when you're finally "done" and all the C code is gone, what you're left with is just a lot of C-like Rust, communicating with each-other through the C API using C types with unsafe code everywhere. Essentially C but in Rust form. You'd have to do a ton of refactoring to turn this into anything like idiomatic Rust, and you can't do that refactoring till all the C code interfacing with that Rust code is completely gone so you can stop supporting interop with C.
The bottom line is that it you could do it, but it won't work well because it's missing the big picture - If you don't write Rust code like Rust, then you don't get the safety guarantee's, and you can't do that if you're trying to recreate a bunch of C interfaces in Rust. C interfaces are not safe in the way that Rust would want them to be safe. It's the same reason why you can't write a converter to convert from C to idiomatic Rust - You have to design the system differently to get the gains from Rust, which is a non-trivial thing to do. Rewriting small parts in Rust isn't going to result in the design differences that you need, and the interop between Rust and C isn't very good, leading to lots of problems while you're dealing with both languages in the same code base.
While this is true in some sense, there are also advantages. A ruby gem written in Rust via C FFI was one of the earliest production uses of Rust; the Skylight people still thought (and think) that it was very, very worth it.
All code is binary, you can write a compiler that turns human readable code (rust) into binary. Then you can push that binary wherever you want (into a custom address in another file for example). Technically we can do _anything_ we can rewrite part of it in JavaScript if we want.
What you need to understand is that almost 100% of engineering is economic. Is it worth the time to rewrite things in a new language, to rebuild the tool chain to make things easy/convenient, to deal with the gotchas in each new language and their compilers ? On and on the list goes. And most of the issue is there isnt incentive, users of free software have a first mover problem in that if someone else pays for the software to be made, nonparticipants get it for free. Thus we all wait for someone else to pay for all the wonderful things that would benefit us all.
Most apps will work out of the box, but you can see in that issue 1+ year of various people debugging and fixing issues to just get existing test cases to pass.
Many applications don't work with anything but glibc, so distributions don't use them; because distributions don't use them, applications don't support anything but glibc.
The number of applications that rely on malloc/free being multithreaded fork safe, like glibc's allocator, is astounding. Off the top of my head, any application that uses glib and g_spawn_* is quite unportable for this reason.
And the C libraries all implement those functions (mostly) differently. The "lighter weight" standard libraries tend to have simpler implementations of these functions, which may be easier to audit for these kinds of bugs.
Glibs uses /etc/nsswitch.conf to decide at runtime which libraries has to be loaded dynamically to implement DNS resolver, user lookup etc. One in principle can compile those libraries statically, but it is rather complicated for no gains, so almost nobody is doing that.
I have a question that hopefully someone can clear up for me.
If I understand the Google sec article correctly, this requires a single packet > 2048 bytes to be received by a host using glibc.
> The vulnerability relies on an oversized (2048+ bytes) UDP or TCP response, which is followed by another response that will overwrite the stack.
Is my understanding correct?
If it is, then it's worth pointing out that many links on the public Internet have an MTU of 1500 bytes. This is an historical legacy of original Ethernet from the 80's. Path-MTU-Detection(PMTUD) doesn't really work on the Internet, so it's safest to assume that you only ever get 1500 bytes.
Given all that, this places a burden on anyone wanting to eploit this. Since they cannot assume a PMTU greater than 1500 bytes between endpoints, they're limited in how they can exploit the bug. Correct?
Please correct me if I'm wrong. I always feel these bug reports are missing that vital piece of information I need to operationalize the bug. And thanks.
Datagrams larger than the MTU can be sent, they'll just be fragmented. The point of PMTU is to avoid fragmentation where necessary, but it's still possible.
Sometimes datagrams larger than 1500 bytes can be sent, sometimes they won't reach their destination. Fragmentation doesn't really work, and at the very least will almost always ensure out-of-order delivery. Either way you can't depend on it.
PMTUD doesn't work at all on the modern Internet, not for IPv4, and not for IPv6. So again, I'm just wondering how dangerous this exploit really is. I'm not saying it isn't dangerous, I'm saying our understanding of it should be tempered somewhat due to many links not fragmenting properly that have MTUs of 1500 bytes.
They are separate vulnerabilities in the same codebase, both affecting DNS clients. This one is new, so having previously patched to protect from CVE-2015-0235 does not make you safe from CVE-2015-7547.
I wonder why alloca+malloc/free was used in the first place and not straightforward malloc/realloc/free. The overhead of the latter should be negligible given that this is a DNS resolver. The overhead in fact could be negative due to simpler code and better cache utilization.
Premature optimization is a root of all evil indeed.
I think new generation wants to take over linux with their new ideas but in reality they just want to prove themselves and prepared to completely ruin it in the process.. well. Cos they just young and want new things. They know big words and think they now the world and they hate their parents... so in short - they want to do to the Linux what the previous generation did to windows... karma.
P.s. whatever language you use its irrelevant. Bugs are in the heads. Code is just the reflection.
If this causes you serious problems, you should really consider using a patched grsecurity kernel (even though their stable versions arent free anymore)
Has anyone actually tried the PoC on their systems? I will test on CentOS 6 and 7 after I have had my coffee. Anyone willing to volunteer to test on Ubuntu and Debian?
Here is CentOS 7
[ 389.064412] do_general_protection: 159 callbacks suppressed
[ 389.064416] traps: CVE-2015-7547-c[1161] general rotection ip:7fa6b0d8fd67 sp:7ffdaf034a30 error:0 in libresolv-2.17.so[7fa6b0d87000+16000]
I tested my ubuntu 12.04 and it did not seem to be affected.
server:
vagrant@precise32:/tmp/CVE-2015-7547$ sudo python CVE-2015-7547-poc.py
[UDP] Total Data len recv 36
[UDP] Total Data len recv 36
client:
./CVE-2015-7547-client
CVE-2015-7547-client: getaddrinfo: Name or service not known
vagrant@precise32:/tmp/CVE-2015-7547$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description: Ubuntu 12.04 LTS
Release: 12.04
Codename: precise
vagrant@precise32:/tmp/CVE-2015-7547$ ldd --version
ldd (Ubuntu EGLIBC 2.15-0ubuntu10) 2.15
Copyright (C) 2012 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
Written by Roland McGrath and Ulrich Drepper.
Debian has published security announcements for glibc [1] and eglibc [2]. Updates should be available from http://security.debian.org/ (and I was able to install them).
Also, keep this in mind:
>While it is only necessary to ensure that all processes are not using the old glibc/eglibc anymore, it is recommended to reboot the machines after applying the security upgrade.
As a brute-force fix, you can disable DNS in /etc/nsswitch.conf and put the addresses you need (at least security.debian.org, YOUR_MIRROR) in /etc/hosts.
Is there something that can be done about this on the network level? I mean, it seems almost impossible to assure that every instance of getaddrinfo is patched.
I'm thinking about a background tool (iptables plugin?) that simply truncates long DNS replies, so that they can never cause a buffer overflow.
It seems tcp_wrappers-libs is using getaddrinfo, so if you have some rules setup there that may be an attack vector. I'm not sure if sshd will want to do a getaddrinfo if you don't have some tcp wrappers rule set up in /etc/hosts.{deny,allow}.
Looking at that code, which is a tangle of goto statements and buffer allocations and accesses, it's a miracle any of it works in the first place. I bet there are tons more bugs in there.
Tangle of gotos? Those gotos all look reasonable -- they go to return conditions no longer than a few lines. Buffer allocations? They allocate to 2-3 buffers. That's all very reasonable. allocations and gotos aren't the devil in C.
The problem is DNS/UDP and DNS/TCP responses that are larger than 2048 bytes hitting your BIND DNS client library. Do you have any reason to believe that Google Public DNS (in your part of the world) always limits its responses to smaller than 2048 bytes?
> [...] when /etc/resolv.conf or /etc/nsswitch.conf specify the use of features that the Go resolver does not implement, and when the name being looked up ends in .local or is an mDNS name.
Those things you can't check at compile time.
In any case, you can tell whether the binary is statically linked or not.
Statically linked:
$ file /usr/bin/consul
/usr/bin/consul: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, not stripped
$ ldd /usr/bin/consul
not a dynamic executable
Can this be used to exploit DNS servers via other rogue DNS servers? What if I setup a rogue DNS and wait for it to be queried by 8.8.8.8. When I own 8.8.8.8 I can exploit every client that queries it. Browsers use getaddrinfo(), don't they?
One uses AI_NUMERICHOST and thus doesn't do any DNS client activity. A second isn't supplied arbitrary domain names as input, they being the ones configured for the location of a specific back-end data source by the server administrator. The third is used only inside a "zone transfer" subsystem and again isn't supplied arbitrary domain names as input, the domain names again being in local data specified by the server administrator.
And the fourth is not even part of the executable program, the code being disabled with #if 0 .
The "S" in "DNS" stands for "system", not "server", by the way. A rogue DNS server would "own" other DNS servers.
I'm out of my element here but is it really so far fetched?
A quick search in the PowerDNS codebase [1] reveals four sites where getaddrinfo() is called. This isn't obviously the code for recursive lookups but that doesn't matter. If an attacker can trick it to query their rogue DNS they have won.
I don't want to suggest that Google uses PowerDNS, I just used is as an example.
Are there any big efforts to rewrite glibc in something like rust? ... Is that a thing that is even possible? An in-place replacement library for dynamic or static linking.
I'm really worried that I still hear about buffer overflows in this day and age. Of all the libraries in the world, glibc should probably be written in some subset of Idris that compiles into 100% safe C. We have the technology to move to this now
I'm not sure how likely that'll be. It's definitely possible, but I think there are going to be a few hurdles. The main one being, symbol versioning.
This is important if you export multiple symbols for a function, that is, have a program that depends on a specific implementation of a function. That could mean you depend on different versions of the same library ("functionname@@VERSION_1, functionname@@VERSION_2"), or completely different implementations of a function ("functionname@@IMPL_X_VER_1", "functionname@@IMPL_Y_VER_2").
Wait a second. That seems like a strange feature to want, right? Well. It's particularly useful for runtime library fun stuff. LD_PRELOAD in a library that magically changes the implementation of function. A cool example of this is of preeny[1], a collection of libraries to help with debugging.
Additionally, it's useful for replacing libraries that would depend on C implementations without having to recompile them, though some preloading magic at runtime may be required.
This is only the thing, for me at least, that's blocking for making Rust system libraries. Now, any sane person may say, "Cargo will take care of versions and stuff", and that's true. Sadly, that's not the case if you ever want to use those nice and safe Rust libraries from C though.
Though, I may be completely wrong about the state of symbol versioning in Rust. I simply ran 'nm -g libopenssl.so' on sfackler's rust-openssl project and failed to see any @@<version> in the output -- that is specifically being exported, and not re-exported, by libopenssl. There may be better libraries to use, but eh.
I could be completely wrong on this though. I've been mainly going off of what I read here[2], and here[3] (alternatively, for a more in-depth version, try here[4]).
TL;DR: symbol versioning may not be a thing.
Disclaimer: I've had to use symbol versioning pretty heavily, so it's important to me. I could also be completely wrong about Rust not having it.
The default name mangling of Rust library symbols is already kinda versioned because it includes a hash of the library, so two different versions of a library can coexist at the same time.
As far explicitly using the symbol versioning scheme you mentioned, you can do it with an attribute today:
(Disclaimer: I haven't actually compiled this as a dynamic library and checked, but its what that attribute is for, and the names seem to be used verbatim in the ASM output)
Are you seriously suggesting that rewriting an implementation of the C standard library in a language that isn't C is something that makes any bit of sense?
I think you have a fundemental understanding of the roll the C-standard library plays in the C language. Not to mention UNIX in general.
Maybe you're suggesting we need an OS based in Rust instead of C (which is more reasonable), but in that case why wouldn't you use Rust's standard library as the backbone of that system?
Let me put it this way, if the GNU/Linux C standard library gets rewritten in Rust or Idris (sigh... seriously?) I will switch to Windows 10 with default settings for the rest of my life. I would rather have the NSA log my every key stroke than participate in a community where that is considered a good idea.
Your post is just a bunch of pathos and doesn't explain why you actually think it's a bad idea.
There's no technical reason why the C standard library can't be written in a language other than C. Lots of language libraries are written in languages other than the one they're primarily used with and work fine.
There's no technical reason why it can't be done, true. It's still a hilariously bad idea that would never succeed. That's aside from the fact that's a completely pointless wheel-reinventing endeavor. Rewrites in general are pretty difficult to justify.
Sorry, I guess my aggressively pragmatic side is showing today. The idea is really dumb.
To be honest, "Let's rewrite the C standard library in Rust" is one of the worst pieces of bikeshedding drivel I've ever read. And I like Rust 100 times more than I like C.
It's still completely pointless. How would mmap be any different if it was implemented in Rust rather than in C? You need unsafe to implement it! The safety mechanisms are practically useless at such a low level.
Wrap existing C with safe Rust, not the other way around! We don't rewrite unsafe C code in Unsafe Rust so that it can be called from all the unsafe C code in UNIX. Such a translation is so pointless and error prone.
I can't even believe I'm the one being called backwards here.
> How would mmap be any different if it was implemented in Rust rather than in C? You need unsafe to implement it! The safety mechanisms are practically useless at such a low level.
They're definitely not. The power of Rust is not avoiding all `unsafe` ever, but wrapping that unsafe into finite-scope, safe wrappers. Something like mmap would presumably interface with the OS's internal memory management routines, which would definitely have to have `unsafe` somewhere since they're hitting the hardware, but it seems very reasonable for the amount of unsafe exposed to much reduced (possibly even to zero). See, for instance, http://os.phil-opp.com/modifying-page-tables.html . Basically, as soon as you're above the absolute raw hardware level, you can start introducing extra help to reduce unsafety/catch bugs.
In any case, code inside `unsafe` still benefits from all the conventional Rust checks, e.g. iterating over a slice won't go out of bounds due to a typo in the loop even inside `unsafe`, nor will references accidentally become dangling. (Of course, the `unsafe` block may do something explicitly that causes either of these problems, but this exact same risk is pervasive in all C code, rather than just around explicitly marked areas.)
> Wrap existing C with safe Rust, not the other way around! We don't rewrite unsafe C code in Unsafe Rust so that it can be called from all the unsafe C code in UNIX. Such a translation is so pointless and error prone.
Note that this is exactly what is done now, the features of Rust for making safe interfaces makes Rust often a far nicer way to use C interfaces than C itself, without overhead (IMO, of course). See http://blog.rust-lang.org/2015/04/24/Rust-Once-Run-Everywher... , for example. Of course, zero-overhead Rust is only as safe as the C code it wraps, it can't (in general) protect against the C code not correctly implementing its stated contract(s).
It seems hard (impossible?) to write a safe, full-featured mmap wrapper in Rust, because of the potential for multiple processes sharing mutable access to the memory and defeating Rust's aliasing checks.
Maybe you could separately map shared RefCells to guard the pages returned by the mmap wrapper? If the region is backed by a file, you'd need to check those RefCells on every IO access too, though.
What you can do for mmap is have several abstractions (or one using generics and phantom types), one for each different set of usecases, with different access modes.
or more generally: &mut WOMemMap -> &mut [WriteOnly<u8>] where WriteOnly<T> has fn set(&mut self, T)
Now for the shared case, consider this: aliasing rules can be avoided with atomic operations, i.e. Arc<AtomicUsize> is shared and can be safely (atomically) read/written by multiple threads.
In the multi-process case, you could provide an atomic API, although we don't currently seem to expose byte-level atomics (likely not present on some platforms) so if you wanted to write a demo you'd need to use the unstable intrinsics atm.
FWIW restricted to single-threaded code, this results in the Cell get/set API which is not hardware-atomic but cannot overlap with other accesses to the same memory, as Cell doesn't implement Share so any threading abstraction will block you from doing any kind of sharing of Cells.
That is, you can share &[Cell<u8>] pointing to any bag of bytes that sits in read-write memory, and anything in the same thread can read or write to it, safely.
I was more focusing on implementing the internals of mmap, since that's what I assumed the question was. For that task, the actual memory/pointer returned is essentially a black-box (i.e. it's never read nor written by mmap itself), mmap is just interacting with the operating system.
However, as you say, exposing a safe mmap version for libraries to call is a different problem with different difficulties. eddyb's sibling suggestion of teasing out the multiple entangled uses of mmap sounds like a good one, although someone would have to experiment with it to be sure.
Practically useless was a bit of a stretch. Unsafe rust is definitely safer than plain C or Assembly. I admit I was a bit riled up after reading the "compile to asm.js" comment.
Cool stuff though, I really hope rust catches fire soon. I love that it has revitalized the appeal of implementing operating system level projects like text editors and shells. I never would have wanted to try and write a windowing system before Rust. Now that's something I'm actively investigating for fun.
You need unsafe to implement the C API part of a DNS resolver, but the API can be given a safe wrapper without much effort. Then parsing and buffer management bugs would occur only in safe code and only affect it.
Are you are that Visual Studio C library is written in C++ with extern "C" and for Microsoft C is legacy, with .NET Native and C++ with compiler static analysis being the way forward?
IIRC, that's because it's a C++ library, not a real C library. Aside from a few exceptions, it only includes the parts of the C standard that are required by the C++ standard. Hence the dismal C99 support on that platform.
Yes, C++ is as unsafe as C, given its copy-paste compatibility, but at least it provides the tools to program safely (if one chooses to) and the C++ community cares much more about safety than the C one.
It would be a wasted effort. Anything that's using glibc (directly) is written in C and therefore inherently unsafe. There are only two kinds of programs written in C: those that need to do unsafe things and those whose developers don't care about safety. Better to look to building systems that don't need to run any C code (see the recent fuss around unikernels).
I know because I did it on purpose, even if it managed code written in AS3 you don't want to reinvent getaddrinfo, you want to reuse it "as is" as much as possible.
Note that DNS lookup doesn't require any syscall other than normal socket i/o, which is available on any non toy language.
One possible reason why java might implements its own name resolution is because it wants to do async name resolution which is not possible (at least portably) via getaddrinfo and friends.
Yes, but you still need to encode the questions and decode the answers. Existing libraries that do this are pretty dismal.
(Dismal enough that I ended up writing my own DNS encoding/decoding library (https://github.com/spc476/SPCDNS) with the aim of being easy to use as well as safe. It assumes nothing about the incoming packet and does extensive validation (enough that it found bugs in other DNS libraries). It also avoids dynamic memory allocations. It's used at work in two projects that get heavy use (millions of DNS queries a day) and so far has performed without incident, so well written C code is possible.)
The problem is not C as one can compile C into NaCl or asm.js and get memory safe code. One can also use a number of static checkers. The real problem is that even this is not done in cases when using C is a must due to project nature.
Compiling to NaCl or asm.js doesn't make an arbitrary C program memory safe. It "merely" prevents any memory safety failure to affect anything outside the container.
This is different from, for example, java bytecode which is verified and guaranteed to be memory safe.
This is not a mere technicality: an heartbleed vulnerable version of openssl compiled on top of asm.js or NaCl would still leak secret information.
Both asm.js and NaCl guarantee execution stack integrity and allow jumps only at predefined targets in code making exploiting bugs like this highly problematic. Data-only exploits are hard and in asm.js case they are father limited as access outside of the memory leads to program termination.
On the other hand memory safety does not prevent bugs like Heartbleed or resent OpenSSH key leak. As the underling issue is related to broken reuse of a cached buffer, such code can be written in Java or even Rust just as well.
Sure, but these are (very effective) mitigation techniques, but still do not guarantee memory safety.
You are right that Heartbleed is not a memory safety issue, I should have been more careful. I just wanted an example of an exploit that doesn't need to escape the process boundary to cause damage.
> Languages like Ruby have had their host of ridiculous security errors.
> Fuck Rust and you naive and self-serving evangelists. Come back to me in 20 years with what you've learned.
This might be a cogent criticism (although a pointlessly mean one) if we hadn't started the Rust project with an analysis of precisely what the contents of our security bugs consist of, and designed the project to target those.
Please stop doing it on HN. We understand the fun of sparring, but on a large, weakly cohesive forum like HN, it causes damage, mostly by provoking others who don't feel the sport. Also the big flamewar discussions are all super generic and thus unsubstantive, and they end up consuming everything more interesting. Just look at what you did to the topic of this thread.
The reason we're so proactive about asking people not to do this isn't that we don't get how stimulating it can be. Rather, it's empirical: experience shows that we can't have both blood sport and a high-quality site in the long run. A high-quality site is more important than blood sport.
Nonsense. How many buffer overflows - a vulnerability that simply doesn't happen in any modern language - would it take to convince you that the language is the problem?
> Languages like Ruby have had their host of ridiculous security errors.
Ruby isn't great, but it's not had anything like the same defect rate as C.
> One advantage of legacy code is that it is battle-tested. It's not sexy, it's not clean, but neither is the real world.
Then why do we keep finding these embarrassingly basic flaws in that "battle-tested" code?
It was literally weeks after Heartbleed. The screams for modern languages with managed runtimes had barely subsided. Then Tomcat, in common use world wide in the poster boy managed language, had a bug with exactly the same impact.
Nobody abandoned Java after that. Just as nobody will abandon C because of this.
> Ruby isn't great, but it's not had anything like the same defect rate
I would argue it's had much worse, altough the comparison is difficult to make. Rails against Apache perhaps, where Rails has had several problems of this magnitude.
I like modern languages as much as the next guy, but I'm very tired of "the language is the problem". Shoving more layers of abstraction in there is not a silver bullet for security. We need to look more at system programming languages.
> It was literally weeks after Heartbleed. The screams for modern languages with managed runtimes had barely subsided. Then Tomcat, in common use world wide in the poster boy managed language, had a bug with exactly the same impact.
You're talking about POODLE? Not remotely the same impact. It was a severe vulnerability, but it only leaked plaintext, not key material.
> I like modern languages as much as the next guy, but I'm very tired of "the language is the problem". Shoving more layers of abstraction in there is not a silver bullet for security. We need to look more at system programming languages.
It's not about adding abstraction. There are plenty of safe systems programming languages. The problem is specifically C.
Why would I? That wasn't Tomcat, and it didn't leak keys.
I won't read through the CVE list right now (because it is long, and that's only counting Tomcat and not the JRE itself), but it reused buffers and leaked potentially anything, very much like Heartbleed. If you were in a Java shop at the time you know about it.
> It's not about adding abstraction. There are plenty of safe systems programming languages.
No true Scotsman, eh?
Most people who go about how the world would be saved if we just got rid of C are provably wrong, because popular application in their pet language are just as bad. It used to be Java fanboys, that's why I mentioned Tomcat. Then it was Ruby (and don't get me started on the security disasters Rails brought with it). Most of these do indeed pile abstractions on popular data structures to minimize the possible errors. There are promising languages, but none in popular use, and very far from something you could audit.
> If you were in a Java shop at the time you know about it.
False; I was in a Java shop at the time and I don't know about it (though we ran things on Jetty (and later Netty) rather than Tomcat because we felt the code quality was higher).
That Ruby bug is a bug in the C implementation of the language. Surely this bug supports the point that modern languages don't have these problems, since you have to look at the bits written in C to find them.
As for "embarrassingly basic," it takes years or decades to find these bugs because we're using tools that hide them. Basic runtime bounds checking would have turned up this bug pretty much immediately. If you use a language where a buffer overflow often causes no runtime error, then of course it's going to be hard to notice when you've made one.
I think rewrites would have fewer bugs, yes. Maybe we know the defect rate more precisely for C than for other languages - but when it's this high, I'd sooner go with the unknown.
There is no single silver bullet, but there exists silver bullets for a variety of problems. When was the last time you saw a pure Python or pure Java code segfaulting from invalid memory accesses? When was the last time you saw a C/C++ code doing the same? It is simply negligent to use a language or system that exposes you to that many more classes of vulnerabilities.
Imagine a language where the compiler randomly accesses another variable if you mistype a name. No error, you just get a random value. Would you defend such a language? "All languages have faults! There is no silver bullet!" Except for this* particular problem, there is a silver bullet.
I agree wholeheartedly with your dismissal of rewriting battle tested code. But the need to migrate is clear, and we don't have to do it at once. It can be done slowly, one piece at a time. And it should be.
* For non-trivial code exposed to untrusted input.
> When was the last time you saw a pure Python or pure Java code segfaulting from invalid memory accesses?
When was the last time you saw Python or pure Java being used to write an interface between applications and kernel?
Those are different languages made for different purposes. They have their own issues (from top of my head pickle module allows to execute code if served with untrusted input), and generally applications written in them are not as wildly deployed as glibc.
Also it's kind of ironic to compare them because both Python and Java are written in C and C++.
I have segfaulted the Java vm plenty of times. Technically that is not pure Java code, because the vm is written in C++, but it was triggered by pure Java code. I don't use much complex code written in C++ that I can see crash, but I do remember Rails being very happy to execute whatever code you feed it as yaml.
Ultimately every language will have bugs when people neglect to validate all possible user input. There is one class of bugs that typically doesn't happen outside C (segfaults) but I can't imagine a C version of Rails executing YAML as code - and at least the segfault is more difficult to exploit.
Nobody is saying (or, at least, nobody should be saying) that memory-safe languages don't have memory safety errors. The point, and this is empirically backed up by tons of data, is that remote code execution due to memory safety problems happens way way less in memory-safe languages.
> I can't imagine a C version of Rails executing YAML as code
This is the crux of the matter, so please take a moment to reflect on why you believe that. Ruby code is used in much more dynamic environments; environments that would exist in some other language if Ruby didn't exist, and probably would show the same error.
By choosing C you are not allowing some vulnerabilities to gain resistance against others. The gains are in other places: performance, code inertia. And that's the problem. You are deliberately sacrificing safety. Truth or not, that's why everyone is so pissed at this class of bugs.
Remote code execution occurs in one of exactly two ways.
- eval/exec() of untrusted turing-complete code
- memory unsafety
There are strange machines and logic vulnerabilities, which as you're aware are pitfalls present in all languages, and can be very subtle. But these are not the same as executing arbitrary operations with all the privileges of the process. This is something we can end today, and Rust would suffice to do it, and can be deployed in many of the same situations as C.
The primary second-system problem with Rust is lack of a stable ABI and upgradable dynamic linking story (e.g. for generics which are effectively inlined even across shared library boundaries), which makes it impossible to upgrade shared libraries. That's a serious problem, which needs to be addressed if Rust is going to improve security for the world outside of the "we static-link 50MB of libraries" Web browser space.
Of course, but then you've hamstrung the typesystem that makes Rust safer than C, and generated a lot of work for developers who have to convert all internal types to #[repr(C)] ones at library boundaries. Rust without cross-crate generics is way less fun--you lose "theorems for free", unless you can make guarantees about the pre-ABI translation to/from C types (which would ultimately amount to defining an ABI).
You've hamstring the type system at the interface, internally you still get all the benefits. In any case, a typical use-case for a shared library like this is embedded into other applications, which may not be written in Rust at all, and hence things like generics don't work anyway.
In fact, this is reflected in a common way to expose an interface in this manner: have a main Rust interface (i.e. with normal generics etc.), with thin C-compatible shims that do the right set-up (e.g. converting arguments) to call that code.
Also, I'm not exactly sure how you expect arbitrary cross-crate generics to work for a dynamically reloadable library: they're somewhat inherently incompatible (a downstream type can be inserted deeply into a library's code). I vaguely recall inklings of some approach for C++, beyond just explicitly instantiating specific template parameter combinations in the shared library, so if you know more about that, I'd love to here.
Personally, I've always thought that type safety provides the most benefit at boundaries between codebases: you have people composing pieces that they don't understand, and the types serve to guide how those pieces can be composed to result in a correct program. Within a library, it's not uncommon that the core data structure or processing is only correct because a human looked hard at it--I would be surprised if Rust implementations of, say, font rendering, used types internally to guarantee the correctness of their rendering relative to a formal specification. Instead they're using types to make sure they process all input, or make sure they handle memory safely.
Generics are another place where this becomes the case: if a library exposes a "fold" function generic over both the accumulator and element type, accepting void* instead of T and U means you lose the ability to make sure user don't mix up places where you want an element and places where you want an accumulator.
Dynamically upgradable cross-crate generics, ultimately, require recompilation if you want the performance benefits of monomorphization. But it would be possible to have patchable sites around inlined generics that the dynamic linker could change to instead jump to a version which did not inline generics and which simply called into a non-type-specialized routine in the other library. You'd end up with two versions of compiled code in the binary around each call site for a generic: an inlined-optimized-and-code-motioned one, as usual, and an assumption-free one compiled considering generics to be opaque blobs and using dynamic dispatch for trait methods so that the same code can be used for all instantiations of the generic function.
For example, a function parameterized over an unconstrained type T can't do anything useful with values of that type, except dropping them. Thus code would be generated in which the call to the drop impl would be dynamic.
I think this can be made to work w/r/t non-object-safe trait bounds as well, but I'm not sure how to justify that.
I think library changes which alter which impls get called on which types (i.e. which affect specialization) would still be ABI-breaking, unless you had some more cleverness (type-id-based dispatch for the deoptimized case? ugh) to deal with that complexity.
I also don't think this solution is ideal, but I think it's very important for Rust to be considering these issues, because given the design of the current modern operating system, having separately upgradable libraries is really important. Maybe Rust should be pushing for changes in the OS design, in a direction like NixOS is taking, but it seems like it isn't ready use for building the future's userspace when it can't provide both safety benefit across library boundaries and separate upgradeability.
> Personally, I've always thought that type safety provides the most benefit at boundaries between codebases: you have people composing pieces that they don't understand, and the types serve to guide how those pieces can be composed to result in a correct program.
Yeah, type safety is definitely a big benefit when joining pieces together, but writing normal Rust code does this too, it's only the cross-language boundary that suffers (this is of course the most critical one, so having tools that avoid mistakes here is important, like autogenerating a C header). Joining random codebases together is especially common with cargo making it so easy to pull in dependencies.
Please don't misinterpret me: I definitely don't disagree with you that having in-language type safety and generics are important, but those are fundamentally not things you can guarantee when you're doing things across languages.
> Within a library, it's not uncommon that the core data structure or processing is only correct because a human looked hard at it--I would be surprised if Rust implementations of, say, font rendering, used types internally to guarantee the correctness of their rendering relative to a formal specification. Instead they're using types to make sure they process all input, or make sure they handle memory safely.
> an assumption-free one compiled considering generics to be opaque blobs and using dynamic dispatch for trait methods so that the same code can be used for all instantiations of the generic function.
> For example, a function parameterized over an unconstrained type T can't do anything useful with values of that type, except dropping them. Thus code would be generated in which the call to the drop impl would be dynamic.
This sounds nice on the face of it... but it also sounds outlandishly complicated to implement. On the latter quote, a function can also move data around, and moving (unknown-size) data around seems difficult, e.g. what's an always-works impl of the following?
fn push<T>(x: &mut Vec<T>, y: T) { let z = y; x.push(z) }
Note, no bounds.
Calling object safe methods (like a destructor) is the easiest part of implementing this.
> I think this can be made to work w/r/t non-object-safe trait bounds as well, but I'm not sure how to justify that.
I'd be... surprised; non-object safe traits suffer from all the problems of the `push` example, and more.
Every engineering decision has costs as well as benefits.
It's the same reason soldiers only wear heavy armour in the centre of their chests and backs. Why don't we armour the rest? Because the cost outweighs the benefits ... and the tooling (i.e. medevac and modern medicine) makes up for the deficits.
> At the very least: the cost of rewriting, re-testing, and re-deploying systems in a memory-safe language.
True. In most cases, this is completely outweighed by the benefit of not having these kinds of security vulnerabilities, as well as the other benefits that come with safety.
> The cost of realizing half-way through adoption that there are things that you need to do, used to be able to do, but cannot do in the new language.
Memory safety costs 10% in execution speed and takes 50% more effort to develop, a fair trade-off anyone can take on behalf of their customers. Shortcomings of C are offset by many eyeballs law and the security life-cycle processes so much so that we can afford to safely abandon most organized auditing as well as any defensive coding.
> Memory safety costs 10% in execution speed and takes 50% more effort to develop, a fair trade-off anyone can take on behalf of their customers.
That doesn't match my experience. Source?
> Shortcomings of C are offset by many eyeballs law and the security life-cycle processes so much so that we can afford to safely abandon most organized auditing as well as any defensive coding.
In effect, you're saying we shouldn't care about security vulnerabilities, because they'll be patched. That may be true for your projects. It's not true for many organizations.
No source. There's always a price to pay. Rust is zero-cost, but only on a subset of algorithms that are valid in idiomatic Rust. If you translate Rust to C you won't gain on speed, but if you translate C to Rust you might have to reorganize it and that will cost you. If your subset involved mostly increments and decrements of data and address registers then language called brainfuck would be zero-cost. Correct me if I'm wrong, but I'm led to believe at least some problems that would be most naturally expressed with graph structures will require reference counting when rewritten in safe Rust. Then again there are trade-offs in Rust that I might not be aware of, Singularity exceeded raw C in some instances by replacing overhead and functionality of MMU with static checking in compiler.
> In effect, you're saying we shouldn't care about security vulnerabilities
Cue a rant from spengler/deraadt. I was being sarcastic. We should rewrite most of our software in Rust 20 years ago. ;)
> If you translate Rust to C you won't gain on speed
Mostly this is the case, but we do give better aliasing info than C in many cases right now, and that can help optimizations. It depends a lot on your use case.
> Correct me if I'm wrong, but I'm led to believe at least some problems that would be most naturally expressed with graph structures will require reference counting when rewritten in safe Rust.
Most people use petgraph nowadays for graphs, which doesn't use RC; instead it uses indices, which are roughly the same cost as individual heap allocations. They have more computation for lookups, but the computation is usually folded into the addressing mode, and you get better cache behavior.
or use an arena (or backing store of handles, or graph of indices, or ...); which is basically the same situation as doing it in C unsafely with more or less the same cost.
> but only on a subset of algorithms that are valid in idiomatic Rust
IME almost all algorithms can be implemented in safe Rust, just that it takes some work to make it safe (e.g. using an arena, or shuffling some code around). The "limitations" of the borrow checker, for example (where it might be considered overzealous) can always be worked around by twiddling a few lines of code.
If it really comes to that (it shouldn't), you can always make your algorithm work with a few lines of unsafe Rust, and verify that locally. Still better than the entire thing being unsafe.
Regarding the 50% more effort to develop, we've gotten consistent feedback that the effort involved in writing Rust code is not more than any other typed language once you internalize some of its rules (which takes some time, but not too much).
>Every engineering decision has costs as well as benefits.
This is only true if you are superstitious or religious and believe that good things that happens to you (or humanity in general) must be balanced out by something bad happening.
Costs and benefits don't usually add up to 0. If they did, everything you do in your life would be absolutely irrelevant. The trick is to chose things with best cost+benefit value, and if it's not easy to express all costs and benefits in a single unit (because it may be hard to estimate them), then you chose so that you get costs and benefits you like the most.
The law of conservation of energy, as close to the metal as it gets. I remember reading about the same thing in terms of algorithmic complexity (in the context of data compression), and how one can only shift entropy about. Anybody got any keywords handy for practical examples? My hour of searching ended with conservation of information and black holes...
Opportunity cost doesn't make all decisions zero-sum. There is a quirk of human psychology that makes us believe that most things must be zero sum (i.e. that anything I do that improves something I care about must have a 'payback' at a later date and that to achieve a good outcome I must always make an equivalent sacrifice).
JabavuAdams is falling for that. Although his inability to control his own violent emotional outbursts is probably going to be the more significant problem in his life.
Are fucking serious? Have you built anything significant?
This is a very empirical view, not a superstition. If you want to learn more about this, and aren't just trolling look at the development of the A4 Skyhawk, the lunar lander, the space shuttle ... hell, any well documented large system.
I wish I could reach through the internet and smack you in the head for such a stupid comment.
> Are fucking serious? Have you built anything significant? [...] I wish I could reach through the internet and smack you in the head for such a stupid comment.
I gave you a general reply upthread, but this comment deserves a specific one. You simply can't attack people like this here. I don't think you intended it in a mean way, but it's still the kind of thing we ban accounts for.
ALL of these buffer overflows in C happen because the C stack grows backward, and old space is after new space.
If anyone have enough of a crap just to standardize a calling convention that went the other way, stack buffer overwrites would ALWAYS go into unused memory. Then security-minded people would switch to this calling convention for secure programs, and many problems would be solved.
(Of course heap problems would still exist but they are much harder to exploit and it is easy to make an allocator that tries to confound heap attacks.)
> ALL of these buffer overflows in C happen because the C stack grows backward, and old space is after new space.
This isn't true, as pointed out elsewhere in the thread. In fact, it even looks likely that this specific bug is still exploitable under your proposal.
Notice that the buffer overflow occurs not in __libc_res_nquery itself but in one of the functions it calls, send_dg. From a quick glance it looks like the call chain is __libc_res_nquery -> __libc_res_nsend -> send_dg.
Because the buffer is alloca'd in __libc_res_nquery but overwritten in a later call frame, an attacker should be able to overwrite either the return address of send_dg or __libc_res_nsend even if stacks grow the other way. So your proposed fix doesn't address this issue.
Yes, this does not solve every possible stack overwrite. But look at the number that have actually happened in the field and whether this would have dramatically reduced vulnerability in those actual real-world cases. Most times it would.
Most notably, for overwrites happening within the local stack frame, you completely remove the possibility of overwriting the return address. This is a fundamental difference in the level of vulnerability of that kind of code.
It's called "reducing the attack surface". Well-known idea.
Maybe my post was a bit hyperbolic, but I chalk that up to being so annoyed at this.
Overflows in functions whose activation records precede the buffer are --- obviously, because they necessarily involve hand-rolled copy loops --- rarer than those that don't.
Additional fun fact: a few early generations of mid-90s stack overflow exploits didn't even target the activation records at all!
I think it's interesting and worthwhile to consider whether stacks growing upwards would have made exploit development meaningfully harder, but I also think the answer to that question is straightforward: no, not really.
Hey jblow. I have a tremendous amount of respect for your work, and advocacy. Thanks.
Something that I've been wondering -- isn't inventing a new language for game developers fighting yesterday's battle?
I've been blown away looking at what Google is doing with C++ indexing, LLVM integration, etc.
I'm experimenting with model-based programming. I.e. toying with the idea that I'm not allowed to write any C++ code, only declarative models that generate C++ code.
This is part of a larger project to essentially automate myself away / transcend, which may or may not be quackery.
If it were "yesterday's battle", I would have a good programming language to use in my domain, without having to make one. But I don't.
Experimenting with model-based programming or whatever other future programming paradigm is healthy. I think we should do a lot of that, because the way we program hundreds of years from now hopefully doesn't look that much like today. BUT, you have to also be aware that there's a reason why these are future paradigms and not current paradigms, and that people building real programs today need to do something that works today. There is no way we could have built The Witness in any model-based system known today.
I agree with the general sentiment: "There are no silver bullets, only tradeoffs." However I disagree that that is the issue at hand. At the very least the trade off is on the wrong side of the equation, it is computers that should do stuff like bound checking not humans.
P.D. It irks me that Rust slogan is 'zero cost' abstraction. There is definitely a cost. The memory model is more complex than C. One shouldn't try to hide the tradeoffs behind 'marketing' slogans.
Well, the term "zero cost" is just borrowed from C++, where it means exactly the same thing: more expressiveness, more features, and a higher learning curve with no runtime performance cost. But I do agree with you that there is a learning curve, and some things that are more easily expressible in C are not as easily expressible in Rust (and vice versa). It's undeniable that writing a doubly linked list from scratch in Rust is more involved than doing the same in C (which is a tradeoff I'm glad we made, but like all tradeoffs it has a downside).
I am aware that it comes from C++. 'Excesive Marketing' is something that seems to pervade through software, not trying to single out Rust. I think 'zero runtime cost abstraction' would be a small improvement for the slogan.
Your overall point is good - reimplementing basic services will be costly and take a lot of time, and has the potential of introducing design-based errors, even if done in languages which avoid buffer-overflow errors. But you undermine it with your last sentence. Please don't do that.
I wonder why it is though, especially on long-distance buses (e.g. intercity), as opposed to public transport inside a city. It feels that a high-speed accident involving a bus would lead to a lot of fatalities and permanent injuries because of lack of seatbelts.
EDIT> Also, do you seriously think humans will be programming in 20-50 years?
We'll be operating at a higher level and just boiling it down to C/asm anyway.
If you want to make a difference, then work on machine programming. If you're inventing a new programming language, then you're fighting yesterday's battle.
Quantity has a quality all its own. If you look at what Google is doing with their code indexing, deep integration with LLVM, etc ... it's about tooling at a certain point, not so much the language.
> "The vectors to trigger this buffer overflow are very common and can include ssh, sudo, and curl. We are confident that the exploitation vectors are diverse and widespread; we have not attempted to enumerate these vectors further."
> "Remote code execution is possible, but not straightforward. It requires bypassing the security mitigations present on the system, such as ASLR."
It is time for the bimonthly Internet security meltdown. Again. When they say that "exploitation vectors are diverse and widespread", they really mean it. Patch ASAP. This is a race; it is only a matter of time before criminals start automatically and systematically scanning every server on the internet for this, and you really really want to be patched before that happens.
Thinking a bit more long term, it's pretty clear at this point that we need to expunge all C language networking code from the world, replacing it with Rust or pretty much anything else. That's not sufficient by itself, but it is necessary, or else the periodic Internet security meltdowns won't ever stop.