glibc timezone integer overflow 2009/06/01

Years ago I found a cute integer overflow in the timezone handling in glibc, but back then I put it on my list of ‘bugs to check out in the future if I have more time’. Of course I never found this time (the density of my blog updates gives a nice impression of my spare time), but was surprised that the problem still exists in recent versions of glibc.

I present it here, as I do not feel like contacting glibc upstream about this issue knowing the maintainer is even more friendly, cooperative, and socially well-adapted than a certain OpenBSD maintainer. http://blog.aurel32.net/?p=47 illustrates this nicely.

Before wasting time of my readers, I want to point out that the impact of this bug is not extensive, and I’d be surprised if someone would manage to make a decent exploit out of this.

The problem is in the __tzfile_read function present in glibc, and a paste of the source code follows. I took the liberty of cutting the irrelevant parts out, to not bother the reader with details.

The first thing I want to point out is the limited scope of this issue. The checks starting on line 17 limit the use of the TZ environment variable (the file parameter to __tzfile_read is derived from the TZ environment variable in other places in the source code), protecting against using arbitrary timezone files in SUID and SGID files. A funny detail is the check on line 21, which does not account for TZ ending with a double-dot, so we’re able to have __tzfile_read open the directory above the default timezone database directory. For the rest something will likely return EISDIR doing this, so it is useless.

Another thing to note is that TZDIR is a variable mentioned in sysdeps/generic/unsecvars.h so we will not be able to use this in SUID or SGID files either. This means that we will not be able to exploit this problem in an easy local situation.

Before continuing, lets look at the bug closely first. In lines 27 through 32 __tzfile_read parses some parameters from a timezone file, and lines 34 through 50 perform some calculations based on them. On line 54 malloc() gets called with parameters which we control, and can easily get to evaluate to 0 or something similar. Finally on line 58 we read the timezone data in this allocated buffer using a variable evaluated in a different way, leading to a perfectly controllable heap overflow.

Now that we know for sure this bug is exploitable, we need to determine the cases where this can actually happen. Due to the security checking glibc does the scope is very limited, and we need to have a program which allows us to control either the TZ or the TZDIR environment variables somehow.

A possible example is the old style method to set timezones in PHP assuming it is not running in safe-mode. This was accomplished through putenv(“TZ=/foo/bar/evil”); and funnily PHP will immediatly call tzset() whenever it encounters a putenv() on TZ. This indeed results in PHP crashes, but of course one needs to be able to upload files in the first place, have PHP perform a putenv on data (either completely, in which case we have many more security issues, or on the value part to TZ or TZDIR) we control, and have PHP not running in safe mode.