Commit a9a5e13f authored by Song Fangzhen's avatar Song Fangzhen Committed by Chromium LUCI CQ

Direct Sockets: Disallow connecting to non-public addresses.

Reject hostnames that resolve to non-public exception unless a raw IP
address or a *.local hostname is entered by the user.

TODO: Show connection dialog and use the hostname provided by the user.

Below documents are from Eric Willigers <ericwilligers@chromium.org>.
Explainer: https://github.com/WICG/raw-sockets/blob/master/docs/explainer.md

Design doc:
https://docs.google.com/document/d/1Xa5nFkIWxkL3hZHvDYWPhT8sZvNeFpCUKNuqIwZHxnE/edit?usp=sharing

Bug: 1119661
Change-Id: I9a1300adf8b4acef2fbc2f18c465f86bc11b3859
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2631664
Commit-Queue: Eric Willigers <ericwilligers@chromium.org>
Reviewed-by: default avatarGlen Robertson <glenrob@chromium.org>
Reviewed-by: default avatarEric Willigers <ericwilligers@chromium.org>
Cr-Commit-Position: refs/heads/master@{#844655}
parent 2a7b2aa0
......@@ -497,6 +497,26 @@ IN_PROC_BROWSER_TEST_F(DirectSocketsBrowserTest, OpenTcp_CannotEvadeCors) {
EvalJs(shell(), script));
}
IN_PROC_BROWSER_TEST_F(DirectSocketsBrowserTest,
OpenTcp_CannotConnectNonPublic) {
EXPECT_TRUE(NavigateToURL(shell(), GetTestPageURL()));
const char kExampleHostname[] = "mail.example.com";
const char kExampleAddress[] = "127.0.0.1";
const std::string mapping_rules =
base::StringPrintf("MAP %s %s", kExampleHostname, kExampleAddress);
MockNetworkContext mock_network_context(net::OK);
mock_network_context.set_host_mapping_rules(mapping_rules);
DirectSocketsServiceImpl::SetNetworkContextForTesting(&mock_network_context);
const std::string script = base::StringPrintf(
"openTcp({remoteAddress: '%s', remotePort: 993})", kExampleHostname);
EXPECT_EQ("openTcp failed: NotAllowedError: Permission denied",
EvalJs(shell(), script));
}
IN_PROC_BROWSER_TEST_F(DirectSocketsBrowserTest, OpenTcp_OptionsOne) {
EXPECT_TRUE(NavigateToURL(shell(), GetTestPageURL()));
......@@ -674,4 +694,24 @@ IN_PROC_BROWSER_TEST_F(DirectSocketsBrowserTest, OpenUdp_CannotEvadeCors) {
EvalJs(shell(), script));
}
IN_PROC_BROWSER_TEST_F(DirectSocketsBrowserTest,
OpenUdp_CannotConnectNonPublic) {
EXPECT_TRUE(NavigateToURL(shell(), GetTestPageURL()));
const char kExampleHostname[] = "mail.example.com";
const char kExampleAddress[] = "127.0.0.1";
const std::string mapping_rules =
base::StringPrintf("MAP %s %s", kExampleHostname, kExampleAddress);
MockNetworkContext mock_network_context(net::OK);
mock_network_context.set_host_mapping_rules(mapping_rules);
DirectSocketsServiceImpl::SetNetworkContextForTesting(&mock_network_context);
const std::string script = base::StringPrintf(
"openUdp({remoteAddress: '%s', remotePort: 993})", kExampleHostname);
EXPECT_EQ("openUdp failed: NotAllowedError: Permission denied",
EvalJs(shell(), script));
}
} // namespace content
......@@ -58,6 +58,15 @@ bool ResemblesMulticastDNSName(const std::string& hostname) {
base::EndsWith(hostname, ".local.");
}
bool ContainNonPubliclyRoutableAddress(const net::AddressList& addresses) {
DCHECK(!addresses.empty());
for (auto ip : addresses) {
if (!ip.address().IsPubliclyRoutable())
return true;
}
return false;
}
} // namespace
DirectSocketsServiceImpl::DirectSocketsServiceImpl(RenderFrameHost& frame_host)
......@@ -112,6 +121,10 @@ class DirectSocketsServiceImpl::ResolveHostAndOpenSocket final
DCHECK(!receiver_.is_bound());
DCHECK(!resolver_.is_bound());
if (net::IPAddress().AssignFromIPLiteral(*options_->remote_hostname)) {
is_raw_address_ = true;
}
mojo::PendingRemote<network::mojom::HostResolver> pending_host_resolver;
network_context_->CreateHostResolver(
base::nullopt, pending_host_resolver.InitWithNewPipeAndPassReceiver());
......@@ -120,8 +133,10 @@ class DirectSocketsServiceImpl::ResolveHostAndOpenSocket final
network::mojom::ResolveHostParametersPtr parameters =
network::mojom::ResolveHostParameters::New();
#if BUILDFLAG(ENABLE_MDNS)
if (ResemblesMulticastDNSName(*options_->remote_hostname))
if (ResemblesMulticastDNSName(*options_->remote_hostname)) {
parameters->source = net::HostResolverSource::MULTICAST_DNS;
is_mdns_name_ = true;
}
#endif // !BUILDFLAG(ENABLE_MDNS)
resolver_->ResolveHost(
net::HostPortPair(*options_->remote_hostname, options_->remote_port),
......@@ -139,6 +154,12 @@ class DirectSocketsServiceImpl::ResolveHostAndOpenSocket final
int result,
const net::ResolveErrorInfo& resolve_error_info,
const base::Optional<net::AddressList>& resolved_addresses) override {
// Reject hostnames that resolve to non-public exception unless a raw IP
// address or a *.local hostname is entered by the user.
if (!is_raw_address_ && !is_mdns_name_ && resolved_addresses &&
ContainNonPubliclyRoutableAddress(*resolved_addresses)) {
result = net::Error::ERR_NETWORK_ACCESS_DENIED;
}
protocol_ == ProtocolType::kTcp ? OpenTCPSocket(result, resolved_addresses)
: OpenUDPSocket(result, resolved_addresses);
}
......@@ -204,6 +225,9 @@ class DirectSocketsServiceImpl::ResolveHostAndOpenSocket final
NOTIMPLEMENTED();
}
bool is_mdns_name_ = false;
bool is_raw_address_ = false;
const ProtocolType protocol_;
network::mojom::NetworkContext* const network_context_;
blink::mojom::DirectSocketOptionsPtr options_;
......@@ -328,8 +352,6 @@ net::Error DirectSocketsServiceImpl::ValidateOptions(
// ValidateOptions() will need to become asynchronous:
// TODO(crbug.com/1119597): Show connection dialog.
// TODO(crbug.com/1119597): Use the hostname provided by the user.
// TODO(crbug.com/1119661): Reject hostnames that resolve to non-public
// addresses.
if (!options.remote_hostname)
return net::ERR_NAME_NOT_RESOLVED;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment