Author: Ramon De C Valle Exploiting glibc __tzfile_read integer overflow to buffer overflow and vsftpd Earlier in 2009, in a blog post[1], dividead published a integer overflow to buffer overflow vulnerability in within __tzfile_read function of glibc, this issue haven’t came into my attention until, in a post to the Full-Disclosure mailing list, Kingcope[2] noted vsftpd as a potential attack vector for this issue. This issue is a integer overflow to buffer overflow vulnerability within __tzfile_read function of glibc. The __tzfile_read function is used internally by some functions of the glibc time library to load specified timezone files. As dividead noted in his blog post, the __tzfile_read function parses values from a specified timezone file, such as number of transition times and others, without any checking: [...] ) 208 goto lose; 209 210 num_transitions = (size_t) decode (tzhead.tzh_timecnt); 211 num_types = (size_t) decode (tzhead.tzh_typecnt); 212 chars = (size_t) decode (tzhead.tzh_charcnt); 213 num_leaps = (size_t) decode (tzhead.tzh_leapcnt); 214 num_isstd = (size_t) decode (tzhead.tzh_ttisstdcnt); 215 num_isgmt = (size_t) decode (tzhead.tzh_ttisgmtcnt); 216 217 /* For platforms with 64-bit time_t we use the new format if available. * / [...] In sequence, the __tz_file_read function uses these values to allocate memory for storing structures from the specified timezone file: [...] 233 234 goto read_again; 235 } 236 237 total_size = num_transitions * (sizeof (time_t) + 1); 238 total_size = ((total_size + __alignof__ (struct ttinfo) - 1) 239 & ~(__alignof__ (struct ttinfo) - 1)); 240 types_idx = total_size; 241 total_size += num_types * sizeof (struct ttinfo) + chars; 242 total_size = ((total_size + __alignof__ (struct leap) - 1) 243 & ~(__alignof__ (struct leap) - 1)); 244 leaps_idx = total_size; 245 total_size += num_leaps * sizeof (struct leap); 246 tzspec_len = (sizeof (time_t) == 8 && trans_width == 8 247 ? st.st_size - (ftello (f) 248 + num_transitions * (8 + 1) 249 + num_types * 6 250 + chars 251 + num_leaps * 12 252 + num_isstd 253 + num_isgmt) - 1 : 0); 254 255 /* Allocate enough memory including the extra block requested by the 256 caller. */ 257 transitions = (time_t *) malloc (total_size + tzspec_len + extra); 258 if (transitions == NULL) 259 goto lose; 260 [...] As also noted by dividead in his blog post, these values can be easily manipulated to make the __tzfile_read function evaluating a value for memory allocation that results in a total size which differs from the real size of the sum of the number of structures, which are subsequently parsed and read from the specified timezone file into this buffer (i.e. transitions). Although this seems to be a totally predictable environment for exploitation, this issue has a very limited scope due to the heap consistency checkings in glibc. Regardless the possibility of making the __tz_file_read function allocating a chunk from fast bins (chunks of size less than 64 bytes), main arena or, eventually, a new mapped memory region, it is very difficult to guarantee a contiguity of known allocated chunks because the __tz_file_read function allocates a single chunk, which is the overflowed chunk, and it always frees this chunk upon returning. In addition, due to the the FIFO feature of the current malloc implementation, this single chunk can be allocated from any of the various heap uncontiguous free spaces, this turns out to be very difficult to predict anything about the adjacent allocated chunk, if it is even allocated, and if so, most important, when it will be freed (and there is also the possibility of this single chunk being the wilderness chunk). This limits the predictable scope to glibc scope, more specifically within __tz_file_read function and considerably increases the difficulty of applying any known public exploitation technique to this issue. However, one thing caught my attention. The __tzfile_read function allocates a FILE structure before allocating this single chunk and closes this structure after the chunk is overflowed but, most important, before it is freed. [...] 179 180 /* Note the file is opened with cancellation in the I/O functions 181 disabled. */ 182 f = fopen (file, "rc"); 183 if (f == NULL) 184 goto ret_free_transitions; 185 186 /* Get information about the file we are actually using. */ 187 if (fstat64 (fileno (f), &st) != 0) 188 { 189 fclose (f); 190 goto ret_free_transitions; 191 } 192 193 free ((void *) transitions); 194 transitions = NULL; 195 196 /* Remember the inode and device number and modification time. */ 197 tzfile_dev = st.st_dev; [...] The single chunk (i.e. transitions) can be overflowed between the above and below listings: [...] 422 /* Don't use an empty TZ string. */ 423 if (tzspec != NULL && tzspec[0] == '\0') 424 tzspec = NULL; 425 426 fclose (f); 427 428 /* First "register" all timezone names. */ 429 for (i = 0; i < num_types; ++i) [...] 497 return; 498 499 lose: 500 fclose (f); 501 ret_free_transitions: 502 free ((void *) transitions); 503 transitions = NULL; 504 } 505 [...] But remember the previously mentioned FIFO feature of the current malloc implementation. If we make the __tzfile_read function allocating a chunk from main arena (file structures are larger than 64 bytes), with approximately the same size of a file structure, it is very likely this single chunk will be allocated from a free uncontiguous space located before the file structure if previous allocations and frees of the approximately same size have already been made (i.e. unsorted chunks feature). This turns out an unpredictable to a very predictable environment for exploitation, not only in glibc scope but completely within __tzfile_read function, by exploiting _IO_file_jumps of the FILE structure. Thus, making almost any application which uses glibc time library exploitable when an arbirary timezone file can be specified. As noted by Kingcope in his post to the Full-Disclosure mailing list, vsftpd uses glibc time library and calls __tzfile_read at some point in which an arbitrary timezone file can be specified. In vsftpd, the __tzfile_read function is reached through calls to gmtime, localtime, and tzset functions in vsf_sysutil_statbuf_get_date, vsf_sysutil_statbuf_get_numeric_date, vsf_sysutil_tzset, and vsf_sysutil_get_current_date functions. All these functions can be used to make the previous allocations and frees previously required. To be short, the most straightforward method is through the vsf_sysutil_get_current_date function called by transfer logging functions of vsftpd. After logging in to vsftpd, we can make an arbitrary (but no so small) sequence of uploads of valid timezone files to the path expected by vsftpd followed by the specially-crafted timezone file to exploit the _IO_file_jumps of the of FILE structure. I haven’t wrote an exploit for this issue yet, however, writing one seems fairly trivial. [1] http://dividead.wordpress.com/2009/06/01/glibc-timezone-integer-overflow/ [2] http://lists.grok.org.uk/pipermail/full-disclosure/2011-December/084452.html