blob: 6bc17243d99616ca9740df6534e6e3dc88eb9646 [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 <memory>
#include <set>
#include <string>
#include <string_view>
#include <vector>
#include "base/command_line.h"
#include "base/memory/raw_ptr.h"
#include "base/strings/strcat.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/synchronization/lock.h"
#include "base/test/scoped_feature_list.h"
#include "base/thread_annotations.h"
#include "build/build_config.h"
#include "content/browser/renderer_host/frame_tree_node.h"
#include "content/browser/renderer_host/render_frame_host_impl.h"
#include "content/public/common/content_client.h"
#include "content/public/common/content_features.h"
#include "content/public/common/content_switches.h"
#include "content/public/common/url_constants.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/content_browser_test.h"
#include "content/public/test/content_browser_test_content_browser_client.h"
#include "content/public/test/content_browser_test_utils.h"
#include "content/public/test/resource_load_observer.h"
#include "content/shell/browser/shell.h"
#include "content/test/content_browser_test_utils_internal.h"
#include "net/dns/mock_host_resolver.h"
#include "net/http/http_byte_range.h"
#include "net/http/http_util.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "net/test/embedded_test_server/embedded_test_server_connection_listener.h"
#include "net/test/embedded_test_server/http_request.h"
#include "services/network/public/cpp/features.h"
#include "services/network/public/cpp/ip_address_space_overrides_test_utils.h"
#include "services/network/public/cpp/network_switches.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "url/gurl.h"
#include "url/origin.h"
namespace content {
namespace {
using ::net::test_server::METHOD_GET;
using ::net::test_server::METHOD_OPTIONS;
using ::testing::ElementsAre;
using ::testing::IsEmpty;
// These domains are mapped to the IP addresses above using the
// `--host-resolver-rules` command-line switch. The exact values come from the
// embedded HTTPS server, which has certificates for these domains
constexpr char kLoopbackHost[] = "a.test";
constexpr char kOtherLoopbackHost[] = "d.test";
// not localhost, but a host with IP address space = kLocal
constexpr char kLocalHost[] = "b.test";
constexpr char kPublicHost[] = "c.test";
// Path to a default response served by all servers in this test.
constexpr char kDefaultPath[] = "/defaultresponse";
// Path to a response with the `treat-as-public-address` CSP directive.
constexpr char kTreatAsPublicAddressPath[] =
"/set-header?Content-Security-Policy: treat-as-public-address";
// Path to a response with a wide-open CORS header. This can be fetched
// cross-origin without triggering CORS violations.
constexpr char kCorsPath[] = "/set-header?Access-Control-Allow-Origin: *";
// Path to a response that passes Private Network Access checks.
constexpr char kPnaPath[] =
"/set-header"
"?Access-Control-Allow-Origin: *"
"&Access-Control-Allow-Private-Network: true";
// Path to a cacheable response.
constexpr char kCacheablePath[] = "/cachetime";
// Path to a cacheable variant of `kCorsPath`.
constexpr char kCacheableCorsPath[] =
"/set-header"
"?Cache-Control: max-age%3D60"
"&Access-Control-Allow-Origin: *";
// Path to a cacheable variant of `kPnaPath`.
constexpr char kCacheablePnaPath[] =
"/set-header"
"?Cache-Control: max-age%3D60"
"&Access-Control-Allow-Origin: *"
"&Access-Control-Allow-Private-Network: true";
// Returns a path to a response that passes Private Network Access checks.
//
// This can be used to construct the `src` URL for an iframe.
std::string MakePnaPathForIframe(const url::Origin& initiator_origin) {
return base::StrCat({
"/set-header"
// Apparently a wildcard `*` is not sufficient in this case, so we need
// to explicitly allow the initiator origin instead.
"?Access-Control-Allow-Origin: ",
initiator_origin.Serialize(),
"&Access-Control-Allow-Private-Network: true"
// It seems navigation requests carry credentials...
"&Access-Control-Allow-Credentials: true"
// And the following couple headers.
"&Access-Control-Allow-Headers: upgrade-insecure-requests,accept",
});
}
// Returns a snippet of Javascript that fetch()es the given URL.
//
// The snippet evaluates to a boolean promise which resolves to true iff the
// fetch was successful. The promise never rejects, as doing so makes it hard
// to assert failure.
std::string FetchSubresourceScript(const GURL& url) {
return JsReplace(
R"(fetch($1).then(
response => response.ok,
error => {
console.log('Error fetching ' + $1, error);
return false;
});
)",
url);
}
// A |ContentBrowserClient| implementation that allows modifying the return
// value of |ShouldAllowInsecurePrivateNetworkRequests()| at will.
class PolicyTestContentBrowserClient
: public ContentBrowserTestContentBrowserClient {
public:
PolicyTestContentBrowserClient() = default;
PolicyTestContentBrowserClient(const PolicyTestContentBrowserClient&) =
delete;
PolicyTestContentBrowserClient& operator=(
const PolicyTestContentBrowserClient&) = delete;
~PolicyTestContentBrowserClient() override = default;
// Adds an origin to the allowlist.
void SetAllowInsecurePrivateNetworkRequestsFrom(const url::Origin& origin) {
allowlisted_origins_.insert(origin);
}
void SetBlockInsteadOfWarn() { block_instead_of_warn_ = true; }
ContentBrowserClient::PrivateNetworkRequestPolicyOverride
ShouldOverridePrivateNetworkRequestPolicy(
content::BrowserContext* browser_context,
const url::Origin& origin) override {
if (block_instead_of_warn_) {
return ContentBrowserClient::PrivateNetworkRequestPolicyOverride::
kBlockInsteadOfWarn;
}
return allowlisted_origins_.find(origin) != allowlisted_origins_.end()
? ContentBrowserClient::PrivateNetworkRequestPolicyOverride::
kForceAllow
: ContentBrowserClient::PrivateNetworkRequestPolicyOverride::
kDefault;
}
private:
bool block_instead_of_warn_ = false;
std::set<url::Origin> allowlisted_origins_;
};
// An embedded test server connection listener that simply counts connections.
// Thread-safe.
class ConnectionCounter
: public net::test_server::EmbeddedTestServerConnectionListener {
public:
ConnectionCounter() = default;
// Instances of this class are neither copyable nor movable.
ConnectionCounter(const ConnectionCounter&) = delete;
ConnectionCounter& operator=(const ConnectionCounter&) = delete;
ConnectionCounter(ConnectionCounter&&) = delete;
ConnectionCounter& operator=(ConnectionCounter&&) = delete;
// Returns the number of sockets accepted by the servers we are listening to.
int count() const {
base::AutoLock guard(lock_);
return count_;
}
private:
// EmbeddedTestServerConnectionListener implementation.
std::unique_ptr<net::StreamSocket> AcceptedSocket(
std::unique_ptr<net::StreamSocket> socket) override {
{
base::AutoLock guard(lock_);
count_++;
}
return socket;
}
void ReadFromSocket(const net::StreamSocket& socket, int rv) override {}
// `count_` is incremented on the embedded test server thread and read on the
// test thread, so we synchronize accesses with a lock.
mutable base::Lock lock_;
int count_ GUARDED_BY(lock_) = 0;
};
class RequestObserver {
public:
RequestObserver() = default;
// The returned callback must not outlive this instance.
net::test_server::EmbeddedTestServer::MonitorRequestCallback BindCallback() {
return base::BindRepeating(&RequestObserver::Observe,
base::Unretained(this));
}
// The origin of the URL is not checked for equality.
std::vector<net::test_server::HttpMethod> RequestMethodsForUrl(
const GURL& url) const {
std::string path = url.PathForRequest();
std::vector<net::test_server::HttpMethod> methods;
{
base::AutoLock guard(lock_);
for (const auto& request : requests_) {
if (request.GetURL().PathForRequest() == path) {
methods.push_back(request.method);
}
}
}
return methods;
}
private:
void Observe(const net::test_server::HttpRequest& request) {
base::AutoLock guard(lock_);
requests_.push_back(request);
}
// `requests_` is mutated on the embedded test server thread and read on the
// test thread, so we synchronize accesses with a lock.
mutable base::Lock lock_;
std::vector<net::test_server::HttpRequest> requests_ GUARDED_BY(lock_);
};
// Removes `prefix` from the start of `str`, if present.
// Returns nullopt otherwise.
std::optional<std::string_view> StripPrefix(std::string_view str,
std::string_view prefix) {
if (!base::StartsWith(str, prefix)) {
return std::nullopt;
}
return str.substr(prefix.size());
}
// Returns a pointer to the value of the `header` header in `request`, if any.
// Returns nullptr otherwise.
const std::string* FindRequestHeader(
const net::test_server::HttpRequest& request,
std::string_view header) {
const auto it = request.headers.find(header);
if (it == request.headers.end()) {
return nullptr;
}
return &it->second;
}
// Returns the `Content-Range` header value for a given `range` of bytes out of
// the given `total_size` number of bytes.
std::string GetContentRangeHeader(const net::HttpByteRange& range,
size_t total_size) {
std::string first = base::NumberToString(range.first_byte_position());
std::string last = base::NumberToString(range.last_byte_position());
std::string total = base::NumberToString(total_size);
return base::StrCat({"bytes ", first, "-", last, "/", total});
}
// An `EmbeddedTestServer` request handler function.
//
// Knows how to respond to CORS and PNA preflight requests, as well as regular
// and range requests.
//
// Route: /echorange?<body>
std::unique_ptr<net::test_server::HttpResponse> HandleRangeRequest(
const net::test_server::HttpRequest& request) {
std::optional<std::string_view> query =
StripPrefix(request.relative_url, "/echorange?");
if (!query) {
return nullptr;
}
auto response = std::make_unique<net::test_server::BasicHttpResponse>();
constexpr std::pair<std::string_view, std::string_view> kCopiedHeaders[] = {
{"Origin", "Access-Control-Allow-Origin"},
{"Access-Control-Request-Private-Network",
"Access-Control-Allow-Private-Network"},
{"Access-Control-Request-Headers", "Access-Control-Allow-Headers"},
};
for (const auto& pair : kCopiedHeaders) {
const std::string* value = FindRequestHeader(request, pair.first);
if (value) {
response->AddCustomHeader(pair.second, *value);
}
}
// No body for a preflight response.
if (request.method == net::test_server::METHOD_OPTIONS) {
response->AddCustomHeader("Access-Control-Max-Age", "60");
return response;
}
// Cache-Control: max-age=X does not work for range request caching. Use a
// strong ETag instead, along with a last modified date. Both are required.
response->AddCustomHeader("ETag", "foo");
response->AddCustomHeader("Last-Modified", "Fri, 1 Apr 2022 12:34:56 UTC");
const std::string* range_header = FindRequestHeader(request, "Range");
if (!range_header) {
// Not a range request. Respond with 200 and the whole query as the body.
response->set_content(*query);
return response;
}
std::vector<net::HttpByteRange> ranges;
if (!net::HttpUtil::ParseRangeHeader(*range_header, &ranges) ||
ranges.size() != 1) {
response->set_code(net::HTTP_BAD_REQUEST);
return response;
}
net::HttpByteRange& range = ranges[0];
if (!range.ComputeBounds(query->size())) {
response->set_code(net::HTTP_REQUESTED_RANGE_NOT_SATISFIABLE);
return response;
}
response->set_code(net::HTTP_PARTIAL_CONTENT);
response->AddCustomHeader("Content-Range",
GetContentRangeHeader(range, query->size()));
response->set_content(query->substr(range.first_byte_position(),
range.last_byte_position() + 1));
return response;
}
// A `net::EmbeddedTestServer` that pretends to be in a given IP address space.
//
// Set up of the command line in order for this server to be considered a part
// of `ip_address_space` must be done outside of server creation.
class FakeAddressSpaceServer {
public:
FakeAddressSpaceServer(net::EmbeddedTestServer::Type type,
net::test_server::HttpConnection::Protocol protocol,
network::mojom::IPAddressSpace ip_address_space,
const base::FilePath& test_data_path)
: server_(type, protocol), ip_address_space_(ip_address_space) {
// Use a certificate valid for multiple domains, which we can use to
// distinguish `loopback`, `local` and `public` address spaces.
server_.SetSSLConfig(net::EmbeddedTestServer::CERT_TEST_NAMES);
server_.SetConnectionListener(&connection_counter_);
server_.RegisterRequestMonitor(request_observer_.BindCallback());
server_.RegisterRequestHandler(base::BindRepeating(&HandleRangeRequest));
server_.AddDefaultHandlers(test_data_path);
CHECK(server_.Start());
}
std::string GenerateCommandLineSwitchOverride() const {
return network::GenerateIpAddressSpaceOverride(server_, ip_address_space_);
}
// Returns the underlying test server.
net::EmbeddedTestServer& Get() { return server_; }
// Returns the total number of sockets accepted by this server.
int ConnectionCount() const { return connection_counter_.count(); }
const RequestObserver& request_observer() const { return request_observer_; }
private:
ConnectionCounter connection_counter_;
RequestObserver request_observer_;
net::EmbeddedTestServer server_;
const network::mojom::IPAddressSpace ip_address_space_;
};
} // namespace
// This being an integration/browser test, we concentrate on a few behaviors
// relevant to Private Network Access:
//
// - testing the values of important properties on top-level documents:
// - address space
// - secure context bit
// - private network request policy
// - testing the inheritance semantics of these properties
// - testing the correct handling of the CSP: treat-as-public-address directive
// - testing that subresource requests are subject to PNA checks
// - and a few other odds and ends
//
// We use the `--ip-address-space-overrides` command-line switch to test against
// `local` and `public` address spaces, even though all responses are actually
// served from localhost. Combined with host resolver rules, this lets us define
// three different domains that map to the different address spaces:
//
// - `a.test` is `loopback`
// - `b.test` is `local`
// - `c.test` is `public`
//
// We also have unit tests that test all possible combinations of source and
// destination IP address spaces in services/network/url_loader_unittest.cc.
class PrivateNetworkAccessBrowserTestBase : public ContentBrowserTest {
public:
RenderFrameHostImpl* root_frame_host() {
return static_cast<RenderFrameHostImpl*>(
shell()->web_contents()->GetPrimaryMainFrame());
}
protected:
// Allows subclasses to construct instances with different features enabled.
explicit PrivateNetworkAccessBrowserTestBase(
const std::vector<base::test::FeatureRef>& enabled_features,
const std::vector<base::test::FeatureRef>& disabled_features)
: insecure_loopback_server_(
net::EmbeddedTestServer::TYPE_HTTP,
net::test_server::HttpConnection::Protocol::kHttp1,
network::mojom::IPAddressSpace::kLoopback,
GetTestDataFilePath()),
insecure_local_server_(
net::EmbeddedTestServer::TYPE_HTTP,
net::test_server::HttpConnection::Protocol::kHttp1,
network::mojom::IPAddressSpace::kLocal,
GetTestDataFilePath()),
insecure_public_server_(
net::EmbeddedTestServer::TYPE_HTTP,
net::test_server::HttpConnection::Protocol::kHttp1,
network::mojom::IPAddressSpace::kPublic,
GetTestDataFilePath()),
secure_loopback_server_(
net::EmbeddedTestServer::TYPE_HTTPS,
net::test_server::HttpConnection::Protocol::kHttp1,
network::mojom::IPAddressSpace::kLoopback,
GetTestDataFilePath()),
secure_local_server_(net::EmbeddedTestServer::TYPE_HTTPS,
net::test_server::HttpConnection::Protocol::kHttp1,
network::mojom::IPAddressSpace::kLocal,
GetTestDataFilePath()),
secure_public_server_(
net::EmbeddedTestServer::TYPE_HTTPS,
net::test_server::HttpConnection::Protocol::kHttp1,
network::mojom::IPAddressSpace::kPublic,
GetTestDataFilePath()) {
feature_list_.InitWithFeatures(enabled_features, disabled_features);
}
void SetUpOnMainThread() override {
ContentBrowserTest::SetUpOnMainThread();
// Rules must be added on the main thread, otherwise `AddRule()` segfaults.
host_resolver()->AddRule(kLoopbackHost, "127.0.0.1");
host_resolver()->AddRule(kOtherLoopbackHost, "127.0.0.1");
host_resolver()->AddRule(kLocalHost, "127.0.0.1");
host_resolver()->AddRule(kPublicHost, "127.0.0.1");
}
void SetUpCommandLine(base::CommandLine* command_line) override {
ContentBrowserTest::SetUpCommandLine(command_line);
// Add correct ip address space overrides.
network::AddIpAddressSpaceOverridesToCommandLine(
{insecure_loopback_server_.GenerateCommandLineSwitchOverride(),
insecure_local_server_.GenerateCommandLineSwitchOverride(),
insecure_public_server_.GenerateCommandLineSwitchOverride(),
secure_loopback_server_.GenerateCommandLineSwitchOverride(),
secure_local_server_.GenerateCommandLineSwitchOverride(),
secure_public_server_.GenerateCommandLineSwitchOverride()},
*command_line);
}
const FakeAddressSpaceServer& InsecureLoopbackServer() const {
return insecure_loopback_server_;
}
const FakeAddressSpaceServer& InsecureLocalServer() const {
return insecure_local_server_;
}
const FakeAddressSpaceServer& InsecurePublicServer() const {
return insecure_public_server_;
}
const FakeAddressSpaceServer& SecureLoopbackServer() const {
return secure_loopback_server_;
}
const FakeAddressSpaceServer& SecureLocalServer() const {
return secure_local_server_;
}
const FakeAddressSpaceServer& SecurePublicServer() const {
return secure_public_server_;
}
GURL InsecureLoopbackURL(const std::string& path) {
return insecure_loopback_server_.Get().GetURL(kLoopbackHost, path);
}
GURL InsecureLocalURL(const std::string& path) {
return insecure_local_server_.Get().GetURL(kLocalHost, path);
}
GURL InsecurePublicURL(const std::string& path) {
return insecure_public_server_.Get().GetURL(kPublicHost, path);
}
GURL SecureLoopbackURL(const std::string& path) {
return secure_loopback_server_.Get().GetURL(kLoopbackHost, path);
}
GURL OtherSecureLoopbackURL(const std::string& path) {
return secure_loopback_server_.Get().GetURL(kOtherLoopbackHost, path);
}
GURL SecureLocalURL(const std::string& path) {
return secure_local_server_.Get().GetURL(kLocalHost, path);
}
GURL SecurePublicURL(const std::string& path) {
return secure_public_server_.Get().GetURL(kPublicHost, path);
}
GURL NullIPURL(const std::string& path) {
return insecure_public_server_.Get().GetURL("0.0.0.0", path);
}
private:
base::test::ScopedFeatureList feature_list_;
FakeAddressSpaceServer insecure_loopback_server_;
FakeAddressSpaceServer insecure_local_server_;
FakeAddressSpaceServer insecure_public_server_;
FakeAddressSpaceServer secure_loopback_server_;
FakeAddressSpaceServer secure_local_server_;
FakeAddressSpaceServer secure_public_server_;
};
// Test with insecure private network subresource requests from the `public`
// address space blocked and preflights otherwise enabled but not enforced.
class PrivateNetworkAccessBrowserTest
: public PrivateNetworkAccessBrowserTestBase {
public:
PrivateNetworkAccessBrowserTest()
: PrivateNetworkAccessBrowserTestBase(
{
features::kBlockInsecurePrivateNetworkRequests,
features::kPrivateNetworkAccessSendPreflights,
},
{
network::features::kLocalNetworkAccessChecks,
}) {}
};
// This definition is required as raw initializer lists aren't allowed in the
// ternary ? operator.
using FeatureVec = std::vector<base::test::FeatureRef>;
// This is the same as PrivateNetworkAccessBrowserTest, but runs each test
// twice, once with kOriginKeyedProcessesByDefault explicitly enabled, and once
// with it explicitly disabled. The tests implemented using this class involve
// sandboxed data: frames whose SiteInfo creation may vary depending on whether
// the feature is enabled or not.
class PrivateNetworkAccessSandboxedDataBrowserTest
: public PrivateNetworkAccessBrowserTestBase,
public testing::WithParamInterface<bool> {
public:
PrivateNetworkAccessSandboxedDataBrowserTest()
: PrivateNetworkAccessBrowserTestBase(
GetParam() ? FeatureVec({
features::kBlockInsecurePrivateNetworkRequests,
features::kPrivateNetworkAccessSendPreflights,
features::kOriginKeyedProcessesByDefault,
})
: FeatureVec({
features::kBlockInsecurePrivateNetworkRequests,
features::kPrivateNetworkAccessSendPreflights,
}),
GetParam() ? FeatureVec({
network::features::kLocalNetworkAccessChecks,
})
: FeatureVec({
features::kOriginKeyedProcessesByDefault,
network::features::kLocalNetworkAccessChecks,
})) {}
};
class PrivateNetworkAccessBrowserTestWithBlockInsteadOfWarnOption
: public PrivateNetworkAccessBrowserTest,
public testing::WithParamInterface<bool> {};
class PrivateNetworkAccessBrowserTestDisableWebSecurity
: public PrivateNetworkAccessBrowserTest {
public:
PrivateNetworkAccessBrowserTestDisableWebSecurity() = default;
protected:
void SetUpCommandLine(base::CommandLine* command_line) override {
PrivateNetworkAccessBrowserTest::SetUpCommandLine(command_line);
command_line->AppendSwitch(switches::kDisableWebSecurity);
}
};
// Test with insecure private network subresource requests blocked, including
// from the `private` address space.
class PrivateNetworkAccessBrowserTestBlockFromPrivate
: public PrivateNetworkAccessBrowserTestBase {
public:
PrivateNetworkAccessBrowserTestBlockFromPrivate()
: PrivateNetworkAccessBrowserTestBase(
{
features::kBlockInsecurePrivateNetworkRequests,
features::kBlockInsecurePrivateNetworkRequestsFromPrivate,
features::kPrivateNetworkAccessRespectPreflightResults,
},
{
network::features::kLocalNetworkAccessChecks,
}) {}
};
// Test with insecure private network subresource requests blocked, including
// from the `private` address space.
class PrivateNetworkAccessBrowserTestBlockFromUnknown
: public PrivateNetworkAccessBrowserTestBase {
public:
PrivateNetworkAccessBrowserTestBlockFromUnknown()
: PrivateNetworkAccessBrowserTestBase(
{
features::kBlockInsecurePrivateNetworkRequests,
features::kBlockInsecurePrivateNetworkRequestsFromUnknown,
features::kPrivateNetworkAccessRespectPreflightResults,
},
{
network::features::kLocalNetworkAccessChecks,
}) {}
};
// Test with PNA checks for iframes enabled.
class PrivateNetworkAccessBrowserTestForNavigations
: public PrivateNetworkAccessBrowserTestBase {
public:
PrivateNetworkAccessBrowserTestForNavigations()
: PrivateNetworkAccessBrowserTestBase(
{
features::kBlockInsecurePrivateNetworkRequests,
features::kBlockInsecurePrivateNetworkRequestsFromPrivate,
features::kPrivateNetworkAccessForNavigations,
features::kPrivateNetworkAccessRespectPreflightResults,
},
{
network::features::kLocalNetworkAccessChecks,
}) {}
};
// Test with PNA checks for navigations enabled in warning-only mode.
class PrivateNetworkAccessBrowserTestForNavigationsWarningOnly
: public PrivateNetworkAccessBrowserTestForNavigations {
private:
base::test::ScopedFeatureList feature_list_{
features::kPrivateNetworkAccessForNavigationsWarningOnly};
};
// Test with the feature to send preflights (unenforced) disabled, and insecure
// private network subresource requests blocked.
class PrivateNetworkAccessBrowserTestNoPreflights
: public PrivateNetworkAccessBrowserTestBase {
public:
PrivateNetworkAccessBrowserTestNoPreflights()
: PrivateNetworkAccessBrowserTestBase(
{
features::kBlockInsecurePrivateNetworkRequests,
},
{
features::kPrivateNetworkAccessSendPreflights,
network::features::kLocalNetworkAccessChecks,
}) {}
};
// Test with the feature to send preflights (enforced) enabled, and insecure
// private network subresource requests blocked.
class PrivateNetworkAccessBrowserTestRespectPreflightResults
: public PrivateNetworkAccessBrowserTestBase {
public:
PrivateNetworkAccessBrowserTestRespectPreflightResults()
: PrivateNetworkAccessBrowserTestBase(
{
features::kBlockInsecurePrivateNetworkRequests,
features::kPrivateNetworkAccessRespectPreflightResults,
},
{
network::features::kLocalNetworkAccessChecks,
}) {}
};
// Test with PNA checks for worker-related fetches enabled.
class PrivateNetworkAccessBrowserTestForWorkers
: public PrivateNetworkAccessBrowserTestBase {
public:
PrivateNetworkAccessBrowserTestForWorkers()
: PrivateNetworkAccessBrowserTestBase(
{
features::kBlockInsecurePrivateNetworkRequests,
features::kPrivateNetworkAccessForWorkers,
},
{
features::kPrivateNetworkAccessForWorkersWarningOnly,
network::features::kLocalNetworkAccessChecks,
}) {}
};
// Test with PNA checks for worker-related fetches enabled and preflight
// enforcement enabled.
class PrivateNetworkAccessBrowserTestRespectPreflightResultsForWorkers
: public PrivateNetworkAccessBrowserTestBase {
public:
PrivateNetworkAccessBrowserTestRespectPreflightResultsForWorkers()
: PrivateNetworkAccessBrowserTestBase(
{
features::kBlockInsecurePrivateNetworkRequests,
features::kPrivateNetworkAccessRespectPreflightResults,
features::kPrivateNetworkAccessForWorkers,
},
{
features::kPrivateNetworkAccessForWorkersWarningOnly,
network::features::kLocalNetworkAccessChecks,
}) {}
};
// Test with PNA checks for worker-related fetches enabled in warning-only mode,
// including preflights.
class
PrivateNetworkAccessBrowserTestRespectPreflightResultsForWorkersWarningOnly
: public PrivateNetworkAccessBrowserTestBase {
public:
PrivateNetworkAccessBrowserTestRespectPreflightResultsForWorkersWarningOnly()
: PrivateNetworkAccessBrowserTestBase(
{
features::kBlockInsecurePrivateNetworkRequests,
features::kPrivateNetworkAccessRespectPreflightResults,
features::kPrivateNetworkAccessForWorkers,
features::kPrivateNetworkAccessForWorkersWarningOnly,
},
{
network::features::kLocalNetworkAccessChecks,
}) {}
};
// Test with insecure private network requests allowed.
class PrivateNetworkAccessBrowserTestNoBlocking
: public PrivateNetworkAccessBrowserTestBase {
public:
PrivateNetworkAccessBrowserTestNoBlocking()
: PrivateNetworkAccessBrowserTestBase(
{},
{
features::kBlockInsecurePrivateNetworkRequests,
features::kBlockInsecurePrivateNetworkRequestsFromPrivate,
features::kPrivateNetworkAccessForNavigations,
features::kPrivateNetworkAccessForWorkers,
features::kPrivateNetworkAccessSendPreflights,
network::features::kLocalNetworkAccessChecks,
}) {}
};
// ===========================
// CLIENT SECURITY STATE TESTS
// ===========================
//
// These tests verify the contents of `ClientSecurityState` for top-level
// documents in various different circumstances.
// This test verifies the contents of the ClientSecurityState for the initial
// empty document in a new main frame created by the browser.
//
// Note: the renderer-created main frame case is exercised by the
// OpeneeInherits* tests below.
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessBrowserTest,
ClientSecurityStateForInitialEmptyDoc) {
// Start a navigation. This forces the RenderFrameHost to initialize its
// RenderFrame. The navigation is then cancelled by a HTTP 204 code.
// We're left with a RenderFrameHost containing the default
// ClientSecurityState values.
//
// Serve the response from a secure public server, to confirm that none of
// the connection's properties are reflected in the committed document, which
// is not a secure context and belongs to the `loopback` address space.
EXPECT_TRUE(
NavigateToURLAndExpectNoCommit(shell(), SecurePublicURL("/nocontent")));
const network::mojom::ClientSecurityStatePtr security_state =
root_frame_host()->BuildClientSecurityState();
ASSERT_FALSE(security_state.is_null());
EXPECT_FALSE(security_state->is_web_secure_context);
EXPECT_EQ(network::mojom::CrossOriginEmbedderPolicyValue::kNone,
security_state->cross_origin_embedder_policy.value);
EXPECT_EQ(network::mojom::PrivateNetworkRequestPolicy::kBlock,
security_state->private_network_request_policy);
// Browser-created empty main frames are trusted to access the local network,
// if they execute code injected via DevTools, WebView APIs or extensions.
EXPECT_EQ(network::mojom::IPAddressSpace::kLoopback,
security_state->ip_address_space);
}
// This test verifies the contents of the ClientSecurityState for `about:blank`
// in a new main frame created by the browser.
//
// Note: the renderer-created main frame case is exercised by the Openee
// inheritance tests below.
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessBrowserTest,
ClientSecurityStateForAboutBlank) {
EXPECT_TRUE(NavigateToURL(shell(), GURL("about:blank")));
const network::mojom::ClientSecurityStatePtr security_state =
root_frame_host()->BuildClientSecurityState();
ASSERT_FALSE(security_state.is_null());
EXPECT_FALSE(security_state->is_web_secure_context);
EXPECT_EQ(network::mojom::IPAddressSpace::kLoopback,
security_state->ip_address_space);
}
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessBrowserTest,
ClientSecurityStateForDataURL) {
EXPECT_TRUE(NavigateToURL(shell(), GURL("data:text/html,foo")));
const network::mojom::ClientSecurityStatePtr security_state =
root_frame_host()->BuildClientSecurityState();
ASSERT_FALSE(security_state.is_null());
EXPECT_FALSE(security_state->is_web_secure_context);
EXPECT_EQ(network::mojom::IPAddressSpace::kUnknown,
security_state->ip_address_space);
}
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessBrowserTest,
ClientSecurityStateForFileURL) {
EXPECT_TRUE(NavigateToURL(shell(), GetTestUrl("", "empty.html")));
const network::mojom::ClientSecurityStatePtr security_state =
root_frame_host()->BuildClientSecurityState();
ASSERT_FALSE(security_state.is_null());
EXPECT_TRUE(security_state->is_web_secure_context);
EXPECT_EQ(network::mojom::IPAddressSpace::kLoopback,
security_state->ip_address_space);
}
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessBrowserTest,
ClientSecurityStateForInsecureLoopbackAddress) {
EXPECT_TRUE(NavigateToURL(shell(), InsecureLoopbackURL(kDefaultPath)));
const network::mojom::ClientSecurityStatePtr security_state =
root_frame_host()->BuildClientSecurityState();
ASSERT_FALSE(security_state.is_null());
EXPECT_FALSE(security_state->is_web_secure_context);
EXPECT_EQ(network::mojom::IPAddressSpace::kLoopback,
security_state->ip_address_space);
}
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessBrowserTest,
ClientSecurityStateForInsecureLocalAddress) {
EXPECT_TRUE(NavigateToURL(shell(), InsecureLocalURL(kDefaultPath)));
const network::mojom::ClientSecurityStatePtr security_state =
root_frame_host()->BuildClientSecurityState();
ASSERT_FALSE(security_state.is_null());
EXPECT_FALSE(security_state->is_web_secure_context);
EXPECT_EQ(network::mojom::IPAddressSpace::kLocal,
security_state->ip_address_space);
}
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessBrowserTest,
ClientSecurityStateForInsecurePublicAddress) {
EXPECT_TRUE(NavigateToURL(shell(), InsecurePublicURL(kDefaultPath)));
const network::mojom::ClientSecurityStatePtr security_state =
root_frame_host()->BuildClientSecurityState();
ASSERT_FALSE(security_state.is_null());
EXPECT_FALSE(security_state->is_web_secure_context);
EXPECT_EQ(network::mojom::IPAddressSpace::kPublic,
security_state->ip_address_space);
}
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessBrowserTest,
ClientSecurityStateForSecureLoopbackAddress) {
EXPECT_TRUE(NavigateToURL(shell(), SecureLoopbackURL(kDefaultPath)));
const network::mojom::ClientSecurityStatePtr security_state =
root_frame_host()->BuildClientSecurityState();
ASSERT_FALSE(security_state.is_null());
EXPECT_TRUE(security_state->is_web_secure_context);
EXPECT_EQ(network::mojom::IPAddressSpace::kLoopback,
security_state->ip_address_space);
}
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessBrowserTest,
ClientSecurityStateForSecureLocalAddress) {
EXPECT_TRUE(NavigateToURL(shell(), SecureLocalURL(kDefaultPath)));
const network::mojom::ClientSecurityStatePtr security_state =
root_frame_host()->BuildClientSecurityState();
ASSERT_FALSE(security_state.is_null());
EXPECT_TRUE(security_state->is_web_secure_context);
EXPECT_EQ(network::mojom::IPAddressSpace::kLocal,
security_state->ip_address_space);
}
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessBrowserTest,
ClientSecurityStateForSecurePublicAddress) {
EXPECT_TRUE(NavigateToURL(shell(), SecurePublicURL(kDefaultPath)));
const network::mojom::ClientSecurityStatePtr security_state =
root_frame_host()->BuildClientSecurityState();
ASSERT_FALSE(security_state.is_null());
EXPECT_TRUE(security_state->is_web_secure_context);
EXPECT_EQ(network::mojom::IPAddressSpace::kPublic,
security_state->ip_address_space);
}
// Tests that a top-level navigation to 0.0.0.0 is in the kLoopback address
// space.
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessBrowserTest,
ClientSecurityStateForNullIP) {
if constexpr (BUILDFLAG(IS_WIN)) {
GTEST_SKIP() << "0.0.0.0 behavior varies across platforms and is "
"unreachable on Windows.";
}
EXPECT_TRUE(NavigateToURL(shell(), NullIPURL(kDefaultPath)));
const network::mojom::ClientSecurityStatePtr security_state =
root_frame_host()->BuildClientSecurityState();
ASSERT_FALSE(security_state.is_null());
EXPECT_FALSE(security_state->is_web_secure_context);
EXPECT_EQ(network::mojom::IPAddressSpace::kLoopback,
security_state->ip_address_space);
}
class PrivateNetworkAccessBrowserTestNullIPKillswitch
: public PrivateNetworkAccessBrowserTestBase {
public:
PrivateNetworkAccessBrowserTestNullIPKillswitch()
: PrivateNetworkAccessBrowserTestBase(
{
network::features::kTreatNullIPAsPublicAddressSpace,
},
{
network::features::kLocalNetworkAccessChecks,
}) {}
};
// Tests that a top-level navigation to 0.0.0.0 is in the kPublic address space
// when a killswitch is enabled to specifically treat it as public.
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessBrowserTestNullIPKillswitch,
ClientSecurityStateForNullIPKillswitch) {
if constexpr (BUILDFLAG(IS_WIN)) {
GTEST_SKIP() << "0.0.0.0 behavior varies across platforms and is "
"unreachable on Windows.";
}
EXPECT_TRUE(NavigateToURL(shell(), NullIPURL(kDefaultPath)));
const network::mojom::ClientSecurityStatePtr security_state =
root_frame_host()->BuildClientSecurityState();
ASSERT_FALSE(security_state.is_null());
EXPECT_FALSE(security_state->is_web_secure_context);
EXPECT_EQ(network::mojom::IPAddressSpace::kPublic,
security_state->ip_address_space);
}
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessBrowserTest,
ClientSecurityStateForTreatAsPublicAddress) {
EXPECT_TRUE(
NavigateToURL(shell(), SecureLoopbackURL(kTreatAsPublicAddressPath)));
const network::mojom::ClientSecurityStatePtr security_state =
root_frame_host()->BuildClientSecurityState();
ASSERT_FALSE(security_state.is_null());
EXPECT_TRUE(security_state->is_web_secure_context);
EXPECT_EQ(network::mojom::IPAddressSpace::kPublic,
security_state->ip_address_space);
}
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessBrowserTest,
ClientSecurityStateForTreatAsPublicAddressReportOnly) {
EXPECT_TRUE(NavigateToURL(
shell(),
SecureLoopbackURL("/set-header?Content-Security-Policy-Report-Only: "
"treat-as-public-address")));
const network::mojom::ClientSecurityStatePtr security_state =
root_frame_host()->BuildClientSecurityState();
ASSERT_FALSE(security_state.is_null());
EXPECT_TRUE(security_state->is_web_secure_context);
EXPECT_EQ(network::mojom::IPAddressSpace::kLoopback,
security_state->ip_address_space);
}
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessBrowserTest,
ClientSecurityStateForCachedSecureLoopbackDocument) {
// Navigate to the cacheable document in order to cache it, then navigate
// away.
const GURL url = SecureLoopbackURL(kCacheablePath);
EXPECT_TRUE(NavigateToURL(shell(), url));
EXPECT_TRUE(NavigateToURL(shell(), SecureLoopbackURL(kDefaultPath)));
// Navigate to the cached document.
ResourceLoadObserver observer(shell());
EXPECT_TRUE(NavigateToURL(shell(), url));
observer.WaitForResourceCompletion(url);
blink::mojom::ResourceLoadInfoPtr* info = observer.GetResource(url);
ASSERT_TRUE(info);
ASSERT_TRUE(*info);
EXPECT_TRUE((*info)->was_cached);
const network::mojom::ClientSecurityStatePtr security_state =
root_frame_host()->BuildClientSecurityState();
ASSERT_FALSE(security_state.is_null());
EXPECT_TRUE(security_state->is_web_secure_context);
EXPECT_EQ(network::mojom::IPAddressSpace::kLoopback,
security_state->ip_address_space);
}
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessBrowserTest,
ClientSecurityStateForCachedInsecurePublicDocument) {
// Navigate to the cacheable document in order to cache it, then navigate
// away.
const GURL url = InsecurePublicURL(kCacheablePath);
EXPECT_TRUE(NavigateToURL(shell(), url));
EXPECT_TRUE(NavigateToURL(shell(), SecureLoopbackURL(kDefaultPath)));
// Navigate to the cached document.
ResourceLoadObserver observer(shell());
EXPECT_TRUE(NavigateToURL(shell(), url));
observer.WaitForResourceCompletion(url);
blink::mojom::ResourceLoadInfoPtr* info = observer.GetResource(url);
ASSERT_TRUE(info);
ASSERT_TRUE(*info);
EXPECT_TRUE((*info)->was_cached);
const network::mojom::ClientSecurityStatePtr security_state =
root_frame_host()->BuildClientSecurityState();
ASSERT_FALSE(security_state.is_null());
EXPECT_FALSE(security_state->is_web_secure_context);
EXPECT_EQ(network::mojom::IPAddressSpace::kPublic,
security_state->ip_address_space);
}
// This test verifies that the chrome:// scheme is considered loopback for the
// purpose of Private Network Access.
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessBrowserTest,
ClientSecurityStateForSpecialSchemeChromeURL) {
// Not all chrome:// hosts are available in content/ but ukm is one of them.
EXPECT_TRUE(NavigateToURL(shell(), GURL("chrome://ukm")));
EXPECT_TRUE(
root_frame_host()->GetLastCommittedURL().SchemeIs(kChromeUIScheme));
const network::mojom::ClientSecurityStatePtr security_state =
root_frame_host()->BuildClientSecurityState();
ASSERT_FALSE(security_state.is_null());
EXPECT_TRUE(security_state->is_web_secure_context);
EXPECT_EQ(network::mojom::IPAddressSpace::kLoopback,
security_state->ip_address_space);
}
// The view-source:// scheme should only ever appear in the display URL. It
// shouldn't affect the IPAddressSpace computation. This test verifies that we
// end up with the response IPAddressSpace.
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessBrowserTest,
ClientSecurityStateForSpecialSchemeViewSourcePublic) {
const GURL url = SecurePublicURL(kDefaultPath);
EXPECT_TRUE(NavigateToURL(shell(), GURL("view-source:" + url.spec())));
EXPECT_FALSE(
root_frame_host()->GetLastCommittedURL().SchemeIs(kViewSourceScheme));
const network::mojom::ClientSecurityStatePtr security_state =
root_frame_host()->BuildClientSecurityState();
ASSERT_FALSE(security_state.is_null());
EXPECT_TRUE(security_state->is_web_secure_context);
EXPECT_EQ(network::mojom::IPAddressSpace::kPublic,
security_state->ip_address_space);
}
// Variation of above test with a local address.
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessBrowserTest,
ClientSecurityStateForSpecialSchemeViewSourceLocal) {
const GURL url = SecureLocalURL(kDefaultPath);
EXPECT_TRUE(NavigateToURL(shell(), GURL("view-source:" + url.spec())));
EXPECT_FALSE(
root_frame_host()->GetLastCommittedURL().SchemeIs(kViewSourceScheme));
const network::mojom::ClientSecurityStatePtr security_state =
root_frame_host()->BuildClientSecurityState();
ASSERT_FALSE(security_state.is_null());
EXPECT_TRUE(security_state->is_web_secure_context);
EXPECT_EQ(network::mojom::IPAddressSpace::kLocal,
security_state->ip_address_space);
}
// The chrome-error:// scheme should only ever appear in origins. It shouldn't
// affect the IPAddressSpace computation. This test verifies that we end up with
// the response IPAddressSpace. Error pages should not be considered secure
// contexts however.
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessBrowserTest,
ClientSecurityStateForSpecialSchemeChromeErrorPublic) {
EXPECT_FALSE(NavigateToURL(shell(), SecurePublicURL("/empty404.html")));
EXPECT_FALSE(
root_frame_host()->GetLastCommittedURL().SchemeIs(kChromeErrorScheme));
const network::mojom::ClientSecurityStatePtr security_state =
root_frame_host()->BuildClientSecurityState();
ASSERT_FALSE(security_state.is_null());
EXPECT_FALSE(security_state->is_web_secure_context);
EXPECT_EQ(network::mojom::IPAddressSpace::kPublic,
security_state->ip_address_space);
}
// Variation of above test with a local address.
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessBrowserTest,
ClientSecurityStateForSpecialSchemeChromeErrorLocal) {
EXPECT_FALSE(NavigateToURL(shell(), SecureLocalURL("/empty404.html")));
EXPECT_FALSE(
root_frame_host()->GetLastCommittedURL().SchemeIs(kChromeErrorScheme));
const network::mojom::ClientSecurityStatePtr security_state =
root_frame_host()->BuildClientSecurityState();
ASSERT_FALSE(security_state.is_null());
EXPECT_FALSE(security_state->is_web_secure_context);
EXPECT_EQ(network::mojom::IPAddressSpace::kLocal,
security_state->ip_address_space);
}
// ========================
// INHERITANCE TEST HELPERS
// ========================
namespace {
// Creates a blob containing dummy HTML, then returns its URL.
// Executes javascript to do so in |frame_host|, which must not be nullptr.
GURL CreateBlobURL(RenderFrameHostImpl* frame_host) {
// Define a variable to avoid awkward `ExtractString()` indentation.
EvalJsResult result = EvalJs(frame_host, R"(
const blob = new Blob(["foo"], {type: "text/html"});
URL.createObjectURL(blob)
)");
return GURL(result.ExtractString());
}
// Executes |script| to add a new child iframe to the given |parent| document.
//
// |parent| must not be nullptr.
//
// Returns a pointer to the child frame host.
RenderFrameHostImpl* AddChildWithScript(RenderFrameHostImpl* parent,
const std::string& script) {
size_t initial_child_count = parent->child_count();
EXPECT_EQ(true, ExecJs(parent, script));
EXPECT_EQ(parent->child_count(), initial_child_count + 1);
if (parent->child_count() < initial_child_count + 1) {
return nullptr;
}
return parent->child_at(initial_child_count)->current_frame_host();
}
RenderFrameHostImpl* GetFirstChild(RenderFrameHostImpl& parent) {
CHECK_NE(parent.child_count(), 0ul);
return parent.child_at(0)->current_frame_host();
}
// Adds a child iframe sourced from `url` to the given `parent` document and
// waits for it to load. Returns the child RFHI.
//
// `parent` must not be nullptr.
RenderFrameHostImpl* AddChildFromURL(RenderFrameHostImpl* parent,
std::string_view url) {
constexpr std::string_view kScriptTemplate = R"(
new Promise((resolve) => {
const iframe = document.createElement("iframe");
iframe.src = $1;
iframe.onload = _ => { resolve(true); };
document.body.appendChild(iframe);
})
)";
return AddChildWithScript(parent, JsReplace(kScriptTemplate, url));
}
// Convenience overload for absolute URLs.
RenderFrameHostImpl* AddChildFromURL(RenderFrameHostImpl* parent,
const GURL& url) {
return AddChildFromURL(parent, url.spec());
}
// Adds a child iframe sourced from `url` to the given `parent` document.
// Does not wait for the child frame to load - this must be done separately.
//
// `parent` must not be nullptr.
void AddChildFromURLWithoutWaiting(RenderFrameHostImpl* parent,
std::string_view url) {
// Define a variable for better indentation.
constexpr std::string_view kScriptTemplate = R"(
const child = document.createElement("iframe");
child.src = $1;
document.body.appendChild(child);
)";
EXPECT_EQ(true, ExecJs(parent, JsReplace(kScriptTemplate, url)));
}
// Convenience overload for absolute URLs.
void AddChildFromURLWithoutWaiting(RenderFrameHostImpl* parent,
const GURL& url) {
return AddChildFromURLWithoutWaiting(parent, url.spec());
}
RenderFrameHostImpl* AddChildFromAboutBlank(RenderFrameHostImpl* parent) {
return AddChildFromURL(parent, "about:blank");
}
RenderFrameHostImpl* AddChildInitialEmptyDoc(RenderFrameHostImpl* parent) {
return AddChildWithScript(parent, R"(
const iframe = document.createElement("iframe");
iframe.src = "/nocontent"; // Returns 204 NO CONTENT, thus no doc commits.
document.body.appendChild(iframe);
true // Do not wait for iframe.onload, which never fires.
)");
}
RenderFrameHostImpl* AddChildFromSrcdoc(RenderFrameHostImpl* parent) {
return AddChildWithScript(parent, R"(
new Promise((resolve) => {
const iframe = document.createElement("iframe");
iframe.srcdoc = "foo";
iframe.onload = _ => { resolve(true); };
document.body.appendChild(iframe);
})
)");
}
RenderFrameHostImpl* AddChildFromDataURL(RenderFrameHostImpl* parent) {
return AddChildFromURL(parent, "data:text/html,foo");
}
RenderFrameHostImpl* AddChildFromJavascriptURL(RenderFrameHostImpl* parent) {
return AddChildFromURL(parent, "javascript:'foo'");
}
RenderFrameHostImpl* AddChildFromBlob(RenderFrameHostImpl* parent) {
GURL blob_url = CreateBlobURL(parent);
return AddChildFromURL(parent, blob_url);
}
RenderFrameHostImpl* AddSandboxedChildFromURL(RenderFrameHostImpl* parent,
const GURL& url) {
std::string script_template = R"(
new Promise((resolve) => {
const iframe = document.createElement("iframe");
iframe.src = $1;
iframe.sandbox = "";
iframe.onload = _ => { resolve(true); };
document.body.appendChild(iframe);
})
)";
return AddChildWithScript(parent, JsReplace(script_template, url));
}
RenderFrameHostImpl* AddSandboxedChildFromAboutBlank(
RenderFrameHostImpl* parent) {
return AddSandboxedChildFromURL(parent, GURL("about:blank"));
}
RenderFrameHostImpl* AddSandboxedChildInitialEmptyDoc(
RenderFrameHostImpl* parent) {
return AddChildWithScript(parent, R"(
const iframe = document.createElement("iframe");
iframe.src = "/nocontent"; // Returns 204 NO CONTENT, thus no doc commits.
iframe.sandbox = "";
document.body.appendChild(iframe);
true // Do not wait for iframe.onload, which never fires.
)");
}
RenderFrameHostImpl* AddSandboxedChildFromSrcdoc(RenderFrameHostImpl* parent) {
return AddChildWithScript(parent, R"(
new Promise((resolve) => {
const iframe = document.createElement("iframe");
iframe.srcdoc = "foo";
iframe.sandbox = "";
iframe.onload = _ => { resolve(true); };
document.body.appendChild(iframe);
})
)");
}
RenderFrameHostImpl* AddSandboxedChildFromDataURL(RenderFrameHostImpl* parent) {
return AddSandboxedChildFromURL(parent, GURL("data:text/html,foo"));
}
RenderFrameHostImpl* AddSandboxedChildFromBlob(RenderFrameHostImpl* parent) {
GURL blob_url = CreateBlobURL(parent);
return AddSandboxedChildFromURL(parent, blob_url);
}
// Returns the main frame RenderFrameHostImpl in the given |shell|.
//
// |shell| must not be nullptr.
//
// Helper for OpenWindow*().
RenderFrameHostImpl* GetPrimaryMainFrameHostImpl(Shell* shell) {
return static_cast<RenderFrameHostImpl*>(
shell->web_contents()->GetPrimaryMainFrame());
}
// Opens a new window from within |parent|, pointed at the given |url|.
// Waits until the openee window has navigated to |url|, then returns a pointer
// to its main frame RenderFrameHostImpl.
//
// |parent| must not be nullptr.
RenderFrameHostImpl* OpenWindowFromURL(RenderFrameHostImpl* parent,
const GURL& url) {
return GetPrimaryMainFrameHostImpl(OpenPopup(parent, url, "_blank"));
}
RenderFrameHostImpl* OpenWindowFromAboutBlank(RenderFrameHostImpl* parent) {
return OpenWindowFromURL(parent, GURL("about:blank"));
}
// Same as above, but with the "noopener" window feature.
RenderFrameHostImpl* OpenWindowFromAboutBlankNoOpener(
RenderFrameHostImpl* parent) {
// Setting the "noopener" window feature makes `window.open()` return `null`.
constexpr bool kNoExpectReturnFromWindowOpen = false;
return GetPrimaryMainFrameHostImpl(OpenPopup(parent, GURL("about:blank"),
"_blank", "noopener",
kNoExpectReturnFromWindowOpen));
}
RenderFrameHostImpl* OpenWindowFromURLExpectNoCommit(
RenderFrameHostImpl* parent,
const GURL& url,
std::string_view features = "") {
ShellAddedObserver observer;
std::string_view script_template = R"(
window.open($1, "_blank", $2);
)";
EXPECT_TRUE(ExecJs(parent, JsReplace(script_template, url, features)));
return GetPrimaryMainFrameHostImpl(observer.GetShell());
}
RenderFrameHostImpl* OpenWindowInitialEmptyDoc(RenderFrameHostImpl* parent) {
// Note: We do not use OpenWindowFromURL() because we do not want to wait for
// a navigation - none will commit.
return OpenWindowFromURLExpectNoCommit(parent, GURL("/nocontent"));
}
// Same as above, but with the "noopener" window feature.
RenderFrameHostImpl* OpenWindowInitialEmptyDocNoOpener(
RenderFrameHostImpl* parent) {
// Note: We do not use OpenWindowFromURL() because we do not want to wait for
// a navigation - none will commit.
return OpenWindowFromURLExpectNoCommit(parent, GURL("/nocontent"),
"noopener");
}
GURL JavascriptURL(std::string_view script) {
return GURL(base::StrCat({"javascript:", script}));
}
RenderFrameHostImpl* OpenWindowFromJavascriptURL(
RenderFrameHostImpl* parent,
std::string_view script = "'foo'") {
// Note: We do not use OpenWindowFromURL() because we do not want to wait for
// a navigation, since the `javascript:` URL will not commit (`about:blank`
// will).
return OpenWindowFromURLExpectNoCommit(parent, JavascriptURL(script));
}
// Same as above, but with the "noopener" window feature.
RenderFrameHostImpl* OpenWindowFromJavascriptURLNoOpener(
RenderFrameHostImpl* parent,
std::string_view script) {
// Note: We do not use OpenWindowFromURL() because we do not want to wait for
// a navigation - none will commit.
return OpenWindowFromURLExpectNoCommit(parent, JavascriptURL(script),
"noopener");
}
RenderFrameHostImpl* OpenWindowFromBlob(RenderFrameHostImpl* parent) {
GURL blob_url = CreateBlobURL(parent);
return OpenWindowFromURL(parent, blob_url);
}
} // namespace
// ===============================
// ADDRESS SPACE INHERITANCE TESTS
// ===============================
//
// These tests verify that `ClientSecurityState.ip_address_space` is correctly
// inherited by child iframes and openee documents for a variety of URLs with
// local schemes.
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessBrowserTest,
IframeInheritsAddressSpaceForAboutBlankFromPublic) {
EXPECT_TRUE(NavigateToURL(shell(), SecurePublicURL(kDefaultPath)));
RenderFrameHostImpl* child_frame = AddChildFromAboutBlank(root_frame_host());
ASSERT_NE(nullptr, child_frame);
const network::mojom::ClientSecurityStatePtr security_state =
child_frame->BuildClientSecurityState();
ASSERT_FALSE(security_state.is_null());
EXPECT_EQ(network::mojom::IPAddressSpace::kPublic,
security_state->ip_address_space);
}
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessBrowserTest,
IframeInheritsAddressSpaceForAboutBlankFromLoopback) {
EXPECT_TRUE(NavigateToURL(shell(), SecureLoopbackURL(kDefaultPath)));
RenderFrameHostImpl* child_frame = AddChildFromAboutBlank(root_frame_host());
ASSERT_NE(nullptr, child_frame);
const network::mojom::ClientSecurityStatePtr security_state =
child_frame->BuildClientSecurityState();
ASSERT_FALSE(security_state.is_null());
EXPECT_EQ(network::mojom::IPAddressSpace::kLoopback,
security_state->ip_address_space);
}
IN_PROC_BROWSER_TEST_F(
PrivateNetworkAccessBrowserTest,
SandboxedIframeInheritsAddressSpaceForAboutBlankFromPublic) {
EXPECT_TRUE(NavigateToURL(shell(), SecurePublicURL(kDefaultPath)));
RenderFrameHostImpl* child_frame =
AddSandboxedChildFromAboutBlank(root_frame_host());
ASSERT_NE(nullptr, child_frame);
const network::mojom::ClientSecurityStatePtr security_state =
child_frame->BuildClientSecurityState();
ASSERT_FALSE(security_state.is_null());
EXPECT_EQ(network::mojom::IPAddressSpace::kPublic,
security_state->ip_address_space);
}
IN_PROC_BROWSER_TEST_F(
PrivateNetworkAccessBrowserTest,
SandboxedIframeInheritsAddressSpaceForAboutBlankFromLoopback) {
EXPECT_TRUE(NavigateToURL(shell(), SecureLoopbackURL(kDefaultPath)));
RenderFrameHostImpl* child_frame =
AddSandboxedChildFromAboutBlank(root_frame_host());
ASSERT_NE(nullptr, child_frame);
const network::mojom::ClientSecurityStatePtr security_state =
child_frame->BuildClientSecurityState();
ASSERT_FALSE(security_state.is_null());
EXPECT_EQ(network::mojom::IPAddressSpace::kLoopback,
security_state->ip_address_space);
}
// This test verifies that a newly-opened window targeting `about:blank`
// inherits its address space from the opener. In this case, the opener's
// address space is `public`.
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessBrowserTest,
OpeneeInheritsAddressSpaceForAboutBlankFromPublic) {
EXPECT_TRUE(NavigateToURL(shell(), SecurePublicURL(kDefaultPath)));
RenderFrameHostImpl* window = OpenWindowFromAboutBlank(root_frame_host());
ASSERT_NE(nullptr, window);
const network::mojom::ClientSecurityStatePtr security_state =
window->BuildClientSecurityState();
ASSERT_FALSE(security_state.is_null());
EXPECT_EQ(network::mojom::IPAddressSpace::kPublic,
security_state->ip_address_space);
}
// This test verifies that a newly-opened window targeting `about:blank`
// inherits its address space from the opener. In this case, the opener's
// address space is `loopback`.
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessBrowserTest,
OpeneeInheritsAddressSpaceForAboutBlankFromLoopback) {
EXPECT_TRUE(NavigateToURL(shell(), SecureLoopbackURL(kDefaultPath)));
RenderFrameHostImpl* window = OpenWindowFromAboutBlank(root_frame_host());
ASSERT_NE(nullptr, window);
const network::mojom::ClientSecurityStatePtr security_state =
window->BuildClientSecurityState();
ASSERT_FALSE(security_state.is_null());
EXPECT_EQ(network::mojom::IPAddressSpace::kLoopback,
security_state->ip_address_space);
}
// This test verifies that a newly-opened window targeting `about:blank`,
// opened with the "noopener" feature, has its address space set to `loopback`
// regardless of the address space of the opener.
//
// Compare and contrast against the above tests without "noopener".
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessBrowserTest,
OpeneeNoOpenerAddressSpaceForAboutBlankIsLoopback) {
EXPECT_TRUE(NavigateToURL(shell(), SecurePublicURL(kDefaultPath)));
RenderFrameHostImpl* window =
OpenWindowFromAboutBlankNoOpener(root_frame_host());
ASSERT_NE(nullptr, window);
const network::mojom::ClientSecurityStatePtr security_state =
window->BuildClientSecurityState();
ASSERT_FALSE(security_state.is_null());
EXPECT_EQ(network::mojom::IPAddressSpace::kLoopback,
security_state->ip_address_space);
}
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessBrowserTest,
IframeInheritsAddressSpaceForInitialEmptyDocFromPublic) {
EXPECT_TRUE(NavigateToURL(shell(), SecurePublicURL(kDefaultPath)));
RenderFrameHostImpl* child_frame = AddChildInitialEmptyDoc(root_frame_host());
ASSERT_NE(nullptr, child_frame);
network::mojom::ClientSecurityStatePtr security_state =
child_frame->BuildClientSecurityState();
ASSERT_FALSE(security_state.is_null());
EXPECT_EQ(network::mojom::IPAddressSpace::kPublic,
security_state->ip_address_space);
}
IN_PROC_BROWSER_TEST_F(
PrivateNetworkAccessBrowserTest,
IframeInheritsAddressSpaceForInitialEmptyDocFromLoopback) {
EXPECT_TRUE(NavigateToURL(shell(), SecureLoopbackURL(kDefaultPath)));
RenderFrameHostImpl* child_frame = AddChildInitialEmptyDoc(root_frame_host());
ASSERT_NE(nullptr, child_frame);
network::mojom::ClientSecurityStatePtr security_state =
child_frame->BuildClientSecurityState();
ASSERT_FALSE(security_state.is_null());
EXPECT_EQ(network::mojom::IPAddressSpace::kLoopback,
security_state->ip_address_space);
}
IN_PROC_BROWSER_TEST_F(
PrivateNetworkAccessBrowserTest,
SandboxedIframeInheritsAddressSpaceForInitialEmptyDocFromPublic) {
EXPECT_TRUE(NavigateToURL(shell(), SecurePublicURL(kDefaultPath)));
RenderFrameHostImpl* child_frame =
AddSandboxedChildInitialEmptyDoc(root_frame_host());
ASSERT_NE(nullptr, child_frame);
network::mojom::ClientSecurityStatePtr security_state =
child_frame->BuildClientSecurityState();
ASSERT_FALSE(security_state.is_null());
EXPECT_EQ(network::mojom::IPAddressSpace::kPublic,
security_state->ip_address_space);
}
IN_PROC_BROWSER_TEST_F(
PrivateNetworkAccessBrowserTest,
SandboxedIframeInheritsAddressSpaceForInitialEmptyDocFromLoopback) {
EXPECT_TRUE(NavigateToURL(shell(), SecureLoopbackURL(kDefaultPath)));
RenderFrameHostImpl* child_frame =
AddSandboxedChildInitialEmptyDoc(root_frame_host());
ASSERT_NE(nullptr, child_frame);
network::mojom::ClientSecurityStatePtr security_state =
child_frame->BuildClientSecurityState();
ASSERT_FALSE(security_state.is_null());
EXPECT_EQ(network::mojom::IPAddressSpace::kLoopback,
security_state->ip_address_space);
}
// This test verifies that a newly-opened window containing the initial empty
// document inherits its address space from the opener. In this case, the
// opener's address space is `public`.
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessBrowserTest,
OpeneeInheritsAddressSpaceForInitialEmptyDocFromPublic) {
EXPECT_TRUE(NavigateToURL(shell(), SecurePublicURL(kDefaultPath)));
RenderFrameHostImpl* window = OpenWindowInitialEmptyDoc(root_frame_host());
ASSERT_NE(nullptr, window);
const network::mojom::ClientSecurityStatePtr security_state =
window->BuildClientSecurityState();
ASSERT_FALSE(security_state.is_null());
EXPECT_EQ(network::mojom::IPAddressSpace::kPublic,
security_state->ip_address_space);
}
// This test verifies that a newly-opened window containing the initial empty
// document inherits its address space from the opener. In this case, the
// opener's address space is `loopback`.
IN_PROC_BROWSER_TEST_F(
PrivateNetworkAccessBrowserTest,
OpeneeInheritsAddressSpaceForInitialEmptyDocFromLoopback) {
EXPECT_TRUE(NavigateToURL(shell(), SecureLoopbackURL(kDefaultPath)));
RenderFrameHostImpl* window = OpenWindowInitialEmptyDoc(root_frame_host());
ASSERT_NE(nullptr, window);
const network::mojom::ClientSecurityStatePtr security_state =
window->BuildClientSecurityState();
ASSERT_FALSE(security_state.is_null());
EXPECT_EQ(network::mojom::IPAddressSpace::kLoopback,
security_state->ip_address_space);
}
// This test verifies that a newly-opened window containing the initial empty
// document, opened with the "noopener" feature, has its address space set to
// `loopback` regardless of the address space of the opener.
//
// Compare and contrast against the above tests without "noopener".
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessBrowserTest,
OpeneeNoOpenerAddressSpaceForInitialEmptyDocIsLoopback) {
EXPECT_TRUE(NavigateToURL(shell(), SecurePublicURL(kDefaultPath)));
RenderFrameHostImpl* window =
OpenWindowInitialEmptyDocNoOpener(root_frame_host());
ASSERT_NE(nullptr, window);
const network::mojom::ClientSecurityStatePtr security_state =
window->BuildClientSecurityState();
ASSERT_FALSE(security_state.is_null());
EXPECT_EQ(network::mojom::IPAddressSpace::kLoopback,
security_state->ip_address_space);
}
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessBrowserTest,
IframeInheritsAddressSpaceForAboutSrcdocFromPublic) {
EXPECT_TRUE(NavigateToURL(shell(), SecurePublicURL(kDefaultPath)));
RenderFrameHostImpl* child_frame = AddChildFromSrcdoc(root_frame_host());
ASSERT_NE(nullptr, child_frame);
const network::mojom::ClientSecurityStatePtr security_state =
child_frame->BuildClientSecurityState();
ASSERT_FALSE(security_state.is_null());
EXPECT_EQ(network::mojom::IPAddressSpace::kPublic,
security_state->ip_address_space);
}
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessBrowserTest,
IframeInheritsAddressSpaceForAboutSrcdocFromLoopback) {
EXPECT_TRUE(NavigateToURL(shell(), SecureLoopbackURL(kDefaultPath)));
RenderFrameHostImpl* child_frame = AddChildFromSrcdoc(root_frame_host());
ASSERT_NE(nullptr, child_frame);
const network::mojom::ClientSecurityStatePtr security_state =
child_frame->BuildClientSecurityState();
ASSERT_FALSE(security_state.is_null());
EXPECT_EQ(network::mojom::IPAddressSpace::kLoopback,
security_state->ip_address_space);
}
IN_PROC_BROWSER_TEST_F(
PrivateNetworkAccessBrowserTest,
SandboxedIframeInheritsAddressSpaceForAboutSrcdocFromPublic) {
EXPECT_TRUE(NavigateToURL(shell(), SecurePublicURL(kDefaultPath)));
RenderFrameHostImpl* child_frame =
AddSandboxedChildFromSrcdoc(root_frame_host());
ASSERT_NE(nullptr, child_frame);
const network::mojom::ClientSecurityStatePtr security_state =
child_frame->BuildClientSecurityState();
ASSERT_FALSE(security_state.is_null());
EXPECT_EQ(network::mojom::IPAddressSpace::kPublic,
security_state->ip_address_space);
}
IN_PROC_BROWSER_TEST_F(
PrivateNetworkAccessBrowserTest,
SandboxedIframeInheritsAddressSpaceForAboutSrcdocFromLoopback) {
EXPECT_TRUE(NavigateToURL(shell(), SecureLoopbackURL(kDefaultPath)));
RenderFrameHostImpl* child_frame =
AddSandboxedChildFromSrcdoc(root_frame_host());
ASSERT_NE(nullptr, child_frame);
const network::mojom::ClientSecurityStatePtr security_state =
child_frame->BuildClientSecurityState();
ASSERT_FALSE(security_state.is_null());
EXPECT_EQ(network::mojom::IPAddressSpace::kLoopback,
security_state->ip_address_space);
}
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessBrowserTest,
IframeInheritsAddressSpaceForDataURLFromPublic) {
EXPECT_TRUE(NavigateToURL(shell(), SecurePublicURL(kDefaultPath)));
RenderFrameHostImpl* child_frame = AddChildFromDataURL(root_frame_host());
ASSERT_NE(nullptr, child_frame);
const network::mojom::ClientSecurityStatePtr security_state =
child_frame->BuildClientSecurityState();
ASSERT_FALSE(security_state.is_null());
EXPECT_EQ(network::mojom::IPAddressSpace::kPublic,
security_state->ip_address_space);
}
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessBrowserTest,
IframeInheritsAddressSpaceForDataURLFromLoopback) {
EXPECT_TRUE(NavigateToURL(shell(), SecureLoopbackURL(kDefaultPath)));
RenderFrameHostImpl* child_frame = AddChildFromDataURL(root_frame_host());
ASSERT_NE(nullptr, child_frame);
const network::mojom::ClientSecurityStatePtr security_state =
child_frame->BuildClientSecurityState();
ASSERT_FALSE(security_state.is_null());
EXPECT_EQ(network::mojom::IPAddressSpace::kLoopback,
security_state->ip_address_space);
}
IN_PROC_BROWSER_TEST_P(
PrivateNetworkAccessSandboxedDataBrowserTest,
SandboxedIframeInheritsAddressSpaceForDataURLFromPublic) {
EXPECT_TRUE(NavigateToURL(shell(), SecurePublicURL(kDefaultPath)));
RenderFrameHostImpl* child_frame =
AddSandboxedChildFromDataURL(root_frame_host());
ASSERT_NE(nullptr, child_frame);
const network::mojom::ClientSecurityStatePtr security_state =
child_frame->BuildClientSecurityState();
ASSERT_FALSE(security_state.is_null());
EXPECT_EQ(network::mojom::IPAddressSpace::kPublic,
security_state->ip_address_space);
}
IN_PROC_BROWSER_TEST_P(
PrivateNetworkAccessSandboxedDataBrowserTest,
SandboxedIframeInheritsAddressSpaceForDataURLFromLoopback) {
EXPECT_TRUE(NavigateToURL(shell(), SecureLoopbackURL(kDefaultPath)));
RenderFrameHostImpl* child_frame =
AddSandboxedChildFromDataURL(root_frame_host());
ASSERT_NE(nullptr, child_frame);
const network::mojom::ClientSecurityStatePtr security_state =
child_frame->BuildClientSecurityState();
ASSERT_FALSE(security_state.is_null());
EXPECT_EQ(network::mojom::IPAddressSpace::kLoopback,
security_state->ip_address_space);
}
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessBrowserTest,
IframeInheritsAddressSpaceForJavascriptURLFromPublic) {
EXPECT_TRUE(NavigateToURL(shell(), SecurePublicURL(kDefaultPath)));
RenderFrameHostImpl* child_frame =
AddChildFromJavascriptURL(root_frame_host());
ASSERT_NE(nullptr, child_frame);
const network::mojom::ClientSecurityStatePtr security_state =
child_frame->BuildClientSecurityState();
ASSERT_FALSE(security_state.is_null());
EXPECT_EQ(network::mojom::IPAddressSpace::kPublic,
security_state->ip_address_space);
}
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessBrowserTest,
IframeInheritsAddressSpaceForJavascriptURLFromLoopback) {
EXPECT_TRUE(NavigateToURL(shell(), SecureLoopbackURL(kDefaultPath)));
RenderFrameHostImpl* child_frame =
AddChildFromJavascriptURL(root_frame_host());
ASSERT_NE(nullptr, child_frame);
const network::mojom::ClientSecurityStatePtr security_state =
child_frame->BuildClientSecurityState();
ASSERT_FALSE(security_state.is_null());
EXPECT_EQ(network::mojom::IPAddressSpace::kLoopback,
security_state->ip_address_space);
}
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessBrowserTest,
OpeneeInheritsAddressSpaceForJavascriptURLFromPublic) {
EXPECT_TRUE(NavigateToURL(shell(), SecurePublicURL(kDefaultPath)));
RenderFrameHostImpl* window = OpenWindowFromJavascriptURL(root_frame_host());
ASSERT_NE(nullptr, window);
const network::mojom::ClientSecurityStatePtr security_state =
window->BuildClientSecurityState();
ASSERT_FALSE(security_state.is_null());
EXPECT_EQ(network::mojom::IPAddressSpace::kPublic,
security_state->ip_address_space);
}
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessBrowserTest,
OpeneeInheritsAddressSpaceForJavascriptURLFromLoopback) {
EXPECT_TRUE(NavigateToURL(shell(), SecureLoopbackURL(kDefaultPath)));
RenderFrameHostImpl* window = OpenWindowFromJavascriptURL(
root_frame_host(), "var injectedCodeWasExecuted = true");
ASSERT_NE(nullptr, window);
// The Javascript in the URL got executed in the new window.
EXPECT_EQ(true, EvalJs(window, "injectedCodeWasExecuted"));
const network::mojom::ClientSecurityStatePtr security_state =
window->BuildClientSecurityState();
ASSERT_FALSE(security_state.is_null());
EXPECT_EQ(network::mojom::IPAddressSpace::kLoopback,
security_state->ip_address_space);
}
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessBrowserTest,
OpeneeNoOpenerAddressSpaceForJavascriptURLIsLoopback) {
EXPECT_TRUE(NavigateToURL(shell(), SecurePublicURL(kDefaultPath)));
RenderFrameHostImpl* window = OpenWindowFromJavascriptURLNoOpener(
root_frame_host(), "var injectedCodeWasExecuted = true");
ASSERT_NE(nullptr, window);
// The Javascript in the URL was not executed in the new window. This ensures
// it is safe to classify the new window as `loopback` without allowing the
// opener to execute arbitrary JS in the `loopback` address space.
EXPECT_EQ("undefined", EvalJs(window, "typeof injectedCodeWasExecuted"));
const network::mojom::ClientSecurityStatePtr security_state =
window->BuildClientSecurityState();
ASSERT_FALSE(security_state.is_null());
EXPECT_EQ(network::mojom::IPAddressSpace::kLoopback,
security_state->ip_address_space);
}
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessBrowserTest,
IframeInheritsAddressSpaceForBlobURLFromPublic) {
EXPECT_TRUE(NavigateToURL(shell(), SecurePublicURL(kDefaultPath)));
RenderFrameHostImpl* child_frame = AddChildFromBlob(root_frame_host());
ASSERT_NE(nullptr, child_frame);
const network::mojom::ClientSecurityStatePtr security_state =
child_frame->BuildClientSecurityState();
ASSERT_FALSE(security_state.is_null());
EXPECT_EQ(network::mojom::IPAddressSpace::kPublic,
security_state->ip_address_space);
}
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessBrowserTest,
IframeInheritsAddressSpaceForBlobURLFromLoopback) {
EXPECT_TRUE(NavigateToURL(shell(), SecureLoopbackURL(kDefaultPath)));
RenderFrameHostImpl* child_frame = AddChildFromBlob(root_frame_host());
ASSERT_NE(nullptr, child_frame);
const network::mojom::ClientSecurityStatePtr security_state =
child_frame->BuildClientSecurityState();
ASSERT_FALSE(security_state.is_null());
EXPECT_EQ(network::mojom::IPAddressSpace::kLoopback,
security_state->ip_address_space);
}
IN_PROC_BROWSER_TEST_F(
PrivateNetworkAccessBrowserTest,
SandboxedIframeInheritsAddressSpaceForBlobURLFromPublic) {
EXPECT_TRUE(NavigateToURL(shell(), SecurePublicURL(kDefaultPath)));
RenderFrameHostImpl* child_frame =
AddSandboxedChildFromBlob(root_frame_host());
ASSERT_NE(nullptr, child_frame);
const network::mojom::ClientSecurityStatePtr security_state =
child_frame->BuildClientSecurityState();
ASSERT_FALSE(security_state.is_null());
EXPECT_EQ(network::mojom::IPAddressSpace::kPublic,
security_state->ip_address_space);
}
IN_PROC_BROWSER_TEST_F(
PrivateNetworkAccessBrowserTest,
SandboxedIframeInheritsAddressSpaceForBlobURLFromLoopback) {
EXPECT_TRUE(NavigateToURL(shell(), SecureLoopbackURL(kDefaultPath)));
RenderFrameHostImpl* child_frame =
AddSandboxedChildFromBlob(root_frame_host());
ASSERT_NE(nullptr, child_frame);
const network::mojom::ClientSecurityStatePtr security_state =
child_frame->BuildClientSecurityState();
ASSERT_FALSE(security_state.is_null());
EXPECT_EQ(network::mojom::IPAddressSpace::kLoopback,
security_state->ip_address_space);
}
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessBrowserTest,
OpeneeInheritsAddressSpaceForBlobURLFromPublic) {
EXPECT_TRUE(NavigateToURL(shell(), SecurePublicURL(kDefaultPath)));
RenderFrameHostImpl* window = OpenWindowFromBlob(root_frame_host());
ASSERT_NE(nullptr, window);
const network::mojom::ClientSecurityStatePtr security_state =
window->BuildClientSecurityState();
ASSERT_FALSE(security_state.is_null());
EXPECT_EQ(network::mojom::IPAddressSpace::kPublic,
security_state->ip_address_space);
}
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessBrowserTest,
OpeneeInheritsAddressSpaceForBlobURLFromLoopback) {
EXPECT_TRUE(NavigateToURL(shell(), SecureLoopbackURL(kDefaultPath)));
RenderFrameHostImpl* window = OpenWindowFromBlob(root_frame_host());
ASSERT_NE(nullptr, window);
const network::mojom::ClientSecurityStatePtr security_state =
window->BuildClientSecurityState();
ASSERT_FALSE(security_state.is_null());
EXPECT_EQ(network::mojom::IPAddressSpace::kLoopback,
security_state->ip_address_space);
}
// ================================
// SECURE CONTEXT INHERITANCE TESTS
// ================================
//
// These tests verify that `ClientSecurityState.is_web_secure_context` is
// correctly inherited by child iframes and openee documents for a variety of
// URLs with local schemes.
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessBrowserTest,
IframeInheritsSecureContextForAboutBlankFromSecure) {
EXPECT_TRUE(NavigateToURL(shell(), SecureLoopbackURL(kDefaultPath)));
RenderFrameHostImpl* child_frame = AddChildFromAboutBlank(root_frame_host());
ASSERT_NE(nullptr, child_frame);
const network::mojom::ClientSecurityStatePtr security_state =
child_frame->BuildClientSecurityState();
ASSERT_FALSE(security_state.is_null());
EXPECT_TRUE(security_state->is_web_secure_context);
}
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessBrowserTest,
IframeInheritsSecureContextForAboutBlankFromInsecure) {
EXPECT_TRUE(NavigateToURL(shell(), InsecureLoopbackURL(kDefaultPath)));
RenderFrameHostImpl* child_frame = AddChildFromAboutBlank(root_frame_host());
ASSERT_NE(nullptr, child_frame);
const network::mojom::ClientSecurityStatePtr security_state =
child_frame->BuildClientSecurityState();
ASSERT_FALSE(security_state.is_null());
EXPECT_FALSE(security_state->is_web_secure_context);
}
IN_PROC_BROWSER_TEST_F(
PrivateNetworkAccessBrowserTest,
SandboxedIframeInheritsSecureContextForAboutBlankFromSecure) {
EXPECT_TRUE(NavigateToURL(shell(), SecureLoopbackURL(kDefaultPath)));
RenderFrameHostImpl* child_frame =
AddSandboxedChildFromAboutBlank(root_frame_host());
ASSERT_NE(nullptr, child_frame);
const network::mojom::ClientSecurityStatePtr security_state =
child_frame->BuildClientSecurityState();
ASSERT_FALSE(security_state.is_null());
EXPECT_TRUE(security_state->is_web_secure_context);
}
IN_PROC_BROWSER_TEST_F(
PrivateNetworkAccessBrowserTest,
SandboxedIframeInheritsSecureContextForAboutBlankFromInsecure) {
EXPECT_TRUE(NavigateToURL(shell(), InsecureLoopbackURL(kDefaultPath)));
RenderFrameHostImpl* child_frame =
AddSandboxedChildFromAboutBlank(root_frame_host());
ASSERT_NE(nullptr, child_frame);
const network::mojom::ClientSecurityStatePtr security_state =
child_frame->BuildClientSecurityState();
ASSERT_FALSE(security_state.is_null());
EXPECT_FALSE(security_state->is_web_secure_context);
}
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessBrowserTest,
OpeneeInheritsSecureContextForAboutBlankFromSecure) {
EXPECT_TRUE(NavigateToURL(shell(), SecureLoopbackURL(kDefaultPath)));
RenderFrameHostImpl* window = OpenWindowFromAboutBlank(root_frame_host());
ASSERT_NE(nullptr, window);
const network::mojom::ClientSecurityStatePtr security_state =
window->BuildClientSecurityState();
ASSERT_FALSE(security_state.is_null());
EXPECT_TRUE(security_state->is_web_secure_context);
}
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessBrowserTest,
OpeneeInheritsSecureContextForAboutBlankFromInsecure) {
EXPECT_TRUE(NavigateToURL(shell(), InsecureLoopbackURL(kDefaultPath)));
RenderFrameHostImpl* window = OpenWindowFromAboutBlank(root_frame_host());
ASSERT_NE(nullptr, window);
const network::mojom::ClientSecurityStatePtr security_state =
window->BuildClientSecurityState();
ASSERT_FALSE(security_state.is_null());
EXPECT_FALSE(security_state->is_web_secure_context);
}
IN_PROC_BROWSER_TEST_F(
PrivateNetworkAccessBrowserTest,
IframeInheritsSecureContextForInitialEmptyDocFromSecure) {
EXPECT_TRUE(NavigateToURL(shell(), SecureLoopbackURL(kDefaultPath)));
RenderFrameHostImpl* child_frame = AddChildInitialEmptyDoc(root_frame_host());
ASSERT_NE(nullptr, child_frame);
const network::mojom::ClientSecurityStatePtr security_state =
child_frame->BuildClientSecurityState();
ASSERT_FALSE(security_state.is_null());
EXPECT_TRUE(security_state->is_web_secure_context);
}
IN_PROC_BROWSER_TEST_F(
PrivateNetworkAccessBrowserTest,
IframeInheritsSecureContextForInitialEmptyDocFromInsecure) {
EXPECT_TRUE(NavigateToURL(shell(), InsecureLoopbackURL(kDefaultPath)));
RenderFrameHostImpl* child_frame = AddChildInitialEmptyDoc(root_frame_host());
ASSERT_NE(nullptr, child_frame);
const network::mojom::ClientSecurityStatePtr security_state =
child_frame->BuildClientSecurityState();
ASSERT_FALSE(security_state.is_null());
EXPECT_FALSE(security_state->is_web_secure_context);
}
IN_PROC_BROWSER_TEST_F(
PrivateNetworkAccessBrowserTest,
SandboxedIframeInheritsSecureContextForInitialEmptyDocFromSecure) {
EXPECT_TRUE(NavigateToURL(shell(), SecureLoopbackURL(kDefaultPath)));
RenderFrameHostImpl* child_frame =
AddSandboxedChildInitialEmptyDoc(root_frame_host());
ASSERT_NE(nullptr, child_frame);
const network::mojom::ClientSecurityStatePtr security_state =
child_frame->BuildClientSecurityState();
ASSERT_FALSE(security_state.is_null());
EXPECT_TRUE(security_state->is_web_secure_context);
}
IN_PROC_BROWSER_TEST_F(
PrivateNetworkAccessBrowserTest,
SandboxedIframeInheritsSecureContextForInitialEmptyDocFromInsecure) {
EXPECT_TRUE(NavigateToURL(shell(), InsecureLoopbackURL(kDefaultPath)));
RenderFrameHostImpl* child_frame =
AddSandboxedChildInitialEmptyDoc(root_frame_host());
ASSERT_NE(nullptr, child_frame);
const network::mojom::ClientSecurityStatePtr security_state =
child_frame->BuildClientSecurityState();
ASSERT_FALSE(security_state.is_null());
EXPECT_FALSE(security_state->is_web_secure_context);
}
IN_PROC_BROWSER_TEST_F(
PrivateNetworkAccessBrowserTest,
OpeneeInheritsSecureContextForInitialEmptyDocFromSecure) {
EXPECT_TRUE(NavigateToURL(shell(), SecureLoopbackURL(kDefaultPath)));
RenderFrameHostImpl* window = OpenWindowInitialEmptyDoc(root_frame_host());
ASSERT_NE(nullptr, window);
const network::mojom::ClientSecurityStatePtr security_state =
window->BuildClientSecurityState();
ASSERT_FALSE(security_state.is_null());
EXPECT_TRUE(security_state->is_web_secure_context);
}
IN_PROC_BROWSER_TEST_F(
PrivateNetworkAccessBrowserTest,
OpeneeInheritsSecureContextForInitialEmptyDocFromInsecure) {
EXPECT_TRUE(NavigateToURL(shell(), InsecureLoopbackURL(kDefaultPath)));
RenderFrameHostImpl* window = OpenWindowInitialEmptyDoc(root_frame_host());
ASSERT_NE(nullptr, window);
const network::mojom::ClientSecurityStatePtr security_state =
window->BuildClientSecurityState();
ASSERT_FALSE(security_state.is_null());
EXPECT_FALSE(security_state->is_web_secure_context);
}
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessBrowserTest,
IframeInheritsSecureContextForAboutSrcdocFromSecure) {
EXPECT_TRUE(NavigateToURL(shell(), SecureLoopbackURL(kDefaultPath)));
RenderFrameHostImpl* child_frame = AddChildFromSrcdoc(root_frame_host());
ASSERT_NE(nullptr, child_frame);
const network::mojom::ClientSecurityStatePtr security_state =
child_frame->BuildClientSecurityState();
ASSERT_FALSE(security_state.is_null());
EXPECT_TRUE(security_state->is_web_secure_context);
}
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessBrowserTest,
IframeInheritsSecureContextForAboutSrcdocFromInsecure) {
EXPECT_TRUE(NavigateToURL(shell(), InsecureLoopbackURL(kDefaultPath)));
RenderFrameHostImpl* child_frame = AddChildFromSrcdoc(root_frame_host());
ASSERT_NE(nullptr, child_frame);
const network::mojom::ClientSecurityStatePtr security_state =
child_frame->BuildClientSecurityState();
ASSERT_FALSE(security_state.is_null());
EXPECT_FALSE(security_state->is_web_secure_context);
}
IN_PROC_BROWSER_TEST_F(
PrivateNetworkAccessBrowserTest,
SandboxedIframeInheritsSecureContextForAboutSrcdocFromSecure) {
EXPECT_TRUE(NavigateToURL(shell(), SecureLoopbackURL(kDefaultPath)));
RenderFrameHostImpl* child_frame =
AddSandboxedChildFromSrcdoc(root_frame_host());
ASSERT_NE(nullptr, child_frame);
const network::mojom::ClientSecurityStatePtr security_state =
child_frame->BuildClientSecurityState();
ASSERT_FALSE(security_state.is_null());
EXPECT_TRUE(security_state->is_web_secure_context);
}
IN_PROC_BROWSER_TEST_F(
PrivateNetworkAccessBrowserTest,
SandboxedIframeInheritsSecureContextForAboutSrcdocFromInsecure) {
EXPECT_TRUE(NavigateToURL(shell(), InsecureLoopbackURL(kDefaultPath)));
RenderFrameHostImpl* child_frame =
AddSandboxedChildFromSrcdoc(root_frame_host());
ASSERT_NE(nullptr, child_frame);
const network::mojom::ClientSecurityStatePtr security_state =
child_frame->BuildClientSecurityState();
ASSERT_FALSE(security_state.is_null());
EXPECT_FALSE(security_state->is_web_secure_context);
}
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessBrowserTest,
IframeInheritsSecureContextForDataURLFromSecure) {
EXPECT_TRUE(NavigateToURL(shell(), SecureLoopbackURL(kDefaultPath)));
RenderFrameHostImpl* child_frame = AddChildFromDataURL(root_frame_host());
ASSERT_NE(nullptr, child_frame);
const network::mojom::ClientSecurityStatePtr security_state =
child_frame->BuildClientSecurityState();
ASSERT_FALSE(security_state.is_null());
EXPECT_TRUE(security_state->is_web_secure_context);
}
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessBrowserTest,
IframeInheritsSecureContextForDataURLFromInsecure) {
EXPECT_TRUE(NavigateToURL(shell(), InsecureLoopbackURL(kDefaultPath)));
RenderFrameHostImpl* child_frame = AddChildFromDataURL(root_frame_host());
ASSERT_NE(nullptr, child_frame);
const network::mojom::ClientSecurityStatePtr security_state =
child_frame->BuildClientSecurityState();
ASSERT_FALSE(security_state.is_null());
EXPECT_FALSE(security_state->is_web_secure_context);
}
IN_PROC_BROWSER_TEST_F(
PrivateNetworkAccessBrowserTest,
SandboxedIframeInheritsSecureContextForDataURLFromSecure) {
EXPECT_TRUE(NavigateToURL(shell(), SecureLoopbackURL(kDefaultPath)));
RenderFrameHostImpl* child_frame =
AddSandboxedChildFromDataURL(root_frame_host());
ASSERT_NE(nullptr, child_frame);
const network::mojom::ClientSecurityStatePtr security_state =
child_frame->BuildClientSecurityState();
ASSERT_FALSE(security_state.is_null());
EXPECT_TRUE(security_state->is_web_secure_context);
}
IN_PROC_BROWSER_TEST_P(
PrivateNetworkAccessSandboxedDataBrowserTest,
SandboxedIframeInheritsSecureContextForDataURLFromInsecure) {
EXPECT_TRUE(NavigateToURL(shell(), InsecureLoopbackURL(kDefaultPath)));
RenderFrameHostImpl* child_frame =
AddSandboxedChildFromDataURL(root_frame_host());
ASSERT_NE(nullptr, child_frame);
const network::mojom::ClientSecurityStatePtr security_state =
child_frame->BuildClientSecurityState();
ASSERT_FALSE(security_state.is_null());
EXPECT_FALSE(security_state->is_web_secure_context);
}
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessBrowserTest,
IframeInheritsSecureContextForJavascriptURLFromSecure) {
EXPECT_TRUE(NavigateToURL(shell(), SecureLoopbackURL(kDefaultPath)));
RenderFrameHostImpl* child_frame =
AddChildFromJavascriptURL(root_frame_host());
ASSERT_NE(nullptr, child_frame);
const network::mojom::ClientSecurityStatePtr security_state =
child_frame->BuildClientSecurityState();
ASSERT_FALSE(security_state.is_null());
EXPECT_TRUE(security_state->is_web_secure_context);
}
IN_PROC_BROWSER_TEST_F(
PrivateNetworkAccessBrowserTest,
IframeInheritsSecureContextForJavascriptURLFromInsecure) {
EXPECT_TRUE(NavigateToURL(shell(), InsecureLoopbackURL(kDefaultPath)));
RenderFrameHostImpl* child_frame =
AddChildFromJavascriptURL(root_frame_host());
ASSERT_NE(nullptr, child_frame);
const network::mojom::ClientSecurityStatePtr security_state =
child_frame->BuildClientSecurityState();
ASSERT_FALSE(security_state.is_null());
EXPECT_FALSE(security_state->is_web_secure_context);
}
IN_PROC_BROWSER_TEST_F(
PrivateNetworkAccessBrowserTest,
OpeneeInheritsSecureContextForJavascriptURLFromInsecure) {
EXPECT_TRUE(NavigateToURL(shell(), InsecureLoopbackURL(kDefaultPath)));
RenderFrameHostImpl* window = OpenWindowFromJavascriptURL(root_frame_host());
ASSERT_NE(nullptr, window);
const network::mojom::ClientSecurityStatePtr security_state =
window->BuildClientSecurityState();
ASSERT_FALSE(security_state.is_null());
EXPECT_FALSE(security_state->is_web_secure_context);
}
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessBrowserTest,
OpeneeInheritsSecureContextForJavascriptURLFromSecure) {
EXPECT_TRUE(NavigateToURL(shell(), SecureLoopbackURL(kDefaultPath)));
RenderFrameHostImpl* window = OpenWindowFromJavascriptURL(root_frame_host());
ASSERT_NE(nullptr, window);
const network::mojom::ClientSecurityStatePtr security_state =
window->BuildClientSecurityState();
ASSERT_FALSE(security_state.is_null());
EXPECT_TRUE(security_state->is_web_secure_context);
}
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessBrowserTest,
IframeInheritsSecureContextForBlobURLFromSecure) {
EXPECT_TRUE(NavigateToURL(shell(), SecureLoopbackURL(kDefaultPath)));
RenderFrameHostImpl* child_frame = AddChildFromBlob(root_frame_host());
ASSERT_NE(nullptr, child_frame);
const network::mojom::ClientSecurityStatePtr security_state =
child_frame->BuildClientSecurityState();
ASSERT_FALSE(security_state.is_null());
EXPECT_TRUE(security_state->is_web_secure_context);
}
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessBrowserTest,
IframeInheritsSecureContextForBlobURLFromInsecure) {
EXPECT_TRUE(NavigateToURL(shell(), InsecureLoopbackURL(kDefaultPath)));
RenderFrameHostImpl* child_frame = AddChildFromBlob(root_frame_host());
ASSERT_NE(nullptr, child_frame);
const network::mojom::ClientSecurityStatePtr security_state =
child_frame->BuildClientSecurityState();
ASSERT_FALSE(security_state.is_null());
EXPECT_FALSE(security_state->is_web_secure_context);
}
IN_PROC_BROWSER_TEST_F(
PrivateNetworkAccessBrowserTest,
SandboxedIframeInheritsSecureContextForBlobURLFromSecure) {
EXPECT_TRUE(NavigateToURL(shell(), SecureLoopbackURL(kDefaultPath)));
RenderFrameHostImpl* child_frame =
AddSandboxedChildFromBlob(root_frame_host());
ASSERT_NE(nullptr, child_frame);
const network::mojom::ClientSecurityStatePtr security_state =
child_frame->BuildClientSecurityState();
ASSERT_FALSE(security_state.is_null());
EXPECT_TRUE(security_state->is_web_secure_context);
}
IN_PROC_BROWSER_TEST_F(
PrivateNetworkAccessBrowserTest,
SandboxedIframeInheritsSecureContextForBlobURLFromInsecure) {
EXPECT_TRUE(NavigateToURL(shell(), InsecureLoopbackURL(kDefaultPath)));
RenderFrameHostImpl* child_frame =
AddSandboxedChildFromBlob(root_frame_host());
ASSERT_NE(nullptr, child_frame);
const network::mojom::ClientSecurityStatePtr security_state =
child_frame->BuildClientSecurityState();
ASSERT_FALSE(security_state.is_null());
EXPECT_FALSE(security_state->is_web_secure_context);
}
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessBrowserTest,
OpeneeInheritsSecureContextForBlobURLFromSecure) {
EXPECT_TRUE(NavigateToURL(shell(), SecureLoopbackURL(kDefaultPath)));
RenderFrameHostImpl* window = OpenWindowFromBlob(root_frame_host());
ASSERT_NE(nullptr, window);
const network::mojom::ClientSecurityStatePtr security_state =
window->BuildClientSecurityState();
ASSERT_FALSE(security_state.is_null());
EXPECT_TRUE(security_state->is_web_secure_context);
}
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessBrowserTest,
OpeneeInheritsSecureContextForBlobURLFromInsecure) {
EXPECT_TRUE(NavigateToURL(shell(), InsecureLoopbackURL(kDefaultPath)));
RenderFrameHostImpl* window = OpenWindowFromBlob(root_frame_host());
ASSERT_NE(nullptr, window);
const network::mojom::ClientSecurityStatePtr security_state =
window->BuildClientSecurityState();
ASSERT_FALSE(security_state.is_null());
EXPECT_FALSE(security_state->is_web_secure_context);
}
// ====================================
// PRIVATE NETWORK REQUEST POLICY TESTS
// ====================================
//
// These tests verify the correct setting of
// `ClientSecurityState.private_network_request_policy` in various situations.
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessBrowserTestDisableWebSecurity,
PrivateNetworkPolicyIsAllowInsecure) {
EXPECT_TRUE(NavigateToURL(shell(), InsecurePublicURL(kDefaultPath)));
const network::mojom::ClientSecurityStatePtr security_state =
root_frame_host()->BuildClientSecurityState();
ASSERT_FALSE(security_state.is_null());
EXPECT_FALSE(security_state->is_web_secure_context);
EXPECT_EQ(security_state->private_network_request_policy,
network::mojom::PrivateNetworkRequestPolicy::kAllow);
}
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessBrowserTestDisableWebSecurity,
PrivateNetworkPolicyIsAllowSecure) {
EXPECT_TRUE(NavigateToURL(shell(), SecurePublicURL(kDefaultPath)));
const network::mojom::ClientSecurityStatePtr security_state =
root_frame_host()->BuildClientSecurityState();
ASSERT_FALSE(security_state.is_null());
EXPECT_TRUE(security_state->is_web_secure_context);
EXPECT_EQ(security_state->private_network_request_policy,
network::mojom::PrivateNetworkRequestPolicy::kAllow);
}
// This test verifies that with the blocking feature disabled, the private
// network request policy used by RenderFrameHostImpl is to warn about requests
// from non-secure contexts.
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessBrowserTestNoBlocking,
PrivateNetworkPolicyIsWarnByDefault) {
EXPECT_TRUE(NavigateToURL(shell(), InsecurePublicURL(kDefaultPath)));
const network::mojom::ClientSecurityStatePtr security_state =
root_frame_host()->BuildClientSecurityState();
ASSERT_FALSE(security_state.is_null());
EXPECT_FALSE(security_state->is_web_secure_context);
EXPECT_EQ(security_state->private_network_request_policy,
network::mojom::PrivateNetworkRequestPolicy::kWarn);
}
// This test verifies that with the blocking feature disabled, the private
// network request policy used by RenderFrameHostImpl is to send unenforced
// preflight requests from secure contexts.
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessBrowserTestNoBlocking,
PrivateNetworkPolicyIsWarnByDefaultForSecureContexts) {
EXPECT_TRUE(NavigateToURL(shell(), SecurePublicURL(kDefaultPath)));
const network::mojom::ClientSecurityStatePtr security_state =
root_frame_host()->BuildClientSecurityState();
ASSERT_FALSE(security_state.is_null());
EXPECT_TRUE(security_state->is_web_secure_context);
EXPECT_EQ(security_state->private_network_request_policy,
network::mojom::PrivateNetworkRequestPolicy::kAllow);
}
INSTANTIATE_TEST_SUITE_P(
,
PrivateNetworkAccessBrowserTestWithBlockInsteadOfWarnOption,
testing::Values(false, true));
INSTANTIATE_TEST_SUITE_P(,
PrivateNetworkAccessSandboxedDataBrowserTest,
testing::Bool(),
[](const testing::TestParamInfo<bool>& info) {
return info.param
? "OriginKeyedProcessesByDefault_disabled"
: "OriginKeyedProcessesByDefault_enabled";
});
// This test verifies that by default, the private network request policy used
// by RenderFrameHostImpl for requests is set to block requests from non-secure
// contexts in the `public` address space.
IN_PROC_BROWSER_TEST_P(
PrivateNetworkAccessBrowserTestWithBlockInsteadOfWarnOption,
PrivateNetworkPolicyIsBlockByDefaultForInsecurePublic) {
PolicyTestContentBrowserClient client;
bool block_instead_of_warn = GetParam();
if (block_instead_of_warn) {
client.SetBlockInsteadOfWarn();
}
EXPECT_TRUE(NavigateToURL(shell(), InsecurePublicURL(kDefaultPath)));
const network::mojom::ClientSecurityStatePtr security_state =
root_frame_host()->BuildClientSecurityState();
ASSERT_FALSE(security_state.is_null());
EXPECT_FALSE(security_state->is_web_secure_context);
EXPECT_EQ(security_state->private_network_request_policy,
network::mojom::PrivateNetworkRequestPolicy::kBlock);
}
// This test verifies that by default, the private network request policy used
// by RenderFrameHostImpl for requests is set to allow requests from non-secure
// contexts in the `local` address space with a warning.
IN_PROC_BROWSER_TEST_P(
PrivateNetworkAccessBrowserTestWithBlockInsteadOfWarnOption,
PrivateNetworkPolicyForInsecureLocal) {
PolicyTestContentBrowserClient client;
bool block_instead_of_warn = GetParam();
if (block_instead_of_warn) {
client.SetBlockInsteadOfWarn();
}
EXPECT_TRUE(NavigateToURL(shell(), InsecureLocalURL(kDefaultPath)));
const network::mojom::ClientSecurityStatePtr security_state =
root_frame_host()->BuildClientSecurityState();
ASSERT_FALSE(security_state.is_null());
EXPECT_FALSE(security_state->is_web_secure_context);
EXPECT_EQ(security_state->private_network_request_policy,
block_instead_of_warn
? network::mojom::PrivateNetworkRequestPolicy::kBlock
: network::mojom::PrivateNetworkRequestPolicy::kWarn);
}
// This test verifies that when the right feature is enabled, the private
// network request policy used by RenderFrameHostImpl for requests is set to
// block requests from non-secure contexts in the local address space.
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessBrowserTestBlockFromPrivate,
PrivateNetworkPolicyIsBlockForInsecureLocal) {
EXPECT_TRUE(NavigateToURL(shell(), InsecureLocalURL(kDefaultPath)));
const network::mojom::ClientSecurityStatePtr security_state =
root_frame_host()->BuildClientSecurityState();
ASSERT_FALSE(security_state.is_null());
EXPECT_FALSE(security_state->is_web_secure_context);
EXPECT_EQ(security_state->private_network_request_policy,
network::mojom::PrivateNetworkRequestPolicy::kBlock);
}
// This test verifies that by default, the private network request policy used
// by RenderFrameHostImpl for requests is set to allow requests from non-secure
// contexts in the `unknown` address space.
IN_PROC_BROWSER_TEST_P(
PrivateNetworkAccessBrowserTestWithBlockInsteadOfWarnOption,
PrivateNetworkPolicyIsAllowByDefaultForInsecureUnknown) {
PolicyTestContentBrowserClient client;
bool block_instead_of_warn = GetParam();
if (block_instead_of_warn) {
client.SetBlockInsteadOfWarn();
}
EXPECT_TRUE(NavigateToURL(shell(), GURL("data:text/html,foo")));
const network::mojom::ClientSecurityStatePtr security_state =
root_frame_host()->BuildClientSecurityState();
ASSERT_FALSE(security_state.is_null());
EXPECT_FALSE(security_state->is_web_secure_context);
EXPECT_EQ(security_state->private_network_request_policy,
network::mojom::PrivateNetworkRequestPolicy::kAllow);
}
// This test verifies that when the right feature is enabled, the private
// network request policy used by RenderFrameHostImpl for requests is set to
// block requests from non-secure contexts in the `unknown` address space.
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessBrowserTestBlockFromUnknown,
PrivateNetworkPolicyIsBlockForInsecureUnknown) {
EXPECT_TRUE(NavigateToURL(shell(), GURL("data:text/html,foo")));
const network::mojom::ClientSecurityStatePtr security_state =
root_frame_host()->BuildClientSecurityState();
ASSERT_FALSE(security_state.is_null());
EXPECT_FALSE(security_state->is_web_secure_context);
EXPECT_EQ(security_state->private_network_request_policy,
network::mojom::PrivateNetworkRequestPolicy::kBlock);
}
// This test verifies that by default, the private network request policy used
// by RenderFrameHostImpl for requests is set to allow requests from secure
// contexts.
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessBrowserTestNoPreflights,
PrivateNetworkPolicyIsAllowByDefaultForSecureContexts) {
EXPECT_TRUE(NavigateToURL(shell(), SecurePublicURL(kDefaultPath)));
const network::mojom::ClientSecurityStatePtr security_state =
root_frame_host()->BuildClientSecurityState();
ASSERT_FALSE(security_state.is_null());
EXPECT_TRUE(security_state->is_web_secure_context);
EXPECT_EQ(security_state->private_network_request_policy,
network::mojom::PrivateNetworkRequestPolicy::kAllow);
}
// This test verifies that when sending preflights is enabled, the private
// network request policy for secure contexts is `kPreflightWarn`.
IN_PROC_BROWSER_TEST_P(
PrivateNetworkAccessBrowserTestWithBlockInsteadOfWarnOption,
PrivateNetworkPolicyForSecureContexts) {
PolicyTestContentBrowserClient client;
bool block_instead_of_warn = GetParam();
if (block_instead_of_warn) {
client.SetBlockInsteadOfWarn();
}
EXPECT_TRUE(NavigateToURL(shell(), SecurePublicURL(kDefaultPath)));
const network::mojom::ClientSecurityStatePtr security_state =
root_frame_host()->BuildClientSecurityState();
ASSERT_FALSE(security_state.is_null());
EXPECT_TRUE(security_state->is_web_secure_context);
EXPECT_EQ(security_state->private_network_request_policy,
block_instead_of_warn
? network::mojom::PrivateNetworkRequestPolicy::kPreflightBlock
: network::mojom::PrivateNetworkRequestPolicy::kPreflightWarn);
}
// This test verifies that blocking insecure private network requests from the
// `kPublic` address space takes precedence over sending preflight requests.
IN_PROC_BROWSER_TEST_P(
PrivateNetworkAccessBrowserTestWithBlockInsteadOfWarnOption,
PrivateNetworkPolicyIsBlockForInsecurePublic) {
PolicyTestContentBrowserClient client;
bool block_instead_of_warn = GetParam();
if (block_instead_of_warn) {
client.SetBlockInsteadOfWarn();
}
EXPECT_TRUE(NavigateToURL(shell(), InsecurePublicURL(kDefaultPath)));
const network::mojom::ClientSecurityStatePtr security_state =
root_frame_host()->BuildClientSecurityState();
ASSERT_FALSE(security_state.is_null());
EXPECT_FALSE(security_state->is_web_secure_context);
EXPECT_EQ(security_state->private_network_request_policy,
network::mojom::PrivateNetworkRequestPolicy::kBlock);
}
// This test verifies that when enforcing preflights is enabled, the private
// network request policy for secure contexts is `kPreflightBlock`.
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessBrowserTestRespectPreflightResults,
PrivateNetworkPolicyIsPreflightBlockForSecureContexts) {
EXPECT_TRUE(NavigateToURL(shell(), SecurePublicURL(kDefaultPath)));
const network::mojom::ClientSecurityStatePtr security_state =
root_frame_host()->BuildClientSecurityState();
ASSERT_FALSE(security_state.is_null());
EXPECT_TRUE(security_state->is_web_secure_context);
EXPECT_EQ(security_state->private_network_request_policy,
network::mojom::PrivateNetworkRequestPolicy::kPreflightBlock);
}
// This test verifies that when enforcing preflights is enabled, the private
// network request policy for non-secure contexts in the `kLocal` address
// space is `kPreflightBlock`.
// This checks that as long as the "block from insecure private" feature flag
// is not enabled, we will only show warnings for these requests.
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessBrowserTestRespectPreflightResults,
PrivateNetworkPolicyIsWarnForInsecureLocal) {
EXPECT_TRUE(NavigateToURL(shell(), InsecureLocalURL(kDefaultPath)));
const network::mojom::ClientSecurityStatePtr security_state =
root_frame_host()->BuildClientSecurityState();
ASSERT_FALSE(security_state.is_null());
EXPECT_FALSE(security_state->is_web_secure_context);
EXPECT_EQ(security_state->private_network_request_policy,
network::mojom::PrivateNetworkRequestPolicy::kWarn);
}
// This test verifies that blocking insecure private network requests from the
// `kPublic` address space takes precedence over enforcing preflight requests.
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessBrowserTestRespectPreflightResults,
PrivateNetworkPolicyIsBlockForInsecurePublic) {
EXPECT_TRUE(NavigateToURL(shell(), InsecurePublicURL(kDefaultPath)));
const network::mojom::ClientSecurityStatePtr security_state =
root_frame_host()->BuildClientSecurityState();
ASSERT_FALSE(security_state.is_null());
EXPECT_FALSE(security_state->is_web_secure_context);
EXPECT_EQ(security_state->private_network_request_policy,
network::mojom::PrivateNetworkRequestPolicy::kBlock);
}
// This test verifies that child frames with distinct origins from their parent
// do not inherit their private network request policy, which is based on the
// origin of the child document instead.
// TODO (crbug.com/324679506) : Fix the test.
IN_PROC_BROWSER_TEST_F(
PrivateNetworkAccessBrowserTest,
DISABLED_PrivateNetworkRequestPolicyCalculatedPerOrigin) {
GURL url = InsecurePublicURL(kDefaultPath);
PolicyTestContentBrowserClient client;
client.SetAllowInsecurePrivateNetworkRequestsFrom(url::Origin::Create(url));
EXPECT_TRUE(NavigateToURL(shell(), url));
RenderFrameHostImpl* child_frame =
AddChildFromURL(root_frame_host(), InsecureLoopbackURL(kDefaultPath));
network::mojom::ClientSecurityStatePtr security_state =
child_frame->BuildClientSecurityState();
ASSERT_FALSE(security_state.is_null());
EXPECT_FALSE(security_state->is_web_secure_context);
EXPECT_EQ(security_state->private_network_request_policy,
network::mojom::PrivateNetworkRequestPolicy::kBlock);
}
// This test verifies that the initial empty document, which inherits its origin
// from the document creator, also inherits its private network request policy.
IN_PROC_BROWSER_TEST_F(
PrivateNetworkAccessBrowserTest,
PrivateNetworkRequestPolicyInheritedWithOriginForInitialEmptyDoc) {
GURL url = InsecurePublicURL(kDefaultPath);
PolicyTestContentBrowserClient client;
client.SetAllowInsecurePrivateNetworkRequestsFrom(url::Origin::Create(url));
EXPECT_TRUE(NavigateToURL(shell(), url));
RenderFrameHostImpl* child_frame = AddChildInitialEmptyDoc(root_frame_host());
network::mojom::ClientSecurityStatePtr security_state =
child_frame->BuildClientSecurityState();
ASSERT_FALSE(security_state.is_null());
EXPECT_EQ(security_state->private_network_request_policy,
network::mojom::PrivateNetworkRequestPolicy::kAllow);
}
// This test verifies that `about:blank` iframes, which inherit their origin
// from the navigation initiator, also inherit their private network request
// policy.
IN_PROC_BROWSER_TEST_F(
PrivateNetworkAccessBrowserTest,
PrivateNetworkRequestPolicyInheritedWithOriginForAboutBlank) {
GURL url = InsecurePublicURL(kDefaultPath);
PolicyTestContentBrowserClient client;
client.SetAllowInsecurePrivateNetworkRequestsFrom(url::Origin::Create(url));
EXPECT_TRUE(NavigateToURL(shell(), url));
RenderFrameHostImpl* child_frame = AddChildFromAboutBlank(root_frame_host());
network::mojom::ClientSecurityStatePtr security_state =
child_frame->BuildClientSecurityState();
ASSERT_FALSE(security_state.is_null());
EXPECT_EQ(security_state->private_network_request_policy,
network::mojom::PrivateNetworkRequestPolicy::kAllow);
}
// This test verifies that `data:` iframes, which commit an opaque origin
// derived from the navigation initiator's origin, do not inherit their private
// network request policy.
IN_PROC_BROWSER_TEST_F(
PrivateNetworkAccessBrowserTest,
PrivateNetworkRequestPolicyNotInheritedWithOriginForDataURL) {
GURL url = InsecurePublicURL(kDefaultPath);
PolicyTestContentBrowserClient client;
client.SetAllowInsecurePrivateNetworkRequestsFrom(url::Origin::Create(url));
EXPECT_TRUE(NavigateToURL(shell(), url));
RenderFrameHostImpl* child_frame = AddChildFromDataURL(root_frame_host());
network::mojom::ClientSecurityStatePtr security_state =
child_frame->BuildClientSecurityState();
ASSERT_FALSE(security_state.is_null());
EXPECT_FALSE(security_state->is_web_secure_context);
EXPECT_EQ(security_state->private_network_request_policy,
network::mojom::PrivateNetworkRequestPolicy::kBlock);
}
// This test verifies that sandboxed iframes, which commit an opaque origin
// derived from the navigation initiator's origin, do not inherit their private
// network request policy.
IN_PROC_BROWSER_TEST_F(
PrivateNetworkAccessBrowserTest,
PrivateNetworkRequestPolicyNotInheritedForSandboxedInitialEmptyDoc) {
GURL url = InsecurePublicURL(kDefaultPath);
PolicyTestContentBrowserClient client;
client.SetAllowInsecurePrivateNetworkRequestsFrom(url::Origin::Create(url));
EXPECT_TRUE(NavigateToURL(shell(), url));
RenderFrameHostImpl* child_frame =
AddSandboxedChildInitialEmptyDoc(root_frame_host());
network::mojom::ClientSecurityStatePtr security_state =
child_frame->BuildClientSecurityState();
ASSERT_FALSE(security_state.is_null());
EXPECT_FALSE(security_state->is_web_secure_context);
EXPECT_EQ(security_state->private_network_request_policy,
network::mojom::PrivateNetworkRequestPolicy::kBlock);
}
// This test verifies that sandboxed iframes, which commit an opaque origin
// derived from the navigation initiator's origin, do not inherit their private
// network request policy. "about:blank" behaves slightly differently from the
// initial empty doc in code, but should have the same policy in the end.
IN_PROC_BROWSER_TEST_F(
PrivateNetworkAccessBrowserTest,
PrivateNetworkRequestPolicyNotInheritedForSandboxedAboutBlank) {
GURL url = InsecurePublicURL(kDefaultPath);
PolicyTestContentBrowserClient client;
client.SetAllowInsecurePrivateNetworkRequestsFrom(url::Origin::Create(url));
EXPECT_TRUE(NavigateToURL(shell(), url));
RenderFrameHostImpl* child_frame =
AddSandboxedChildFromAboutBlank(root_frame_host());
network::mojom::ClientSecurityStatePtr security_state =
child_frame->BuildClientSecurityState();
ASSERT_FALSE(security_state.is_null());
EXPECT_FALSE(security_state->is_web_secure_context);
EXPECT_EQ(security_state->private_network_request_policy,
network::mojom::PrivateNetworkRequestPolicy::kBlock);
}
// This test verifies that error pages have a set private network request
// policy of `kAllow` irrespective of the navigation initiator.
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessBrowserTest,
PrivateNetworkRequestPolicyIsAllowForErrorPage) {
GURL url = InsecurePublicURL(kDefaultPath);
EXPECT_TRUE(NavigateToURL(shell(), url));
RenderFrameHostImpl* child_frame =
AddChildFromURL(root_frame_host(), "/close-socket");
network::mojom::ClientSecurityStatePtr security_state =
child_frame->BuildClientSecurityState();
ASSERT_FALSE(security_state.is_null());
EXPECT_FALSE(security_state->is_web_secure_context);
EXPECT_EQ(security_state->private_network_request_policy,
network::mojom::PrivateNetworkRequestPolicy::kAllow);
}
// =======================
// SUBRESOURCE FETCH TESTS
// =======================
//
// These tests verify the behavior of the browser when fetching subresources
// across IP address spaces. When the right features are enabled, private
// network requests are blocked.
// This test mimics the tests below, with all blocking features disabled. It
// verifies that by default requests:
// - from an insecure page with the "treat-as-public-address" CSP directive
// - to a loopback IP address
// are not blocked.
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessBrowserTestNoBlocking,
PrivateNetworkRequestIsNotBlockedByDefault) {
EXPECT_TRUE(NavigateToURL(shell(), InsecureLoopbackURL(kDefaultPath)));
// Check that the page can load a loopback resource.
EXPECT_EQ(true,
EvalJs(root_frame_host(),
FetchSubresourceScript(InsecureLoopbackURL(kCorsPath))));
}
// Check that the `--disable-web-security` command-line switch disables PNA
// checks.
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessBrowserTestDisableWebSecurity,
PrivateNetworkRequestIsNotBlocked) {
EXPECT_TRUE(NavigateToURL(shell(), InsecureLoopbackURL(kDefaultPath)));
// Check that the page can load a loopback resource.
EXPECT_EQ(true,
EvalJs(root_frame_host(),
FetchSubresourceScript(InsecureLoopbackURL(kCorsPath))));
}
// This test verifies that when preflights are disabled, requests:
// - from a secure page with the "treat-as-public-address" CSP directive
// - to a loopback IP address
// are not blocked.
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessBrowserTest,
FromSecureTreatAsPublicToLoopbackIsNotBlocked) {
EXPECT_TRUE(
NavigateToURL(shell(), SecureLoopbackURL(kTreatAsPublicAddressPath)));
// Check that the page can load a loopback resource. We load it from a secure
// origin to avoid running afoul of mixed content restrictions.
EXPECT_EQ(true,
EvalJs(root_frame_host(),
FetchSubresourceScript(OtherSecureLoopbackURL(kCorsPath))));
}
// This test verifies that when preflights are disabled, requests:
// - from a secure page served from a public IP address
// - to a loopback IP address
// are not blocked.
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessBrowserTestNoPreflights,
FromSecurePublicToLoopbackIsNotBlocked) {
EXPECT_TRUE(NavigateToURL(shell(), SecurePublicURL(kDefaultPath)));
// Check that the page can load a loopback resource. We load it from a secure
// origin to avoid running afoul of mixed content restrictions.
EXPECT_EQ(true, EvalJs(root_frame_host(),
FetchSubresourceScript(SecureLoopbackURL(kCorsPath))));
}
// This test verifies that when preflights are sent but not enforced, requests:
// - from a secure page served from a public IP address
// - to a loopback IP address
// - for which the target server does not respond OK to the preflight request
// are not blocked.
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessBrowserTest,
FromSecurePublicToLoopbackIsNotBlocked) {
EXPECT_TRUE(NavigateToURL(shell(), SecurePublicURL(kDefaultPath)));
// Check that the page can load a loopback resource.
//
// We load the resource from a secure origin to avoid running afoul of mixed
// content restrictions.
EXPECT_EQ(true, EvalJs(root_frame_host(),
FetchSubresourceScript(SecureLoopbackURL(kCorsPath))));
}
// This test verifies that when preflights are sent and enforced, requests:
// - from a secure page served from a public IP address
// - to a loopback IP address
// - when the target server does not respond OK to the preflight request
// are blocked.
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessBrowserTestRespectPreflightResults,
FromSecurePublicToLoopbackIsBlocked) {
EXPECT_TRUE(NavigateToURL(shell(), SecurePublicURL(kDefaultPath)));
// We load the resource from a secure origin to avoid running afoul of mixed
// content restrictions.
EXPECT_EQ(false,
EvalJs(root_frame_host(),
FetchSubresourceScript(SecureLoopbackURL(kCorsPath))));
}
// This test verifies that when preflights are disabled, requests:
// - from a secure page served from a local IP address
// - to a loopback IP address
// are not blocked.
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessBrowserTestNoPreflights,
FromSecureLocalToLoopbackIsNotBlocked) {
EXPECT_TRUE(NavigateToURL(shell(), SecureLocalURL(kDefaultPath)));
// Check that the page can load a loopback resource. We load it from a secure
// origin to avoid running afoul of mixed content restrictions.
EXPECT_EQ(true, EvalJs(root_frame_host(),
FetchSubresourceScript(SecureLoopbackURL(kCorsPath))));
}
// This test verifies that when preflights are sent but not enforced, requests:
// - from a secure page served from a local IP address
// - to a loopback IP address
// - for which the target server does not respond OK to the preflight request
// are not blocked.
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessBrowserTest,
FromSecureLocalToLoopbackIsNotBlocked) {
EXPECT_TRUE(NavigateToURL(shell(), SecureLocalURL(kDefaultPath)));
// Check that the page can load a loopback resource.
//
// We load it from a secure origin to avoid running afoul of mixed content
// restrictions.
EXPECT_EQ(true, EvalJs(root_frame_host(),
FetchSubresourceScript(SecureLoopbackURL(kCorsPath))));
}
// This test verifies that when preflights are sent and enforced, requests:
// - from a secure page served from a local IP address
// - to a loopback IP address
// - for which the target server does not respond OK to the preflight request
// are blocked.
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessBrowserTestRespectPreflightResults,
FromSecureLocalToLoopbackIsBlocked) {
EXPECT_TRUE(NavigateToURL(shell(), SecureLocalURL(kDefaultPath)));
// We load the resource from a secure origin to avoid running afoul of mixed
// content restrictions.
EXPECT_EQ(false,
EvalJs(root_frame_host(),
FetchSubresourceScript(SecureLoopbackURL(kCorsPath))));
}
// This test verifies that when preflights are disabled, requests:
// - from a secure page served from a loopback IP address
// - to a loopback IP address
// are not blocked.
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessBrowserTestNoPreflights,
FromSecureLoopbackToLoopbackIsNotBlocked) {
EXPECT_TRUE(NavigateToURL(shell(), SecureLoopbackURL(kDefaultPath)));
// Check that the page can load a loopback resource. We load it from a secure
// origin to avoid running afoul of mixed content restrictions.
EXPECT_EQ(true,
EvalJs(root_frame_host(),
FetchSubresourceScript(OtherSecureLoopbackURL(kCorsPath))));
}
// This test verifies that when preflights are sent but not enforced, requests:
// - from a secure page served from a loopback IP address
// - to a loopback IP address
// are not blocked.
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessBrowserTest,
FromSecureLoopbackToLoopbackIsNotBlocked) {
EXPECT_TRUE(NavigateToURL(shell(), SecureLoopbackURL(kDefaultPath)));
// Check that the page can load a loopback resource. We load it from a secure
// origin to avoid running afoul of mixed content restrictions.
EXPECT_EQ(true,
EvalJs(root_frame_host(),
FetchSubresourceScript(OtherSecureLoopbackURL(kCorsPath))));
}
// This test verifies that when preflights are sent and enforced, requests:
// - from a secure page served from a loopback IP address
// - to a loopback IP address
// - for which the target server does not respond OK to the preflight request
// are not blocked.
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessBrowserTestRespectPreflightResults,
FromSecureLoopbackToLoopbackIsNotBlocked) {
EXPECT_TRUE(NavigateToURL(shell(), SecureLoopbackURL(kDefaultPath)));
// Check that the page can load a loopback resource. We load it from a secure
// origin to avoid running afoul of mixed content restrictions.
EXPECT_EQ(true,
EvalJs(root_frame_host(),
FetchSubresourceScript(OtherSecureLoopbackURL(kCorsPath))));
}
// This test verifies that when preflights are sent but not enforced, requests:
// - from a secure page served from a loopback IP address
// - to a loopback IP address
// - for which the target server responds OK to the preflight request
// are not blocked.
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessBrowserTest,
FromSecurePublicToLoopbackPreflightOK) {
EXPECT_TRUE(NavigateToURL(shell(), SecurePublicURL(kDefaultPath)));
// Check that the page can load a loopback resource. We load it from a secure
// origin to avoid running afoul of mixed content restrictions.
EXPECT_EQ(true, EvalJs(root_frame_host(),
FetchSubresourceScript(SecureLoopbackURL(kPnaPath))));
}
// This test verifies that when preflights are sent and enforced, requests:
// - from a secure page served from a loopback IP address
// - to a loopback IP address
// - for which the target server responds OK to the preflight request
// are not blocked.
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessBrowserTestRespectPreflightResults,
FromSecurePublicToLoopbackPreflightOK) {
EXPECT_TRUE(NavigateToURL(shell(), SecurePublicURL(kDefaultPath)));
// Check that the page can load a loopback resource. We load it from a secure
// origin to avoid running afoul of mixed content restrictions.
EXPECT_EQ(true, EvalJs(root_frame_host(),
FetchSubresourceScript(SecureLoopbackURL(kPnaPath))));
}
// TODO(crbug.com/40221632): Re-enable this test
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessBrowserTestRespectPreflightResults,
DISABLED_PreflightConnectionReusedHttp1) {
EXPECT_TRUE(NavigateToURL(shell(), SecurePublicURL(kDefaultPath)));
EXPECT_EQ(true, EvalJs(root_frame_host(),
FetchSubresourceScript(SecureLoopbackURL(kPnaPath))));
// Expect 3 requests, but only 2 connections:
//
// 1. The initial request opens a connection, then is cancelled when the
// private network request is detected.
// 2. The preflight request likely reuses this connection.
// 3. The actual request opens a new connection (the embedded test server does
// not handle keep-alives).
//
// The socket opened by the initial request is returned to the socket pools
// asynchronously, so there is the potential for a race condition here, if
// the following requests are sent very quickly. Sometimes the preflight
// request might not reuse the initial connection and opens its own. In those
// cases, it is extremely likely that the final request will reuse the first
// socket.
//
// TODO(crbug.com/40221632): Find out why the connection is not re-used
// on Mac 11. Likely culprit is some kind of race condition, since the socket
// closure during 1) above is not synchronized with 2) and 3).
#if BUILDFLAG(IS_MAC)
int connection_count = SecureLoopbackServer().ConnectionCount();
EXPECT_GE(connection_count, 2); // At least 2 connections.
EXPECT_LE(connection_count, 3); // No more than 3 connections.
#else
EXPECT_EQ(SecureLoopbackServer().ConnectionCount(), 2);
#endif
}
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessBrowserTestRespectPreflightResults,
PreflightConnectionReusedHttp2) {
EXPECT_TRUE(NavigateToURL(shell(), SecurePublicURL(kDefaultPath)));
ConnectionCounter counter;
net::EmbeddedTestServer http2_server(
net::EmbeddedTestServer::TYPE_HTTPS,
net::test_server::HttpConnection::Protocol::kHttp2);
http2_server.SetConnectionListener(&counter);
http2_server.AddDefaultHandlers(GetTestDataFilePath());
ASSERT_TRUE(http2_server.Start());
EXPECT_EQ(true,
EvalJs(root_frame_host(),
FetchSubresourceScript(http2_server.GetURL(kPnaPath))));
EXPECT_EQ(counter.count(), 1);
}
// This test verifies that when the right feature is enabled but the content
// browser client overrides it, requests:
// - from an insecure page with the "treat-as-public-address" CSP directive
// - to a loopback IP address
// are not blocked.
IN_PROC_BROWSER_TEST_F(
PrivateNetworkAccessBrowserTest,
FromInsecureTreatAsPublicToLoopbackWithPolicySetToAllowIsNotBlocked) {
GURL url = InsecureLoopbackURL(kTreatAsPublicAddressPath);
PolicyTestContentBrowserClient client;
client.SetAllowInsecurePrivateNetworkRequestsFrom(url::Origin::Create(url));
EXPECT_TRUE(NavigateToURL(shell(), url));
const network::mojom::ClientSecurityStatePtr security_state =
root_frame_host()->BuildClientSecurityState();
ASSERT_FALSE(security_state.is_null());
EXPECT_EQ(security_state->private_network_request_policy,
network::mojom::PrivateNetworkRequestPolicy::kAllow);
// Check that the page can load a loopback resource.
EXPECT_EQ(true,
EvalJs(root_frame_host(),
FetchSubresourceScript(InsecureLoopbackURL(kCorsPath))));
}
// This test verifies that when the right feature is enabled, requests:
// - from an insecure page with the "treat-as-public-address" CSP directive
// - to a loopback IP address
// are blocked.
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessBrowserTest,
FromInsecureTreatAsPublicToLoopbackIsBlocked) {
EXPECT_TRUE(
NavigateToURL(shell(), InsecureLoopbackURL(kTreatAsPublicAddressPath)));
// Check that the page cannot load a loopback resource.
EXPECT_EQ(false,
EvalJs(root_frame_host(),
FetchSubresourceScript(InsecureLoopbackURL(kCorsPath))));
}
// This test verifies that when the right feature is enabled, requests:
// - from an insecure page served by a public IP address
// - to loopback IP addresses
// are blocked.
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessBrowserTest,
FromInsecurePublicToLoopbackIsBlocked) {
EXPECT_TRUE(NavigateToURL(shell(), InsecurePublicURL(kDefaultPath)));
// Check that the page cannot load a loopback resource.
EXPECT_EQ(false,
EvalJs(root_frame_host(),
FetchSubresourceScript(InsecureLoopbackURL(kCorsPath))));
}
// This test verifies that when the right feature is disabled, requests:
// - from an insecure page served by a local IP address
// - to loopback IP addresses
// are not blocked.
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessBrowserTest,
FromInsecureLocalToLoopbackIsNotBlocked) {
EXPECT_TRUE(NavigateToURL(shell(), InsecureLocalURL(kDefaultPath)));
// Check that the page can load a loopback resource.
EXPECT_EQ(true,
EvalJs(root_frame_host(),
FetchSubresourceScript(InsecureLoopbackURL(kCorsPath))));
}
// This test verifies that when the right feature is enabled, requests:
// - from an insecure page served by a local IP address
// - to loopback IP addresses
// are blocked.
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessBrowserTestBlockFromPrivate,
FromInsecureLocalToLoopbackIsBlocked) {
EXPECT_TRUE(NavigateToURL(shell(), InsecureLocalURL(kDefaultPath)));
// Check that the page cannot load a loopback resource.
EXPECT_EQ(false,
EvalJs(root_frame_host(),
FetchSubresourceScript(InsecureLoopbackURL(kCorsPath))));
}
// This test verifies that when the right feature is enabled, requests:
// - from an insecure page served by a loopback IP address
// - to loopback IP addresses
// are not blocked.
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessBrowserTest,
FromInsecureLoopbackToLoopbackIsNotBlocked) {
EXPECT_TRUE(NavigateToURL(shell(), InsecureLoopbackURL(kDefaultPath)));
// Check that the page can load a loopback resource.
EXPECT_EQ(true,
EvalJs(root_frame_host(),
FetchSubresourceScript(InsecureLoopbackURL(kCorsPath))));
}
// This test verifies that when the right feature is enabled, requests:
// - from a secure page with the "treat-as-public-address" CSP directive
// - embedded in an insecure page served from a loopback IP address
// - to loopback IP addresses
// are blocked.
IN_PROC_BROWSER_TEST_F(
PrivateNetworkAccessBrowserTest,
FromSecurePublicEmbeddedInInsecureLoopbackToLoopbackIsBlocked) {
// First navigate to an insecure page served by a loopback IP address.
EXPECT_TRUE(NavigateToURL(shell(), InsecureLoopbackURL(kDefaultPath)));
// Then embed a secure public iframe.
std::string script = JsReplace(
R"(
const iframe = document.createElement("iframe");
iframe.src = $1;
document.body.appendChild(iframe);
)",
SecurePublicURL(kDefaultPath));
EXPECT_TRUE(ExecJs(root_frame_host(), script));
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
ASSERT_EQ(1ul, root_frame_host()->child_count());
RenderFrameHostImpl* child_frame =
root_frame_host()->child_at(0)->current_frame_host();
const network::mojom::ClientSecurityStatePtr security_state =
child_frame->BuildClientSecurityState();
ASSERT_FALSE(security_state.is_null());
// Even though the iframe document was loaded from a secure connection, the
// context is deemed insecure because it was embedded by an insecure context.
EXPECT_FALSE(security_state->is_web_secure_context);
// The address space of the document, however, is not influenced by the
// parent's address space.
EXPECT_EQ(network::mojom::IPAddressSpace::kPublic,
security_state->ip_address_space);
// Check that the iframe cannot load a loopback resource.
EXPECT_EQ(false, EvalJs(child_frame, FetchSubresourceScript(
InsecureLoopbackURL(kCorsPath))));
}
// This test verifies that even when the right feature is enabled, requests:
// - from a non-secure context in the `loopback` IP address space
// - to a subresource cached from a `loopback` IP address
// are not blocked.
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessBrowserTest,
FromInsecureLoopbackToCachedLoopbackIsNotBlocked) {
GURL target = InsecureLoopbackURL(kCacheablePath);
// Cache the resource first. The server receives a GET request.
EXPECT_TRUE(NavigateToURL(shell(), InsecureLoopbackURL(kDefaultPath)));
EXPECT_EQ(true, EvalJs(root_frame_host(), FetchSubresourceScript(target)));
EXPECT_THAT(
InsecureLoopbackServer().request_observer().RequestMethodsForUrl(target),
ElementsAre(METHOD_GET));
// Check that the page can still load the subresource from cache. The server
// does not receive any new request.
EXPECT_EQ(true, EvalJs(root_frame_host(), FetchSubresourceScript(target)));
EXPECT_THAT(
InsecureLoopbackServer().request_observer().RequestMethodsForUrl(target),
ElementsAre(METHOD_GET));
}
// This test verifies that when the right feature is enabled, requests:
// - from a non-secure context in the `public` IP address space
// - to a subresource cached from a `loopback` IP address
// are blocked.
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessBrowserTest,
FromInsecurePublicToCachedLoopbackIsBlocked) {
GURL target = InsecureLoopbackURL(kCacheablePath);
// Cache the resource first, by fetching it from a document in the same IP
// address space. The server receives a GET request.
EXPECT_TRUE(NavigateToURL(shell(), InsecureLoopbackURL(kDefaultPath)));
EXPECT_EQ(true, EvalJs(root_frame_host(), FetchSubresourceScript(target)));
EXPECT_THAT(
InsecureLoopbackServer().request_observer().RequestMethodsForUrl(target),
ElementsAre(METHOD_GET));
// Now navigate to a document in the `public` address space belonging to the
// same site as the previous document (this will use the same cache key).
EXPECT_TRUE(
NavigateToURL(shell(), InsecureLoopbackURL(kTreatAsPublicAddressPath)));
// Check that the page cannot load the resource, even from cache. The server
// does not receive any new request.
EXPECT_EQ(false, EvalJs(root_frame_host(), FetchSubresourceScript(target)));
EXPECT_THAT(
InsecureLoopbackServer().request_observer().RequestMethodsForUrl(target),
ElementsAre(METHOD_GET));
}
// This test verifies that when preflights are sent and enforced, requests:
// - from a secure context in the `loopback` IP address space
// - to a subresource cached from a `loopback` IP address
// - for which the target server does not respond OK to the preflight request
// are not blocked.
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessBrowserTestRespectPreflightResults,
FromSecureLoopbackToCachedLoopbackIsNotBlocked) {
GURL target = SecureLoopbackURL(kCacheablePath);
// Cache the resource first. The server receives a GET request.
EXPECT_TRUE(NavigateToURL(shell(), SecureLoopbackURL(kDefaultPath)));
EXPECT_EQ(true, EvalJs(root_frame_host(), FetchSubresourceScript(target)));
EXPECT_THAT(
SecureLoopbackServer().request_observer().RequestMethodsForUrl(target),
ElementsAre(METHOD_GET));
// Check that the page can still load the subresource from cache. The server
// does not receive any new request.
EXPECT_EQ(true, EvalJs(root_frame_host(), FetchSubresourceScript(target)));
EXPECT_THAT(
SecureLoopbackServer().request_observer().RequestMethodsForUrl(target),
ElementsAre(METHOD_GET));
}
// This test verifies that when preflights are sent but not enforced, requests:
// - from a secure page served in the `public` IP address space
// - to a subresource cached from a `loopback` IP address
// - for which the target server does not respond OK to the preflight request
// are not blocked.
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessBrowserTest,
FromSecurePublicToCachedLoopbackIsNotBlocked) {
GURL target = OtherSecureLoopbackURL(kCacheableCorsPath);
// Cache the resource first.
EXPECT_TRUE(NavigateToURL(shell(), SecureLoopbackURL(kDefaultPath)));
EXPECT_EQ(true, EvalJs(root_frame_host(), FetchSubresourceScript(target)));
EXPECT_THAT(
SecureLoopbackServer().request_observer().RequestMethodsForUrl(target),
ElementsAre(METHOD_GET));
EXPECT_TRUE(
NavigateToURL(shell(), SecureLoopbackURL(kTreatAsPublicAddressPath)));
// Check that the page can still load the subresource from cache.
EXPECT_EQ(true, EvalJs(root_frame_host(), FetchSubresourceScript(target)));
// The server receives a preflight request because the preflight response is
// not cached, but no second GET request.
EXPECT_THAT(
SecureLoopbackServer().request_observer().RequestMethodsForUrl(target),
ElementsAre(METHOD_GET, METHOD_OPTIONS));
}
// This test verifies that when preflights are sent and enforced, requests:
// - from a secure page served in the `public` IP address space
// - to a subresource cached from a `loopback` IP address
// - for which the target server does not respond OK to the preflight request
// are blocked.
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessBrowserTestRespectPreflightResults,
FromSecurePublicToCachedLoopbackIsBlocked) {
GURL target = OtherSecureLoopbackURL(kCacheableCorsPath);
// Cache the resource first.
EXPECT_TRUE(NavigateToURL(shell(), SecureLoopbackURL(kDefaultPath)));
EXPECT_EQ(true, EvalJs(root_frame_host(), FetchSubresourceScript(target)));
EXPECT_THAT(
SecureLoopbackServer().request_observer().RequestMethodsForUrl(target),
ElementsAre(METHOD_GET));
EXPECT_TRUE(
NavigateToURL(shell(), SecureLoopbackURL(kTreatAsPublicAddressPath)));
// Check that the page cannot load the subresource from cache.
EXPECT_EQ(false, EvalJs(root_frame_host(), FetchSubresourceScript(target)));
// The server receives a preflight request because the preflight response is
// not cached, but no second GET request.
EXPECT_THAT(
SecureLoopbackServer().request_observer().RequestMethodsForUrl(target),
ElementsAre(METHOD_GET, METHOD_OPTIONS));
}
// This test verifies that when preflights are sent and enforced, requests:
// - from a secure page served in the `public` IP address space
// - to a subresource cached from a `loopback` IP address
// - for which the target server responds OK to the preflight request
// are not blocked.
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessBrowserTestRespectPreflightResults,
FromSecurePublicToCachedLoopbackIsNotBlocked) {
GURL target = OtherSecureLoopbackURL(kCacheablePnaPath);
// Cache the resource first.
EXPECT_TRUE(NavigateToURL(shell(), SecureLoopbackURL(kDefaultPath)));
EXPECT_EQ(true, EvalJs(root_frame_host(), FetchSubresourceScript(target)));
EXPECT_THAT(
SecureLoopbackServer().request_observer().RequestMethodsForUrl(target),
ElementsAre(METHOD_GET));
EXPECT_TRUE(
NavigateToURL(shell(), SecureLoopbackURL(kTreatAsPublicAddressPath)));
// Check that the page can still load the subresource from cache.
EXPECT_EQ(true, EvalJs(root_frame_host(), FetchSubresourceScript(target)));
// The server receives a preflight request because the preflight response is
// not cached, but no second GET request.
EXPECT_THAT(
SecureLoopbackServer().request_observer().RequestMethodsForUrl(target),
ElementsAre(METHOD_GET, METHOD_OPTIONS));
}
// This test verifies that even with the blocking feature disabled, an insecure
// page in the `loopback` address space cannot fetch a `file:` URL.
//
// This is relevant to Private Network Access, since `file:` URLs are considered
// to be in the `loopback` IP address space.
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessBrowserTestNoBlocking,
InsecurePageCannotRequestFile) {
EXPECT_TRUE(NavigateToURL(shell(), InsecureLoopbackURL(kDefaultPath)));
// Check that the page cannot load a `file:` URL.
EXPECT_EQ(false, EvalJs(root_frame_host(), FetchSubresourceScript(GetTestUrl(
"", "empty.html"))));
}
// This test verifies that even with the blocking feature disabled, a secure
// page in the `loopback` address space cannot fetch a `file:` URL.
//
// This is relevant to Private Network Access, since `file:` URLs are considered
// to be in the `loopback` IP address space.
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessBrowserTestNoBlocking,
SecurePageCannotRequestFile) {
EXPECT_TRUE(NavigateToURL(shell(), SecureLoopbackURL(kDefaultPath)));
// Check that the page cannot load a `file:` URL.
EXPECT_EQ(false, EvalJs(root_frame_host(), FetchSubresourceScript(GetTestUrl(
"", "empty.html"))));
}
// This test verifies that if a page redirects after responding to a private
// network request to a server in a different address space, the request does
// not fail.
// Regression test for https://crbug.com/1293891.
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessBrowserTest, Redirect) {
EXPECT_TRUE(NavigateToURL(shell(), SecureLocalURL(kDefaultPath)));
GURL target =
SecureLoopbackURL("/server-redirect?" + SecureLocalURL(kCorsPath).spec());
EXPECT_EQ(true, EvalJs(root_frame_host(), FetchSubresourceScript(target)));
}
// This test verifies that if a request is made for a resource of which a
// partial prefix range of bytes was cached, a preflight is correctly sent for
// the non-cached portion, and the renderer does not crash.
// Regression test for https://crbug.com/1279376.
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessBrowserTest, PrefixRangePreflight) {
EXPECT_TRUE(NavigateToURL(shell(), SecurePublicURL(kDefaultPath)));
const GURL url = SecureLoopbackURL("/echorange?this-is-a-test");
constexpr std::string_view kFetchRangeScript = R"(
(async () => {
const url = $1;
const range = $2;
const headers = {};
if (range) {
headers["Range"] = range;
}
const response = await fetch(url, { headers });
const body = await response.text();
return body;
})()
)";
// Cache a portion of the target resource.
EXPECT_EQ("this", EvalJs(root_frame_host(),
JsReplace(kFetchRangeScript, url, "bytes=0-3")));
// The server received a preflight request, followed by a GET request.
EXPECT_THAT(
SecureLoopbackServer().request_observer().RequestMethodsForUrl(url),
ElementsAre(net::test_server::METHOD_OPTIONS,
net::test_server::METHOD_GET));
// Fetch the whole resource.
EXPECT_EQ(
"this-is-a-test",
EvalJs(root_frame_host(), JsReplace(kFetchRangeScript, url, "bytes=0-")));
// The server received a single GET request for the non-cached suffix. The
// preflight response was previously cached, so there is no second preflight.
EXPECT_THAT(
SecureLoopbackServer().request_observer().RequestMethodsForUrl(url),
ElementsAre(net::test_server::METHOD_OPTIONS,
net::test_server::METHOD_GET, net::test_server::METHOD_GET));
}
// =========================
// WORKER SCRIPT FETCH TESTS
// =========================
namespace {
// Path to a worker script that posts a message to its creator once loaded.
constexpr char kWorkerScriptPath[] = "/workers/post_ready.js";
// Same as above, but with PNA headers set correctly for preflight requests.
constexpr char kWorkerScriptWithPnaHeadersPath[] =
"/workers/post_ready_with_pna_headers.js";
// Instantiates a dedicated worker script from `path`.
// If it loads successfully, the worker should post a message to its creator to
// signal success.
std::string FetchWorkerScript(std::string_view path) {
constexpr char kTemplate[] = R"(
new Promise((resolve) => {
const worker = new Worker($1);
worker.addEventListener("message", () => resolve(true));
worker.addEventListener("error", () => resolve(false));
})
)";
return JsReplace(kTemplate, path);
}
// Path to a worker script that posts a message to each client that connects.
constexpr char kSharedWorkerScriptPath[] = "/workers/shared_post_ready.js";
// Same as above, but with PNA headers set correctly for preflight requests.
constexpr char kSharedWorkerScriptWithPnaHeadersPath[] =
"/workers/shared_post_ready_with_pna_headers.js";
// Instantiates a shared worker script from `path`.
// If it loads successfully, the worker should post a message to each client
// that connects to it to signal success.
std::string FetchSharedWorkerScript(std::string_view path) {
constexpr char kTemplate[] = R"(
new Promise((resolve) => {
const worker = new SharedWorker($1);
worker.port.addEventListener("message", () => resolve(true));
worker.addEventListener("error", () => resolve(false));
worker.port.start();
})
)";
return JsReplace(kTemplate, path);
}
// TODO(crbug.com/40290702): Remove this and replace calls below with
// calls to `EXPECT_EQ` directly once Shared Workers are supported on Android.
void ExpectFetchSharedWorkerScriptResult(bool expected,
const EvalJsResult& result) {
#if !BUILDFLAG(IS_ANDROID)
EXPECT_EQ(expected, result);
#else
EXPECT_FALSE(result.is_ok());
#endif
}
} // namespace
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessBrowserTest,
FetchWorkerFromInsecureTreatAsPublicToLoopback) {
EXPECT_TRUE(
NavigateToURL(shell(), InsecureLoopbackURL(kTreatAsPublicAddressPath)));
EXPECT_EQ(true,
EvalJs(root_frame_host(), FetchWorkerScript(kWorkerScriptPath)));
}
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessBrowserTestForWorkers,
FetchWorkerFromInsecureTreatAsPublicToLoopback) {
EXPECT_TRUE(
NavigateToURL(shell(), InsecureLoopbackURL(kTreatAsPublicAddressPath)));
EXPECT_EQ(false,
EvalJs(root_frame_host(), FetchWorkerScript(kWorkerScriptPath)));
}
IN_PROC_BROWSER_TEST_F(
PrivateNetworkAccessBrowserTestRespectPreflightResultsForWorkers,
FetchWorkerFromInsecureTreatAsPublicToLoopback) {
EXPECT_TRUE(
NavigateToURL(shell(), InsecureLoopbackURL(kTreatAsPublicAddressPath)));
EXPECT_EQ(false,
EvalJs(root_frame_host(), FetchWorkerScript(kWorkerScriptPath)));
}
IN_PROC_BROWSER_TEST_F(
PrivateNetworkAccessBrowserTestRespectPreflightResultsForWorkersWarningOnly,
FetchWorkerFromInsecurePublicToLoopback) {
EXPECT_TRUE(NavigateToURL(shell(), InsecurePublicURL(kDefaultPath)));
EXPECT_EQ(true,
EvalJs(root_frame_host(), FetchWorkerScript(kWorkerScriptPath)));
}
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessBrowserTest,
FetchWorkerFromSecureTreatAsPublicToLoopback) {
EXPECT_TRUE(
NavigateToURL(shell(), SecureLoopbackURL(kTreatAsPublicAddressPath)));
EXPECT_EQ(true,
EvalJs(root_frame_host(), FetchWorkerScript(kWorkerScriptPath)));
}
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessBrowserTestForWorkers,
FetchWorkerFromSecureTreatAsPublicToLoopback) {
EXPECT_TRUE(
NavigateToURL(shell(), SecureLoopbackURL(kTreatAsPublicAddressPath)));
EXPECT_EQ(true,
EvalJs(root_frame_host(), FetchWorkerScript(kWorkerScriptPath)));
}
IN_PROC_BROWSER_TEST_F(
PrivateNetworkAccessBrowserTestRespectPreflightResultsForWorkers,
FetchWorkerFromSecureTreatAsPublicToLoopback) {
EXPECT_TRUE(
NavigateToURL(shell(), SecureLoopbackURL(kTreatAsPublicAddressPath)));
// The request is exempt from Private Network Access checks because it is
// same-origin and the origin is potentially trustworthy.
EXPECT_EQ(true,
EvalJs(root_frame_host(), FetchWorkerScript(kWorkerScriptPath)));
}
IN_PROC_BROWSER_TEST_F(
PrivateNetworkAccessBrowserTestRespectPreflightResultsForWorkersWarningOnly,
FetchWorkerFromSecurePublicToLoopbackFailedPreflight) {
EXPECT_TRUE(NavigateToURL(shell(), SecurePublicURL(kDefaultPath)));
EXPECT_EQ(true,
EvalJs(root_frame_host(), FetchWorkerScript(kWorkerScriptPath)));
}
IN_PROC_BROWSER_TEST_F(
PrivateNetworkAccessBrowserTestRespectPreflightResultsForWorkers,
FetchWorkerFromSecureTreatAsPublicToLoopbackSuccess) {
EXPECT_TRUE(
NavigateToURL(shell(), SecureLoopbackURL(kTreatAsPublicAddressPath)));
EXPECT_EQ(true, EvalJs(root_frame_host(),
FetchWorkerScript(kWorkerScriptWithPnaHeadersPath)));
}
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessBrowserTest,
FetchSharedWorkerFromInsecureTreatAsPublicToLoopback) {
EXPECT_TRUE(
NavigateToURL(shell(), InsecureLoopbackURL(kTreatAsPublicAddressPath)));
ExpectFetchSharedWorkerScriptResult(
true, EvalJs(root_frame_host(),
FetchSharedWorkerScript(kSharedWorkerScriptPath)));
}
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessBrowserTestForWorkers,
FetchSharedWorkerFromInsecureTreatAsPublicToLoopback) {
EXPECT_TRUE(
NavigateToURL(shell(), InsecureLoopbackURL(kTreatAsPublicAddressPath)));
ExpectFetchSharedWorkerScriptResult(
false, EvalJs(root_frame_host(),
FetchSharedWorkerScript(kSharedWorkerScriptPath)));
}
IN_PROC_BROWSER_TEST_F(
PrivateNetworkAccessBrowserTestRespectPreflightResultsForWorkers,
FetchSharedWorkerFromInsecureTreatAsPublicToLoopback) {
EXPECT_TRUE(
NavigateToURL(shell(), InsecureLoopbackURL(kTreatAsPublicAddressPath)));
ExpectFetchSharedWorkerScriptResult(
false, EvalJs(root_frame_host(),
FetchSharedWorkerScript(kSharedWorkerScriptPath)));
}
IN_PROC_BROWSER_TEST_F(
PrivateNetworkAccessBrowserTestRespectPreflightResultsForWorkersWarningOnly,
FetchSharedWorkerFromInsecurePublicToLoopback) {
EXPECT_TRUE(NavigateToURL(shell(), InsecurePublicURL(kDefaultPath)));
ExpectFetchSharedWorkerScriptResult(
true, EvalJs(root_frame_host(),
FetchSharedWorkerScript(kSharedWorkerScriptPath)));
}
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessBrowserTest,
FetchSharedWorkerFromSecureTreatAsPublicToLoopback) {
EXPECT_TRUE(
NavigateToURL(shell(), SecureLoopbackURL(kTreatAsPublicAddressPath)));
ExpectFetchSharedWorkerScriptResult(
true, EvalJs(root_frame_host(),
FetchSharedWorkerScript(kSharedWorkerScriptPath)));
}
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessBrowserTestForWorkers,
FetchSharedWorkerFromSecureTreatAsPublicToLoopback) {
EXPECT_TRUE(
NavigateToURL(shell(), SecureLoopbackURL(kTreatAsPublicAddressPath)));
ExpectFetchSharedWorkerScriptResult(
true, EvalJs(root_frame_host(),
FetchSharedWorkerScript(kSharedWorkerScriptPath)));
}
IN_PROC_BROWSER_TEST_F(
PrivateNetworkAccessBrowserTestRespectPreflightResultsForWorkers,
FetchSharedWorkerFromSecureTreatAsPublicToLoopback) {
EXPECT_TRUE(
NavigateToURL(shell(), SecureLoopbackURL(kTreatAsPublicAddressPath)));
// The request is exempt from Private Network Access checks because it is
// same-origin and the origin is potentially trustworthy.
ExpectFetchSharedWorkerScriptResult(
true, EvalJs(root_frame_host(),
FetchSharedWorkerScript(kSharedWorkerScriptPath)));
}
IN_PROC_BROWSER_TEST_F(
PrivateNetworkAccessBrowserTestRespectPreflightResultsForWorkersWarningOnly,
FetchSharedWorkerFromSecurePublicToLoopbackFailedPreflight) {
EXPECT_TRUE(NavigateToURL(shell(), SecurePublicURL(kDefaultPath)));
ExpectFetchSharedWorkerScriptResult(
true, EvalJs(root_frame_host(),
FetchSharedWorkerScript(kSharedWorkerScriptPath)));
}
IN_PROC_BROWSER_TEST_F(
PrivateNetworkAccessBrowserTestRespectPreflightResultsForWorkers,
FetchSharedWorkerFromSecureTreatAsPublicToLoopbackSuccess) {
EXPECT_TRUE(
NavigateToURL(shell(), SecureLoopbackURL(kTreatAsPublicAddressPath)));
ExpectFetchSharedWorkerScriptResult(
true,
EvalJs(root_frame_host(),
FetchSharedWorkerScript(kSharedWorkerScriptWithPnaHeadersPath)));
}
// ======================
// NAVIGATION FETCH TESTS
// ======================
//
// These tests verify the behavior of the browser when navigating across IP
// address spaces.
//
// Iframe navigations are effectively treated as subresource fetches of the
// initiator document: they are handled by checking the resource's address space
// against the initiator document's address space.
//
// Top-level navigations are never blocked.
//
// TODO(crbug.com/40149351): Revisit this when top-level navigations are
// subject to Private Network Access checks.
// When the `PrivateNetworkAccessForIframes` feature is disabled, iframe fetches
// are not subject to PNA checks.
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessBrowserTest,
IframeFromInsecurePublicToLoopbackIsNotBlocked) {
EXPECT_TRUE(NavigateToURL(shell(), InsecurePublicURL(kDefaultPath)));
GURL url = InsecureLoopbackURL("/empty.html");
TestNavigationManager child_navigation_manager(shell()->web_contents(), url);
AddChildFromURLWithoutWaiting(root_frame_host(), url);
ASSERT_TRUE(child_navigation_manager.WaitForNavigationFinished());
// Check that the child iframe navigated successfully.
EXPECT_TRUE(child_navigation_manager.was_successful());
EXPECT_EQ(url, EvalJs(GetFirstChild(*root_frame_host()),
"document.location.href"));
EXPECT_THAT(
InsecureLoopbackServer().request_observer().RequestMethodsForUrl(url),
ElementsAre(METHOD_GET));
}
// When the `PrivateNetworkAccessForIframes` feature is disabled, iframe fetches
// are not subject to PNA checks.
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessBrowserTest,
IframeFromSecurePublicToLoopbackIsNotBlocked) {
EXPECT_TRUE(NavigateToURL(shell(), SecurePublicURL(kDefaultPath)));
GURL url = SecureLoopbackURL("/empty.html");
TestNavigationManager child_navigation_manager(shell()->web_contents(), url);
AddChildFromURLWithoutWaiting(root_frame_host(), url);
ASSERT_TRUE(child_navigation_manager.WaitForNavigationFinished());
// Check that the child iframe navigated successfully.
EXPECT_TRUE(child_navigation_manager.was_successful());
EXPECT_EQ(url, EvalJs(GetFirstChild(*root_frame_host()),
"document.location.href"));
EXPECT_THAT(
SecureLoopbackServer().request_observer().RequestMethodsForUrl(url),
ElementsAre(METHOD_GET));
}
// This test verifies that when iframe support is enabled in warning-only mode,
// iframe requests:
// - from an insecure page served from a public IP address
// - to a loopback IP address
// are not blocked.
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessBrowserTestForNavigationsWarningOnly,
IframeFromInsecurePublicToLoopbackIsNotBlocked) {
EXPECT_TRUE(NavigateToURL(shell(), InsecurePublicURL(kDefaultPath)));
GURL url = InsecureLoopbackURL("/empty.html");
TestNavigationManager child_navigation_manager(shell()->web_contents(), url);
AddChildFromURLWithoutWaiting(root_frame_host(), url);
ASSERT_TRUE(child_navigation_manager.WaitForNavigationFinished());
// Check that the child iframe fetched successfully.
EXPECT_TRUE(child_navigation_manager.was_successful());
EXPECT_EQ(url, EvalJs(GetFirstChild(*root_frame_host()),
"document.location.href"));
EXPECT_THAT(
InsecureLoopbackServer().request_observer().RequestMethodsForUrl(url),
ElementsAre(METHOD_GET));
}
// This test verifies that when the right feature is enabled, iframe requests:
// - from an insecure page served from a public IP address
// - to a loopback IP address
// are blocked.
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessBrowserTestForNavigations,
IframeFromInsecurePublicToLoopbackIsBlocked) {
EXPECT_TRUE(NavigateToURL(shell(), InsecurePublicURL(kDefaultPath)));
GURL url = InsecureLoopbackURL("/empty.html");
TestNavigationManager child_navigation_manager(shell()->web_contents(), url);
AddChildFromURLWithoutWaiting(root_frame_host(), url);
ASSERT_TRUE(child_navigation_manager.WaitForNavigationFinished());
// Check that the child iframe failed to fetch.
EXPECT_FALSE(child_navigation_manager.was_successful());
RenderFrameHostImpl* child_frame = GetFirstChild(*root_frame_host());
EXPECT_EQ(GURL(kUnreachableWebDataURL),
EvalJs(child_frame, "document.location.href"));
// The frame committed an error page but retains the original URL so that
// reloading the page does the right thing. The committed origin on the other
// hand is opaque, which it would not be if the navigation had succeeded.
EXPECT_EQ(url, child_frame->GetLastCommittedURL());
EXPECT_TRUE(child_frame->GetLastCommittedOrigin().opaque());
// Blocked before we ever sent a request.
EXPECT_THAT(
InsecureLoopbackServer().request_observer().RequestMethodsForUrl(url),
IsEmpty());
}
// Same as above, testing the "treat-as-public-address" CSP directive.
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessBrowserTestForNavigations,
IframeFromInsecureTreatAsPublicToLoopbackIsBlocked) {
EXPECT_TRUE(
NavigateToURL(shell(), InsecureLoopbackURL(kTreatAsPublicAddressPath)));
GURL url = InsecureLoopbackURL("/empty.html");
TestNavigationManager child_navigation_manager(shell()->web_contents(), url);
AddChildFromURLWithoutWaiting(root_frame_host(), url);
ASSERT_TRUE(child_navigation_manager.WaitForNavigationFinished());
// Check that the child iframe failed to fetch.
EXPECT_FALSE(child_navigation_manager.was_successful());
}
// This test verifies that when an iframe navigation fails due to PNA, the
// iframe navigates to an error page, even if it had previously committed a
// document.
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessBrowserTestForNavigations,
FailedNavigationCommitsErrorPage) {
EXPECT_TRUE(NavigateToURL(shell(), InsecurePublicURL(kDefaultPath)));
// First add a child frame, which successfully commits a document.
AddChildFromURL(root_frame_host(), "/empty.html");
GURL url = InsecureLoopbackURL("/empty.html");
TestNavigationManager child_navigation_manager(shell()->web_contents(), url);
// Then try to navigate that frame in a way that fails PNA checks.
EXPECT_TRUE(ExecJs(
root_frame_host(),
JsReplace("document.getElementsByTagName('iframe')[0].src = $1;", url)));
ASSERT_TRUE(child_navigation_manager.WaitForNavigationFinished());
// Check that the child iframe failed to fetch.
EXPECT_FALSE(child_navigation_manager.was_successful());
RenderFrameHostImpl* child_frame = GetFirstChild(*root_frame_host());
EXPECT_EQ(GURL(kUnreachableWebDataURL),
EvalJs(child_frame, "document.location.href"));
// The frame committed an error page but retains the original URL so that
// reloading the page does the right thing. The committed origin on the other
// hand is opaque, which it would not be if the navigation had succeeded.
EXPECT_EQ(url, child_frame->GetLastCommittedURL());
EXPECT_TRUE(child_frame->GetLastCommittedOrigin().opaque());
// Blocked before we ever sent a request.
EXPECT_THAT(
InsecureLoopbackServer().request_observer().RequestMethodsForUrl(url),
IsEmpty());
}
// This test verifies that when iframe support is enabled in warning-only mode,
// iframe requests:
// - from a secure page served from a public IP address
// - to a loopback IP address
// are preceded by a preflight request which is allowed to fail.
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessBrowserTestForNavigationsWarningOnly,
IframeFromSecurePublicToLoopbackIsNotBlocked) {
EXPECT_TRUE(NavigateToURL(shell(), SecurePublicURL(kDefaultPath)));
GURL url = SecureLoopbackURL("/empty.html");
TestNavigationManager child_navigation_manager(shell()->web_contents(), url);
AddChildFromURLWithoutWaiting(root_frame_host(), url);
ASSERT_TRUE(child_navigation_manager.WaitForNavigationFinished());
EXPECT_TRUE(child_navigation_manager.was_successful());
EXPECT_EQ(url, EvalJs(GetFirstChild(*root_frame_host()),
"document.location.href"));
// A preflight request first, then the GET request.
EXPECT_THAT(
SecureLoopbackServer().request_observer().RequestMethodsForUrl(url),
ElementsAre(METHOD_OPTIONS, METHOD_GET));
}
// This test verifies that when the right feature is enabled, iframe requests:
// - from a secure page served from a public IP address
// - to a loopback IP address
// are preceded by a preflight request which must succeed.
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessBrowserTestForNavigations,
IframeFromSecurePublicToLoopbackIsBlocked) {
EXPECT_TRUE(NavigateToURL(shell(), SecurePublicURL(kDefaultPath)));
GURL url = SecureLoopbackURL("/empty.html");
TestNavigationManager child_navigation_manager(shell()->web_contents(), url);
AddChildFromURLWithoutWaiting(root_frame_host(), url);
ASSERT_TRUE(child_navigation_manager.WaitForNavigationFinished());
// Check that the child iframe failed to fetch.
EXPECT_FALSE(child_navigation_manager.was_successful());
RenderFrameHostImpl* child_frame = GetFirstChild(*root_frame_host());
EXPECT_EQ(GURL(kUnreachableWebDataURL),
EvalJs(child_frame, "document.location.href"));
// The frame committed an error page but retains the original URL so that
// reloading the page does the right thing. The committed origin on the other
// hand is opaque, which it would not be if the navigation had succeeded.
EXPECT_EQ(url, child_frame->GetLastCommittedURL());
EXPECT_TRUE(child_frame->GetLastCommittedOrigin().opaque());
// A preflight request only.
EXPECT_THAT(
SecureLoopbackServer().request_observer().RequestMethodsForUrl(url),
ElementsAre(METHOD_OPTIONS));
}
// This test verifies that when the right feature is enabled, iframe requests:
// - from a secure page served from a public IP address
// - to a loopback IP address
// are preceded by a preflight request, to which the server must respond
// correctly.
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessBrowserTestForNavigations,
IframeFromSecurePublicToLoopbackIsNotBlocked) {
GURL initiator_url = SecurePublicURL(kDefaultPath);
EXPECT_TRUE(NavigateToURL(shell(), initiator_url));
GURL url = SecureLoopbackURL(
MakePnaPathForIframe(url::Origin::Create(initiator_url)));
TestNavigationManager child_navigation_manager(shell()->web_contents(), url);
AddChildFromURLWithoutWaiting(root_frame_host(), url);
ASSERT_TRUE(child_navigation_manager.WaitForNavigationFinished());
// Check that the child iframe navigated successfully.
EXPECT_TRUE(child_navigation_manager.was_successful());
RenderFrameHostImpl* child_frame = GetFirstChild(*root_frame_host());
EXPECT_EQ(url, EvalJs(child_frame, "document.location.href"));
EXPECT_EQ(url, child_frame->GetLastCommittedURL());
// A preflight request first, then the GET request.
EXPECT_THAT(
SecureLoopbackServer().request_observer().RequestMethodsForUrl(url),
ElementsAre(METHOD_OPTIONS, METHOD_GET));
}
// Same as above, testing the "treat-as-public-address" CSP directive.
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessBrowserTestForNavigations,
IframeFromSecureTreatAsPublicToLoopbackIsNotBlocked) {
GURL initiator_url = SecureLoopbackURL(kTreatAsPublicAddressPath);
EXPECT_TRUE(NavigateToURL(shell(), initiator_url));
GURL url = OtherSecureLoopbackURL(
MakePnaPathForIframe(url::Origin::Create(initiator_url)));
TestNavigationManager child_navigation_manager(shell()->web_contents(), url);
AddChildFromURLWithoutWaiting(root_frame_host(), url);
ASSERT_TRUE(child_navigation_manager.WaitForNavigationFinished());
// Check that the child iframe navigated successfully.
EXPECT_TRUE(child_navigation_manager.was_successful());
// A preflight request first, then the GET request.
EXPECT_THAT(
SecureLoopbackServer().request_observer().RequestMethodsForUrl(url),
ElementsAre(METHOD_OPTIONS, METHOD_GET));
}
IN_PROC_BROWSER_TEST_F(
PrivateNetworkAccessBrowserTestForNavigations,
FormSubmissionFromInsecurePublicToLoopbackIsBlockedInMainFrame) {
EXPECT_TRUE(NavigateToURL(shell(), InsecurePublicURL(kDefaultPath)));
GURL url = InsecureLoopbackURL(kDefaultPath);
TestNavigationManager navigation_manager(shell()->web_contents(), url);
std::string_view script_template = R"(
const form = document.createElement("form");
form.action = $1;
form.method = "post";
document.body.appendChild(form);
form.submit();
)";
EXPECT_TRUE(ExecJs(root_frame_host(), JsReplace(script_template, url)));
ASSERT_TRUE(navigation_manager.WaitForNavigationFinished());
// Check that the form submission was blocked.
EXPECT_FALSE(navigation_manager.was_successful());
}
IN_PROC_BROWSER_TEST_F(
PrivateNetworkAccessBrowserTestForNavigations,
FormSubmissionFromInsecurePublicToLoopbackIsBlockedInChildFrame) {
EXPECT_TRUE(NavigateToURL(shell(), InsecurePublicURL(kDefaultPath)));
GURL url = InsecureLoopbackURL(kDefaultPath);
TestNavigationManager navigation_manager(shell()->web_contents(), url);
std::string_view script_template = R"(
const iframe = document.createElement("iframe");
document.body.appendChild(iframe);
const childDoc = iframe.contentDocument;
const form = childDoc.createElement("form");
form.action = $1;
form.method = "post";
childDoc.body.appendChild(form);
form.submit();
)";
EXPECT_TRUE(ExecJs(root_frame_host(), JsReplace(script_template, url)));
ASSERT_TRUE(navigation_manager.WaitForNavigationFinished());
// Check that the child iframe was blocked.
EXPECT_FALSE(navigation_manager.was_successful());
ASSERT_EQ(1ul, root_frame_host()->child_count());
RenderFrameHostImpl* child_frame =
root_frame_host()->child_at(0)->current_frame_host();
// Failed navigation.
EXPECT_EQ(GURL(kUnreachableWebDataURL),
EvalJs(child_frame, "document.location.href"));
// The URL is the form target URL, to allow for reloading.
// The origin is opaque though, a symptom of the failed navigation.
EXPECT_EQ(url, child_frame->GetLastCommittedURL());
EXPECT_TRUE(child_frame->GetLastCommittedOrigin().opaque());
}
IN_PROC_BROWSER_TEST_F(
PrivateNetworkAccessBrowserTestForNavigations,
FormSubmissionGetFromInsecurePublicToLoopbackIsBlockedInChildFrame) {
EXPECT_TRUE(NavigateToURL(shell(), InsecurePublicURL(kDefaultPath)));
GURL target_url = InsecureLoopbackURL(kDefaultPath);
// The page navigates to `url` followed by an empty query: '?'.
GURL expected_url = GURL(target_url.spec() + "?");
TestNavigationManager navigation_manager(shell()->web_contents(),
expected_url);
std::string_view script_template = R"(
const iframe = document.createElement("iframe");
document.body.appendChild(iframe);
const childDoc = iframe.contentDocument;
const form = childDoc.createElement("form");
form.action = $1;
form.method = "get";
childDoc.body.appendChild(form);
form.submit();
)";
EXPECT_TRUE(
ExecJs(root_frame_host(), JsReplace(script_template, target_url)));
ASSERT_TRUE(navigation_manager.WaitForNavigationFinished());
// Check that the child iframe was blocked.
EXPECT_FALSE(navigation_manager.was_successful());
ASSERT_EQ(1ul, root_frame_host()->child_count());
RenderFrameHostImpl* child_frame =
root_frame_host()->child_at(0)->current_frame_host();
// Failed navigation.
EXPECT_EQ(GURL(kUnreachableWebDataURL),
EvalJs(child_frame, "document.location.href"));
// The URL is the form target URL, to allow for reloading.
// The origin is opaque though, a symptom of the failed navigation.
EXPECT_EQ(expected_url, child_frame->GetLastCommittedURL());
EXPECT_TRUE(child_frame->GetLastCommittedOrigin().opaque());
}
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessBrowserTestForNavigations,
SiblingNavigationFromInsecurePublicToLoopbackIsBlocked) {
EXPECT_TRUE(NavigateToURL(shell(), InsecureLoopbackURL(kDefaultPath)));
// Named targeting only works if the initiator is one of:
//
// - the target's parent -> uninteresting
// - the target's opener -> implies the target is a main frame
// - same-origin with the target -> the only option left
//
// Thus we use CSP: treat-as-public-address to place the initiator in a
// different IP address space as its same-origin target.
GURL initiator_url = InsecureLoopbackURL(kTreatAsPublicAddressPath);
GURL target_url = InsecureLoopbackURL(kDefaultPath);
constexpr std::string_view kScriptTemplate = R"(
function addChild(name, src) {
return new Promise((resolve) => {
const iframe = document.createElement("iframe");
iframe.name = name;
iframe.src = src;
iframe.onload = () => resolve(iframe);
document.body.appendChild(iframe);
});
}
Promise.all([
addChild("initiator", $1),
addChild("target", "/empty.html"),
]).then(() => true);
)";
EXPECT_EQ(true, EvalJs(root_frame_host(),
JsReplace(kScriptTemplate, initiator_url)));
ASSERT_EQ(2ul, root_frame_host()->child_count());
RenderFrameHostImpl* initiator = root_frame_host()->child_at(0)->current_frame_host();
EXPECT_EQ(initiator->GetLastCommittedURL(), initiator_url);
TestNavigationManager navigation_manager(shell()->web_contents(), target_url);
EXPECT_TRUE(
ExecJs(initiator, JsReplace("window.open($1, 'target')", target_url)));
ASSERT_TRUE(navigation_manager.WaitForNavigationFinished());
// Check that the child iframe was blocked.
EXPECT_FALSE(navigation_manager.was_successful());
// Request was blocked before it was even sent.
EXPECT_THAT(SecureLoopbackServer().request_observer().RequestMethodsForUrl(
target_url),
IsEmpty());
}
class LocalNetworkAccessBrowserTest
: public PrivateNetworkAccessBrowserTestBase {
public:
LocalNetworkAccessBrowserTest()
: PrivateNetworkAccessBrowserTestBase(
{
network::features::kLocalNetworkAccessChecks,
},
{}) {}
};
IN_PROC_BROWSER_TEST_F(LocalNetworkAccessBrowserTest, CheckSecurityState) {
EXPECT_TRUE(NavigateToURL(shell(), SecurePublicURL(kDefaultPath)));
const network::mojom::ClientSecurityStatePtr security_state =
root_frame_host()->BuildClientSecurityState();
ASSERT_FALSE(security_state.is_null());
EXPECT_TRUE(security_state->is_web_secure_context);
EXPECT_EQ(network::mojom::IPAddressSpace::kPublic,
security_state->ip_address_space);
EXPECT_EQ(security_state->private_network_request_policy,
network::mojom::PrivateNetworkRequestPolicy::kPermissionWarn);
}
IN_PROC_BROWSER_TEST_F(LocalNetworkAccessBrowserTest, CheckBlockInsteadOfWarn) {
PolicyTestContentBrowserClient client;
client.SetBlockInsteadOfWarn();
EXPECT_TRUE(NavigateToURL(shell(), SecurePublicURL(kDefaultPath)));
const network::mojom::ClientSecurityStatePtr security_state =
root_frame_host()->BuildClientSecurityState();
ASSERT_FALSE(security_state.is_null());
EXPECT_TRUE(security_state->is_web_secure_context);
EXPECT_EQ(network::mojom::IPAddressSpace::kPublic,
security_state->ip_address_space);
EXPECT_EQ(security_state->private_network_request_policy,
network::mojom::PrivateNetworkRequestPolicy::kPermissionBlock);
}
} // namespace content