blob: 37e7053d27cc865b5ff411961f0327357734ffb9 [file] [log] [blame]
// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "services/network/private_network_access_checker.h"
#include <string_view>
#include "base/metrics/field_trial_params.h"
#include "base/strings/strcat.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "net/base/ip_address.h"
#include "net/base/ip_endpoint.h"
#include "net/base/transport_info.h"
#include "services/network/public/cpp/features.h"
#include "services/network/public/cpp/resource_request.h"
#include "services/network/public/mojom/client_security_state.mojom-shared.h"
#include "services/network/public/mojom/client_security_state.mojom.h"
#include "services/network/public/mojom/cors.mojom.h"
#include "services/network/public/mojom/ip_address_space.mojom.h"
#include "services/network/public/mojom/url_loader_factory.mojom.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"
using ::testing::Optional;
namespace network {
namespace {
using Result = PrivateNetworkAccessCheckResult;
constexpr std::string_view kCheckResultHistogramName =
"Security.PrivateNetworkAccess.CheckResult";
constexpr char kNoAcceptChFrame[] = "";
// For better readability than literal `nullptr`.
constexpr mojom::ClientSecurityState* kNullClientSecurityState = nullptr;
net::IPEndPoint LoopbackEndpoint() {
return net::IPEndPoint(net::IPAddress::IPv4Localhost(), 80);
}
net::IPEndPoint PrivateEndpoint() {
return net::IPEndPoint(net::IPAddress(10, 0, 0, 1), 80);
}
GURL PrivateEndpointUrl() {
return GURL("http://10.0.0.1");
}
net::IPEndPoint PublicEndpoint() {
return net::IPEndPoint(net::IPAddress(1, 2, 3, 4), 80);
}
net::TransportInfo DirectTransport(const net::IPEndPoint& endpoint) {
return net::TransportInfo(
net::TransportType::kDirect, endpoint, kNoAcceptChFrame,
/*cert_is_issued_by_known_root=*/false, net::NextProto::kProtoUnknown);
}
net::TransportInfo ProxiedTransport(const net::IPEndPoint& endpoint) {
return net::TransportInfo(
net::TransportType::kProxied, endpoint, kNoAcceptChFrame,
/*cert_is_issued_by_known_root=*/false, net::NextProto::kProtoUnknown);
}
net::TransportInfo CachedTransport(const net::IPEndPoint& endpoint) {
return net::TransportInfo(
net::TransportType::kCached, endpoint, kNoAcceptChFrame,
/*cert_is_issued_by_known_root=*/false, net::NextProto::kProtoUnknown);
}
net::TransportInfo MakeTransport(net::TransportType type,
const net::IPEndPoint& endpoint) {
return net::TransportInfo(type, endpoint, kNoAcceptChFrame,
/*cert_is_issued_by_known_root=*/false,
net::NextProto::kProtoUnknown);
}
TEST(PrivateNetworkAccessCheckerTest, ClientSecurityStateNull) {
PrivateNetworkAccessChecker checker(
ResourceRequest(), kNullClientSecurityState, mojom::kURLLoadOptionNone);
EXPECT_EQ(checker.client_security_state(), nullptr);
EXPECT_EQ(checker.ClientAddressSpace(), mojom::IPAddressSpace::kUnknown);
}
TEST(PrivateNetworkAccessCheckerTest, ClientSecurityStateFromFactory) {
mojom::ClientSecurityState client_security_state;
client_security_state.ip_address_space = mojom::IPAddressSpace::kPublic;
PrivateNetworkAccessChecker checker(ResourceRequest(), &client_security_state,
mojom::kURLLoadOptionNone);
EXPECT_EQ(checker.client_security_state(), &client_security_state);
EXPECT_EQ(checker.ClientAddressSpace(), mojom::IPAddressSpace::kPublic);
}
// Check that the ClientSecurityState in the ResourceRequest is ignored. When
// present, it should match the value passed to the PrivateNetworkAccessChecker,
// anyways, but safest to make sure of that.
TEST(PrivateNetworkAccessCheckerTest, ClientSecurityStateFromRequestIgnored) {
// Test a case with a TrustedParams but no ClientSecurityState, and also a
// case with a TrustedParams and populated ClientSecurityState.
for (bool populate_client_security_state : {false, true}) {
SCOPED_TRACE(populate_client_security_state);
ResourceRequest request;
request.trusted_params.emplace();
if (populate_client_security_state) {
request.trusted_params->client_security_state =
mojom::ClientSecurityState::New();
request.trusted_params->client_security_state->ip_address_space =
mojom::IPAddressSpace::kLocal;
}
PrivateNetworkAccessChecker checker(request, kNullClientSecurityState,
mojom::kURLLoadOptionNone);
EXPECT_EQ(checker.client_security_state(), nullptr);
EXPECT_EQ(checker.ClientAddressSpace(), mojom::IPAddressSpace::kUnknown);
}
}
TEST(PrivateNetworkAccessCheckerTest, CheckLoadOptionUnknown) {
base::HistogramTester histogram_tester;
PrivateNetworkAccessChecker checker(ResourceRequest(),
kNullClientSecurityState,
mojom::kURLLoadOptionBlockLocalRequest);
EXPECT_EQ(checker.Check(DirectTransport(net::IPEndPoint())),
Result::kAllowedMissingClientSecurityState);
}
TEST(PrivateNetworkAccessCheckerTest, CheckLoadOptionPublic) {
base::HistogramTester histogram_tester;
PrivateNetworkAccessChecker checker(ResourceRequest(),
kNullClientSecurityState,
mojom::kURLLoadOptionBlockLocalRequest);
EXPECT_EQ(checker.Check(DirectTransport(PublicEndpoint())),
Result::kAllowedMissingClientSecurityState);
}
TEST(PrivateNetworkAccessCheckerTest, CheckLoadOptionPrivate) {
base::HistogramTester histogram_tester;
PrivateNetworkAccessChecker checker(ResourceRequest(),
kNullClientSecurityState,
mojom::kURLLoadOptionBlockLocalRequest);
EXPECT_EQ(checker.Check(DirectTransport(PrivateEndpoint())),
Result::kBlockedByLoadOption);
histogram_tester.ExpectUniqueSample(kCheckResultHistogramName,
Result::kBlockedByLoadOption, 1);
}
TEST(PrivateNetworkAccessCheckerTest, CheckLoadOptionLoopback) {
base::HistogramTester histogram_tester;
PrivateNetworkAccessChecker checker(ResourceRequest(),
kNullClientSecurityState,
mojom::kURLLoadOptionBlockLocalRequest);
EXPECT_EQ(checker.Check(DirectTransport(LoopbackEndpoint())),
Result::kBlockedByLoadOption);
histogram_tester.ExpectUniqueSample(kCheckResultHistogramName,
Result::kBlockedByLoadOption, 1);
}
TEST(PrivateNetworkAccessCheckerTest,
CheckAllowedPotentiallyTrustworthySameOrigin) {
base::HistogramTester histogram_tester;
ResourceRequest request;
request.url = GURL("https://a.com/subresource");
request.request_initiator = url::Origin::Create(request.url);
mojom::ClientSecurityState client_security_state;
client_security_state.ip_address_space = mojom::IPAddressSpace::kPublic;
client_security_state.private_network_request_policy =
mojom::PrivateNetworkRequestPolicy::kPreflightBlock;
PrivateNetworkAccessChecker checker(request, &client_security_state,
mojom::kURLLoadOptionNone);
EXPECT_EQ(checker.Check(DirectTransport(LoopbackEndpoint())),
Result::kAllowedPotentiallyTrustworthySameOrigin);
histogram_tester.ExpectUniqueSample(
kCheckResultHistogramName,
Result::kAllowedPotentiallyTrustworthySameOrigin, 1);
}
TEST(PrivateNetworkAccessCheckerTest,
CheckDisallowedPotentiallyTrustworthyCrossOrigin) {
base::HistogramTester histogram_tester;
ResourceRequest request;
request.url = GURL("https://example.com/subresource");
request.request_initiator =
url::Origin::Create(GURL("https://subdomain.example.com"));
mojom::ClientSecurityState client_security_state;
client_security_state.ip_address_space = mojom::IPAddressSpace::kPublic;
client_security_state.private_network_request_policy =
mojom::PrivateNetworkRequestPolicy::kPreflightBlock;
PrivateNetworkAccessChecker checker(request, &client_security_state,
mojom::kURLLoadOptionNone);
EXPECT_EQ(checker.Check(DirectTransport(LoopbackEndpoint())),
Result::kBlockedByPolicyPreflightBlock);
histogram_tester.ExpectUniqueSample(
kCheckResultHistogramName, Result::kBlockedByPolicyPreflightBlock, 1);
}
TEST(PrivateNetworkAccessCheckerTest, CheckDisallowedUntrustworthySameOrigin) {
base::HistogramTester histogram_tester;
ResourceRequest request;
request.url = GURL("http://example.com/subresource");
request.request_initiator = url::Origin::Create(request.url);
mojom::ClientSecurityState client_security_state;
client_security_state.ip_address_space = mojom::IPAddressSpace::kPublic;
client_security_state.private_network_request_policy =
mojom::PrivateNetworkRequestPolicy::kBlock;
PrivateNetworkAccessChecker checker(request, &client_security_state,
mojom::kURLLoadOptionNone);
EXPECT_EQ(checker.Check(DirectTransport(LoopbackEndpoint())),
Result::kBlockedByPolicyBlock);
histogram_tester.ExpectUniqueSample(kCheckResultHistogramName,
Result::kBlockedByPolicyBlock, 1);
}
TEST(PrivateNetworkAccessCheckerTest,
CheckDisallowedPotentiallyTrustworthyCrossOriginAfterResetForRedirect) {
base::HistogramTester histogram_tester;
ResourceRequest request;
request.url = GURL("https://example.com/subresource");
request.request_initiator = url::Origin::Create(request.url);
mojom::ClientSecurityState client_security_state;
client_security_state.ip_address_space = mojom::IPAddressSpace::kPublic;
client_security_state.private_network_request_policy =
mojom::PrivateNetworkRequestPolicy::kPreflightBlock;
PrivateNetworkAccessChecker checker(request, &client_security_state,
mojom::kURLLoadOptionNone);
checker.ResetForRedirect(GURL("https://subdomain.example.com/subresource"));
EXPECT_EQ(checker.Check(DirectTransport(LoopbackEndpoint())),
Result::kBlockedByPolicyPreflightBlock);
histogram_tester.ExpectUniqueSample(
kCheckResultHistogramName, Result::kBlockedByPolicyPreflightBlock, 1);
}
TEST(PrivateNetworkAccessCheckerTest,
CheckAllowedPotentiallyTrustworthySameOriginAfterResetForRedirect) {
base::HistogramTester histogram_tester;
ResourceRequest request;
request.url = GURL("https://example.com/subresource");
request.request_initiator =
url::Origin::Create(GURL("https://subdomain.example.com"));
mojom::ClientSecurityState client_security_state;
client_security_state.ip_address_space = mojom::IPAddressSpace::kPublic;
client_security_state.private_network_request_policy =
mojom::PrivateNetworkRequestPolicy::kPreflightBlock;
PrivateNetworkAccessChecker checker(request, &client_security_state,
mojom::kURLLoadOptionNone);
checker.ResetForRedirect(GURL("https://subdomain.example.com/subresource"));
EXPECT_EQ(checker.Check(DirectTransport(LoopbackEndpoint())),
Result::kAllowedPotentiallyTrustworthySameOrigin);
histogram_tester.ExpectUniqueSample(
kCheckResultHistogramName,
Result::kAllowedPotentiallyTrustworthySameOrigin, 1);
}
TEST(PrivateNetworkAccessCheckerTest, CheckAllowedMissingClientSecurityState) {
base::HistogramTester histogram_tester;
PrivateNetworkAccessChecker checker(
ResourceRequest(), kNullClientSecurityState, mojom::kURLLoadOptionNone);
EXPECT_EQ(checker.Check(DirectTransport(LoopbackEndpoint())),
Result::kAllowedMissingClientSecurityState);
histogram_tester.ExpectUniqueSample(
kCheckResultHistogramName, Result::kAllowedMissingClientSecurityState, 1);
}
TEST(PrivateNetworkAccessCheckerTest,
CheckAllowedMissingClientSecurityStateInconsistentIpAddressSpace) {
base::HistogramTester histogram_tester;
PrivateNetworkAccessChecker checker(
ResourceRequest(), kNullClientSecurityState, mojom::kURLLoadOptionNone);
EXPECT_EQ(checker.Check(DirectTransport(LoopbackEndpoint())),
Result::kAllowedMissingClientSecurityState);
// Even though this is inconsistent with the previous IP address space, the
// inconsistency is ignored because of the missing client security state.
//
// This does not risk triggering https://crbug.com/1279376, because no check
// with this checker will ever return `kBlocked*`.
EXPECT_EQ(checker.Check(DirectTransport(PublicEndpoint())),
Result::kAllowedMissingClientSecurityState);
}
TEST(PrivateNetworkAccessCheckerTest, CheckAllowedNoLessPublic) {
base::HistogramTester histogram_tester;
mojom::ClientSecurityState client_security_state;
client_security_state.ip_address_space = mojom::IPAddressSpace::kLocal;
client_security_state.private_network_request_policy =
mojom::PrivateNetworkRequestPolicy::kBlock;
PrivateNetworkAccessChecker checker(ResourceRequest(), &client_security_state,
mojom::kURLLoadOptionNone);
EXPECT_EQ(checker.Check(DirectTransport(PrivateEndpoint())),
Result::kAllowedNoLessPublic);
histogram_tester.ExpectUniqueSample(kCheckResultHistogramName,
Result::kAllowedNoLessPublic, 1);
}
// LNA collapses local and loopback address spaces so local -> loopback should
// be allowed.
TEST(PrivateNetworkAccessCheckerTest,
CheckAllowedNoLessPublicCollapseLocalLoopback) {
base::test::ScopedFeatureList feature_list;
base::FieldTrialParams params;
params["LocalNetworkAccessChecksWarn"] = "false";
feature_list.InitAndEnableFeatureWithParameters(
features::kLocalNetworkAccessChecks, params);
base::HistogramTester histogram_tester;
mojom::ClientSecurityState client_security_state;
client_security_state.ip_address_space = mojom::IPAddressSpace::kLocal;
client_security_state.private_network_request_policy =
mojom::PrivateNetworkRequestPolicy::kBlock;
PrivateNetworkAccessChecker checker(ResourceRequest(), &client_security_state,
mojom::kURLLoadOptionNone);
EXPECT_EQ(checker.Check(DirectTransport(LoopbackEndpoint())),
Result::kAllowedNoLessPublic);
histogram_tester.ExpectUniqueSample(kCheckResultHistogramName,
Result::kAllowedNoLessPublic, 1);
}
TEST(PrivateNetworkAccessCheckerTest, CheckAllowedByPolicyAllow) {
base::HistogramTester histogram_tester;
mojom::ClientSecurityState client_security_state;
client_security_state.ip_address_space = mojom::IPAddressSpace::kPublic;
client_security_state.private_network_request_policy =
mojom::PrivateNetworkRequestPolicy::kAllow;
PrivateNetworkAccessChecker checker(ResourceRequest(), &client_security_state,
mojom::kURLLoadOptionNone);
EXPECT_EQ(checker.Check(DirectTransport(PrivateEndpoint())),
Result::kAllowedByPolicyAllow);
histogram_tester.ExpectUniqueSample(kCheckResultHistogramName,
Result::kAllowedByPolicyAllow, 1);
}
TEST(PrivateNetworkAccessCheckerTest,
CheckAllowedByPolicyWarnInconsistentIpAddressSpace) {
base::HistogramTester histogram_tester;
mojom::ClientSecurityState client_security_state;
client_security_state.ip_address_space = mojom::IPAddressSpace::kPublic;
client_security_state.private_network_request_policy =
mojom::PrivateNetworkRequestPolicy::kWarn;
PrivateNetworkAccessChecker checker(ResourceRequest(), &client_security_state,
mojom::kURLLoadOptionNone);
EXPECT_EQ(checker.Check(DirectTransport(PrivateEndpoint())),
Result::kAllowedByPolicyWarn);
// Even though this is inconsistent with the previous IP address space, the
// inconsistency is ignored because of the `kWarn` policy.
EXPECT_EQ(checker.Check(DirectTransport(PublicEndpoint())),
Result::kAllowedByPolicyWarn);
}
TEST(PrivateNetworkAccessCheckerTest,
CheckAllowedByPolicyAllowInconsistentIpAddressSpace) {
base::HistogramTester histogram_tester;
mojom::ClientSecurityState client_security_state;
client_security_state.ip_address_space = mojom::IPAddressSpace::kPublic;
client_security_state.private_network_request_policy =
mojom::PrivateNetworkRequestPolicy::kAllow;
PrivateNetworkAccessChecker checker(ResourceRequest(), &client_security_state,
mojom::kURLLoadOptionNone);
EXPECT_EQ(checker.Check(DirectTransport(PrivateEndpoint())),
Result::kAllowedByPolicyAllow);
// Even though this is inconsistent with the previous IP address space, the
// inconsistency is ignored because of the `kAllow` policy.
//
// This does not risk triggering https://crbug.com/1279376, because no check
// with this checker will ever return `kBlocked*`.
EXPECT_EQ(checker.Check(DirectTransport(PublicEndpoint())),
Result::kAllowedByPolicyAllow);
}
TEST(PrivateNetworkAccessCheckerTest, CheckAllowedByPolicyWarn) {
base::HistogramTester histogram_tester;
mojom::ClientSecurityState client_security_state;
client_security_state.ip_address_space = mojom::IPAddressSpace::kPublic;
client_security_state.private_network_request_policy =
mojom::PrivateNetworkRequestPolicy::kWarn;
PrivateNetworkAccessChecker checker(ResourceRequest(), &client_security_state,
mojom::kURLLoadOptionNone);
EXPECT_EQ(checker.Check(DirectTransport(PrivateEndpoint())),
Result::kAllowedByPolicyWarn);
histogram_tester.ExpectUniqueSample(kCheckResultHistogramName,
Result::kAllowedByPolicyWarn, 1);
}
TEST(PrivateNetworkAccessCheckerTest, CheckBlockedByPolicyBlock) {
base::HistogramTester histogram_tester;
mojom::ClientSecurityState client_security_state;
client_security_state.ip_address_space = mojom::IPAddressSpace::kPublic;
client_security_state.private_network_request_policy =
mojom::PrivateNetworkRequestPolicy::kBlock;
PrivateNetworkAccessChecker checker(ResourceRequest(), &client_security_state,
mojom::kURLLoadOptionNone);
EXPECT_EQ(checker.Check(DirectTransport(PrivateEndpoint())),
Result::kBlockedByPolicyBlock);
histogram_tester.ExpectUniqueSample(kCheckResultHistogramName,
Result::kBlockedByPolicyBlock, 1);
}
TEST(PrivateNetworkAccessCheckerTest, CheckBlockedByPolicyPreflightWarn) {
base::HistogramTester histogram_tester;
mojom::ClientSecurityState client_security_state;
client_security_state.ip_address_space = mojom::IPAddressSpace::kPublic;
client_security_state.private_network_request_policy =
mojom::PrivateNetworkRequestPolicy::kPreflightWarn;
PrivateNetworkAccessChecker checker(ResourceRequest(), &client_security_state,
mojom::kURLLoadOptionNone);
EXPECT_EQ(checker.Check(DirectTransport(PrivateEndpoint())),
Result::kBlockedByPolicyPreflightWarn);
histogram_tester.ExpectUniqueSample(kCheckResultHistogramName,
Result::kBlockedByPolicyPreflightWarn, 1);
}
TEST(PrivateNetworkAccessCheckerTest, CheckBlockedByPolicyPreflightBlock) {
base::HistogramTester histogram_tester;
mojom::ClientSecurityState client_security_state;
client_security_state.ip_address_space = mojom::IPAddressSpace::kPublic;
client_security_state.private_network_request_policy =
mojom::PrivateNetworkRequestPolicy::kPreflightBlock;
PrivateNetworkAccessChecker checker(ResourceRequest(), &client_security_state,
mojom::kURLLoadOptionNone);
EXPECT_EQ(checker.Check(DirectTransport(PrivateEndpoint())),
Result::kBlockedByPolicyPreflightBlock);
histogram_tester.ExpectUniqueSample(
kCheckResultHistogramName, Result::kBlockedByPolicyPreflightBlock, 1);
}
TEST(PrivateNetworkAccessCheckerTest, CheckBlockedByTargetIpAddressSpace) {
base::HistogramTester histogram_tester;
mojom::ClientSecurityState client_security_state;
client_security_state.ip_address_space = mojom::IPAddressSpace::kPublic;
client_security_state.private_network_request_policy =
mojom::PrivateNetworkRequestPolicy::kPreflightBlock;
ResourceRequest request;
request.target_ip_address_space = mojom::IPAddressSpace::kPublic;
PrivateNetworkAccessChecker checker(request, &client_security_state,
mojom::kURLLoadOptionNone);
EXPECT_EQ(checker.Check(DirectTransport(PrivateEndpoint())),
Result::kBlockedByTargetIpAddressSpace);
histogram_tester.ExpectUniqueSample(
kCheckResultHistogramName, Result::kBlockedByTargetIpAddressSpace, 1);
}
TEST(PrivateNetworkAccessCheckerTest, CheckAllowedByPolicyPreflightWarn) {
base::HistogramTester histogram_tester;
mojom::ClientSecurityState client_security_state;
client_security_state.ip_address_space = mojom::IPAddressSpace::kPublic;
client_security_state.private_network_request_policy =
mojom::PrivateNetworkRequestPolicy::kPreflightWarn;
ResourceRequest request;
request.target_ip_address_space = mojom::IPAddressSpace::kLoopback;
PrivateNetworkAccessChecker checker(request, &client_security_state,
mojom::kURLLoadOptionNone);
EXPECT_EQ(checker.Check(DirectTransport(PrivateEndpoint())),
Result::kAllowedByPolicyPreflightWarn);
histogram_tester.ExpectUniqueSample(kCheckResultHistogramName,
Result::kAllowedByPolicyPreflightWarn, 1);
}
TEST(PrivateNetworkAccessCheckerTest, CheckAllowedByTargetIpAddressSpace) {
base::HistogramTester histogram_tester;
mojom::ClientSecurityState client_security_state;
client_security_state.ip_address_space = mojom::IPAddressSpace::kPublic;
client_security_state.private_network_request_policy =
mojom::PrivateNetworkRequestPolicy::kPreflightBlock;
ResourceRequest request;
request.target_ip_address_space = mojom::IPAddressSpace::kLocal;
PrivateNetworkAccessChecker checker(request, &client_security_state,
mojom::kURLLoadOptionNone);
EXPECT_EQ(checker.Check(DirectTransport(PrivateEndpoint())),
Result::kAllowedByTargetIpAddressSpace);
histogram_tester.ExpectUniqueSample(
kCheckResultHistogramName, Result::kAllowedByTargetIpAddressSpace, 1);
}
TEST(PrivateNetworkAccessCheckerTest,
CheckAllowedByPolicyPreflightWarnInconsistent) {
mojom::ClientSecurityState client_security_state;
client_security_state.ip_address_space = mojom::IPAddressSpace::kPublic;
client_security_state.private_network_request_policy =
mojom::PrivateNetworkRequestPolicy::kPreflightWarn;
PrivateNetworkAccessChecker checker(ResourceRequest(), &client_security_state,
mojom::kURLLoadOptionNone);
checker.Check(DirectTransport(PublicEndpoint()));
base::HistogramTester histogram_tester;
EXPECT_EQ(checker.Check(DirectTransport(PrivateEndpoint())),
Result::kAllowedByPolicyPreflightWarn);
histogram_tester.ExpectUniqueSample(kCheckResultHistogramName,
Result::kAllowedByPolicyPreflightWarn, 1);
}
TEST(PrivateNetworkAccessCheckerTest,
CheckBlockedByInconsistentIpAddressSpace) {
mojom::ClientSecurityState client_security_state;
client_security_state.ip_address_space = mojom::IPAddressSpace::kLocal;
client_security_state.private_network_request_policy =
mojom::PrivateNetworkRequestPolicy::kPreflightBlock;
PrivateNetworkAccessChecker checker(ResourceRequest(), &client_security_state,
mojom::kURLLoadOptionNone);
EXPECT_EQ(checker.Check(DirectTransport(PublicEndpoint())),
Result::kAllowedNoLessPublic);
base::HistogramTester histogram_tester;
EXPECT_EQ(checker.Check(DirectTransport(PrivateEndpoint())),
Result::kBlockedByInconsistentIpAddressSpace);
histogram_tester.ExpectUniqueSample(
kCheckResultHistogramName, Result::kBlockedByInconsistentIpAddressSpace,
1);
}
// Disabled because this relies on
// network::features::kPrivateNetworkAccessPermissionPrompt being enabled; not
// deleted because we want to keep this test when
// features::kLocalNetworkAccessChecks is enabled.
//
// TODO(crbug.com/394636065): re-enable test when cleaning up PNA and porting
// over to LNA
TEST(
PrivateNetworkAccessCheckerTest,
DISABLED_CheckBlockedByUnmatchedRequiredAddressSpaceAndResourceAddressSpace) {
mojom::ClientSecurityState client_security_state;
client_security_state.ip_address_space = mojom::IPAddressSpace::kPublic;
client_security_state.private_network_request_policy =
mojom::PrivateNetworkRequestPolicy::kPreflightBlock;
ResourceRequest request;
request.target_ip_address_space = mojom::IPAddressSpace::kUnknown;
request.required_ip_address_space = mojom::IPAddressSpace::kLocal;
PrivateNetworkAccessChecker checker(request, &client_security_state,
mojom::kURLLoadOptionNone);
EXPECT_EQ(checker.Check(DirectTransport(PublicEndpoint())),
Result::kBlockedByTargetIpAddressSpace);
}
TEST(PrivateNetworkAccessCheckerTest, ResponseAddressSpace) {
PrivateNetworkAccessChecker checker(
ResourceRequest(), kNullClientSecurityState, mojom::kURLLoadOptionNone);
EXPECT_EQ(checker.ResponseAddressSpace(), std::nullopt);
checker.Check(DirectTransport(PublicEndpoint()));
EXPECT_THAT(checker.ResponseAddressSpace(),
Optional(mojom::IPAddressSpace::kPublic));
checker.Check(DirectTransport(PrivateEndpoint()));
EXPECT_THAT(checker.ResponseAddressSpace(),
Optional(mojom::IPAddressSpace::kLocal));
}
TEST(PrivateNetworkAccessCheckerTest, ProxiedTransportAddressSpaceIsUnknown) {
PrivateNetworkAccessChecker checker(ResourceRequest(),
kNullClientSecurityState,
mojom::kURLLoadOptionBlockLocalRequest);
// This succeeds in spite of the load option, because the proxied transport
// is not considered any less public than `kPublic`.
EXPECT_EQ(checker.Check(ProxiedTransport(LoopbackEndpoint())),
Result::kAllowedMissingClientSecurityState);
// In fact, it is considered unknown.
EXPECT_EQ(checker.ResponseAddressSpace(), mojom::IPAddressSpace::kUnknown);
}
TEST(PrivateNetworkAccessCheckerTest,
CachedFromProxyTransportAddressSpaceIsUnknown) {
PrivateNetworkAccessChecker checker(ResourceRequest(),
kNullClientSecurityState,
mojom::kURLLoadOptionBlockLocalRequest);
// This succeeds in spite of the load option, because the cached-from-proxy
// transport is not considered any less public than `kPublic`.
EXPECT_EQ(checker.Check(MakeTransport(net::TransportType::kCachedFromProxy,
LoopbackEndpoint())),
Result::kAllowedMissingClientSecurityState);
// In fact, it is considered unknown.
EXPECT_EQ(checker.ResponseAddressSpace(), mojom::IPAddressSpace::kUnknown);
}
TEST(PrivateNetworkAccessCheckerTest, CachedTransportAddressSpace) {
PrivateNetworkAccessChecker checker(ResourceRequest(),
kNullClientSecurityState,
mojom::kURLLoadOptionBlockLocalRequest);
// The cached transport is treated like a direct transport to the same
// endpoint, so the load option does not fail the check.
EXPECT_EQ(checker.Check(CachedTransport(PublicEndpoint())),
Result::kAllowedMissingClientSecurityState);
EXPECT_EQ(checker.ResponseAddressSpace(), mojom::IPAddressSpace::kPublic);
// When the endpoint is loopback, the check fails as for a direct transport.
EXPECT_EQ(checker.Check(CachedTransport(LoopbackEndpoint())),
Result::kBlockedByLoadOption);
EXPECT_EQ(checker.ResponseAddressSpace(), mojom::IPAddressSpace::kLoopback);
}
TEST(PrivateNetworkAccessCheckerTest, ResetTargetAddressSpace) {
mojom::ClientSecurityState client_security_state;
client_security_state.ip_address_space = mojom::IPAddressSpace::kLocal;
client_security_state.private_network_request_policy =
mojom::PrivateNetworkRequestPolicy::kPreflightBlock;
ResourceRequest request;
request.target_ip_address_space = mojom::IPAddressSpace::kPublic;
PrivateNetworkAccessChecker checker(request, &client_security_state,
mojom::kURLLoadOptionNone);
checker.ResetForRedirect(GURL("http://foo.com"));
// The target address space has been cleared.
EXPECT_EQ(checker.TargetAddressSpace(), mojom::IPAddressSpace::kUnknown);
// This succeeds even though the IP address space does not match the target
// passed at construction time, thanks to `ResetForRedirect()`.
EXPECT_EQ(checker.Check(DirectTransport(PrivateEndpoint())),
Result::kAllowedNoLessPublic);
}
TEST(PrivateNetworkAccessCheckerTest, ResetResponseAddressSpace) {
PrivateNetworkAccessChecker checker(
ResourceRequest(), kNullClientSecurityState, mojom::kURLLoadOptionNone);
checker.Check(DirectTransport(PrivateEndpoint()));
checker.ResetForRedirect(GURL("http://foo.com"));
EXPECT_EQ(checker.ResponseAddressSpace(), std::nullopt);
// This succeeds even though the IP address space does not match that of the
// previous endpoint passed to `Check()`, thanks to `ResetForRedirect()`.
EXPECT_EQ(checker.Check(DirectTransport(LoopbackEndpoint())),
Result::kAllowedMissingClientSecurityState);
}
TEST(PrivateNetworkAccessCheckerTest,
DoesNotRecordPrivateIpInferrableHistogramForPublicEndpoint) {
base::HistogramTester histogram_tester;
ResourceRequest request;
request.url = GURL("http://192.168.1.1");
PrivateNetworkAccessChecker checker(request, kNullClientSecurityState,
mojom::kURLLoadOptionNone);
checker.Check(DirectTransport(PublicEndpoint()));
histogram_tester.ExpectTotalCount(
"Security.PrivateNetworkAccess.PrivateIpInferrable", 0);
}
TEST(PrivateNetworkAccessCheckerTest,
DoesNotRecordPrivateIpInferrableHistogramForHttpsScheme) {
base::HistogramTester histogram_tester;
ResourceRequest request;
request.url = GURL("https://192.168.1.1");
PrivateNetworkAccessChecker checker(request, kNullClientSecurityState,
mojom::kURLLoadOptionNone);
checker.Check(DirectTransport(PrivateEndpoint()));
histogram_tester.ExpectTotalCount(
"Security.PrivateNetworkAccess.PrivateIpInferrable", 0);
}
TEST(PrivateNetworkAccessCheckerTest,
DoesNotRecordPrivateIpInferrableHistogramWithTargetIpAddressSpace) {
mojom::ClientSecurityState client_security_state;
client_security_state.ip_address_space = mojom::IPAddressSpace::kPublic;
client_security_state.private_network_request_policy =
mojom::PrivateNetworkRequestPolicy::kPreflightWarn;
base::HistogramTester histogram_tester;
ResourceRequest request;
request.url = GURL("http://192.168.1.1");
request.target_ip_address_space = mojom::IPAddressSpace::kLocal;
PrivateNetworkAccessChecker checker(request, &client_security_state,
mojom::kURLLoadOptionNone);
checker.Check(DirectTransport(PrivateEndpoint()));
histogram_tester.ExpectTotalCount(
"Security.PrivateNetworkAccess.PrivateIpInferrable", 0);
}
TEST(PrivateNetworkAccessCheckerTest,
RecordsPrivateIpInferrableHistogramPolicyAllow) {
mojom::ClientSecurityState client_security_state;
client_security_state.ip_address_space = mojom::IPAddressSpace::kPublic;
client_security_state.private_network_request_policy =
mojom::PrivateNetworkRequestPolicy::kAllow;
base::HistogramTester histogram_tester;
ResourceRequest request;
request.url = GURL("http://192.168.1.1");
PrivateNetworkAccessChecker checker(request, &client_security_state,
mojom::kURLLoadOptionNone);
checker.Check(DirectTransport(PrivateEndpoint()));
histogram_tester.ExpectUniqueSample(
"Security.PrivateNetworkAccess.PrivateIpInferrable", true, 1);
}
TEST(PrivateNetworkAccessCheckerTest,
RecordsPrivateIpInferrableHistogramNoLessPublic) {
mojom::ClientSecurityState client_security_state;
client_security_state.ip_address_space = mojom::IPAddressSpace::kLocal;
client_security_state.private_network_request_policy =
mojom::PrivateNetworkRequestPolicy::kBlock;
base::HistogramTester histogram_tester;
ResourceRequest request;
request.url = GURL("http://192.168.1.1");
PrivateNetworkAccessChecker checker(request, &client_security_state,
mojom::kURLLoadOptionNone);
checker.Check(DirectTransport(PrivateEndpoint()));
histogram_tester.ExpectUniqueSample(
"Security.PrivateNetworkAccess.PrivateIpInferrable", true, 1);
}
TEST(PrivateNetworkAccessCheckerTest, RecordsPrivateIpInferrableHistogramTrue) {
mojom::ClientSecurityState client_security_state;
client_security_state.ip_address_space = mojom::IPAddressSpace::kPublic;
client_security_state.private_network_request_policy =
mojom::PrivateNetworkRequestPolicy::kBlock;
base::HistogramTester histogram_tester;
ResourceRequest request;
request.url = GURL("http://192.168.1.1");
PrivateNetworkAccessChecker checker(request, &client_security_state,
mojom::kURLLoadOptionNone);
checker.Check(DirectTransport(PrivateEndpoint()));
histogram_tester.ExpectUniqueSample(
"Security.PrivateNetworkAccess.PrivateIpInferrable", true, 1);
}
TEST(PrivateNetworkAccessCheckerTest,
RecordsPrivateIpInferrableHistogramTrueIpv6) {
mojom::ClientSecurityState client_security_state;
client_security_state.ip_address_space = mojom::IPAddressSpace::kPublic;
client_security_state.private_network_request_policy =
mojom::PrivateNetworkRequestPolicy::kBlock;
base::HistogramTester histogram_tester;
ResourceRequest request;
request.url = GURL("http://[fc00::]");
PrivateNetworkAccessChecker checker(request, &client_security_state,
mojom::kURLLoadOptionNone);
checker.Check(DirectTransport(net::IPEndPoint(
net::IPAddress(0xfc, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0), 80)));
histogram_tester.ExpectUniqueSample(
"Security.PrivateNetworkAccess.PrivateIpInferrable", true, 1);
}
TEST(PrivateNetworkAccessCheckerTest,
RecordsPrivateIpInferrableHistogramFalse) {
mojom::ClientSecurityState client_security_state;
client_security_state.ip_address_space = mojom::IPAddressSpace::kPublic;
client_security_state.private_network_request_policy =
mojom::PrivateNetworkRequestPolicy::kBlock;
base::HistogramTester histogram_tester;
ResourceRequest request;
request.url = GURL("http://foo");
PrivateNetworkAccessChecker checker(request, &client_security_state,
mojom::kURLLoadOptionNone);
checker.Check(DirectTransport(PrivateEndpoint()));
histogram_tester.ExpectUniqueSample(
"Security.PrivateNetworkAccess.PrivateIpInferrable", false, 1);
}
TEST(PrivateNetworkAccessCheckerTest,
RecordsPrivateIpInferrableHistogramAfterResetForRetry) {
base::HistogramTester histogram_tester;
ResourceRequest request;
request.url = PrivateEndpointUrl();
PrivateNetworkAccessChecker checker(request, kNullClientSecurityState,
mojom::kURLLoadOptionNone);
checker.ResetForRetry();
checker.Check(DirectTransport(PrivateEndpoint()));
histogram_tester.ExpectUniqueSample(
"Security.PrivateNetworkAccess.PrivateIpResolveMatch", true, 1);
}
TEST(PrivateNetworkAccessCheckerTest,
RecordsPrivateIpInferrableHistogramAfterResetForRedirect) {
base::HistogramTester histogram_tester;
ResourceRequest request;
request.url = GURL("http://1.2.3.4");
PrivateNetworkAccessChecker checker(request, kNullClientSecurityState,
mojom::kURLLoadOptionNone);
checker.ResetForRedirect(PrivateEndpointUrl());
checker.Check(DirectTransport(PrivateEndpoint()));
histogram_tester.ExpectUniqueSample(
"Security.PrivateNetworkAccess.PrivateIpResolveMatch", true, 1);
}
TEST(PrivateNetworkAccessCheckerTest,
DoesNotRecordPrivateIpResolveMatchHistogramLocalhost) {
base::HistogramTester histogram_tester;
ResourceRequest request;
request.url = GURL("http://127.0.0.1");
PrivateNetworkAccessChecker checker(request, kNullClientSecurityState,
mojom::kURLLoadOptionNone);
checker.Check(DirectTransport(PrivateEndpoint()));
histogram_tester.ExpectTotalCount(
"Security.PrivateNetworkAccess.PrivateIpResolveMatch", 0);
}
TEST(PrivateNetworkAccessCheckerTest,
DoesNotRecordPrivateIpResolveMatchHistogramDomain) {
base::HistogramTester histogram_tester;
ResourceRequest request;
request.url = GURL("http://foo");
PrivateNetworkAccessChecker checker(request, kNullClientSecurityState,
mojom::kURLLoadOptionNone);
checker.Check(DirectTransport(PrivateEndpoint()));
histogram_tester.ExpectTotalCount(
"Security.PrivateNetworkAccess.PrivateIpResolveMatch", 0);
}
TEST(PrivateNetworkAccessCheckerTest,
DoesNotRecordPrivateIpResolveMatchHistogramProxy) {
base::HistogramTester histogram_tester;
ResourceRequest request;
request.url = PrivateEndpointUrl();
PrivateNetworkAccessChecker checker(request, kNullClientSecurityState,
mojom::kURLLoadOptionNone);
checker.Check(ProxiedTransport(LoopbackEndpoint()));
histogram_tester.ExpectTotalCount(
"Security.PrivateNetworkAccess.PrivateIpResolveMatch", 0);
}
TEST(PrivateNetworkAccessCheckerTest,
RecordsPrivateIpResolveMatchHistogramFalse) {
base::HistogramTester histogram_tester;
ResourceRequest request;
request.url = GURL("http://192.168.1.1");
PrivateNetworkAccessChecker checker(request, kNullClientSecurityState,
mojom::kURLLoadOptionNone);
checker.Check(DirectTransport(PrivateEndpoint()));
histogram_tester.ExpectUniqueSample(
"Security.PrivateNetworkAccess.PrivateIpResolveMatch", false, 1);
}
TEST(PrivateNetworkAccessCheckerTest,
RecordsPrivateIpResolveMatchHistogramTrue) {
base::HistogramTester histogram_tester;
ResourceRequest request;
request.url = PrivateEndpointUrl();
PrivateNetworkAccessChecker checker(request, kNullClientSecurityState,
mojom::kURLLoadOptionNone);
checker.Check(DirectTransport(PrivateEndpoint()));
histogram_tester.ExpectUniqueSample(
"Security.PrivateNetworkAccess.PrivateIpResolveMatch", true, 1);
}
TEST(PrivateNetworkAccessCheckerTest,
RecordsPrivateIpResolveMatchHistogramTrueIpv6) {
base::HistogramTester histogram_tester;
ResourceRequest request;
request.url = GURL("http://[fc00::]");
PrivateNetworkAccessChecker checker(request, kNullClientSecurityState,
mojom::kURLLoadOptionNone);
checker.Check(DirectTransport(net::IPEndPoint(
net::IPAddress(0xfc, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0), 80)));
histogram_tester.ExpectUniqueSample(
"Security.PrivateNetworkAccess.PrivateIpResolveMatch", true, 1);
}
TEST(PrivateNetworkAccessCheckerTest,
RecordsPrivateIpResolveMatchHistogramAfterResetForRetry) {
base::HistogramTester histogram_tester;
ResourceRequest request;
request.url = GURL("http://192.168.1.1");
PrivateNetworkAccessChecker checker(request, kNullClientSecurityState,
mojom::kURLLoadOptionNone);
checker.ResetForRetry();
checker.Check(DirectTransport(PrivateEndpoint()));
histogram_tester.ExpectUniqueSample(
"Security.PrivateNetworkAccess.PrivateIpResolveMatch", false, 1);
}
TEST(PrivateNetworkAccessCheckerTest,
RecordsPrivateIpResolveMatchHistogramAfterResetForRedirect) {
base::HistogramTester histogram_tester;
ResourceRequest request;
request.url = GURL("http://192.168.1.1");
PrivateNetworkAccessChecker checker(request, kNullClientSecurityState,
mojom::kURLLoadOptionNone);
checker.ResetForRedirect(PrivateEndpointUrl());
checker.Check(DirectTransport(PrivateEndpoint()));
histogram_tester.ExpectUniqueSample(
"Security.PrivateNetworkAccess.PrivateIpResolveMatch", true, 1);
}
} // namespace
} // namespace network