This is about the easiness of "hiding a backdoor in plain sight", namely in the source code. We are no longer talking about compilers, but about human brains. The whole idea is to make the source code "look legit" for a human code inspector, who do not have time to really delve into details, and who uses his human mind for the task, with all the quirks that a human mind can have.
Since this is psychology, there will be no clear-cut, mathematical answer. However, we can somehow argue that it has a lot to do with language complexity. To "hide in plain sight", the attacker must do two things:
- Use as backdoor an obscure rarely used sub-feature of the programming language.
- Make use of this sub-feature in a code structure which still looks "natural", as in: "an honest programmer would have written code which that structure".
At one extreme, you have assembly. Assembly is not complex. Assembly is very "poor": it works over a few constructions (just "jumps") and a restricted list of opcodes. The code writer may find some rarely used opcodes or opcode behaviours, but he will have a hard time fulfilling the second condition, because "normal assembly code" must have a lot of structure that the programmer enforces on himself, because otherwise his task becomes infeasible. Any use of a rarely used opcode will appear as such; the code will look funky, and this will raise alarms in the brain of the code inspector. Thus, a failure at hiding.
At the other extreme, you have quite complex languages such as, say, C# or Python. There are a lot of subtle details and behaviours (in particular, operator overload in C# can be used to trigger a lot of things from code which looks "normal"). On the other hand, such languages tend to have a very "deterministic" specification, with no or very few undefined behaviours, which helps the code inspector.
Thus, C and, even more so, C++, are a soft spot for backdoor hiding. They combine an awful lot of undefined behaviours with relatively complex syntax games. C++ adds many extra twists to the syntax, and many ways to add "hidden behaviours" (operator overloading, implicitly called constructors and destructors...).
For these reasons, we could claim that different languages are differently vulnerable to plain-sight-hiding attacks, and C++ is very vulnerable, while bare-bone languages like Assembly are less vulnerable through a poorer syntax, and higher-level languages are less vulnerable through a stricter language specification on corner cases.
All of this is of course very debatable. As I said, it is about psychology, and when it comes to analysing how human minds work, each human will think about his own mind first. Since not everybody thinks in the same way, answers are bound to vary.
Also, I cheated. My argument above about how assembly makes it harder to hide backdoors in plain sight is valid only for a code inspector with a lot of time on his hands. To implement a given, non-trivial task, you usually need a lot more lines of code in assembly than in C++. This makes code inspection much slower in assembly if we count it per task.
Indeed, if we set the situation such that the code inspector has a fixed time budget for going through the source code of an application which fulfils a given set of functionalities, then the code inspector will be more likely to find backdoors if he gets the C++ source code than if he gets the assembly. Indeed, given the C++ source code he can compile it to assembly (the -S
flag on GCC...), so his task cannot be harder with C++ than with assembly.
So defining the exact question is important. When we talk about hidden backdoors and code inspectors, we must clearly state what kind of code inspection is performed, for how long and under what conditions. Otherwise, just about any answer can be given and justified.
The whole point of the Ken Thompson is that the backdoor is not in the source code -- it is in the compiler, and more precisely in the compiler's executable file, not in the compiler's source code as well.
With more details: there is an "evil code" E1 which, when inserted into the system login application, grants access to the attacker for some "hidden" password which the attacker knows. There is another evil code E2, meant to be resident in the compiler's executable file. E2 does two things:
- If the file which is being compiled is the source code of the system login application, then it inserts E1 into the produced executable.
- If the file which is being compiled is the source code of the compiler itself, then it inserts E2 (i.e. itself) into the produced executable.
That way, neither the source code of the system login application, nor the source code of the compiler, contains anything evil. The evil code E2 propagates a bit like a virus, by infecting executable files (but it is picky since it infects only the compiler's executable file, and only when that executable file is being produced by action of the compiler). A secondary feature of that evil code E2 is to plant the evil code E1 into the system login application.
Since there is nothing evil in any source code, whether the source is "open source" or not has no relevance whatsoever. That's the whole point of planting the backdoor in the compiler: so that the honest source code needs not be modified in any way.
Nothing here is specific to C or C++. It would equally apply to every programming language which is compiled; and a similar backdoor could be devised for interpreted languages too (evil code E3 in interpreter modifies behaviour of a script upon execution; evil code E4 is planted in the compiler in order to replicate itself into new compiler instances, and to plant E3 into the interpreter when the interpreter itself is compiled).
If that is still unclear to you, go read the Dragon Book (available for free from any decent University library) until you grasp the notion of compilation.