Commit message: None
CVE: CVE-2019-5786
CWE: CWE-416
Vulnerability description: Object lifetime issue in Blink in Google Chrome prior to 72.0.3626.121 allowed a remote attacker to potentially perform out of bounds memory access via a crafted HTML page.

Project: Chrome

void FileReaderLoader::AdjustReportedMemoryUsageToV8(int64_t usage) {
  if (!usage)
    return;
  memory_usage_reported_to_v8_ += usage;
  v8::Isolate::GetCurrent()->AdjustAmountOfExternalAllocatedMemory(usage);
  DCHECK_GE(memory_usage_reported_to_v8_, 0);
}

Project: Chrome

void FileReaderLoader::Cancel() {
  error_code_ = FileErrorCode::kAbortErr;
  Cleanup();
}

Project: Chrome

void FileReaderLoader::Cleanup() {
  handle_watcher_.Cancel();
  consumer_handle_.reset();

  if (error_code_ != FileErrorCode::kOK) {
    raw_data_.reset();
    string_result_ = "";
    is_raw_data_converted_ = true;
    decoder_.reset();
    array_buffer_result_ = nullptr;
    UnadjustReportedMemoryUsageToV8();
  }
}

Project: Chrome

String FileReaderLoader::ConvertToDataURL() {
  StringBuilder builder;
  builder.Append("data:");

  if (!bytes_loaded_)
    return builder.ToString();

  if (data_type_.IsEmpty()) {
    builder.Append("application/octet-stream");
  } else {
    builder.Append(data_type_);
  }
  builder.Append(";base64,");

  Vector<char> out;
  Base64Encode(static_cast<const char*>(raw_data_->Data()),
               raw_data_->ByteLength(), out);
  out.push_back('\0');
  builder.Append(out.data());

  return builder.ToString();
}

Project: Chrome

String FileReaderLoader::ConvertToText() {
  if (!bytes_loaded_)
    return "";

  StringBuilder builder;
  if (!decoder_) {
    decoder_ = TextResourceDecoder::Create(TextResourceDecoderOptions(
        TextResourceDecoderOptions::kPlainTextContent,
        encoding_.IsValid() ? encoding_ : UTF8Encoding()));
  }
  builder.Append(decoder_->Decode(static_cast<const char*>(raw_data_->Data()),
                                  raw_data_->ByteLength()));

  if (finished_loading_)
    builder.Append(decoder_->Flush());

  return builder.ToString();
}

Project: Chrome

void FileReaderLoader::Failed(FileErrorCode error_code, FailureType type) {
  DEFINE_THREAD_SAFE_STATIC_LOCAL(EnumerationHistogram, failure_histogram,
                                  ("Storage.Blob.FileReaderLoader.FailureType",
                                   static_cast<int>(FailureType::kCount)));
  if (error_code_ != FileErrorCode::kOK)
    return;
  error_code_ = error_code;
  failure_histogram.Count(static_cast<int>(type));
  Cleanup();
  if (client_)
    client_->DidFail(error_code_);
}

Project: Chrome

void FileReaderLoader::OnCalculatedSize(uint64_t total_size,
                                        uint64_t expected_content_size) {
  auto weak_this = weak_factory_.GetWeakPtr();
  OnStartLoading(expected_content_size);
  if (!weak_this)
    return;

  if (expected_content_size == 0) {
    received_all_data_ = true;
    return;
  }

  if (IsSyncLoad()) {
    OnDataPipeReadable(MOJO_RESULT_OK);
  } else {
    handle_watcher_.Watch(
        consumer_handle_.get(), MOJO_HANDLE_SIGNAL_READABLE,
        WTF::BindRepeating(&FileReaderLoader::OnDataPipeReadable,
                           WTF::Unretained(this)));
  }
}

Project: Chrome

void FileReaderLoader::OnComplete(int32_t status, uint64_t data_length) {
  DEFINE_THREAD_SAFE_STATIC_LOCAL(SparseHistogram,
                                  file_reader_loader_read_errors_histogram,
                                  ("Storage.Blob.FileReaderLoader.ReadError"));
  if (status != net::OK) {
    net_error_ = status;
    file_reader_loader_read_errors_histogram.Sample(std::max(0, -net_error_));
    Failed(status == net::ERR_FILE_NOT_FOUND ? FileErrorCode::kNotFoundErr
                                             : FileErrorCode::kNotReadableErr,
           FailureType::kBackendReadError);
    return;
  }
  if (data_length != total_bytes_) {
    Failed(FileErrorCode::kNotReadableErr, FailureType::kReadSizesIncorrect);
    return;
  }

  received_on_complete_ = true;
  if (received_all_data_)
    OnFinishLoading();
}

Project: Chrome

void FileReaderLoader::OnDataPipeReadable(MojoResult result) {
  if (result != MOJO_RESULT_OK) {
    if (!received_all_data_) {
      Failed(FileErrorCode::kNotReadableErr,
             FailureType::kDataPipeNotReadableWithBytesLeft);
    }
    return;
  }

  while (true) {
    uint32_t num_bytes;
    const void* buffer;
    MojoResult result = consumer_handle_->BeginReadData(
        &buffer, &num_bytes, MOJO_READ_DATA_FLAG_NONE);
    if (result == MOJO_RESULT_SHOULD_WAIT) {
      if (!IsSyncLoad())
        return;

      result = mojo::Wait(consumer_handle_.get(), MOJO_HANDLE_SIGNAL_READABLE);
      if (result == MOJO_RESULT_OK)
        continue;
    }
    if (result == MOJO_RESULT_FAILED_PRECONDITION) {
      if (!received_all_data_) {
        Failed(FileErrorCode::kNotReadableErr,
               FailureType::kMojoPipeClosedEarly);
      }
      return;
    }
    if (result != MOJO_RESULT_OK) {
      Failed(FileErrorCode::kNotReadableErr,
             FailureType::kMojoPipeUnexpectedReadError);
      return;
    }

    auto weak_this = weak_factory_.GetWeakPtr();
    OnReceivedData(static_cast<const char*>(buffer), num_bytes);
    if (!weak_this)
      return;

    consumer_handle_->EndReadData(num_bytes);
    if (BytesLoaded() >= total_bytes_) {
      received_all_data_ = true;
      if (received_on_complete_)
        OnFinishLoading();
      return;
    }
  }
}

Project: Chrome

void FileReaderLoader::OnReceivedData(const char* data, unsigned data_length) {
  DCHECK(data);

  if (error_code_ != FileErrorCode::kOK)
    return;

  if (read_type_ == kReadByClient) {
    bytes_loaded_ += data_length;

    if (client_)
      client_->DidReceiveDataForClient(data, data_length);
    return;
  }

  unsigned bytes_appended = raw_data_->Append(data, data_length);
  if (!bytes_appended) {
    raw_data_.reset();
    bytes_loaded_ = 0;
    Failed(FileErrorCode::kNotReadableErr,
           FailureType::kArrayBufferBuilderAppend);
    return;
  }
  bytes_loaded_ += bytes_appended;
  is_raw_data_converted_ = false;
  AdjustReportedMemoryUsageToV8(bytes_appended);

  if (client_)
    client_->DidReceiveData();
}

Project: Chrome

void FileReaderLoader::OnStartLoading(uint64_t total_bytes) {
  total_bytes_ = total_bytes;

  DCHECK(!raw_data_);

  if (read_type_ != kReadByClient) {
    if (total_bytes > std::numeric_limits<unsigned>::max()) {
      Failed(FileErrorCode::kNotReadableErr, FailureType::kTotalBytesTooLarge);
      return;
    }

    raw_data_ = std::make_unique<ArrayBufferBuilder>(total_bytes);
    if (!raw_data_->IsValid()) {
      Failed(FileErrorCode::kNotReadableErr,
             FailureType::kArrayBufferBuilderCreation);
      return;
    }
    raw_data_->SetVariableCapacity(false);
  }

  if (client_)
    client_->DidStartLoading();
}

Project: Chrome

void FileReaderLoader::SetEncoding(const String& encoding) {
  if (!encoding.IsEmpty())
    encoding_ = WTF::TextEncoding(encoding);
}

Project: Chrome

void FileReaderLoader::SetStringResult(const String& result) {
  AdjustReportedMemoryUsageToV8(
      -1 * static_cast<int64_t>(string_result_.CharactersSizeInBytes()));
  is_raw_data_converted_ = true;
  string_result_ = result;
  AdjustReportedMemoryUsageToV8(string_result_.CharactersSizeInBytes());
}

Project: Chrome

void FileReaderLoader::Start(scoped_refptr<BlobDataHandle> blob_data) {
#if DCHECK_IS_ON()
  DCHECK(!started_loading_) << "FileReaderLoader can only be used once";
  started_loading_ = true;
#endif  // DCHECK_IS_ON()

  MojoCreateDataPipeOptions options;
  options.struct_size = sizeof(MojoCreateDataPipeOptions);
  options.flags = MOJO_CREATE_DATA_PIPE_FLAG_NONE;
  options.element_num_bytes = 1;
  options.capacity_num_bytes =
      blink::BlobUtils::GetDataPipeCapacity(blob_data->size());

  mojo::ScopedDataPipeProducerHandle producer_handle;
  MojoResult rv = CreateDataPipe(&options, &producer_handle, &consumer_handle_);
  if (rv != MOJO_RESULT_OK) {
    Failed(FileErrorCode::kNotReadableErr, FailureType::kMojoPipeCreation);
    return;
  }

  mojom::blink::BlobReaderClientPtr client_ptr;
  binding_.Bind(MakeRequest(&client_ptr, task_runner_), task_runner_);
  blob_data->ReadAll(std::move(producer_handle), std::move(client_ptr));

  if (IsSyncLoad()) {
    binding_.WaitForIncomingMethodCall();
    if (received_on_complete_)
      return;
    if (!received_all_data_) {
      Failed(FileErrorCode::kNotReadableErr,
             FailureType::kSyncDataNotAllLoaded);
      return;
    }

    binding_.WaitForIncomingMethodCall();
    if (!received_on_complete_) {
      Failed(FileErrorCode::kNotReadableErr,
             FailureType::kSyncOnCompleteNotReceived);
    }
  }
}

Project: Chrome

 String FileReaderLoader::StringResult() {
  DCHECK_NE(read_type_, kReadAsArrayBuffer);
  DCHECK_NE(read_type_, kReadByClient);

  if (!raw_data_ || (error_code_ != FileErrorCode::kOK) ||
      is_raw_data_converted_) {
    return string_result_;
  }

  switch (read_type_) {
    case kReadAsArrayBuffer:
      return string_result_;
    case kReadAsBinaryString:
      SetStringResult(raw_data_->ToString());
      break;
    case kReadAsText:
      SetStringResult(ConvertToText());
      break;
    case kReadAsDataURL:
      if (finished_loading_)
        SetStringResult(ConvertToDataURL());
      break;
    default:
      NOTREACHED();
  }

  if (finished_loading_) {
    DCHECK(is_raw_data_converted_);
    AdjustReportedMemoryUsageToV8(
        -1 * static_cast<int64_t>(raw_data_->ByteLength()));
    raw_data_.reset();
  }
  return string_result_;
}

Project: Chrome

FileReaderLoader::~FileReaderLoader() {
  Cleanup();
  UnadjustReportedMemoryUsageToV8();
}