Commit message: None
CVE: CVE-2018-4117
CWE: CWE-200
Vulnerability description: An issue was discovered in certain Apple products. iOS before 11.3 is affected. Safari before 11.1 is affected. iCloud before 7.4 on Windows is affected. iTunes before 12.7.4 on Windows is affected. watchOS before 4.3 is affected. The issue involves the fetch API in the "WebKit" component. It allows remote attackers to bypass the Same Origin Policy and obtain sensitive information via a crafted web site.

Project: Chrome

void FetchManager::Loader::Abort() {
  if (resolver_) {
    resolver_->Reject(DOMException::Create(kAbortError));
    resolver_.Clear();
  }
  if (threadable_loader_) {
    auto loader = threadable_loader_;
    threadable_loader_ = nullptr;
    loader->Cancel();
  }
  probe::didFailFetch(execution_context_, this);
  NotifyFinished();
}

Project: Chrome

    void Cancel() {
      reader_ = nullptr;
      handle_ = nullptr;
    }

Project: Chrome

void FetchManager::ContextDestroyed(ExecutionContext*) {
  for (auto& loader : loaders_)
    loader->Dispose();
}

Project: Chrome

  static Loader* Create(ExecutionContext* execution_context,
                        FetchManager* fetch_manager,
                        ScriptPromiseResolver* resolver,
                        FetchRequestData* request,
                        bool is_isolated_world,
                        AbortSignal* signal) {
    return new Loader(execution_context, fetch_manager, resolver, request,
                      is_isolated_world, signal);
  }

Project: Chrome

FetchManager* FetchManager::Create(ExecutionContext* execution_context) {
  return new FetchManager(execution_context);
}

Project: Chrome

void FetchManager::Loader::DidFail(const ResourceError& error) {
  Failed(String());
}

Project: Chrome

void FetchManager::Loader::DidFailRedirectCheck() {
  Failed("Fetch API cannot load " + fetch_request_data_->Url().GetString() +
         ". Redirect failed.");
}

Project: Chrome

void FetchManager::Loader::DidFinishLoading(unsigned long, double) {
  did_finish_loading_ = true;
  if (integrity_verifier_ && !integrity_verifier_->IsFinished())
    return;

  LoadSucceeded();
}

Project: Chrome

void FetchManager::Loader::DidReceiveRedirectTo(const KURL& url) {
  url_list_.push_back(url);
}

Project: Chrome

void FetchManager::Loader::DidReceiveResponse(
    unsigned long,
    const ResourceResponse& response,
    std::unique_ptr<WebDataConsumerHandle> handle) {
  DCHECK(handle);
  DCHECK(response.Url() == url_list_.back());
  ScriptState* script_state = resolver_->GetScriptState();
  ScriptState::Scope scope(script_state);

  response_http_status_code_ = response.HttpStatusCode();
  FetchRequestData::Tainting tainting = fetch_request_data_->ResponseTainting();

  if (response.Url().ProtocolIsData()) {
    if (fetch_request_data_->Url() == response.Url()) {
      tainting = FetchRequestData::kBasicTainting;
    } else {
      switch (fetch_request_data_->Mode()) {
        case FetchRequestMode::kNoCORS:
          tainting = FetchRequestData::kOpaqueTainting;
          break;
        case FetchRequestMode::kSameOrigin:
        case FetchRequestMode::kCORS:
        case FetchRequestMode::kCORSWithForcedPreflight:
        case FetchRequestMode::kNavigate:
          PerformNetworkError("Fetch API cannot load " +
                              fetch_request_data_->Url().GetString() +
                              ". Redirects to data: URL are allowed only when "
                              "mode is \"no-cors\".");
          return;
      }
    }
  } else if (!SecurityOrigin::Create(response.Url())
                  ->IsSameSchemeHostPort(fetch_request_data_->Origin().get())) {
    switch (fetch_request_data_->Mode()) {
      case FetchRequestMode::kSameOrigin:
        NOTREACHED();
        break;
      case FetchRequestMode::kNoCORS:
        tainting = FetchRequestData::kOpaqueTainting;
        break;
      case FetchRequestMode::kCORS:
      case FetchRequestMode::kCORSWithForcedPreflight:
        tainting = FetchRequestData::kCORSTainting;
        break;
      case FetchRequestMode::kNavigate:
        LOG(FATAL);
        break;
    }
  }
  if (response.WasFetchedViaServiceWorker()) {
    switch (response.ResponseTypeViaServiceWorker()) {
      case FetchResponseType::kBasic:
      case FetchResponseType::kDefault:
        tainting = FetchRequestData::kBasicTainting;
        break;
      case FetchResponseType::kCORS:
        tainting = FetchRequestData::kCORSTainting;
        break;
      case FetchResponseType::kOpaque:
        tainting = FetchRequestData::kOpaqueTainting;
        break;
      case FetchResponseType::kOpaqueRedirect:
        DCHECK(
            NetworkUtils::IsRedirectResponseCode(response_http_status_code_));
        break;  // The code below creates an opaque-redirect filtered response.
      case FetchResponseType::kError:
        LOG(FATAL) << "When ServiceWorker respond to the request from fetch() "
                      "with an error response, FetchManager::Loader::didFail() "
                      "must be called instead.";
        break;
    }
  }

  FetchResponseData* response_data = nullptr;
  SRIBytesConsumer* sri_consumer = nullptr;
  if (fetch_request_data_->Integrity().IsEmpty()) {
    response_data = FetchResponseData::CreateWithBuffer(new BodyStreamBuffer(
        script_state,
        new BytesConsumerForDataConsumerHandle(
            ExecutionContext::From(script_state), std::move(handle)),
        signal_));
  } else {
    sri_consumer = new SRIBytesConsumer();
    response_data = FetchResponseData::CreateWithBuffer(
        new BodyStreamBuffer(script_state, sri_consumer, signal_));
  }
  response_data->SetStatus(response.HttpStatusCode());
  response_data->SetStatusMessage(response.HttpStatusText());
  for (auto& it : response.HttpHeaderFields())
    response_data->HeaderList()->Append(it.key, it.value);
  if (response.UrlListViaServiceWorker().IsEmpty()) {
    response_data->SetURLList(url_list_);
  } else {
    DCHECK(response.WasFetchedViaServiceWorker());
    response_data->SetURLList(response.UrlListViaServiceWorker());
  }
  response_data->SetMIMEType(response.MimeType());
  response_data->SetResponseTime(response.ResponseTime());

  FetchResponseData* tainted_response = nullptr;

  DCHECK(!(NetworkUtils::IsRedirectResponseCode(response_http_status_code_) &&
           HasNonEmptyLocationHeader(response_data->HeaderList()) &&
           fetch_request_data_->Redirect() != FetchRedirectMode::kManual));

  if (NetworkUtils::IsRedirectResponseCode(response_http_status_code_) &&
      fetch_request_data_->Redirect() == FetchRedirectMode::kManual) {
    tainted_response = response_data->CreateOpaqueRedirectFilteredResponse();
  } else {
    switch (tainting) {
      case FetchRequestData::kBasicTainting:
        tainted_response = response_data->CreateBasicFilteredResponse();
        break;
      case FetchRequestData::kCORSTainting: {
        WebHTTPHeaderSet header_names =
            WebCORS::ExtractCorsExposedHeaderNamesList(
                fetch_request_data_->Credentials(),
                WrappedResourceResponse(response));
        tainted_response =
            response_data->CreateCORSFilteredResponse(header_names);
        break;
      }
      case FetchRequestData::kOpaqueTainting:
        tainted_response = response_data->CreateOpaqueFilteredResponse();
        break;
    }
  }

  Response* r =
      Response::Create(resolver_->GetExecutionContext(), tainted_response);
  if (response.Url().ProtocolIsData()) {
    r->headers()->HeaderList()->Remove(HTTPNames::Access_Control_Allow_Origin);
  }
  r->headers()->SetGuard(Headers::kImmutableGuard);

  if (fetch_request_data_->Integrity().IsEmpty()) {
    resolver_->Resolve(r);
    resolver_.Clear();
  } else {
    DCHECK(!integrity_verifier_);
    integrity_verifier_ = new SRIVerifier(
        std::move(handle), sri_consumer, r, this,
        fetch_request_data_->Integrity(), response.Url(),
        r->GetResponse()->GetType(),
        resolver_->GetExecutionContext()->GetTaskRunner(TaskType::kNetworking));
  }
}

Project: Chrome

void FetchManager::Loader::Dispose() {
  probe::detachClientRequest(execution_context_, this);
  fetch_manager_ = nullptr;
  if (threadable_loader_) {
    if (fetch_request_data_->Keepalive())
      threadable_loader_->Detach();
    else
      threadable_loader_->Cancel();
    threadable_loader_ = nullptr;
  }
  if (integrity_verifier_)
    integrity_verifier_->Cancel();
  execution_context_ = nullptr;
}

Project: Chrome

void FetchManager::Loader::Failed(const String& message) {
  if (failed_ || finished_)
    return;
  failed_ = true;
  if (execution_context_->IsContextDestroyed())
    return;
  if (!message.IsEmpty()) {
    execution_context_->AddConsoleMessage(
        ConsoleMessage::Create(kJSMessageSource, kErrorMessageLevel, message));
  }
  if (resolver_) {
    ScriptState* state = resolver_->GetScriptState();
    ScriptState::Scope scope(state);
    resolver_->Reject(V8ThrowException::CreateTypeError(state->GetIsolate(),
                                                        "Failed to fetch"));
  }
  probe::didFailFetch(execution_context_, this);
  NotifyFinished();
}

Project: Chrome

ScriptPromise FetchManager::Fetch(ScriptState* script_state,
                                  FetchRequestData* request,
                                  AbortSignal* signal) {
  ScriptPromiseResolver* resolver = ScriptPromiseResolver::Create(script_state);
  ScriptPromise promise = resolver->Promise();

  DCHECK(signal);
  if (signal->aborted()) {
    resolver->Reject(DOMException::Create(kAbortError));
    return promise;
  }

  request->SetContext(WebURLRequest::kRequestContextFetch);

  Loader* loader =
      Loader::Create(GetExecutionContext(), this, resolver, request,
                     script_state->World().IsIsolatedWorld(), signal);
  loaders_.insert(loader);
  signal->AddAlgorithm(WTF::Bind(&Loader::Abort, WrapWeakPersistent(loader)));
  loader->Start();
  return promise;
}

Project: Chrome

FetchManager::FetchManager(ExecutionContext* execution_context)
    : ContextLifecycleObserver(execution_context) {}

Project: Chrome

Document* FetchManager::Loader::GetDocument() const {
  if (execution_context_->IsDocument()) {
    return ToDocument(execution_context_);
  }
  return nullptr;
}

Project: Chrome

    bool IsFinished() const { return finished_; }

Project: Chrome

void FetchManager::Loader::LoadSucceeded() {
  DCHECK(!failed_);

  finished_ = true;

  if (GetDocument() && GetDocument()->GetFrame() &&
      GetDocument()->GetFrame()->GetPage() &&
      CORS::IsOkStatus(response_http_status_code_)) {
    GetDocument()->GetFrame()->GetPage()->GetChromeClient().AjaxSucceeded(
        GetDocument()->GetFrame());
  }
  probe::didFinishFetch(execution_context_, this, fetch_request_data_->Method(),
                        fetch_request_data_->Url().GetString());
  NotifyFinished();
}

Project: Chrome

FetchManager::Loader::Loader(ExecutionContext* execution_context,
                             FetchManager* fetch_manager,
                             ScriptPromiseResolver* resolver,
                             FetchRequestData* fetch_request_data,
                             bool is_isolated_world,
                             AbortSignal* signal)
    : fetch_manager_(fetch_manager),
      resolver_(resolver),
      fetch_request_data_(fetch_request_data),
      failed_(false),
      finished_(false),
      response_http_status_code_(0),
      integrity_verifier_(nullptr),
      did_finish_loading_(false),
      is_isolated_world_(is_isolated_world),
      signal_(signal),
      execution_context_(execution_context) {
  url_list_.push_back(fetch_request_data->Url());
}

Project: Chrome

void FetchManager::Loader::NotifyFinished() {
  if (fetch_manager_)
    fetch_manager_->OnLoaderFinished(this);
}

Project: Chrome

void FetchManager::OnLoaderFinished(Loader* loader) {
  loaders_.erase(loader);
  loader->Dispose();
}

Project: Chrome

void FetchManager::Loader::PerformDataFetch() {
  DCHECK(fetch_request_data_->Url().ProtocolIsData());

  ResourceRequest request(fetch_request_data_->Url());
  request.SetRequestContext(fetch_request_data_->Context());
  request.SetUseStreamOnResponse(true);
  request.SetHTTPMethod(fetch_request_data_->Method());
  request.SetFetchCredentialsMode(network::mojom::FetchCredentialsMode::kOmit);
  request.SetFetchRedirectMode(FetchRedirectMode::kError);

  ResourceLoaderOptions resource_loader_options;
  resource_loader_options.data_buffering_policy = kDoNotBufferData;
  resource_loader_options.security_origin = fetch_request_data_->Origin().get();

  ThreadableLoaderOptions threadable_loader_options;

  probe::willStartFetch(execution_context_, this);
  threadable_loader_ = ThreadableLoader::Create(*execution_context_, this,
                                                threadable_loader_options,
                                                resource_loader_options);
  threadable_loader_->Start(request);
}

Project: Chrome

void FetchManager::Loader::PerformHTTPFetch() {

  ResourceRequest request(fetch_request_data_->Url());
  request.SetRequestContext(fetch_request_data_->Context());
  request.SetHTTPMethod(fetch_request_data_->Method());

  switch (fetch_request_data_->Mode()) {
    case FetchRequestMode::kSameOrigin:
    case FetchRequestMode::kNoCORS:
    case FetchRequestMode::kCORS:
    case FetchRequestMode::kCORSWithForcedPreflight:
      request.SetFetchRequestMode(fetch_request_data_->Mode());
      break;
    case FetchRequestMode::kNavigate:
      request.SetFetchRequestMode(FetchRequestMode::kSameOrigin);
      break;
  }

  request.SetFetchCredentialsMode(fetch_request_data_->Credentials());
  for (const auto& header : fetch_request_data_->HeaderList()->List()) {
    DCHECK(!CORS::IsForbiddenHeaderName(header.first));

    request.AddHTTPHeaderField(AtomicString(header.first),
                               AtomicString(header.second));
  }

  if (fetch_request_data_->Method() != HTTPNames::GET &&
      fetch_request_data_->Method() != HTTPNames::HEAD) {
    if (fetch_request_data_->Buffer())
      request.SetHTTPBody(fetch_request_data_->Buffer()->DrainAsFormData());
  }
  request.SetCacheMode(fetch_request_data_->CacheMode());
  request.SetFetchRedirectMode(fetch_request_data_->Redirect());
  request.SetUseStreamOnResponse(true);
  request.SetExternalRequestStateFromRequestorAddressSpace(
      execution_context_->GetSecurityContext().AddressSpace());

  const ReferrerPolicy referrer_policy =
      fetch_request_data_->GetReferrerPolicy() == kReferrerPolicyDefault
          ? execution_context_->GetReferrerPolicy()
          : fetch_request_data_->GetReferrerPolicy();
  const String referrer_string =
      fetch_request_data_->ReferrerString() ==
              FetchRequestData::ClientReferrerString()
          ? execution_context_->OutgoingReferrer()
          : fetch_request_data_->ReferrerString();
  request.SetHTTPReferrer(SecurityPolicy::GenerateReferrer(
      referrer_policy, fetch_request_data_->Url(), referrer_string));
  request.SetSkipServiceWorker(is_isolated_world_);

  if (fetch_request_data_->Keepalive()) {
    if (!CORS::IsCORSSafelistedMethod(request.HttpMethod()) ||
        !CORS::ContainsOnlyCORSSafelistedOrForbiddenHeaders(
            request.HttpHeaderFields())) {
      PerformNetworkError(
          "Preflight request for request with keepalive "
          "specified is currently not supported");
      return;
    }
    request.SetKeepalive(true);
  }




  ResourceLoaderOptions resource_loader_options;
  resource_loader_options.data_buffering_policy = kDoNotBufferData;
  resource_loader_options.security_origin = fetch_request_data_->Origin().get();
  if (fetch_request_data_->URLLoaderFactory()) {
    network::mojom::blink::URLLoaderFactoryPtr factory_clone;
    fetch_request_data_->URLLoaderFactory()->Clone(MakeRequest(&factory_clone));
    resource_loader_options.url_loader_factory = base::MakeRefCounted<
        base::RefCountedData<network::mojom::blink::URLLoaderFactoryPtr>>(
        std::move(factory_clone));
  }

  ThreadableLoaderOptions threadable_loader_options;

  probe::willStartFetch(execution_context_, this);
  threadable_loader_ = ThreadableLoader::Create(*execution_context_, this,
                                                threadable_loader_options,
                                                resource_loader_options);
  threadable_loader_->Start(request);
}

Project: Chrome

void FetchManager::Loader::PerformNetworkError(const String& message) {
  Failed(message);
}

Project: Chrome

void FetchManager::Loader::PerformSchemeFetch() {
  if (SchemeRegistry::ShouldTreatURLSchemeAsSupportingFetchAPI(
          fetch_request_data_->Url().Protocol()) ||
      fetch_request_data_->Url().ProtocolIs("blob")) {
    PerformHTTPFetch();
  } else if (fetch_request_data_->Url().ProtocolIsData()) {
    PerformDataFetch();
  } else {
    PerformNetworkError(
        "Fetch API cannot load " + fetch_request_data_->Url().GetString() +
        ". URL scheme \"" + fetch_request_data_->Url().Protocol() +
        "\" is not supported.");
  }
}

Project: Chrome

    SRIVerifier(std::unique_ptr<WebDataConsumerHandle> handle,
                SRIBytesConsumer* updater,
                Response* response,
                FetchManager::Loader* loader,
                String integrity_metadata,
                const KURL& url,
                FetchResponseType response_type,
                scoped_refptr<base::SingleThreadTaskRunner> task_runner)
        : handle_(std::move(handle)),
          updater_(updater),
          response_(response),
          loader_(loader),
          integrity_metadata_(integrity_metadata),
          url_(url),
          response_type_(response_type),
          finished_(false) {
      reader_ = handle_->ObtainReader(this, std::move(task_runner));
    }

Project: Chrome

    void Trace(blink::Visitor* visitor) {
      visitor->Trace(updater_);
      visitor->Trace(response_);
      visitor->Trace(loader_);
    }

Project: Chrome

void FetchManager::Loader::Trace(blink::Visitor* visitor) {
  visitor->Trace(fetch_manager_);
  visitor->Trace(resolver_);
  visitor->Trace(fetch_request_data_);
  visitor->Trace(threadable_loader_);
  visitor->Trace(integrity_verifier_);
  visitor->Trace(signal_);
  visitor->Trace(execution_context_);
}

Project: Chrome

void FetchManager::Trace(blink::Visitor* visitor) {
  visitor->Trace(loaders_);
  ContextLifecycleObserver::Trace(visitor);
}

Project: Chrome

  void Update(BytesConsumer* consumer) {
    DCHECK(!underlying_);
    if (is_cancelled_) {
      return;
    }

    underlying_ = consumer;
    if (client_) {
      Client* client = client_;
      client_ = nullptr;
      underlying_->SetClient(client);
      if (GetPublicState() != PublicState::kReadableOrWaiting)
        client->OnStateChange();
    }
  }

Project: Chrome

FetchManager::Loader::~Loader() {
  DCHECK(!threadable_loader_);
}