The first principle was security: The principle that every syntactically incorrect program should be
rejected by the compiler and that every syntactically correct program should give a result or an
error message that was predictable and comprehensible in terms of the source language program
itself. Thus no core dumps should ever be necessary. It was logically impossible for any source
language program to cause the computer to run wild, either at compile time or at run time. A
consequence of this principle is that every occurrence of every subscript of every subscripted
variable was on every occasion checked at run time against both the upper and the lower declared
bounds of the array. 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.
-- Turing Award lecture 1981
This is why having C on our foundations matters, even if our daily programming languages happen to be safer and not susceptible to memory corruption.
What's really surprising is that arrays never made it to the instruction sets of CPUs where the bounds checking could have been done in hardware and be essentially free.
The 80286 (and above) were segmented architectures where automatic bounds checking of each segment (which could hold an array, or code for that matter) was done invisibly in hardware. The segmented nature of the 80286 (or even the 8088, predecessor of the 80286, which had segments but no automatic checks) was ... not universally liked (mainly because of a 64K limit, but even when the 80386 upped that, not many systems took advantage of it).
The Intel 432 (developed in the very early 80s) had automatic bounds checking. The architecture as a whole failed because it was too complex, too slow, and too buggy to be viable.
x86 has the BOUND instruction that checks an index against a lower and upper bound and raises an exception on failure. It has been a very slow microcoded instruction for a long time,thus practically unused (which lead to a catch-22 as intel never saw fit to improve it). IIRC it was removed in AMD64.
Sure, I wasn't making any judgement on its utility.
BOUND would have been great for compiling any language with built-in bound checking (I wouldn't be surprised if it was made with pascal in mind, same ENTER with nesting level > 1).
MPX was specifically designed for the C family languages, but it has a fairly high cost, we will have to see whether it gets widespread.
Re culture, aren't there quite a bit of high profile projects that are compiled with hardening by default ? At least firefox comes to mind.
Yes, but Mozilla cares enough about security that they created Rust.
Also although C++ shares the same flaws as C, due to the compatibility, the overall culture is a bit different.
There are the C expats that basically use it as C with Classes, and there there are the Ada/Pascal/ML expats that take advantage of the type system and standard library to write safer code.
The problem with security is that most projects tend to have a mix of those cultures, and also there is no control over 3rd party binary libraries.
Now imagine you know a way to change the first byte in the password entry (i.e, what becomes correct_pass) to \0. Now pass a blank password to this function and it will always return true for any username.
I know you were intentionally using strcmp() to prove a different point, but I want to point out that strcmp() does not run in constant time and this function also contains a completely different vulnerability: timing attacks. You could retrieve the correct password by measuring how long it takes passwords to be rejected even if you weren't able to inject a null byte.
I'm curious, has this ever been demonstrated to be feasible in practice? I mean a real example of someone getting a full password through a timing attack in a real-life (or reasonably similar to real-life) scenario.
I don't remember a concrete example out of the top of my head but it has been demonstrated that it is possible to pull of this kind of timing attack if the attacker has a computer in the same datacenter as the target.
The initial Wii boot firmware had a similar issue when checking the signature of the next stage firmware as it used strcmp instead of memcmp. A carefully crated firmware would bypass the check.
A strategy to mitigate that kind of exploit would be to re-validate any passwords passed to the system against the rules for entering the password in the first place (for instance: minimum password length).
Then you might still get some mileage out of it (you'd be able to shorten the password).
You'd also hope that in all production systems live at the moment the passwords stored would not be in plaintext but properly hashed so writing random nuls into either one of the hashes would simply result in a mismatch.
I don't think one should mix validation and safety as such since it risks becoming non-obvious. When the validation criteria changes six month later someone might rewrite the validation without considering that it's also a safety feature. By making the proper way to use it restrictive one might end up bypassing the validation and using the unsafe part by re-implementation if not directly. It could also be that the error isn't malicious, but that the hash for some reason ends up being faulty.
Yep, that's all true -- this type of attack can be mitigated in various easy ways. But there are lots of other examples where writing one byte can break a system. I was just trying to prove the point by showing a particularly simple one.
The allocator used on that project sure seems to think that off by one errors never happen. Why on earth would you put the in use bit in the first byte in your object header? Good grief.
What would you do instead? Add a padding byte in case of a buffer overflow? If you write code with lots of what-if-this-happened cases instead of just concentrating on creating correct code then you're most likely going to create an even bigger mess.
If you also have a known (preferably random) value as padding, and you check it at deallocation, it is called a canary, as in canaries in coal mines. It's a commonly used method to find/protect against overflows.
For stack overflows, see -fstack-protector(-...) for gcc and clang.
For heap overflows, it would depend on the implementation of malloc. glibc has mcheck() and MALLOC_CHECK_.
If you're doing this as a part of release testing, ASan (and other *Sans like MSan) is worth looking into.
Not a padding byte, but a word sized sentinel before and after the allocated block that gets checked upon free. Sentinel gone? -> overwrite bug detected.
Good thing Chrome's bug bounty is so high, otherwise white hats would probably never spend this much time exploiting bugs like this to show how big of a deal they are.
The security community has been doing much more than this for free for like 25 years. This exact attack was documented and demonstrated about 15 years ago.
There used to be no such thing as getting paid for security bugs and exploits, yet there was no shortage of free published research on mailing lists and other ascii-friendly distribution channels, including exploits and walkthroughs.
Given that this involves memory allocation and the fact that it has to be triggered with a specific sequence of HTTP requests, does it mean that the possibilty of this happening is extremely rare? Any system on which this attempt is done probably needs to be having not many others processes running which might trigger memory allocations and thus break this specific set of steps to exploit the issue?
Not trying to belittle the issue or the efforts spent to report it, but trying to understand how frequently it could be exploited.
The entire details of the exploit are apparently in a 37 page writeup which is yet unreleased. So it's safe to assume that it is requisite on a very specific chain of events that are more likely to happen in an OS like ChromeOS where there are a lot fewer simultaneous actions. Additionally, ChromeOS' multiprocess model means that you don't have to worry nearly as much about those other actions, because the allocators are probably going to be running according to your assumptions.
> " the fact that it has to be triggered with a specific sequence of HTTP requests,"
Does not sound like a limitation at all. This is the very normal "browser attack model" where you assume that an attacker can execute code in your browser. (aka he either controls a webpage you are visiting, a banner on a webpage you are visiting or the network stream and messes with http webpages you are visiting).
I don't understand how this leads to a root exploit - I assume c-ares is running in userland inside a user's host process (a web-browser) - or if there is a systemwide daemon for DNS or other network services hosting c-ares then it should run under limited privilege. Which component is already running at root that allows this to happen?
The exploit was made in the c-ares function, that was running on the HTTP proxy that was running as root (and on the same system).
The attack requires javascript and an evil webserver; its not a fully local exploit.
Firstly, the attacker has a level of control over the type of data that lies before the freed and wrongly coalesced block, and so they have a level of control over how the data they are overwriting will be interpreted. It might actually be possible to arrange that the data block is expected to contain dynamically generated executable code (e.g. output from JavaScript JIT compilation), and by replacing that with arbitrary position-independent code you immediately still have arbitrary code execution.
Secondly, this class of bug potentially also allows for information leakage. Block A precedes block B in memory, and B's header is overwritten. A is freed and merged with B. Now the attacker induces (A+B) to be allocated as a block type that lets them read out data, and then induces some data that includes addresses to be written to B by way of an update. The attacker then reads out (A+B), gaining information about the address space that they can then use for a successful exploit.
ASLR certainly can make attacks harder and some attacks impossible, but it is not safe to assume it gives you immunity from exploitation for this type of bug.
"Being exposed to djbdns I was never tempted at all to try c-ares.
Not sure what I missed. It must have some other redeeming qualities besides this one. :)
Learning to master nc and tcpclient before curl* had the same effect. I guess I am missing all the fun.
*There are so many features, so much rarely used code, I'm not sure one could ever hope to fully understand all the implications." jingo 22 hours ago [dead] [-]
The first principle was security: The principle that every syntactically incorrect program should be rejected by the compiler and that every syntactically correct program should give a result or an error message that was predictable and comprehensible in terms of the source language program itself. Thus no core dumps should ever be necessary. It was logically impossible for any source language program to cause the computer to run wild, either at compile time or at run time. A consequence of this principle is that every occurrence of every subscript of every subscripted variable was on every occasion checked at run time against both the upper and the lower declared bounds of the array. 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.
-- Turing Award lecture 1981
This is why having C on our foundations matters, even if our daily programming languages happen to be safer and not susceptible to memory corruption.