|  | // Copyright 2022 The Chromium Authors | 
|  | // Use of this source code is governed by a BSD-style license that can be | 
|  | // found in the LICENSE file. | 
|  |  | 
|  | #include "base/feature_list.h" | 
|  | #include "base/run_loop.h" | 
|  | #include "base/test/scoped_feature_list.h" | 
|  | #include "content/browser/buildflags.h" | 
|  | #include "content/browser/net/socket_broker_impl.h" | 
|  | #include "content/public/browser/browser_context.h" | 
|  | #include "content/public/browser/network_service_instance.h" | 
|  | #include "content/public/browser/storage_partition.h" | 
|  | #include "content/public/browser/web_contents.h" | 
|  | #include "content/public/common/content_features.h" | 
|  | #include "content/public/common/network_service_util.h" | 
|  | #include "content/public/test/browser_test.h" | 
|  | #include "content/public/test/content_browser_test.h" | 
|  | #include "content/shell/browser/shell.h" | 
|  | #include "mojo/public/cpp/bindings/receiver_set.h" | 
|  | #include "mojo/public/cpp/system/data_pipe_utils.h" | 
|  | #include "mojo/public/cpp/system/handle.h" | 
|  | #include "net/base/ip_endpoint.h" | 
|  | #include "net/socket/tcp_server_socket.h" | 
|  | #include "net/test/embedded_test_server/embedded_test_server.h" | 
|  | #include "net/traffic_annotation/network_traffic_annotation_test_helper.h" | 
|  | #include "sandbox/features.h" | 
|  | #include "sandbox/policy/features.h" | 
|  | #include "services/cert_verifier/public/mojom/cert_verifier_service_factory.mojom.h" | 
|  | #include "services/network/public/mojom/network_context.mojom.h" | 
|  | #include "services/network/public/mojom/tcp_socket.mojom.h" | 
|  | #include "third_party/abseil-cpp/absl/types/optional.h" | 
|  |  | 
|  | #if BUILDFLAG(IS_ANDROID) | 
|  | #include "base/android/build_info.h" | 
|  | #endif | 
|  |  | 
|  | namespace content { | 
|  | namespace { | 
|  |  | 
|  | const char kTestResponse[] = "hello socket broker"; | 
|  |  | 
|  | class SandboxedSocketBrokerBrowserTest : public ContentBrowserTest { | 
|  | public: | 
|  | SandboxedSocketBrokerBrowserTest() { | 
|  | #if BUILDFLAG(IS_ANDROID) | 
|  | // On older Android the Connect callback is not called with the featurelist | 
|  | // enabled. Since it is not strictly necessary to test socket brokering core | 
|  | // functionality with the featurelist we won't enable it on Android versions | 
|  | // prior to R. | 
|  | const int sdk_version = base::android::BuildInfo::GetInstance()->sdk_int(); | 
|  | if (sdk_version >= base::android::SdkVersion::SDK_VERSION_R) { | 
|  | std::vector<base::Feature> enabled_features = { | 
|  | sandbox::policy::features::kNetworkServiceSandbox, | 
|  | }; | 
|  | scoped_feature_list_.InitWithFeatures( | 
|  | enabled_features, {features::kNetworkServiceInProcess}); | 
|  | } else { | 
|  | check_sandbox_ = false; | 
|  | } | 
|  | #else | 
|  | #if BUILDFLAG(IS_WIN) | 
|  | if (!sandbox::features::IsAppContainerSandboxSupported()) | 
|  | check_sandbox_ = false; | 
|  | #endif  // BUILDFLAG(IS_WIN) | 
|  | std::vector<base::Feature> enabled_features = { | 
|  | #if !BUILDFLAG(IS_MAC) && !BUILDFLAG(IS_FUCHSIA) | 
|  | // Network Service Sandboxing is unconditionally enabled on these | 
|  | // platforms. | 
|  | sandbox::policy::features::kNetworkServiceSandbox, | 
|  | #endif  // !BUILDFLAG(IS_MAC) && !BUILDFLAG(IS_FUCHSIA) | 
|  | }; | 
|  | scoped_feature_list_.InitWithFeatures(enabled_features, | 
|  | {features::kNetworkServiceInProcess}); | 
|  | #endif  // BUILDFLAG(IS_ANDROID) | 
|  | } | 
|  |  | 
|  | void SetUp() override { | 
|  | #if BUILDFLAG(USE_SOCKET_BROKER) | 
|  | if (check_sandbox_) { | 
|  | ASSERT_TRUE(IsOutOfProcessNetworkService()); | 
|  | ASSERT_TRUE(sandbox::policy::features::IsNetworkSandboxEnabled()); | 
|  | } | 
|  |  | 
|  | embedded_test_server_.RegisterRequestHandler( | 
|  | base::BindRepeating(&SandboxedSocketBrokerBrowserTest::HandleRequest, | 
|  | base::Unretained(this))); | 
|  |  | 
|  | ASSERT_TRUE(embedded_test_server_.InitializeAndListen()); | 
|  | ContentBrowserTest::SetUp(); | 
|  | #else | 
|  | GTEST_SKIP(); | 
|  | #endif | 
|  | } | 
|  |  | 
|  | #if BUILDFLAG(USE_SOCKET_BROKER) | 
|  | void SetUpOnMainThread() override { | 
|  | embedded_test_server_.StartAcceptingConnections(); | 
|  | } | 
|  | #endif | 
|  |  | 
|  | std::unique_ptr<net::test_server::HttpResponse> HandleRequest( | 
|  | const net::test_server::HttpRequest& request) { | 
|  | GURL absolute_url = embedded_test_server_.GetURL(request.relative_url); | 
|  | if (absolute_url.path() != "/test") | 
|  | return nullptr; | 
|  |  | 
|  | auto http_response = | 
|  | std::make_unique<net::test_server::BasicHttpResponse>(); | 
|  | http_response->set_code(net::HTTP_OK); | 
|  | http_response->set_content(kTestResponse); | 
|  | http_response->set_content_type("text/plain"); | 
|  | return http_response; | 
|  | } | 
|  |  | 
|  | protected: | 
|  | base::test::ScopedFeatureList scoped_feature_list_; | 
|  | net::test_server::EmbeddedTestServer embedded_test_server_; | 
|  | bool check_sandbox_ = true; | 
|  | }; | 
|  |  | 
|  | mojo::Remote<network::mojom::NetworkContext> CreateNetworkContext() { | 
|  | mojo::Remote<network::mojom::NetworkContext> network_context; | 
|  | network::mojom::NetworkContextParamsPtr context_params = | 
|  | network::mojom::NetworkContextParams::New(); | 
|  | context_params->cert_verifier_params = GetCertVerifierParams( | 
|  | cert_verifier::mojom::CertVerifierCreationParams::New()); | 
|  | CreateNetworkContextInNetworkService( | 
|  | network_context.BindNewPipeAndPassReceiver(), std::move(context_params)); | 
|  | return network_context; | 
|  | } | 
|  |  | 
|  | void OnConnected(base::OnceClosure quit_closure, | 
|  | int result, | 
|  | const absl::optional<net::IPEndPoint>& local_addr, | 
|  | const absl::optional<net::IPEndPoint>& peer_addr, | 
|  | mojo::ScopedDataPipeConsumerHandle receive_stream, | 
|  | mojo::ScopedDataPipeProducerHandle send_stream) { | 
|  | base::ScopedClosureRunner closure_runner(std::move(quit_closure)); | 
|  | ASSERT_EQ(result, net::OK); | 
|  | const std::string request = "GET /test HTTP/1.0\r\n\r\n"; | 
|  | ASSERT_TRUE(BlockingCopyFromString(request, std::move(send_stream))); | 
|  | std::string response; | 
|  | ASSERT_TRUE(BlockingCopyToString(std::move(receive_stream), &response)); | 
|  | LOG(ERROR) << response; | 
|  | EXPECT_NE(response.find(kTestResponse), std::string::npos); | 
|  | } | 
|  |  | 
|  | // Creates a TCPConnectedSocket that attempts one GET request to | 
|  | // `embedded_test_server`. | 
|  | void RunTcpEndToEndTest( | 
|  | network::mojom::NetworkContext* network_context, | 
|  | net::test_server::EmbeddedTestServer& embedded_test_server) { | 
|  | mojo::PendingRemote<network::mojom::TCPConnectedSocket> | 
|  | tcp_connected_socket_remote; | 
|  | net::AddressList addr; | 
|  | ASSERT_TRUE(embedded_test_server.GetAddressList(&addr)); | 
|  |  | 
|  | base::RunLoop run_loop; | 
|  | network_context->CreateTCPConnectedSocket( | 
|  | absl::nullopt, addr, nullptr, | 
|  | net::MutableNetworkTrafficAnnotationTag(TRAFFIC_ANNOTATION_FOR_TESTS), | 
|  | tcp_connected_socket_remote.InitWithNewPipeAndPassReceiver(), | 
|  | mojo::NullRemote(), base::BindOnce(&OnConnected, run_loop.QuitClosure())); | 
|  | run_loop.Run(); | 
|  | } | 
|  |  | 
|  | IN_PROC_BROWSER_TEST_F(SandboxedSocketBrokerBrowserTest, | 
|  | TcpEndToEndDefaultContext) { | 
|  | network::mojom::NetworkContext* network_context = | 
|  | shell() | 
|  | ->web_contents() | 
|  | ->GetBrowserContext() | 
|  | ->GetDefaultStoragePartition() | 
|  | ->GetNetworkContext(); | 
|  | ASSERT_TRUE(network_context); | 
|  |  | 
|  | RunTcpEndToEndTest(network_context, embedded_test_server_); | 
|  | } | 
|  |  | 
|  | // Implementation of network::mojom::SocketBroker that tracks the number of | 
|  | // times CreateTcpSocket has been called. | 
|  | class CountingSocketBrokerImpl : public SocketBrokerImpl { | 
|  | public: | 
|  | void CreateTcpSocket(net::AddressFamily address_family, | 
|  | CreateTcpSocketCallback callback) override { | 
|  | ++tcp_socket_count_; | 
|  | SocketBrokerImpl::CreateTcpSocket(address_family, std::move(callback)); | 
|  | } | 
|  | int tcp_socket_count() { return tcp_socket_count_; } | 
|  |  | 
|  | private: | 
|  | int tcp_socket_count_ = 0; | 
|  | }; | 
|  |  | 
|  | IN_PROC_BROWSER_TEST_F(SandboxedSocketBrokerBrowserTest, | 
|  | TcpEndToEndBrokeredContext) { | 
|  | CountingSocketBrokerImpl socket_broker; | 
|  | network::mojom::NetworkContextParamsPtr network_context_params = | 
|  | network::mojom::NetworkContextParams::New(); | 
|  | network_context_params->socket_broker = socket_broker.BindNewRemote(); | 
|  | auto file_paths = network::mojom::NetworkContextFilePaths::New(); | 
|  | base::FilePath context_path = | 
|  | shell()->web_contents()->GetBrowserContext()->GetPath().Append( | 
|  | FILE_PATH_LITERAL("TestContext")); | 
|  | file_paths->data_directory = context_path.Append(FILE_PATH_LITERAL("Data")); | 
|  | file_paths->unsandboxed_data_path = context_path; | 
|  | file_paths->trigger_migration = true; | 
|  | network_context_params->file_paths = std::move(file_paths); | 
|  | network_context_params->cert_verifier_params = GetCertVerifierParams( | 
|  | cert_verifier::mojom::CertVerifierCreationParams::New()); | 
|  | network_context_params->http_cache_enabled = true; | 
|  | network_context_params->http_cache_directory = | 
|  | shell() | 
|  | ->web_contents() | 
|  | ->GetBrowserContext() | 
|  | ->GetPath() | 
|  | .Append(FILE_PATH_LITERAL("TestContext")) | 
|  | .Append(FILE_PATH_LITERAL("Cache")); | 
|  | mojo::Remote<network::mojom::NetworkContext> network_context; | 
|  | CreateNetworkContextInNetworkService( | 
|  | network_context.BindNewPipeAndPassReceiver(), | 
|  | std::move(network_context_params)); | 
|  |  | 
|  | RunTcpEndToEndTest(network_context.get(), embedded_test_server_); | 
|  | EXPECT_EQ(socket_broker.tcp_socket_count(), 1); | 
|  | } | 
|  |  | 
|  | IN_PROC_BROWSER_TEST_F(SandboxedSocketBrokerBrowserTest, | 
|  | TcpEndToEndCrashingService) { | 
|  | if (IsInProcessNetworkService()) | 
|  | GTEST_SKIP(); | 
|  |  | 
|  | auto network_context = CreateNetworkContext(); | 
|  |  | 
|  | ASSERT_TRUE(network_context.is_bound()); | 
|  |  | 
|  | // Run test on the first network context. | 
|  | RunTcpEndToEndTest(network_context.get(), embedded_test_server_); | 
|  |  | 
|  | SimulateNetworkServiceCrash(); | 
|  |  | 
|  | auto network_context2 = CreateNetworkContext(); | 
|  | ASSERT_TRUE(network_context2.is_bound()); | 
|  |  | 
|  | // Run the test again, in the new network service. | 
|  | RunTcpEndToEndTest(network_context2.get(), embedded_test_server_); | 
|  | } | 
|  |  | 
|  | }  // namespace | 
|  | }  // namespace content |