Exploit-DB #40944
Description: Google Chrome < 31.0.1650.48 - HTTP 1xx base::String­Tokenizer­T<...>::Quick­Get­Next Out-of-Bounds Read
Type: dos
Platform: multiple
Date: 2016-12-19
Author: Skylined

Exploit:
'''

Source: http://blog.skylined.nl/[PII_REDACTED]1.html

Synopsis

A specially crafted HTTP response can allow a malicious web-page to trigger a out-of-bounds read vulnerability in Google Chrome. The data is read from the main process' memory.

Known affected software, attack vectors and potential mitigations

Google Chrome up to, but not including, 31.0.1650.48

An attacker would need to get a target user to open a specially crafted web-page. Disabling JavaôÙScript does not prevent an attacker from triggering the vulnerable code path, but may prevent exfiltration of information.
Since the affected code has not been changed since 2009, I assume this affects all versions of Chrome released in the last few years.

Details

The HttpôÙStreamôÙParser class is used to send HTTP requests and receive HTTP responses. Its read_ôÙbuf_ member is a buffer used to store HTTP response data received from the server. Parts of the code are written under the assumption that the response currently being parsed is always stored at the start of this buffer (as returned by read_ôÙbuf_->StartôÙOfôÙBuffer()), other parts take into account that this may not be the case (read_ôÙbuf_->StartôÙOfôÙBuffer() + read_ôÙbuf_ôÙunused_ôÙoffset_). In most cases, responses are removed from the buffer once they have been parsed and any superfluous data is moved to the beginning of the buffer, to be treated as part of the next response. However, the code special cases HTTP 1xx replies and returns a result without removing the request from the buffer. This means that the response to the next request will not be stored at the start of the buffer, but after this HTTP 1xx response and read_ôÙbuf_ôÙunused_ôÙoffset_ should be used to find where it starts.

The code that special cases HTTP 1xx responses is:

  if (end_ôÙof_ôÙheader_ôÙoffset == -1) {
<<<snip>>>
  } else {
    // Note where the headers stop.
    read_ôÙbuf_ôÙunused_ôÙoffset_ = end_ôÙof_ôÙheader_ôÙoffset;

    if (response_->headers->response_ôÙcode() / 100 == 1) {
      // After processing a 1xx response, the caller will ask for the next
      // header, so reset state to support that.  We don't just skip these
      // completely because 1xx codes aren't acceptable when establishing a
      // tunnel.
      io_ôÙstate_ = STATE_ôÙREQUEST_ôÙSENT;
      response_ôÙheader_ôÙstart_ôÙoffset_ = -1;
<<<Note: the code above does not remove the HTTP 1xx response from the
         buffer.>>>
    } else {
<<<Note: the code that follows either removes the response from the buffer
         immediately, or expects it to be removed in a call to
         ReadôÙResponseôÙBody later.>>>
<<<snip>>>
  return result;
}

A look through the code has revealed one location where this can lead to a security issue (also in DoôÙReadôÙHeadersôÙComplete). The code uses an offset from the start of the buffer (rather than the start of the current responses) to pass as an argument to a DoôÙParseôÙResponseôÙHeaders.

  if (result == ERR_ôÙCONNECTION_ôÙCLOSED) {
<<<snip>>>
    // Parse things as well as we can and let the caller decide what to do.
    int end_ôÙoffset;
    if (response_ôÙheader_ôÙstart_ôÙoffset_ >= 0) {
      io_ôÙstate_ = STATE_ôÙREAD_ôÙBODY_ôÙCOMPLETE;
      end_ôÙoffset = read_ôÙbuf_->offset();
<<<Note: "end_ôÙoffset" is relative to the start of the buffer>>>
    } else {
      io_ôÙstate_ = STATE_ôÙBODY_ôÙPENDING;
      end_ôÙoffset = 0;
<<<Note: "end_ôÙoffset" is relative to the start of the current response
         i.e. start + read_ôÙbuf_ôÙunused_ôÙoffset_.>>>
    }
    int rv = DoôÙParseôÙResponseôÙHeaders(end_ôÙoffset);
<<<snip>>>
DoôÙParseôÙResponseôÙHeaders passes the argument unchanged to HttpôÙUtil::AssembleôÙRawôÙHeaders:

int HttpôÙStreamôÙParser::DoôÙParseôÙResponseôÙHeaders(int end_ôÙoffset) {
  scoped_ôÙrefptr<HttpôÙResponseôÙHeaders> headers;
  if (response_ôÙheader_ôÙstart_ôÙoffset_ >= 0) {
    headers = new HttpôÙResponseôÙHeaders(HttpôÙUtil::AssembleôÙRawôÙHeaders(
        read_ôÙbuf_->StartôÙOfôÙBuffer() + read_ôÙbuf_ôÙunused_ôÙoffset_, end_ôÙoffset));
<<<snip>>>

The HttpôÙUtil::AssembleôÙRawôÙHeaders method takes two arguments: a pointer to a buffer, and the length of the buffer. The pointer is calculated correctly (in DoôÙParseôÙResponseôÙHeaders) and points to the start of the current response. The length is the offset that was calculated incorrectly in DoôÙReadôÙHeadersôÙComplete. If the current response is preceded by a HTTP 1xx response in the buffer, this length is larger than it should be: the calculated value will be the correct length plus the size of the previous HTTP 1xx response (read_ôÙbuf_ôÙunused_ôÙoffset_).

std::string HttpôÙUtil::AssembleôÙRawôÙHeaders(const char* input_ôÙbegin,
                                         int input_ôÙlen) {
  std::string raw_ôÙheaders;
  raw_ôÙheaders.reserve(input_ôÙlen);

  const char* input_ôÙend = input_ôÙbegin + input_ôÙlen;
input_ôÙbegin was calculated as read_ôÙbuf_->StartôÙOfôÙBuffer() + read_ôÙbuf_ôÙunused_ôÙoffset_,
input_ôÙlen was incorrectly calculated as len(headers) + read_ôÙbuf_ôÙunused_ôÙoffset_,
input_ôÙend will be read_ôÙbuf_->StartôÙOfôÙBuffer() + 2 * read_ôÙbuf_ôÙunused_ôÙoffset_ + len(headers)
input_ôÙend is now beyond the end of the actual headers. The code will continue to rely on this incorrect value to try to create a copy of the headers, inadvertently making a copy of data that is not part of this response and may not even be part of the read_ôÙbuf_ buffer. This could cause the code to copy data from memory that is stored immediately after read_ôÙbuf_ into a string that represents the response headers. This string is passed to the renderer process that made the request, allowing a web-page inside the sandbox to read memory from the main process' heap.

An ASCII diagram might be useful to illustrate what is going on:

read_ôÙbuf_:                      "HTTP 100 Continue\r\n...HTTP XXX Current response\r\n..