Reject invalid requests with "same-origin" credentials mode

Reject invalid use found in
https://crrev.com/c/chromium/src/+/1695341/.

Bug: 862184
Change-Id: I174d90bec44b8f077168a446b671a76ad38f0317
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1699587
Commit-Queue: Yutaka Hirano <yhirano@chromium.org>
Reviewed-by: David Benjamin <davidben@chromium.org>
Reviewed-by: Tom Sepez <tsepez@chromium.org>
Reviewed-by: Takashi Toyoshima <toyoshim@chromium.org>
Cr-Commit-Position: refs/heads/master@{#682188}
diff --git a/services/network/cors/cors_url_loader_factory.cc b/services/network/cors/cors_url_loader_factory.cc
index 74fb33be..ebda635 100644
--- a/services/network/cors/cors_url_loader_factory.cc
+++ b/services/network/cors/cors_url_loader_factory.cc
@@ -207,6 +207,26 @@
   LogConcerningRequestHeaders(request.headers,
                               false /* added_during_redirect */);
 
+  // Specifying CredentialsMode::kSameOrigin without an initiator origin doesn't
+  // make sense.
+  if (request.credentials_mode == mojom::CredentialsMode::kSameOrigin &&
+      !request.request_initiator) {
+    LOG(WARNING) << "same-origin credentials mode without initiator";
+    mojo::ReportBadMessage(
+        "CorsURLLoaderFactory: same-origin credentials mode without initiator");
+    return false;
+  }
+
+  // We only support |kInclude| credentials mode with navigations. See also:
+  // a note at https://fetch.spec.whatwg.org/#concept-request-credentials-mode.
+  if (request.credentials_mode != mojom::CredentialsMode::kInclude &&
+      request.mode == mojom::RequestMode::kNavigate) {
+    LOG(WARNING) << "unsupported credentials mode on a navigation request";
+    mojo::ReportBadMessage(
+        "CorsURLLoaderFactory: unsupported credentials mode on navigation");
+    return false;
+  }
+
   // TODO(yhirano): If the request mode is "no-cors", the redirect mode should
   // be "follow".
   return true;
diff --git a/services/network/cors/cors_url_loader_unittest.cc b/services/network/cors/cors_url_loader_unittest.cc
index 1b05d77..95962de2 100644
--- a/services/network/cors/cors_url_loader_unittest.cc
+++ b/services/network/cors/cors_url_loader_unittest.cc
@@ -1705,6 +1705,75 @@
   EXPECT_EQ(net::ERR_INVALID_ARGUMENT, client().completion_status().error_code);
 }
 
+TEST_F(CorsURLLoaderTest, SameOriginCredentialsModeWithoutInitiator) {
+  ResourceRequest request;
+  request.mode = mojom::RequestMode::kNoCors;
+  request.credentials_mode = mojom::CredentialsMode::kSameOrigin;
+  request.url = GURL("http://example.com/");
+  request.request_initiator = base::nullopt;
+
+  BadMessageTestHelper bad_message_helper;
+  CreateLoaderAndStart(request);
+  RunUntilComplete();
+
+  EXPECT_FALSE(IsNetworkLoaderStarted());
+  EXPECT_FALSE(client().has_received_redirect());
+  EXPECT_FALSE(client().has_received_response());
+  EXPECT_TRUE(client().has_received_completion());
+  EXPECT_EQ(net::ERR_INVALID_ARGUMENT, client().completion_status().error_code);
+  EXPECT_THAT(bad_message_helper.bad_message_reports(),
+              ::testing::ElementsAre("CorsURLLoaderFactory: same-origin "
+                                     "credentials mode without initiator"));
+}
+
+TEST_F(CorsURLLoaderTest, SameOriginCredentialsModeOnNavigation) {
+  ResetFactory(base::nullopt /* initiator */, mojom::kBrowserProcessId);
+
+  ResourceRequest request;
+  request.mode = mojom::RequestMode::kNavigate;
+  request.credentials_mode = mojom::CredentialsMode::kSameOrigin;
+  request.url = GURL("http://example.com/");
+  request.request_initiator = url::Origin::Create(request.url);
+
+  BadMessageTestHelper bad_message_helper;
+  CreateLoaderAndStart(request);
+  RunUntilComplete();
+
+  EXPECT_FALSE(IsNetworkLoaderStarted());
+  EXPECT_FALSE(client().has_received_redirect());
+  EXPECT_FALSE(client().has_received_response());
+  EXPECT_TRUE(client().has_received_completion());
+  EXPECT_EQ(net::ERR_INVALID_ARGUMENT, client().completion_status().error_code);
+  EXPECT_THAT(
+      bad_message_helper.bad_message_reports(),
+      ::testing::ElementsAre(
+          "CorsURLLoaderFactory: unsupported credentials mode on navigation"));
+}
+
+TEST_F(CorsURLLoaderTest, OmitCredentialsModeOnNavigation) {
+  ResetFactory(base::nullopt /* initiator */, mojom::kBrowserProcessId);
+
+  ResourceRequest request;
+  request.mode = mojom::RequestMode::kNavigate;
+  request.credentials_mode = mojom::CredentialsMode::kOmit;
+  request.url = GURL("http://example.com/");
+  request.request_initiator = url::Origin::Create(request.url);
+
+  BadMessageTestHelper bad_message_helper;
+  CreateLoaderAndStart(request);
+  RunUntilComplete();
+
+  EXPECT_FALSE(IsNetworkLoaderStarted());
+  EXPECT_FALSE(client().has_received_redirect());
+  EXPECT_FALSE(client().has_received_response());
+  EXPECT_TRUE(client().has_received_completion());
+  EXPECT_EQ(net::ERR_INVALID_ARGUMENT, client().completion_status().error_code);
+  EXPECT_THAT(
+      bad_message_helper.bad_message_reports(),
+      ::testing::ElementsAre(
+          "CorsURLLoaderFactory: unsupported credentials mode on navigation"));
+}
+
 }  // namespace
 
 }  // namespace cors
diff --git a/services/network/public/mojom/url_loader.mojom b/services/network/public/mojom/url_loader.mojom
index ec053d34..03e054e 100644
--- a/services/network/public/mojom/url_loader.mojom
+++ b/services/network/public/mojom/url_loader.mojom
@@ -238,8 +238,8 @@
 
   // https://fetch.spec.whatwg.org/#concept-request-credentials-mode
   // Controls whether credentials are attached to this request.
-  // Currently kSameOrigin does not work with |mode: kNavigate|.
-  // TODO(yhirano): Fix this.
+  // |kSameOrigin| credentials mode requires a non-null |request_initiator|.
+  // When |mode| is |kNavigate|, this needs to be |kInclude|.
   CredentialsMode credentials_mode;
 
   // https://fetch.spec.whatwg.org/#concept-request-redirect-mode