Hacker News new | ask | show | jobs
by bigcheesegs 2946 days ago
The problem with this kind of approach is over time it removes the ability to use any other implementation. You are no longer using C, you are using <Implementation>C.

This becomes a problem when a different implementation adds amazing tools for finding bugs (the sanitizer suite, for instance), and you can't use them because your code doesn't build with any other implementation.

7 comments

Large and complex pieces of software, and operating systems in particular, tend to be tightly tied to their compilers. It is never easy and in some cases practically impossible to port to a different compiler. I expect that Microsoft has come to terms with the fact that Windows will only support being compiled by their compiler.

When a different toolchain introduces a new feature for finding bugs that would be useful for Windows, the Microsoft compiler team can add that functionality to their own tools instead of porting Windows. An advantage of this is that they can customize the feature for exactly their use case. Yes, this is the definition of NIH syndrome, but that’s how large companies work.

Large pieces of sw have been able to switch some plateform specific code to other compilers (chrome for windows comes to mind).

This is probably way smaller than the whole Windows, but I would not be surprised if some MS dev are already internally compiling some of their components with clang for their own dev/testing (even if just for extra warnings, etc.)

And a major part of the work of the MSVC team today seems to be about standard compliance.

But yes, I do not really expect that they switch, and actually they probably don't even have the beginning of a serious reason to do so. This is not even a case of NIH. Their compiler derives from an ancient codebase and has been continuously maintained for several decades. They "invented" it. The only modern serious competition (that cares enough about Windows compat and some of their specific techs) has been started way after... They probably also have all kind of patents and whatnot about some security mitigations that are implemented by collaboration between the generated code and (the most modern versions of) low level parts of the Windows platform.

> . You are no longer using C, you are using <Implementation>C.

If you go deep and gnarly enough with your system, this is always the case, I'm afraid. There's a famous talk at Stanford by a Coverity... founder? consultant? about this.

> You are no longer using C, you are using <Implementation>C.

This is always the case because the C standard is only a partial spec. There's no such thing as "a program written in C(++)", there's only "a program written in <Implementation> C(++)". If you compile your program with a different implementation, then it's a different program. It may work, but it may not.

What I'd really like to see is a modified version of the C/C++ standards which keeps the language the same "in spirit" but removes all undefined and implementation-dependent behaviour. This would give compiler writers a stationary (or at least slower-moving) target to aim for and make it possible for C to be portable in theory as well as (sorta) in practice.

However in this case 'Standard C' is broken and <Implementation C> is not. The proper thing is to fix the standard to require padding bytes be zero'd.
It's easy to see both sides here. The overwhelming majority of code is not written to be run a in a kernel at the kernel-user security boundary, so for most code paying something to initialize padding might not be a great tradeoff. That is, the vast majority of code is running within a single security domain and doesn't need to protect itself from itself.

Still, avoiding initializing padding is probably not a great example of a performance win through standards exploitation: in the example given it's not clear why you'd not just do one 8 byte zeroing write to cover the whole structure, rather than apparently splitting it into a 4-byte and a 1-byte write. Perhaps this was 32-bit code, where 8-byte write are slightly trickier, but even two 4-byte writes are likely to perform just as well or better. Probably it's just bad codegen to treat the initialization of the struct[11] in two parts: a single struct and a 10-member array.

Luckily clang does a good job at being GCC compatible, so I don't worry too much about using GCC extensions. It's quite unlikely one of them will go away anytime soon, and theory pretty much cover all architectures/platforms that have ever existed.
Usually true, unless you want to target embedded, mainframes or some industrial OSes.
It doesn't support everything; for example, Clang doesn't do nested functions.
This was pretty much how writting C code between K&R, ANSI C89 and subsets e.g. Small-C used to look like.
I fail to see how implementing an unspecified part of the standard in a way which _doesn't_ leak kernel memory could ever be a problem.
It's not a problem for the compiler, of course.

The problem is the C code that relies on it: effectively you are using a dialect of C which gives stronger guarantees, so you lose the ability to use any other implementation which doesn't provide those guarantees.

Yeah, I (somehow...) missed the part where the GP explained that the kernel dev's fix was to just rely on the now updated unspecified behavior. I assumed the changed the compiler, but he also memset the structure before sending.
>The problem is the C code that relies on it: effectively you are using a dialect of C which gives stronger guarantees, so you lose the ability to use any other implementation which doesn't provide those guarantees.

How do you lose it in this case? You shouldn't been reading values from those padding bits anyway...

Tell that to a hacker. The problem here isn't that the code is functionally dependent on padding bytes, it's that when you copy those padding bytes around you are leaking information that you probably never meant to copy.

This can be problematic if you are copying kernel space memory to a user space process, for example. Let's say there's a call into the kernel that returns a copy of this 4+1 struct with three more bytes of padding. Maybe what was on the stack before the space was assigned to those last padding bytes are some information the kernel definitely shouldn't leak to user space, like some bytes of a password, and now any user space process could potentially read them simply by calling some unrelated kernel function.