Make one QuicClientPushPromiseIndex per QuicChromiumClientSession.

QuicClientPushPromiseIndex allows at most one push per URL, which
resulted in leaking data across NetworkIsolationKeys. Since this is
a quiche-provided interface, the simplest workaround is to just create
one per QuicChromiumClientSession instead.

Bug: 1105544
Change-Id: I2bc975f4710bdc07f2a24f0ec6785c96000c5deb
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2302831
Reviewed-by: Zhongyi Shi <[email protected]>
Commit-Queue: Matt Menke <[email protected]>
Cr-Commit-Position: refs/heads/master@{#790557}
diff --git a/net/quic/quic_network_transaction_unittest.cc b/net/quic/quic_network_transaction_unittest.cc
index 741bf3f..33372b9 100644
--- a/net/quic/quic_network_transaction_unittest.cc
+++ b/net/quic/quic_network_transaction_unittest.cc
@@ -6692,6 +6692,231 @@
   EXPECT_LT(0, pos);
 }
 
+// Checks that if the same resource is pushed using two different
+// NetworkIsolationKeys, both pushed resources are tracked independently, and
+// NetworkIsolationKeys are respected.
+TEST_P(QuicNetworkTransactionTest, QuicServerPushRespectsNetworkIsolationKey) {
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitAndEnableFeature(
+      features::kPartitionConnectionsByNetworkIsolationKey);
+  client_maker_->set_max_allowed_push_id(quic::kMaxQuicStreamId);
+  context_.params()->max_allowed_push_id = quic::kMaxQuicStreamId;
+  context_.params()->origins_to_force_quic_on.insert(
+      HostPortPair::FromString("mail.example.org:443"));
+
+  MockQuicData mock_quic_data1(version_);
+  uint64_t client_packet_number1 = 1;
+  if (VersionUsesHttp3(version_.transport_version)) {
+    mock_quic_data1.AddWrite(
+        SYNCHRONOUS, ConstructInitialSettingsPacket(client_packet_number1++));
+  }
+  mock_quic_data1.AddWrite(
+      SYNCHRONOUS, ConstructClientRequestHeadersPacket(
+                       client_packet_number1++,
+                       GetNthClientInitiatedBidirectionalStreamId(0), true,
+                       true, GetRequestHeaders("GET", "https", "/")));
+  mock_quic_data1.AddRead(
+      ASYNC, ConstructServerPushPromisePacket(
+                 1, GetNthClientInitiatedBidirectionalStreamId(0),
+                 GetNthServerInitiatedUnidirectionalStreamId(0), false,
+                 GetRequestHeaders("GET", "https", "/pushed.jpg")));
+  const bool should_send_priority_packet =
+      client_headers_include_h2_stream_dependency_ &&
+      !VersionUsesHttp3(version_.transport_version);
+  if (should_send_priority_packet) {
+    mock_quic_data1.AddWrite(
+        SYNCHRONOUS,
+        ConstructClientAckAndPriorityPacket(
+            client_packet_number1++, false,
+            /*largest_received=*/1, /*smallest_received=*/1,
+            GetNthServerInitiatedUnidirectionalStreamId(0),
+            GetNthClientInitiatedBidirectionalStreamId(0), DEFAULT_PRIORITY));
+  }
+  mock_quic_data1.AddRead(
+      ASYNC, ConstructServerResponseHeadersPacket(
+                 2, GetNthClientInitiatedBidirectionalStreamId(0), false, false,
+                 GetResponseHeaders("200 OK")));
+  if (!should_send_priority_packet) {
+    mock_quic_data1.AddWrite(
+        SYNCHRONOUS, ConstructClientAckPacket(client_packet_number1++, 2, 1));
+  }
+  mock_quic_data1.AddRead(
+      ASYNC, ConstructServerResponseHeadersPacket(
+                 3, GetNthServerInitiatedUnidirectionalStreamId(0), false,
+                 false, GetResponseHeaders("200 OK")));
+  if (should_send_priority_packet) {
+    mock_quic_data1.AddWrite(
+        SYNCHRONOUS, ConstructClientAckPacket(client_packet_number1++, 3, 1));
+  }
+  std::string header = ConstructDataHeader(6);
+  mock_quic_data1.AddRead(
+      ASYNC, ConstructServerDataPacket(
+                 4, GetNthClientInitiatedBidirectionalStreamId(0), false, true,
+                 header + "hello1"));
+  if (!should_send_priority_packet) {
+    mock_quic_data1.AddWrite(
+        SYNCHRONOUS, ConstructClientAckPacket(client_packet_number1++, 4, 3));
+  }
+  std::string header2 = ConstructDataHeader(10);
+  mock_quic_data1.AddRead(
+      ASYNC, ConstructServerDataPacket(
+                 5, GetNthServerInitiatedUnidirectionalStreamId(0), false, true,
+                 header2 + "and hello1"));
+  if (should_send_priority_packet) {
+    mock_quic_data1.AddWrite(
+        SYNCHRONOUS, ConstructClientAckPacket(client_packet_number1++, 5, 3));
+  }
+  if (!VersionUsesHttp3(version_.transport_version)) {
+    mock_quic_data1.AddWrite(SYNCHRONOUS,
+                             ConstructClientAckAndRstPacket(
+                                 client_packet_number1++,
+                                 GetNthServerInitiatedUnidirectionalStreamId(0),
+                                 quic::QUIC_RST_ACKNOWLEDGEMENT, 5, 5));
+  } else {
+    mock_quic_data1.AddWrite(
+        SYNCHRONOUS, client_maker_->MakeAckAndPriorityUpdatePacket(
+                         client_packet_number1++, true, 5, 5, 3,
+                         GetNthServerInitiatedUnidirectionalStreamId(0)));
+  }
+  mock_quic_data1.AddRead(ASYNC, ERR_IO_PENDING);  // No more data to read
+  mock_quic_data1.AddRead(ASYNC, 0);               // EOF
+  mock_quic_data1.AddSocketDataToFactory(&socket_factory_);
+
+  QuicTestPacketMaker client_maker2(
+      version_,
+      quic::QuicUtils::CreateRandomConnectionId(context_.random_generator()),
+      context_.clock(), kDefaultServerHostName, quic::Perspective::IS_CLIENT,
+      client_headers_include_h2_stream_dependency_);
+  client_maker2.set_max_allowed_push_id(quic::kMaxQuicStreamId);
+  QuicTestPacketMaker server_maker2(
+      version_,
+      quic::QuicUtils::CreateRandomConnectionId(context_.random_generator()),
+      context_.clock(), kDefaultServerHostName, quic::Perspective::IS_SERVER,
+      false);
+  MockQuicData mock_quic_data2(version_);
+  uint64_t client_packet_number2 = 1;
+  if (VersionUsesHttp3(version_.transport_version)) {
+    mock_quic_data2.AddWrite(
+        SYNCHRONOUS,
+        client_maker2.MakeInitialSettingsPacket(client_packet_number2++));
+  }
+  mock_quic_data2.AddWrite(
+      SYNCHRONOUS,
+      client_maker2.MakeRequestHeadersPacket(
+          client_packet_number2++,
+          GetNthClientInitiatedBidirectionalStreamId(0), true, true,
+          ConvertRequestPriorityToQuicPriority(DEFAULT_PRIORITY),
+          GetRequestHeaders("GET", "https", "/"), 0, nullptr));
+  mock_quic_data2.AddRead(
+      ASYNC, server_maker2.MakePushPromisePacket(
+                 1, GetNthClientInitiatedBidirectionalStreamId(0),
+                 GetNthServerInitiatedUnidirectionalStreamId(0), false, false,
+                 GetRequestHeaders("GET", "https", "/pushed.jpg"), nullptr));
+  if (should_send_priority_packet) {
+    mock_quic_data2.AddWrite(
+        SYNCHRONOUS,
+        client_maker2.MakeAckAndPriorityPacket(
+            client_packet_number2++, false,
+            /*largest_received=*/1, /*smallest_received=*/1,
+            GetNthServerInitiatedUnidirectionalStreamId(0),
+            GetNthClientInitiatedBidirectionalStreamId(0),
+            ConvertRequestPriorityToQuicPriority(DEFAULT_PRIORITY)));
+  }
+  mock_quic_data2.AddRead(
+      ASYNC, server_maker2.MakeResponseHeadersPacket(
+                 2, GetNthClientInitiatedBidirectionalStreamId(0), false, false,
+                 GetResponseHeaders("200 OK"), nullptr));
+  if (!should_send_priority_packet) {
+    mock_quic_data2.AddWrite(SYNCHRONOUS, client_maker2.MakeAckPacket(
+                                              client_packet_number2++, 2, 1));
+  }
+  mock_quic_data2.AddRead(
+      ASYNC, server_maker2.MakeResponseHeadersPacket(
+                 3, GetNthServerInitiatedUnidirectionalStreamId(0), false,
+                 false, GetResponseHeaders("200 OK"), nullptr));
+  if (should_send_priority_packet) {
+    mock_quic_data2.AddWrite(SYNCHRONOUS, client_maker2.MakeAckPacket(
+                                              client_packet_number2++, 3, 1));
+  }
+  std::string header3 = ConstructDataHeader(6);
+  mock_quic_data2.AddRead(
+      ASYNC, server_maker2.MakeDataPacket(
+                 4, GetNthClientInitiatedBidirectionalStreamId(0), false, true,
+                 header3 + "hello2"));
+  if (!should_send_priority_packet) {
+    mock_quic_data2.AddWrite(SYNCHRONOUS, client_maker2.MakeAckPacket(
+                                              client_packet_number2++, 4, 3));
+  }
+  std::string header4 = ConstructDataHeader(10);
+  mock_quic_data2.AddRead(
+      ASYNC, server_maker2.MakeDataPacket(
+                 5, GetNthServerInitiatedUnidirectionalStreamId(0), false, true,
+                 header4 + "and hello2"));
+  if (should_send_priority_packet) {
+    mock_quic_data2.AddWrite(SYNCHRONOUS, client_maker2.MakeAckPacket(
+                                              client_packet_number2++, 5, 3));
+  }
+  if (!VersionUsesHttp3(version_.transport_version)) {
+    mock_quic_data2.AddWrite(SYNCHRONOUS,
+                             client_maker2.MakeAckAndRstPacket(
+                                 client_packet_number2++, false,
+                                 GetNthServerInitiatedUnidirectionalStreamId(0),
+                                 quic::QUIC_RST_ACKNOWLEDGEMENT, 5, 5));
+  } else {
+    mock_quic_data2.AddWrite(
+        SYNCHRONOUS, client_maker2.MakeAckAndPriorityUpdatePacket(
+                         client_packet_number2++, true, 5, 5, 3,
+                         GetNthServerInitiatedUnidirectionalStreamId(0)));
+  }
+  mock_quic_data2.AddRead(ASYNC, ERR_IO_PENDING);  // No more data to read
+  mock_quic_data2.AddRead(ASYNC, 0);               // EOF
+  mock_quic_data2.AddSocketDataToFactory(&socket_factory_);
+
+  scoped_refptr<X509Certificate> cert(
+      ImportCertFromFile(GetTestCertsDirectory(), "wildcard.pem"));
+  verify_details_.cert_verify_result.verified_cert = cert;
+  verify_details_.cert_verify_result.is_issued_by_known_root = true;
+  crypto_client_stream_factory_.AddProofVerifyDetails(&verify_details_);
+
+  // The non-alternate protocol jobs need to hang in order to guarantee that
+  // the alternate-protocol job will "win". Use "AddTcpSocketDataProvider()", as
+  // the connection requests may or may not actually make it to the socket
+  // factory, there may or may not be a TCP connection made in between the two
+  // QUIC connection attempts.
+  StaticSocketDataProvider hanging_data1;
+  hanging_data1.set_connect_data(MockConnect(SYNCHRONOUS, ERR_IO_PENDING));
+  socket_factory_.AddTcpSocketDataProvider(&hanging_data1);
+  StaticSocketDataProvider hanging_data2;
+  hanging_data2.set_connect_data(MockConnect(SYNCHRONOUS, ERR_IO_PENDING));
+  socket_factory_.AddTcpSocketDataProvider(&hanging_data2);
+
+  CreateSession();
+
+  NetworkIsolationKey network_isolation_key1 =
+      NetworkIsolationKey::CreateTransient();
+  NetworkIsolationKey network_isolation_key2 =
+      NetworkIsolationKey::CreateTransient();
+
+  // Creates the first QUIC session, and triggers the first push.
+  request_.network_isolation_key = network_isolation_key1;
+  SendRequestAndExpectQuicResponse("hello1");
+
+  // Use a different NIK, which creates another QUIC session, triggering a
+  // second push,
+  request_.network_isolation_key = network_isolation_key2;
+  SendRequestAndExpectQuicResponse("hello2");
+
+  // Use the second NIK again, should receive the push body from the second
+  // session.
+  request_.url = GURL("https://mail.example.org/pushed.jpg");
+  SendRequestAndExpectQuicResponse("and hello2");
+
+  // Use the first NIK again, should receive the push body from the first
+  // session.
+  request_.network_isolation_key = network_isolation_key1;
+  SendRequestAndExpectQuicResponse("and hello1");
+}
+
 // Regression test for http://crbug.com/719461 in which a promised stream
 // is closed before the pushed headers arrive, but after the connection
 // is closed and before the callbacks are executed.