Just in from “C is fine just know what you’re doing”.
> Exploiting the bug does not require sudo permissions, merely that pwfeedback be enabled.
> the stack overflow may allow unprivileged users to escalate to the root account. Because the attacker has complete control of the data used to overflow the buffer, there is a high likelihood of exploitability.
Every time I see a fiddly fix like this where we're changing a buffer counter to a postfix instead of a prefix increment, I always think "why are we doing this shit ourselves?"
We should have standardized a C API to safely read/write buffers from files, networks, etc. decades ago. But here we are in 2020 manually moving pointers around - and what a surprise that we're finding "whoops, another off-by-one error, you know how it goes."
No one should be writing new code to read/write bytes. This should be settled. It's like a carpenter building their own nail gun each time they want to build a house.
The prefix/postfix change is a red herring, that doesn't do anything (since the expression result isn't used). Which means that the security fix is interspersed with code style changes. Maybe there's a reason to do this here, but it's often not considered good practice because it makes it harder to review the code.
AFAICT, the only real changes are in (new) line 411, resetting the cp variable after the loop, and line 416, ignoring write errors. The former is probably the relevant one.
198 also disables password feedback mode in !tty mode. So, style changes, behavior changes, and security fixes all in one changelist.
This is a small enough diff that I wouldn't complain if given this to review - but I probably also would've reverted the style changes (and would give myself at least 50/50 odds of splitting the changelist in two)
Perhaps I've just been too broken by 1000+ LOC diffs that intersperse multiple behavior changes and style and refactoring and ...
> The prefix/postfix change is a red herring, that doesn't do anything
The prefix/postfix change does something to the reader. It's a distraction trying to figure out if that change was intended, whether the author knows it has no effect, or whether I'm wrong about the change having no effect.
I appreciate the author doing the hard work of fixing this, especially with the code in full view of the public, but if I were an official reviewer I'd ask to get all the unnecessary changes removed.
Doesn't really make a difference, hg offers similar facilities (formerly the record extension, now hg commit -i). There's no index, but it's essentially the same as git commit -p.
Yes. This thread is blind-leading-the-blind. Threads against C almost always are.
When I was a pentester, out of more than 50 projects I was on, I always found at least a medium-severity bug (a bug which could allow an attacker to pwn the app using nothing but a web browser). Guess how many of those codebases were written in C? Zero.
The smugness is endemic to people outside of security. I was smug too. There are comments I wrote that I cringe at, looking back. Pentesting changes your perspective on things.
C isn't going away. Yes, let's make it safer (by pentesting). No, it's not a mortal sin to write C. I'll find ways to exploit your webapp regardless of the language.
>I always found at least a medium-severity bug
>Guess how many of those codebases were written in C? Zero.
So, based on those two comments, you have zero data (anecdotal or other) comparing the rate of bugs in C programs versus other languages since you didn't test any C programs.
Code security is not about eliminating bugs but about lowering the rate they show up at since things always get through and money/time is finite. Pen testing doesn't catch everything and many projects that have been tested still yield bugs. There's also a limit to how much money can be spent on testing so using it find (and document, review, etc.) low hanging bugs due to using C means you're more likely to miss a deeper bug.
Or in other words, spending the same effort to pen test a safe language will leave fewer bugs left over than spending it to pen test an unsafe language. So using an unsafe language is still worse.
I'm reminded of Scott Meyers most important design guideline, which I may be paraphrasing slightly, but it goes:
"An API should be easy to use correctly and hard to use incorrectly"
The dumbest link in the chain is always the guy writing the code, not the programming language, and not the machine executing it. So things should be designed in a way that makes it as hard as possible for programmers to make errors.
Right, this is more broadly applicable than even just APIs or software.
Think about how often you blindly inserted a USB A or B (mini-B, micro-B) plug the wrong way first and had to try again. Why is the wrong way even possible? USB C's plug fixes this.
A huge improvement to railway safety was the invention of Mechanical Interlocking. It was made essentially impossible to set signals in some inherently unsafe ways. When you tried to give a Clear signal to the Express, through a junction a late running Coal Train is already crossing, the huge metal lever you're trying to move to do that won't budge because all your controls are connected via elaborate mechanical interlocking to prevent this mistake. The signal remains at Danger, the Express stops, tragedy is averted.
> Think about how often you blindly inserted a USB A or B (mini-B, micro-B) plug the wrong way first and had to try again. Why is the wrong way even possible? USB C's plug fixes this.
To be honest this isn't really an issue once you notice that the logo side of the USB connector indicates up. I suspect this is mostly an issue because USB connectors have only a tiny self-alignment range (<1 mm), which leads to the "USB exists in a 4D state" meme.
This is a naive and dangerous perspective. You're basically saying that memory safe languages don't solve all problems so it's fine to use unsafe languages.
Don't write code in unsafe languages unless you have to (almost never for new code). Period. You will have some problems either way, but you will have fewer problems in a memory safe language.
Pen testing is not even remotely a good mitigation to depend on against the dangers of a language like C. Its fine to do for defense in depth and to find problems not related to memory safety, but it's no substitute for making a more sensible language choice.
Sudo isn't new code in any sense. "Rewrite the world in memory safe languages" is appealing if you completely discount the time and cost required to do so.
Can I ask what kind of apps you typically worked on?
A big part of why people make a big deal of C is that there are lots of code bases that have been under a lot of scrutiny from security minded code reviewers and pentesters and still keep on yielding more vulnerabilities. Ie it's hard to make C safe even when you really try and are a good programmer.
In contrast IME most pentesters in the biz work on code bases that have seen much less scrutiny and are often in-house and/or "enterprise" apps whose code was written by people who are probably more domain experts than generally good programmers or security-aware developers...
> When I was a pentester, out of more than 50 projects I was on, I always found at least a medium-severity bug
Perhaps you were a good pentester. More likely the code you attacked was rubbish.
I worked on a relatively important Internet-facing web application for about a decade, it was pen tested by a variety of outfits both when it was owned by a small start-up and when it was owned by a huge corporation.
Good design meant that we never saw any pen tester come anywhere close to "pwn the app" in either its public UI or the APIs which were (by my insistence) publicly accessible rather than wasting our time chasing endless "please change the allowed IP ranges" requests through layers of bureaucracy.
My experience was pen testers would bang on the predictable places, try obvious things, maybe trigger some alarms which I then had to pause - and get nowhere. We'd get back a report that said things like if I put in this nonsense email address I get a different error message than this other nonsense email address. OK, that's nice we'll put it on the backlog, did you find any actual security problems? No.
I'd say that using a memory safe language (Java) helped make that happen, IMNSHO as a very good C programmer for whom writing it in Java was not my first choice (nor was C).
I think most of the people you'll find writing code in C today have decades of experience programming. And I think the knowledge and experience they have makes a huge difference in practice when it comes to security engineering.
In comparison, so much modern javascript is written by junior developers - often without a lot of oversight. Junior developers can't "see" security vulnerabilities yet because they haven't learned to look for them. So I'm not surprised there's lots of critical vulnerabilities in modern software. Many coding bootcamps don't bother to teach any security engineering or best practices, and doing consulting work I've (often by chance) caught a disastrous amount of awful code people have tried to push to production simply because they didn't know any better. A lot of it is really simple stuff - guessable database IDs used as session tokens. JSON objects passed from the browser directly into mongodb. Authentication checks accidentally missing from certain admin dashboard APIs. Passwords and credit card information stored in plaintext, handrolled crypto, and so on.
Given the choice between C code and Rust code written by someone who's been programming for 30 years I expect the rust code would be safer. But if I'm asked to choose between C code written by an experienced engineer and javascript code written by a junior engineer, it wouldn't surprise me if it turned out that the C code was on average still more secure.
I think your vastly underestimating the amount of C and C++ written by junior programmers and you're also overestimating the benefits of decades of C experience.
Occasionally working with people who have spent decades coding almost solely in C or C++ at one of my old jobs. (Not just one person and not just one team.)
I don't remember too many specifics (it was a while ago) other than being brought onboard and having to introduce the tech lead to the "static" keyword.
So you are saying pentesting makes C safe? I wouldn't be so sure, I've seen several 50k+ LOC projects in C that had vulnerabilities even after several audits.
Is it so wrong to recommend a solution that will kill a good chunk of the problems right at the door?
And FYI, you are being pretty smug here as well. The fact that people criticised unrelated parts of the PR in addition to the relevant parts shouldn't give you a free license to dismiss the bigger criticism.
The idea that pentesting will make code safe is pretty crazy... This is basically the same as saying C code doesn't have safety issues as long as you code carefully.
Perhaps it's meant to match a style guide that recommends preferring postfix operators? Or perhaps it's editing shrapnel from an earlier version of the changelist, that maybe used the result of that statement?
As far as I can tell, the prefix/postfix thing here is irrelevant and mostly done for style reasons? It’s on its own line and the value of the expression isn’t used, unless I’m missing something obvious.
If anything the solution is more low-level education, not the exact opposite. People who understand these essential basics, e.g. by starting with Asm, are going to be far less likely to make these sorts of mistakes and write less buggy code in general. I mean, a buffer overflow is a very simple concept and it's not hard to understand. How many bytes could be written, and how much storage is reserved? It's really just arithmetic.
The amount of anti-education authoritarian fearmongering in these discussions is disturbing. Then again, given how much everything seems to be rapidly moving in the direction of dystopian corporatocracy, perhaps that's not so surprising.
There's already a few comments about how "safer" languages don't really solve anything. They just push the problems up higher in the stack of abstractions... and when that time comes, what are the chances the "security" fearmongerers will just start blaming something else? It's fundamentally a problem of competence, and doing everything you can to make the world a prison in pursuit of that "perfect security" is really not a good idea. There's no replacement for intelligence.
The existence or non-existence of exploits isn't very interesting. The rate that users make vulnerable code is more interesting. The fact that remote-code execution vulnerabilities are still very common in C codebases even after people and the ecosystem have had decades to learn the ins and outs of C is concerning. Maybe the rate of RCE occurrences in C codebases has gone down over time as people have been educated about vulnerabilities more, but I would guess that the rate of RCE vulnerabilities in C codebases is still much higher than the rate ever was for safer languages.
Pjmlp's post was directly responding to this part:
>People who understand these essential basics, e.g. by starting with Asm, are going to be far less likely to make these sorts of mistakes and write less buggy code in general.
Userbinator's post continues and seems to paint the ecosystem as getting worse over time, as if it were much better in this area in the past. Pjmlp's point was that even the original C users who came from assembly, who were presumably more familiar with the essential basics of assembly and memory management, still pumped out vulnerable code as we see today. It doesn't seem like having a userbase made up of experienced assembly programmers is enough for C to be predictably safe.
And C pre-89 is rather different than C11. The language changes, so comparing that which wasn't even specified against what we have to do isn't an argument one way or the other.
Some pre-89 code won't even compile today without putting in considerable effort.
Sure it is, a couple of CVE queries plotting C related exploits per year.
Morris worm was the first widely know C exploit, which still gives us about 30 years of exploits to look into.
Regarding how many exploits other languages have, for starters not memory corruption due to out of bounds access, integer overflow or implicit casts, as Algol derived system languages, since 1961 (8 years before C was even an idea), had checks for those kind of errors.
C only made it to fame, because original UNIX could not be sold and its source code tapes went from university to university.
Had it not been so, probably we would be using Bliss, PL/I, Mesa, PL/S, and not having arguments how good one needs to be to write code that doesn't allow for memory corruption errors.
"Rather different" is a bit much. I've been working with C since the 1989 or so. Other than K&R style function declarations / prototypes, C feels like it has barely changed over the years. It certainly hasn't changed as much as C++.
stdlib was introduced in '89. I specifically said pre-89, because this is what many C programmer's today depend on. malloc and free were brand new in '89 (mmap and friends didn't even exist yet). Instead you'd be playing with calloc and alloca.
Here's one for how safe that was: alloca could not guarantee the pointer it handed you is either valid, or the size you requested.
POSIX.1 only came along in '88 (with our first definition of stdlib). Whilst IO had mostly stabilised by that point, there were still plenty of platforms that didn't use the Bells header (stdio.h).
Files, signals, floating point behaviour, process creation - none of that was standardised yet.
Understanding these concepts is not the problem. The problem is that even people who understand them very well regularly make mistakes when programming with them, and in C these mistakes have disastrous consequences.
> There's already a few comments about how "safer" languages don't really solve anything. They just push the problems up higher in the stack of abstractions...
The problems that exist higher in the stack of abstractions are present in C programs too. C just creates a lot of additional ways to subvert your program.
The fact that pointers and memory management may be so simple on paper, but programmers still routinely mess them up in the same way after decades implies that hoping programmers will finally just start getting it right all the time isn't a good solution. The idea that to solve the problem, everyone else just needs to finally get better, feels like the sort of thing one would say to feel superior to others writing buggy code rather than be a realistic path forward for decreasing the amount of bugs.
I used to have the attitude around vulnerable code of "The problem would be solved if everyone else was just better [like me]". I was desperately trying to prove myself. I'm good at finding and solving tricky bugs, including exploitable memory corruption issues. It felt nice having a thing that I was clearly superior to others in. I loved languages like C where I had a great knowledge of the footguns and I could save the day in. But then I got a good job, I no longer felt undervalued and needing to prove myself at all costs, and my attitude shifted.
I can't write the whole codebase myself or double-check everyone's work. This didn't really matter if I was just concerned with making myself look good, but if the thing I cared about was the product itself and stopping bugs (especially exploitable ones) from getting in to begin with, then it wasn't enough to drop in occasionally to save the day and chide others for not knowing enough about the specific footguns lying around. If there were a bunch of tools that were misused 2/3 of the times they were used in the company, then I could accomplish far more by finding and advocating for safer replacement tools than I could accomplish if I tried to double-check every time the old tool was used and endlessly reminded people that the flesh-magnetic hammer will seek out your thumb unless you know to use some specific swinging strategy.
>There's already a few comments about how "safer" languages don't really solve anything. They just push the problems up higher in the stack of abstractions
A program written in any general purpose language can have high-level bugs like forgetting to check the user's password when they sign in, but only languages with manual memory management make it easy to also have an exploitable memory corruption bug when handling the memory containing the user's password. Solving some kinds of issues is valuable because that can help reduce the count, likelihood, and severity of issues that do happen.
>The amount of anti-education authoritarian fearmongering in these discussions is disturbing. Then again, given how much everything seems to be rapidly moving in the direction of dystopian corporatocracy, perhaps that's not so surprising.
>... and when that time comes, what are the chances the "security" fearmongerers will just start blaming something else?
What possible ulterior motive do you think people advocating safer languages have? I really can't tell if this is parody. Do you think Mozilla made Rust as part of some bigger political narrative, rather than to just make it easier for them to write bug-free code?
It's not about being "better" insomuch as being knowledgeable. My comp. architectures course had a lab where we manually implemented a buffer overflow from seemingly innocuous code, and since learning Rust, I've started writing all my C/C++ code at work to manually check for nullptrs and bounds. If I get it right, branch prediction makes it free. If I don't have it at all, well shit, I might make a critical error in production code!
This reiterates the idea that you should avoid rarely-used features of security-critical software, and is perhaps an argument that those features shouldn't exist in the first place. An extremely-minimal `sudo` alternative would be a nice idea.
Yep, one of the first things I did after OpenBSD switched was:
alias sudo='doas'
It's tough to remember to type 'doas' instead of 'sudo', especially when you use both Linux and OpenBSD all the time (which is why I also have a "doas" alias on my Linux hosts!).
Why change the name of the command just to switch to a different implemention? It should be possible to make a drop-in replacement for sufo itself in Rust or your favorite safe language, and a drop-in safe replacement is likely to get much more traction than some completely new thing.
doas is not intended to be a drop in placement for sudo. While the core feature (run a command as another user) is the same, many of the subfeatures are different.
That's my point: instead of telling everyone to switch to doas, which is a major disruption, someonee shold make a drop-in in sudo reimplementation, which can be used without disrupting existing workflows.
Instead if writing a drop-in replacement, which would be a lot of work, the authors wrote a replacement for the workflows they cared about, without nearly as much flexibility or complexity.
A 3rd party rewrite is a great time to assess what features are core features and which are extraneous. I haven't evaluated doas, but I'm definitely in favor of priviledged utilities having less code in general and having less complexity.
Sudo features exist for a reason. People use them. If you delete random features that you don't happen to use, the people who use sudo today won't switch to your new tool. It doesn't matter that in your opinion those missing features are unnecessary. You don't get to make the call.
If your goal is to eliminate unsafe C code from critical paths, you want a drop-in sudo replacement. If your goal is to just be opinionated, sure, make a non-sudo thing with a selection of features you personally consider important --- but don't be surprised if people keep using sudo.
Because sudo's full behavior is infamously complex. You might be able to make it better with a rewrite, but you're talking about a massive effort with significant risk, and all to reproduce a system that really should be redesigned to be easier to secure in the first place (hence doas).
The thing about C that's always baffled me is the resistance to abstraction that makes its lack of memory safety worse than it has to be. Nothing stops you, even in C, from defining a "string" data type that maintains an internal buffer size and read position. You can even give such a thing an elegant API, as many people have when they've implemented things like this.
But all over the place in C code, you see people open-coding all sorts of fiddly nonsense over and over again. You see people manually manipulate buffer positions and lengths; they'll laboriously type "!strcmp(a, b)" instead of defining a tiny little "streq" function. It's silly. However unsafe C-the-language's memory safety is, the problem is exacerbated by C-the-culture's resistance to using the abstraction features of the language. Why so little imagination?
Maybe it's a kind of self-selection thing: maybe if you're the kind of person comfortable with abstraction, you switch to C++, which is much better at it than C and just as efficient, leaving only the open-code-all-string-manipulation people writing raw C.
Exactly that, that is why every attempt to fix C security holes has failed, the culture just isn't willing to accept it.
Even C++ community is aware of the flaws inherited from C, hence why standard library supports arrays, strings with bounds checking enabled in debug builds and eventually in release as well, if one so wishes.
Papers are going forward to reduce amount of UB in C++.
Meanwhile, on C's side Annex K was moved into optional in C11, instead of trying to fix whatever implementation issues it might have had on C99.
Annex K has never been non-optional. No one has ever implemented a full compliant Annex K, and it's a terrible abstraction. It predates basic static analysis and suffers from more or less the same problems as non-Annex K variants. E.g., if you pass in the wrong buffer length to the K variant, you still get buffer overflows. How do you tell if the wrong length is passed? Static analysis. The same analyzers work with existing libc functions. I do think C or at least POSIX should have adopted strlcpy and strlcat, though; strncpy doesn't do what most people think it does.
I do agree that Annex K doesn't provide a proper solution, as data and length keep being separated.
However what I disagree is the way it was left to rotten without adoption, instead of actually fixing it.
Static analysis is not a solution, given that all surveys point out that only a minority cares to actually make use of them, or works in environments where they are welcomed, with supportive toolchains.
Also they don't work across third party binary dependencies, heavily used in the corporate environments.
So unwillingness to improve C language security in the standard shows how little those driving WG 14 care about security.
At least C11 made VLAs optional, which never were a good idea to start with.
I'm not saying static analysis is a complete solution, just that Annex K doesn't actually function without it. I.e., any criticism you want to leverage at static analysis also applies to Annex K.
(The compiler is one form of static analysis that users can't really get away from, though. Users can turn all the warnings off, but only the extremely novice or foolhardy do so.)
I agree letting Annex K hang out optionally in the standard isn't especially useful; in its current form it should be removed. It isn't clear how it would be "fixed" without being something completely different from Annex K. Unfortunately, C hasn't had an actual language update since C11; C18 was just a clarification and errata incorporation update. The C2x committee is in progress but hasn't yet made any binding decisions on formal language changes, AFAIK.
I don't know that 3rd party binary dependencies are heavily used in corporate environments. I find that a little difficult to believe. I work in an enterprise environment with a huge codebase and we have no 3rd party binary libraries. We have a number of 3rd party open source or NDA source libraries, but we can and do use static analysis on them. It would be impossible to get to the kind of reliability numbers essential to our business with unauditable, unfixable 3rd party code in our product.
WG 14 don't view bounded string copying as some core part of the language, I think. The C standard is about providing a minimal definition of a portable abstract machine; this leaves implementors or more concrete standards, like POSIX, free to provide their own safe abstractions. Quite a lot of the C standard library is sort of legacy accidents that might not be put in the standard language again if reinvented today. But there is obviously a lot of motivation to retain backwards compatibility.
> So I think that after the lengthy discussion here, this issue can now be closed. In summary: the use-after-free potential with string_view is accounted for in the Lifetime profile, even if there may be bugs or limitations in preliminary implementations that mean it is not diagnosed.
This is the big difference across both language communities, being willing to improve instead of being stuck with what was born alongside UNIX.
C++ is certainly a better boat to be in thanks to RAII and other things, but static analysis is not a panacea here.
> even if there may be bugs or limitations in preliminary implementations that mean it is not diagnosed.
The huge assumption there is that finding all or even most lifetime bugs involving string_view and other dangerous C++ idioms via static analysis is tractable, that it's just a matter of "overcoming bugs in preliminary implementations of the analyzer". There is no reason to believe that; in fact there are good reasons to disbelieve that, including:
* Languages with static lifetime checking, i.e. Rust, found it necessary to add lifetime annotations, which the C++ lifetime guidelines don't have.
* Just after that Github discussion, Herb Sutter released version 1.0 of the C++ Lifetime Guidelines with dramatically scaled-down ambitions. See https://robert.ocallahan.org/2018/09/more-realistic-goals-fo.... Anyone relying on the unrealistic promises of version 0.9, probably including the commenters in that Github discussion, would have had their hopes dashed.
Relying on the emergence of some crazy-powerful static analysis to save us reminds me of the Itanium fiasco, where poor performance was portrayed as a temporary problem while we wait for compilers to "catch up", which of course they never did.
No one is going to port DriverKit, Project Treble drivers, Metal Shaders, WinUI, Windows Universal drivers, ARM mbed, Arduino, AUTOSAR security certification, Qt/KDE, Unreal, PS4/5, Xbox XDK, CUDA, LLVM, DirectX, C++/CLI, C++/WinRT, SYSCL, C++AMP, and plenty of other C++ based tooling to Rust.
At very least, not in the next couple of decades. Keep in mind that it took 30 years for C++ to get where it is, and there are still domains it keeps failing to take away from C.
So having C++ lifetime annotations is better than nothing, in what concerns improving the language's security.
One of the issues I see with C is that there is a core set of C developers that sees C as the only systems language, being "just" a portable assembler, and if you're not good enough to avoid these pitfalls in C, you shouldn't be programming it. And, as you allude to, I suspect that many of the people with different viewpoints have moved on from the language, leaving this conservative C core in charge of language evolution design.
I'm reminded of hand-washing in medicine. Even after it became indisputable that hand-washing improved patient outcomes, doctors didn't want to do it: they felt insulted by the implication that their hands weren't already clean. The truth is a doctor could be the most hygienic person in the world and still walk into a surgery with germ-coated hands. A hand-washing requirement isn't a personal insult: it's just a reflection of reality. But pride gets in the way.
Likewise, even if you're the best programmer in the world, if you write a large C program, sooner or later, you're going to introduce a security vulnerability. Switching to a safe language isn't an admission of some personal inadequacy: it just reflects how the world works. But pride gets in the way.
People should also not forget that the C culture comes from a age when the philosophy of letting your executable be as simple as possible, do only one thing and do it perfectly & interoperable was so prevalent it did not need discussion. It's a much different ecosystem compared to today; thousands of abstractions, tons of includes, unknown functions etc. In one environment it's "ok" to make "a" mistake, in another it ain't.
It's actually pretty hard to write an elegant API for C strings and have it perform well and be safe. Either you do a lot of copying, or you pass pointers around and risk use-after-free.
But perhaps the bigger problem is that consuming third-party libraries in C is so painful. It's not too bad if you stick to shipping on Linux distributions which you're sure package the libraries you depend on. Otherwise, you basically have to vendor the library manually. It is always more convenient to just write "strcpy" in a few (more) places.
> It's actually pretty hard to write an elegant API for C strings and have it perform well and be safe. Either you do a lot of copying, or you pass pointers around and risk use-after-free.
Reference-counted heap-allocated strings are fine in practice for almost all programs, and it's easy to write an API that implements this model.
Thread-safe refcounts or not? Do you have separate types for "mutable owned string" and "immutable ref-counted string"? Small-string optimizations? You may think there's a single obvious answer for all these decisions but major libraries for other languages have made different decisions for each one.
Refcounted strings are actually very nasty to use in C because you have no RAII or other automatic memory management. You have to manually deref every string value when it goes out of scope, or get leaks --- and if you accidentally ever double-deref along some path, you get probably-exploitable use-after-free.
Once you've picked some tradeoffs for your program, you have to live with the fact any foreign C you call isn't using your library so you'll be converting to/from their types (typically const char*) all over the place anyway, so the temptation to "just use chars" will be ever-present.
Given those issues, I'm not surprised I actually haven't seen any C programs use this model.
Yes, these are all questions you need to answer. Thing is, no matter how you answer these questions, what you produce is still far more convenient and much safer than random char buffers everywhere. If sudo had a 200 line internal string library, this vulnerability wouldn't have happened. There's no excuse for open coding lots of random string stuff.
char buf[16];
sprintf(buf, "/proc/%d/mem", pid);
int fd = open(buf, O_RDONLY);
If you accidentally double-free or have UAF along some error path, then it's not safer either (given compilers have strong mitigations for stack buffer overflows these days, but not double-free or UAF).
The code you're using to demonstrate the simplicity of char arrays has a buffer overflow vulnerability: what happens when the input int is large? You're proving my point.
In practice, dynamic resource allocation doesn't lead to rampant use after free bugs, especially if you follow regular and simple rules for ownership. It certainly results in fewer bugs and fewer severe bugs than cowboy char arrays code.
> The code you're using to demonstrate the simplicity of char arrays has a buffer overflow vulnerability: what happens when the input int is large? You're proving my point.
I did that deliberately to show how tempting, but also dangerous, it is to use char buffers in C.
I'm not advocating char buffers. I'm advocating not using C, and not buying arguments of the form "C's fine if you just do this thing that no-one does".
Just a naive question probably but, is there any particular reason why security related executables like sudo aren't replaced with versions written with a bounds checked language like say Go or Rust?
Microsoft does this. They are reimplementing a lot of internet protocols using Fstar. So not only bounds checking, but also verified. But it’s a huge undertaking!
There are people doing that, there is one or more kernles written in Rust but still even Rust evangelist are not using it daily but demand for people to waste time rewriting in Rust.
A technical reason for GNU/Linux not to use Rust or Go is that C supports a lot more platforms so you can't replace core components at this moment, also I want to remind you that memory safe languages existed before Go and Rust and the only project I am aware to create a safe OS and utilities was Midori by Microsoft.
Firefox and it's dependencies is not yet 100% Rust so I honestly expect a new browser started from scratch in a safe language to be done before Firefox is "ported".
> I honestly expect a new browser started from scratch in a safe language to be done before Firefox is "ported".
That is a gargantuan project considering all the standards you need to support to have a competitive web browser. Especially javascript engine, video codecs and webgl are attack surfaces that is difficult to replace with code written in a safe language.
I think is less work then pressuring existing projects to be rewritten in Rust. I would usggest Rust fans to pcik one of those dependencies like a codec and reimplementit in Rust or gather money to pay someone to do it, I think it would be faster and less hostile.
From what I read Mozilla fired one or more Rust developers related to the JIT or the interpreter and IMO the JIT should have been the first thing rewritten in Rust since that is one of the components that run arbitrary code. I really hope they can finish the port and I can't wait to see how many zero days will it have.
most of the JIT bugs were probably due to generating unsafe code instead of generating from unsafe code. Rewriting a new JIT will probably increase the number of zero days.
That's a ridiculous statement. Most of Linux isn't reviewed by security experts either... and the codebase for linux absolutely dwarfs redox, and every line of Linux is C.
For starters from Google, with their Linux Kernel Self Preservation project, where an endless list of exploits have been fixed, starting by removal from all VLAs in the kernel source.
Going forward, all Android devices on ARM are required to use ARM MTE, because the only way to keep C develoeprs on track is to have hardware control their pointer usage.
Given that Linux kernel downstream on Qubes , ChromeOS and Android are the only ones with all of the security counter measured turned on, that speaks a lot for the standard Linux kernel on random distribution X.
Just thinking numbers, there might be 100 free software projects that you'd consider "security related", and each might take a couple person-years of development time to port on average. If you put a ballpark salary of $50k to that time, that's around $100,000*100 = $10M. And that's a conservative estimate because each of those factors are probably too low.
There have been efforts to create bounds checked compilers for C. And split stacks as well. Both of those solve 90% of these sorts of problems.
I think the real problem is
a) C and C++ need a divorce. Because the things that you would do to really fix C make it incompatible with C++.
b) The senior maintainers of the gcc compiler need to be replaced with people willing to take these issues seriously. Right now they are focused on how fast their micro benchmarks run at the expense of everything else.
> Both of those solve 90% of these sorts of problems.
A lot less than 90% in the graphs at https://www.zdnet.com/article/microsoft-70-percent-of-all-se.... Use-after-free and type-confusion bugs definitely aren't addressed by bounds-checking or split-stack approaches; many heap corruption bugs aren't either.
I've always considered root escalations to be pretty minor because you can just launch a phishing attack by adding the following to the user's .bashrc.
alias sudo='sudo ./badscript; sudo'
Most Linux/Mac installations are single user anyway, so you can steal the user's private information without even gaining root.
The threat here is if a non-interactive non-root user (e.g. one used for a service like Apache's httpd) is pwned by another means and then this is used to further escalate to root.
Otherwise you're right, despite much cargo cult, in practice pwning an user that can interactively run as root, is the same as pwning root.
That's true for your local machine, and I think it's a reasonable threat model for that.
If you do development for servers, though, (1) multiuser systems do exist where no or most users do not have sudo access, and (2) privilege separation accounts (such as www for httpd) generally do not run interactive shells. If you can escalate from an untrusted user on a multiuser system or from a non-interactive shell account to root, that's a problem. Dropping this alias won't help you on either of these kinds of system, which are important real world scenarios (but maybe not a scenario you need to care about personally).
To use sudo normally requires the user's password; to edit bashrc does not. So yes, this is a trivial privilege escalation mechanism of a sort.
It does require though that the user both is allowed to run an interactive shell with sudo, and actually does so, since you have to wait for him to run sudo in bash.
Reminding people that this is a viable threat model is why sudolph.in exists, albeit for the drive-by someone-left-their-laptop-unlocked case rather than regular local privilege escalation.
I should probably add a check for catching someone within the sudo timeout and give them a hard time if so.
Has anyone started a project to rewrite `sudo` in Rust or some other memory-safe language? It seems like a prime candidate: very security sensitive, well-defined functionality and API, low total complexity.
"Just a tiny change, password feedback is now enabled by default. So when you type your sudo password you’ll see asterisks instead of wondering if you’re typing anything at all ;)"
The standard for non-Windows/DOS terminals has always been to show nothing during password entry. Does elementary show password feedback in all terminal contexts, or just for sudo?
I think my response to all of that is "Who cares?". It's a totally fine and reasonable UI change and it's incredibly unreasonable to think "Ah, this minor UI change might lead to a buffer overflow because we code like it's 98".
>The code that erases the line of asterisks does not properly reset the buffer position if there is a write error, but it does reset the remaining buffer length...
Then the next paragraph:
>Because the remaining buffer length is not reset correctly on write error when the line is erased, a buffer on the stack can be overflowed.
So which is it? Does it correctly reset the buffer remaining length or not?
These types of contradictions existing in bug descriptions do not fill me with confidence that someone has actually fixed the issue. Guess I'll have to dig into this one myself. Just what I wanted to have to do. Excuse me while I go and learn way more about the innards of sudo than I ever wanted to.
Buffer overflows in sudo (seen on debian/ubuntu mainly) have bothered me for a long time since they lead to crashes. I'm glad they are finally fixing some of them!
I'm willing to work on replacing most GNU / UNIX tools with Rust equivalents. Anyone willing to fund such an effort?
Seriously though, as others are saying sarcastically, I'll join them right up: "C is completely fine, you are just doing it wrong, it's a safe language when you know what you are doing".
Yeah, about that. <Glances the article again>
It's not a matter of "if", guys. It's only a matter of "when".
No, not yet. The Rust ecosystem is still not mature enough. One of the big problems is Cargo which sidesteps distro packaging and pulls dependencies from some random server. This doesn't fly if you actually want reproducible builds. The other problems are the complete disregard for dynamic linking and the amount of churn in the standard library and compiler. Give Rust a few more years for things to stabilize and for people to figure out a reasonable way to handle these problems. The work will probably fall on the distro maintainers.
My opinion is that if you're looking for a short-term investment that someone might want to make towards fixing these problems, try working on static analysis security tools for C and C++. It could not be easier to make these now with libclang. Alternatively, maybe get your company to fund a purchase of PVS-Studio or something like that.
Distribution maintainers have already figured out how to package Rust in a modular fashion and build without touching the network. Cargo specifically provides facilities to help distribution maintainers provide dependencies themselves. Based on those facilities, I built the initial prototype of Rust packaging for Debian ("debcargo"), and the current Debian Rust maintainers have made further huge advances in packaging.
So no, Cargo doesn't "sidestep" distribution packaging, it works with distribution packaging.
Also, neither the standard library nor the compiler have "churn"; we're very careful to make sure old code still compiles with current Rust.
Yes, debcargo is a great solution. Thank you for writing that. And really, I mean it, it's awesome. But my point (which you seem to be agreeing with) is that we have a while to wait while the distro people do their thing. Not just Debian. We can't just take random packages off github and put them in any distro, there is more to it than just running debcargo.
>Also, neither the standard library nor the compiler have "churn"; we're very careful to make sure old code still compiles with current Rust.
When did this happen? Last I checked Rust still had no LTS policy and there were no stability guarantees around the syntax, ABI, API, compiler flags, Cargo file format, etc. If some policy has changed here, please link me to any info.
> Yes, debcargo is a great solution. Thank you for writing that. And really, I mean it, it's awesome.
Just to reiterate: I wrote the initial prototype, and massive credit goes to the current maintainers who picked it up and ran with it into production.
> there were no stability guarantees around the syntax, ABI, API, compiler flags, Cargo file format, etc.
There are absolutely stability guarantees around syntax, around compiler flags, around API, and around the Cargo file format. Code from old Rust and old Cargo will build and run with new Rust and new Cargo. We take a great deal of care to preserve that stability guarantee. The policies here have remained essentially the same since 1.0 (https://blog.rust-lang.org/2014/10/30/Stability.html), though we've since taken further care to specify them more precisely and to stabilize further areas.
(Source: I'm one of the leads of the Rust language team, and I'm on the Rust cargo team. Other Rust teams, such as the Rust library team, follow the same principles.)
That post doesn't have any concrete promises about what
stability actually means. Furthermore I don't see that in practice. To be able to say you're stable you have to have a spec and stick to it over a number of years. Is there one? Unit tests in rustc don't count because contributors just regularly delete or change those. I still see statements coming out from other contributors describing regular regressions and breakages to the core language happening even in the last few months:
Disclosure, I don't work on Rust or any other language, I just keep an eye on it out of interest. I give it another few years before these big regressions stop happening. Please keep at it and don't take any of my statements as being discouraging.
Compilers are big, complex programs, which means they of course have bugs in them. If you think rustc is unstable because of a half-dozen regressions in the last release cycle, then gcc must seem to you to be a dangerously rickety piece of software:
I don't see how that's relevant. Most of those bugs are in GCC 10 which isn't released yet specifically because those regressions are all still open. Meanwhile, GCC 7 just got a bugfix release a few months ago. It just doesn't make sense to compare C/C++ compilers to rustc; my point is that rustc doesn't have a stable LTS release yet. The only way I could see this happening is if developers of other Rust compilers put pressure on your team to make a spec and stick to it for a number of years. Otherwise you're headed for a situation like Haskell where there is only one real compiler and it's permanently in a state of flux. Distro packaging for Haskell and its libraries is still a pretty complex task BTW.
I feel like this is combining several different things that don't inherently need to be combined.
There's active work on Rust specifications with varying degrees of formality, but I don't think that would address the problem you're describing. Specifications don't make bugs go away.
There are, occasionally, regressions in rustc. We catch the vast majority of those before they hit the stable branch, but bugs do happen. We try to minimize them, and we also put out stable point releases with fixes.
I also don't think that having multiple Rust compilers would affect the problem you're talking about. On the contrary, you'd then have multiple different sets of occasional bugs to deal with, and multiple implementations with differences that you'd have to cope with.
Speaking personally, (and not wearing my language team hat), I personally don't see value in having multiple compiler frontends for Rust, given that rustc is Open Source. (I absolutely see value in having multiple code generation backends, but not in having multiple frontends.)
Finally, regarding an "LTS" release: stable releases of Rust are supposed to be stable, not stagnant.
(as of iirc 1.39) Cargo supports custom registries on stable. That means you can pull your dep to whatever hosting solution you want and leave it.
As well, pulling from a random server isn't the problem. Trusting a version to be a particular commit is, which crates.io handles as well as Cargo via cargo publish and the lock file. If you pull a dep, the lock file will guarantee that whatever you pull is going to be identical to what was published (assuming you didn't add a git field to the dependencies list or say version = "*").
As for churn in the std or dynamic linking, I've never had an issue using Rust in production and getting reproducible builds otherwise. Same is true for Clang or GCC/stdlib. APIs aren't being changed, binaries may shift, but unless you're on nightly you're probably not going to see anything break - just get better. But you can handle that, just don't update the compiler until you're ready.
Distros still won't ship that because it causes needless code duplication. Edit: I don't mean this as a general dismissal of that strategy, but more to illustrate what problems you'll run into trying to get Rust packages into a distro. A huge amount of work has gone into most distros to "un-vendor" dependencies because it leads to a lot of problems.
It is possible to vendor with a system-wide repository of packages to use. This is how Debian handles cargo dependencies, and I think Nix does something similar as well.
This only works if you keep your list of dependencies small and limited to the ones that are stable and have been tested and packaged. It still seems common to get in npm-like situations with Rust programs where typing "cargo build" installs hundreds of packages. This is what I mean by the ecosystem lacking maturity.
Morris worm was caused by a buffer overflow, 30 years ago!
Oracle has SPARC ADI, ARM MTE (now adopted by iOS, in the future Android as well), because the only way to have those magical excellent C coders, is to have the hardware be their tiny wheels.
Why not just build a complete Linux replacement, since the kernel is also C?
I'm not really that concerned about C so I won't be able to fund the efforts, but I'm sure once you have a bootable operating system that works with existing software that people will be willing to donate to you on Patreon.
> Exploiting the bug does not require sudo permissions, merely that pwfeedback be enabled.
> the stack overflow may allow unprivileged users to escalate to the root account. Because the attacker has complete control of the data used to overflow the buffer, there is a high likelihood of exploitability.