Compiling for old glibc - the story of fscanf
An exploit in C
A while ago I tried to compile a local privesc exploit targeting an old Linux machine. It built fine on my Kali VM but when I ran it on the target the dynamic linker complained that I needed a minimum of glibc 2.7. The installed glibc was a few versions behind.
I had a look at the binary and saw that it was trying to locate the symbol
# objdump -T exploit | grep GLIBC_2.7 00000000 DF *UND* 00000000 GLIBC_2.7 __isoc99_fscanf
The exploit source code uses plain old
fscanf, which should be available in all versions of libc. I did some Googling and found a Red Hat bug report that claims this can be fixed by defining the
_GNU_SOURCE macro. Indeed it does:
# gcc 33321.c -o exploit_fix -D_GNU_SOURCE # objdump -T exploit_fix | grep fscanf 00000000 DF *UND* 00000000 GLIBC_2.0 fscanf
Problem solved. (Well, mostly—I didn’t get the exploit to work.) But I was curious and made a note to find out why this works later.
The problem stems from a conflict with the C99 standard. Back in the day glibc introduced a non-standard extension to the
scanf functions. If you were extracting string content and didn’t want to prepare your own buffer space you could use the
%a modifier. This way it would
malloc memory of sufficient size automatically and fill in your argument with the pointer. You would place flags in your format string like
%a[a-zA-Z0-9]. Life was good provided you were on a GNU system.
Then ISO C99 came along. It introduced a different meaning for
%a—the input should be parsed as a floating point number. Under this interpretation
%as would be “a floating point number followed by the literal letter
s”, whereas glibc would put the entire string into a newly allocated buffer and return the pointer. This inconsistency spawned the fairly epic Debian bug #155835 which began all the way back in 2002.
Ultimately the situation was resolved in glibc 2.7. According to the changelog, on 2007-09-17 a redirect to the ISO C99 version was added. Unless the
__USE_GNU flag is set any use of
fscanf would actually default to the ISO implementation
Separately, POSIX.1-2008 introduced the
%m modifier to do the same job as the old
%a. It’s slightly more flexible and also deliberately chosen not to conflict with the ISO
%a. Support for this was also introduced in glibc 2.7 according to the
fscanf man page. This appears to be the state of the art if you want to do auto-allocating buffers in C code.
Now the original problem is clear. When the exploit was originally written, using
fscanf would give you the GNU implementation. After a few years it would compile pointing to the ISO implementation instead, even though it made no difference for this particular code. The
__isoc99_fscanf symbol did not exist in earlier versions so a minimum version of 2.7 had to be specified. Using the
-D_GNU_SOURCE flag explicitly opts us in to the GNU extensions. The GNU implementation uses the symbol
fscanf and is backwards compatible all the way to glibc 2.0. This works fine on my old Linux target.