_FORTIFY_SOURCE, optimisations, and other details

While writing about the -O0 optimisation level I found some interesting problems related to the fortified source support in recent GCC and GLIBC releases, which we enable by default since GCC 4.3.3-r1 and that is used as a partial replacement of stack-smashing (which hopefully, thanks to Magnus, will soon come back into Gentoo!).

As I said in that post, the special fortified functions (those that are available in the form of __$func_chk in the libc.so file and provide warnings at build time, and proper stack traces at runtime) only get enabled if inline functions are enabled, so are totally ignored at -O0 (simply disabling inlines, by using -fno-inline won’t stop them from being used, though). This means that if we have some software that does not respect the CFLAGS variables, and uses -O0 in whatever context, it’s currently not using fortified sources, and as such, it can crash without warning about it beforehand.

But lack of optimisation – which is a quite rare occurrence, luckily – is not the only thing that may cause the fortified versions of functions to not be emitted. Since the fortified versions of the functions are declared (and the wrappers defined) in files such as /usr/include/bits/stdio2.h, you need to include them properly for them to work. Unfortunately, it’s way too common for projects not to take headers seriously and leave functions to be implicitly defined. But the implicit-definition does not take care of the fortified sources.

On the other hand, during testing of these little differences, I found some things that aren’t obvious at all. For instance, when you do use fortified sources, GCC fails to optimise some slightly more sophisticated cases. For instance this is the code I used to test implicit declarations:

#ifndef IMPLICIT
# include <stdio.h>
#endif

int main() {
  char foo[12];
  return sprintf(foo, "ABCDEFGHIJKLMNOPQRSTUVWXYZ");
}
Code language: PHP (php)

When built with -DIMPLICIT -O1 (so causing sprintf() to be implicitly defined), the produced executable does not crash, while it does crash with -O0 (see why I say -O0 produces different code?). The reason for that is that GCC picks up the call to sprintf() as a built-in function, whose semantic is known to it. In general, GCC cannot drop function calls that even just might have side effects, but in this case, GCC knows what the side-effect is (writing to the foo variable); its parameters are also known, so it can replace the function with its effects straight during the build phase. But since the foo variable is never read from, the whole copy is a moot effect. The only thing that we care about the sprintf call is the return value, which represent the length of the content written to the string, and is also constant since the compiler knows would be 26.

Indeed, the emitted code for that function, compiled that way is as such:

main:
        movl    $26, %eax
        ret

Interesting, isn’t it? Now this has two related side effects: the first is that the fortified function is not used, so there is no warning printed at build-time, the other is that the code will not crash at runtime because the buffer overflow is gone altogether! Now, can this be considered a bug in GCC? I don’t think so, it’s the code that is wrong to begin with. But you can see how disabling optimisations has now introduced a crash site. You can see how the code would otherwise work by adding -fno-builtin to the compiler. This will remove the semantic knowledge of the call from the compiler optimisers; this results in a straight call to sprintf().

But this post need to have at least a bottom line to be worth anything, so here there are some:

Exit mobile version