Commit message: None
CVE: CVE-2017-15407
CWE: CWE-787
Vulnerability description: Out-of-bounds Write in the QUIC networking stack in Google Chrome prior to 63.0.3239.84 allowed a remote attacker to gain code execution via a malicious server.

Project: Chrome

size_t QuicStreamSequencerBuffer::BytesBuffered() const {
  return num_bytes_buffered_;
}

Project: Chrome

QuicStreamOffset QuicStreamSequencerBuffer::BytesConsumed() const {
  return total_bytes_read_;
}

Project: Chrome

void QuicStreamSequencerBuffer::Clear() {
  if (blocks_ != nullptr) {
    for (size_t i = 0; i < blocks_count_; ++i) {
      if (blocks_[i] != nullptr) {
        RetireBlock(i);
      }
    }
  }
  num_bytes_buffered_ = 0;
  gaps_ = std::list<Gap>(
      1, Gap(total_bytes_read_, std::numeric_limits<QuicStreamOffset>::max()));
  frame_arrival_time_map_.clear();
}

Project: Chrome

bool QuicStreamSequencerBuffer::Empty() const {
  return gaps_.size() == 1 && gaps_.front().begin_offset == total_bytes_read_;
}

Project: Chrome

size_t QuicStreamSequencerBuffer::FlushBufferedFrames() {
  size_t prev_total_bytes_read = total_bytes_read_;
  total_bytes_read_ = gaps_.back().begin_offset;
  Clear();
  return total_bytes_read_ - prev_total_bytes_read;
}

Project: Chrome

QuicStreamSequencerBuffer::FrameInfo::FrameInfo()
    : length(1), timestamp(QuicTime::Zero()) {}

Project: Chrome

QuicStreamSequencerBuffer::FrameInfo::FrameInfo(size_t length,
                                                QuicTime timestamp)
    : length(length), timestamp(timestamp) {}

Project: Chrome

QuicStreamSequencerBuffer::Gap::Gap(QuicStreamOffset begin_offset,
                                    QuicStreamOffset end_offset)
    : begin_offset(begin_offset), end_offset(end_offset) {}

Project: Chrome

string QuicStreamSequencerBuffer::GapsDebugString() {
  string current_gaps_string;
  for (const Gap& gap : gaps_) {
    QuicStreamOffset current_gap_begin = gap.begin_offset;
    QuicStreamOffset current_gap_end = gap.end_offset;
    current_gaps_string.append(
        QuicStrCat("[", current_gap_begin, ", ", current_gap_end, ") "));
  }
  return current_gaps_string;
}

Project: Chrome

size_t QuicStreamSequencerBuffer::GetBlockCapacity(size_t block_index) const {
  if ((block_index + 1) == blocks_count_) {
    size_t result = max_buffer_capacity_bytes_ % kBlockSizeBytes;
    if (result == 0) {  // whole block
      result = kBlockSizeBytes;
    }
    return result;
  } else {
    return kBlockSizeBytes;
  }
}

Project: Chrome

size_t QuicStreamSequencerBuffer::GetBlockIndex(QuicStreamOffset offset) const {
  return (offset % max_buffer_capacity_bytes_) / kBlockSizeBytes;
}

Project: Chrome

size_t QuicStreamSequencerBuffer::GetInBlockOffset(
    QuicStreamOffset offset) const {
  return (offset % max_buffer_capacity_bytes_) % kBlockSizeBytes;
}

Project: Chrome

bool QuicStreamSequencerBuffer::GetReadableRegion(iovec* iov,
                                                  QuicTime* timestamp) const {
  CHECK_EQ(destruction_indicator_, 123456) << "This object has been destructed";

  if (ReadableBytes() == 0) {
    iov[0].iov_base = nullptr;
    iov[0].iov_len = 0;
    return false;
  }

  size_t start_block_idx = NextBlockToRead();
  iov->iov_base = blocks_[start_block_idx]->buffer + ReadOffset();
  size_t readable_bytes_in_block = std::min<size_t>(
      GetBlockCapacity(start_block_idx) - ReadOffset(), ReadableBytes());
  size_t region_len = 0;
  auto iter = frame_arrival_time_map_.begin();
  *timestamp = iter->second.timestamp;
  QUIC_DVLOG(1) << "Readable bytes in block: " << readable_bytes_in_block;
  for (; iter != frame_arrival_time_map_.end() &&
         region_len + iter->second.length <= readable_bytes_in_block;
       ++iter) {
    if (iter->second.timestamp != *timestamp) {
      QUIC_DVLOG(1) << "Meet frame with different timestamp.";
      break;
    }
    region_len += iter->second.length;
    QUIC_DVLOG(1) << "Added bytes to region: " << iter->second.length;
  }
  if (iter == frame_arrival_time_map_.end() ||
      iter->second.timestamp == *timestamp) {
    QUIC_DVLOG(1) << "Got all readable bytes in first block.";
    region_len = readable_bytes_in_block;
  }
  iov->iov_len = region_len;
  return true;
}

Project: Chrome

bool QuicStreamSequencerBuffer::HasBytesToRead() const {
  return ReadableBytes() > 0;
}

Project: Chrome

bool QuicStreamSequencerBuffer::MarkConsumed(size_t bytes_used) {
  CHECK_EQ(destruction_indicator_, 123456) << "This object has been destructed";

  if (bytes_used > ReadableBytes()) {
    return false;
  }
  size_t bytes_to_consume = bytes_used;
  while (bytes_to_consume > 0) {
    size_t block_idx = NextBlockToRead();
    size_t offset_in_block = ReadOffset();
    size_t bytes_available = std::min<size_t>(
        ReadableBytes(), GetBlockCapacity(block_idx) - offset_in_block);
    size_t bytes_read = std::min<size_t>(bytes_to_consume, bytes_available);
    total_bytes_read_ += bytes_read;
    num_bytes_buffered_ -= bytes_read;
    bytes_to_consume -= bytes_read;
    if (bytes_available == bytes_read) {
      RetireBlockIfEmpty(block_idx);
    }
  }
  if (bytes_used > 0) {
    UpdateFrameArrivalMap(total_bytes_read_);
  }
  return true;
}

Project: Chrome

QuicStreamSequencerBuffer::QuicStreamSequencerBuffer(size_t max_capacity_bytes)
    : max_buffer_capacity_bytes_(max_capacity_bytes),
      blocks_count_(
          ceil(static_cast<double>(max_capacity_bytes) / kBlockSizeBytes)),
      total_bytes_read_(0),
      blocks_(nullptr),
      destruction_indicator_(123456) {
  CHECK_GT(blocks_count_, 1u)
      << "blocks_count_ = " << blocks_count_
      << ", max_buffer_capacity_bytes_ = " << max_buffer_capacity_bytes_;
  Clear();
}

Project: Chrome

size_t QuicStreamSequencerBuffer::ReadOffset() const {
  return GetInBlockOffset(total_bytes_read_);
}

Project: Chrome

size_t QuicStreamSequencerBuffer::ReadableBytes() const {
  return gaps_.front().begin_offset - total_bytes_read_;
}

Project: Chrome

QuicErrorCode QuicStreamSequencerBuffer::Readv(const iovec* dest_iov,
                                               size_t dest_count,
                                               size_t* bytes_read,
                                               string* error_details) {
  CHECK_EQ(destruction_indicator_, 123456) << "This object has been destructed";

  *bytes_read = 0;
  for (size_t i = 0; i < dest_count && ReadableBytes() > 0; ++i) {
    char* dest = reinterpret_cast<char*>(dest_iov[i].iov_base);
    CHECK_NE(dest, nullptr);
    size_t dest_remaining = dest_iov[i].iov_len;
    while (dest_remaining > 0 && ReadableBytes() > 0) {
      size_t block_idx = NextBlockToRead();
      size_t start_offset_in_block = ReadOffset();
      size_t block_capacity = GetBlockCapacity(block_idx);
      size_t bytes_available_in_block = std::min<size_t>(
          ReadableBytes(), block_capacity - start_offset_in_block);
      size_t bytes_to_copy =
          std::min<size_t>(bytes_available_in_block, dest_remaining);
      DCHECK_GT(bytes_to_copy, 0UL);
      if (blocks_[block_idx] == nullptr || dest == nullptr) {
        *error_details = QuicStrCat(
            "QuicStreamSequencerBuffer error:"
            " Readv() dest == nullptr: ",
            (dest == nullptr), " blocks_[", block_idx,
            "] == nullptr: ", (blocks_[block_idx] == nullptr),
            " Gaps: ", GapsDebugString(),
            " Remaining frames: ", ReceivedFramesDebugString(),
            " total_bytes_read_ = ", total_bytes_read_);
        return QUIC_STREAM_SEQUENCER_INVALID_STATE;
      }
      memcpy(dest, blocks_[block_idx]->buffer + start_offset_in_block,
             bytes_to_copy);
      dest += bytes_to_copy;
      dest_remaining -= bytes_to_copy;
      num_bytes_buffered_ -= bytes_to_copy;
      total_bytes_read_ += bytes_to_copy;
      *bytes_read += bytes_to_copy;

      if (bytes_to_copy == bytes_available_in_block) {
        bool retire_successfully = RetireBlockIfEmpty(block_idx);
        if (!retire_successfully) {
          *error_details = QuicStrCat(
              "QuicStreamSequencerBuffer error: fail to retire block ",
              block_idx,
              " as the block is already released, total_bytes_read_ = ",
              total_bytes_read_, " Gaps: ", GapsDebugString());
          return QUIC_STREAM_SEQUENCER_INVALID_STATE;
        }
      }
    }
  }

  if (*bytes_read > 0) {
    UpdateFrameArrivalMap(total_bytes_read_);
  }
  return QUIC_NO_ERROR;
}

Project: Chrome

string QuicStreamSequencerBuffer::ReceivedFramesDebugString() {
  string current_frames_string;
  for (auto it : frame_arrival_time_map_) {
    QuicStreamOffset current_frame_begin_offset = it.first;
    QuicStreamOffset current_frame_end_offset =
        it.second.length + current_frame_begin_offset;
    current_frames_string.append(QuicStrCat(
        "[", current_frame_begin_offset, ", ", current_frame_end_offset,
        ") receiving time ", it.second.timestamp.ToDebuggingValue()));
  }
  return current_frames_string;
}

Project: Chrome

bool QuicStreamSequencerBuffer::RetireBlock(size_t idx) {
  if (blocks_[idx] == nullptr) {
    QUIC_BUG << "Try to retire block twice";
    return false;
  }
  delete blocks_[idx];
  blocks_[idx] = nullptr;
  QUIC_DVLOG(1) << "Retired block with index: " << idx;
  return true;
}

Project: Chrome

bool QuicStreamSequencerBuffer::RetireBlockIfEmpty(size_t block_index) {
  DCHECK(ReadableBytes() == 0 || GetInBlockOffset(total_bytes_read_) == 0)
      << "RetireBlockIfEmpty() should only be called when advancing to next "
      << "block or a gap has been reached.";
  if (Empty()) {
    return RetireBlock(block_index);
  }

  if (GetBlockIndex(gaps_.back().begin_offset - 1) == block_index) {
    return true;
  }

  if (NextBlockToRead() == block_index) {
    Gap first_gap = gaps_.front();
    DCHECK(first_gap.begin_offset == total_bytes_read_);
    bool gap_ends_in_this_block =
        (GetBlockIndex(first_gap.end_offset) == block_index);
    if (gap_ends_in_this_block) {
      return true;
    }
  }
  return RetireBlock(block_index);
}

Project: Chrome

void QuicStreamSequencerBuffer::UpdateFrameArrivalMap(QuicStreamOffset offset) {
  auto next_frame = frame_arrival_time_map_.upper_bound(offset);
  DCHECK(next_frame != frame_arrival_time_map_.begin());
  auto iter = frame_arrival_time_map_.begin();
  while (iter != next_frame) {
    auto erased = *iter;
    iter = frame_arrival_time_map_.erase(iter);
    QUIC_DVLOG(1) << "Removed FrameInfo with offset: " << erased.first
                  << " and length: " << erased.second.length;
    if (erased.first + erased.second.length > offset) {
      auto updated = std::make_pair(
          offset, FrameInfo(erased.first + erased.second.length - offset,
                            erased.second.timestamp));
      QUIC_DVLOG(1) << "Inserted FrameInfo with offset: " << updated.first
                    << " and length: " << updated.second.length;
      frame_arrival_time_map_.insert(updated);
    }
  }
}

Project: Chrome

QuicStreamSequencerBuffer::~QuicStreamSequencerBuffer() {
  Clear();
  destruction_indicator_ = 654321;
}

Project: Chrome

  void CreateSourceAndShuffle(size_t max_chunk_size_bytes) {
    max_chunk_size_bytes_ = max_chunk_size_bytes;
    std::unique_ptr<OffsetSizePair[]> chopped_stream(
        new OffsetSizePair[bytes_to_buffer_]);

    size_t start_chopping_offset = 0;
    size_t iterations = 0;
    while (start_chopping_offset < bytes_to_buffer_) {
      size_t max_chunk = std::min<size_t>(
          max_chunk_size_bytes_, bytes_to_buffer_ - start_chopping_offset);
      size_t chunk_size = rng_.RandUint64() % max_chunk + 1;
      chopped_stream[iterations] =
          OffsetSizePair(start_chopping_offset, chunk_size);
      start_chopping_offset += chunk_size;
      ++iterations;
    }
    DCHECK(start_chopping_offset == bytes_to_buffer_);
    size_t chunk_num = iterations;

    for (int i = chunk_num - 1; i >= 0; --i) {
      size_t random_idx = rng_.RandUint64() % (i + 1);
      QUIC_DVLOG(1) << "chunk offset " << chopped_stream[random_idx].first
                    << " size " << chopped_stream[random_idx].second;
      shuffled_buf_.push_front(chopped_stream[random_idx]);
      chopped_stream[random_idx] = chopped_stream[i];
    }
  }

Project: Chrome

char GetCharFromIOVecs(size_t offset, iovec iov[], size_t count) {
  size_t start_offset = 0;
  for (size_t i = 0; i < count; i++) {
    if (iov[i].iov_len == 0) {
      continue;
    }
    size_t end_offset = start_offset + iov[i].iov_len - 1;
    if (offset >= start_offset && offset <= end_offset) {
      const char* buf = reinterpret_cast<const char*>(iov[i].iov_base);
      return buf[offset - start_offset];
    }
    start_offset += iov[i].iov_len;
  }
  LOG(ERROR) << "Could not locate char at offset " << offset << " in " << count
             << " iovecs";
  for (size_t i = 0; i < count; ++i) {
    LOG(ERROR) << "  iov[" << i << "].iov_len = " << iov[i].iov_len;
  }
  return '\0';
}

Project: Chrome

  void Initialize() {
    buffer_.reset(new QuicStreamSequencerBuffer(max_capacity_bytes_));
    helper_.reset(new QuicStreamSequencerBufferPeer(buffer_.get()));
  }

Project: Chrome

  void WriteNextChunkToBuffer() {
    OffsetSizePair& chunk = shuffled_buf_.front();
    QuicStreamOffset offset = chunk.first;
    const size_t num_to_write = chunk.second;
    std::unique_ptr<char[]> write_buf{new char[max_chunk_size_bytes_]};
    for (size_t i = 0; i < num_to_write; ++i) {
      write_buf[i] = (offset + i) % 256;
    }
    QuicStringPiece string_piece_w(write_buf.get(), num_to_write);
    size_t written;
    auto result =
        buffer_->OnStreamData(offset, string_piece_w, clock_.ApproximateNow(),
                              &written, &error_details_);
    if (result == QUIC_NO_ERROR) {
      shuffled_buf_.pop_front();
      total_bytes_written_ += num_to_write;
    } else {
      shuffled_buf_.push_back(chunk);
      shuffled_buf_.pop_front();
    }
    QUIC_DVLOG(1) << " write at offset: " << offset
                  << " len to write: " << num_to_write
                  << " write result: " << result
                  << " left over: " << shuffled_buf_.size();
  }