0

Enforce Direct Socket isolation in the browser.

This patch builds on the Blink-side enforcment of [DirectSocketEnabled],
adding browser-side checks that the RenderFrameHost from which a socket
is opened adheres to the level of isolation deemed necessary.

Patch 5/5:
1.  https://chromium-review.googlesource.com/c/chromium/src/+/2871670
2.  https://chromium-review.googlesource.com/c/chromium/src/+/2874306
3.  https://chromium-review.googlesource.com/c/chromium/src/+/2874288
4.  https://chromium-review.googlesource.com/c/chromium/src/+/2874890
5.  This patch.

Bug: 1206150
Change-Id: I0c64c94503ef057473a2dca1c89bbcc283175ad6
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2875217
Commit-Queue: Mike West <mkwst@chromium.org>
Reviewed-by: Alex Moshchuk <alexmos@chromium.org>
Reviewed-by: Chris Hamilton <chrisha@chromium.org>
Reviewed-by: Camille Lamy <clamy@chromium.org>
Reviewed-by: Eric Willigers <ericwilligers@chromium.org>
Cr-Commit-Position: refs/heads/master@{#880688}
This commit is contained in:
Mike West
2021-05-08 05:44:44 +00:00
committed by Chromium LUCI CQ
parent ecf9aff0d7
commit 1df5150128
7 changed files with 126 additions and 56 deletions

@@ -78,8 +78,8 @@ void CheckIsCrossOriginIsolatedOnUISeq(
// Frame was deleted before the task ran. // Frame was deleted before the task ran.
return; return;
} }
if (rfh->GetCrossOriginIsolationStatus() == if (rfh->GetWebExposedIsolationLevel() ==
content::RenderFrameHost::CrossOriginIsolationStatus::kNotIsolated && content::RenderFrameHost::WebExposedIsolationLevel::kNotIsolated &&
!base::CommandLine::ForCurrentProcess()->HasSwitch( !base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kDisableWebSecurity)) { switches::kDisableWebSecurity)) {
std::move(bad_message_callback) std::move(bad_message_callback)

@@ -287,6 +287,12 @@ void DirectSocketsServiceImpl::OpenTcpSocket(
mojo::PendingReceiver<network::mojom::TCPConnectedSocket> receiver, mojo::PendingReceiver<network::mojom::TCPConnectedSocket> receiver,
mojo::PendingRemote<network::mojom::SocketObserver> observer, mojo::PendingRemote<network::mojom::SocketObserver> observer,
OpenTcpSocketCallback callback) { OpenTcpSocketCallback callback) {
if (!frame_host_ || frame_host_->GetWebExposedIsolationLevel() <
RenderFrameHost::WebExposedIsolationLevel::
kMaybeIsolatedApplication) {
mojo::ReportBadMessage("Insufficient isolation to open socket.");
return;
}
if (!options) { if (!options) {
mojo::ReportBadMessage("Invalid request to open socket"); mojo::ReportBadMessage("Invalid request to open socket");
return; return;
@@ -314,6 +320,12 @@ void DirectSocketsServiceImpl::OpenUdpSocket(
mojo::PendingReceiver<network::mojom::UDPSocket> receiver, mojo::PendingReceiver<network::mojom::UDPSocket> receiver,
mojo::PendingRemote<network::mojom::UDPSocketListener> listener, mojo::PendingRemote<network::mojom::UDPSocketListener> listener,
OpenUdpSocketCallback callback) { OpenUdpSocketCallback callback) {
if (!frame_host_ || frame_host_->GetWebExposedIsolationLevel() <
RenderFrameHost::WebExposedIsolationLevel::
kMaybeIsolatedApplication) {
mojo::ReportBadMessage("Insufficient isolation to open socket.");
return;
}
if (!options) { if (!options) {
mojo::ReportBadMessage("Invalid request to open socket"); mojo::ReportBadMessage("Invalid request to open socket");
return; return;

@@ -1875,18 +1875,23 @@ bool RenderFrameHostImpl::RequiresProxyToParent() {
return GetSiteInstance() != parent_->GetSiteInstance(); return GetSiteInstance() != parent_->GetSiteInstance();
} }
RenderFrameHost::CrossOriginIsolationStatus RenderFrameHost::WebExposedIsolationLevel
RenderFrameHostImpl::GetCrossOriginIsolationStatus() { RenderFrameHostImpl::GetWebExposedIsolationLevel() {
ProcessLock process_lock = GetSiteInstance()->GetProcessLock(); ProcessLock process_lock = GetSiteInstance()->GetProcessLock();
if (process_lock.is_invalid() || if (process_lock.is_invalid())
!process_lock.web_exposed_isolation_info().is_isolated()) { return RenderFrameHost::WebExposedIsolationLevel::kNotIsolated;
// Cross-origin isolated frames must be hosted in cross-origin isolated
// processes. WebExposedIsolationInfo info = process_lock.web_exposed_isolation_info();
return RenderFrameHost::CrossOriginIsolationStatus::kNotIsolated; if (info.is_isolated_application()) {
// TODO(crbug.com/1159832): Check the document policy once it's available to
// find out if this frame is actually isolated.
return RenderFrameHost::WebExposedIsolationLevel::kMaybeIsolatedApplication;
} else if (info.is_isolated()) {
// TODO(crbug.com/1159832): Check the document policy once it's available to
// find out if this frame is actually isolated.
return RenderFrameHost::WebExposedIsolationLevel::kMaybeIsolated;
} }
// TODO(crbug.com/1159832): Check the document policy once it's available to return RenderFrameHost::WebExposedIsolationLevel::kNotIsolated;
// find out if this frame is actually isolated.
return RenderFrameHost::CrossOriginIsolationStatus::kMaybeIsolated;
} }
const GURL& RenderFrameHostImpl::GetLastCommittedURL() { const GURL& RenderFrameHostImpl::GetLastCommittedURL() {

@@ -336,7 +336,7 @@ class CONTENT_EXPORT RenderFrameHostImpl
const base::Optional<gfx::Size>& GetFrameSize() override; const base::Optional<gfx::Size>& GetFrameSize() override;
size_t GetFrameDepth() override; size_t GetFrameDepth() override;
bool IsCrossProcessSubframe() override; bool IsCrossProcessSubframe() override;
CrossOriginIsolationStatus GetCrossOriginIsolationStatus() override; WebExposedIsolationLevel GetWebExposedIsolationLevel() override;
const GURL& GetLastCommittedURL() override; const GURL& GetLastCommittedURL() override;
const url::Origin& GetLastCommittedOrigin() override; const url::Origin& GetLastCommittedOrigin() override;
const net::NetworkIsolationKey& GetNetworkIsolationKey() override; const net::NetworkIsolationKey& GetNetworkIsolationKey() override;

@@ -5139,20 +5139,56 @@ IN_PROC_BROWSER_TEST_F(RenderFrameHostImplBrowserTest,
} }
IN_PROC_BROWSER_TEST_F(RenderFrameHostImplBrowserTest, IN_PROC_BROWSER_TEST_F(RenderFrameHostImplBrowserTest,
GetCrossOriginIsolationStatus) { GetWebExposedIsolationLevel) {
// Not isolated:
EXPECT_TRUE( EXPECT_TRUE(
NavigateToURL(shell(), embedded_test_server()->GetURL("/empty.html"))); NavigateToURL(shell(), embedded_test_server()->GetURL("/empty.html")));
EXPECT_EQ(RenderFrameHost::CrossOriginIsolationStatus::kNotIsolated, EXPECT_EQ(RenderFrameHost::WebExposedIsolationLevel::kNotIsolated,
root_frame_host()->GetCrossOriginIsolationStatus()); root_frame_host()->GetWebExposedIsolationLevel());
// Cross-Origin Isolated:
EXPECT_TRUE(NavigateToURL(shell(), EXPECT_TRUE(NavigateToURL(shell(),
embedded_test_server()->GetURL( embedded_test_server()->GetURL(
"/set-header?" "/set-header?"
"Cross-Origin-Opener-Policy: same-origin&" "Cross-Origin-Opener-Policy: same-origin&"
"Cross-Origin-Embedder-Policy: require-corp"))); "Cross-Origin-Embedder-Policy: require-corp")));
// Status can be kIsolated or kMaybeIsolated. // Status can be kIsolated or kMaybeIsolated.
EXPECT_NE(RenderFrameHost::CrossOriginIsolationStatus::kNotIsolated, EXPECT_LT(RenderFrameHost::WebExposedIsolationLevel::kNotIsolated,
root_frame_host()->GetCrossOriginIsolationStatus()); root_frame_host()->GetWebExposedIsolationLevel());
EXPECT_GT(
RenderFrameHost::WebExposedIsolationLevel::kMaybeIsolatedApplication,
root_frame_host()->GetWebExposedIsolationLevel());
}
class RenderFrameHostImplBrowserTestWithDirectSockets
: public RenderFrameHostImplBrowserTest {
public:
RenderFrameHostImplBrowserTestWithDirectSockets() {
feature_list_.InitAndEnableFeature(features::kDirectSockets);
}
private:
base::test::ScopedFeatureList feature_list_;
};
IN_PROC_BROWSER_TEST_F(RenderFrameHostImplBrowserTestWithDirectSockets,
GetWebExposedIsolationLevel) {
// Not isolated:
EXPECT_TRUE(
NavigateToURL(shell(), embedded_test_server()->GetURL("/empty.html")));
EXPECT_EQ(RenderFrameHost::WebExposedIsolationLevel::kNotIsolated,
root_frame_host()->GetWebExposedIsolationLevel());
// Isolated Application:
EXPECT_TRUE(NavigateToURL(shell(),
embedded_test_server()->GetURL(
"/set-header?"
"Cross-Origin-Opener-Policy: same-origin&"
"Cross-Origin-Embedder-Policy: require-corp")));
// Status can be kIsolatedApplication or kMaybeIsolatedApplication.
EXPECT_LT(RenderFrameHost::WebExposedIsolationLevel::kIsolated,
root_frame_host()->GetWebExposedIsolationLevel());
} }
IN_PROC_BROWSER_TEST_F(RenderFrameHostImplBrowserTest, IN_PROC_BROWSER_TEST_F(RenderFrameHostImplBrowserTest,

@@ -338,8 +338,8 @@ IN_PROC_BROWSER_TEST_P(WorkerTest, SharedWorkerWithoutCoepInDifferentProcess) {
shell()->web_contents()->GetMainFrame()); shell()->web_contents()->GetMainFrame());
auto page_lock = page_rfh->GetSiteInstance()->GetProcessLock(); auto page_lock = page_rfh->GetSiteInstance()->GetProcessLock();
EXPECT_TRUE(page_lock.web_exposed_isolation_info().is_isolated()); EXPECT_TRUE(page_lock.web_exposed_isolation_info().is_isolated());
EXPECT_NE(page_rfh->GetCrossOriginIsolationStatus(), EXPECT_GT(page_rfh->GetWebExposedIsolationLevel(),
RenderFrameHost::CrossOriginIsolationStatus::kNotIsolated); RenderFrameHost::WebExposedIsolationLevel::kNotIsolated);
// Create a shared worker from the cross-origin-isolated page. // Create a shared worker from the cross-origin-isolated page.
// The worker must be in a different process because shared workers isn't // The worker must be in a different process because shared workers isn't

@@ -334,60 +334,77 @@ class CONTENT_EXPORT RenderFrameHost : public IPC::Listener,
// Returns true if the frame is out of process relative to its parent. // Returns true if the frame is out of process relative to its parent.
virtual bool IsCrossProcessSubframe() = 0; virtual bool IsCrossProcessSubframe() = 0;
// Indicates whether this frame is in a cross-origin isolated agent cluster. // Reflects the web-exposed isolation properties of a given frame, which
// See [1] and [2] for a description of what this means for web content. // depends both on the process in which the frame lives, as well as the agent
// Specifically, an agent cluster may be cross-origin isolated if: // cluster into which it has been placed.
// - its top-level document has "Cross-Origin-Opener-Policy: same-origin" and
// "Cross-Origin-Embedder-Policy: require-corp" HTTP headers; or,
// - its top-level worker script has a
// "Cross-Origin-Embedder-Policy: require-corp" HTTP header.
// //
// In practice this means that the frame is guaranteed to be hosted in a // Three broad categories are possible:
// process that is isolated to the frame's origin. The process may also host
// cross-origin frames and workers only if they have opted in to being
// embedded with CORS or CORP headers.
// //
// Certain advanced web platform APIs are gated behind this property. It will // 1. The frame may not be isolated in a web-facing way.
// correspond to the value returned by accessing
// "WindowOrWorkerGlobalScope.crossOriginIsolated" in Javascript.
// //
// NOTE: some of the information needed to fully determine a frame's // 2. The frame may be "cross-origin isolated", corresponding to the value
// cross-isolation status is currently not available in the browser process. // returned by `WorkerOrWindowGlobalScope.crossOriginIsolated`, and gating
// Access to web platform API's must be checked in the renderer, with the // the set of APIs which specify [CrossOriginIsolated] attributes. The
// CrossOriginIsolationStatus on the browser side only used as a backup to // requirements for this level of isolation are described in [1] and [2]
// catch misbehaving renderers. // below.
//
// In practice this means that the frame is guaranteed to be hosted in a
// process that is isolated to the frame's origin. The process may also
// host cross-origin frames and workers only if they have opted in to
// being embedded by asserting CORS or CORP headers.
//
// 3. The frame may be an "isolated application", corresponding to a mostly
// TBD set of restrictions we're exploring in https://crbug.com/1206150,
// and which currently gate the set of APIs which specify
// [DirectSocketEnabled] attributes.
//
// The enum below is ordered from least-isolated to most-isolated.
// //
// [1] // [1]
// https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/crossOriginIsolated // https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/crossOriginIsolated
// [2] https://w3c.github.io/webappsec-permissions-policy/ // [2] https://w3c.github.io/webappsec-permissions-policy/
enum class CrossOriginIsolationStatus { //
// The frame is in a cross-origin isolated process and agent cluster. // NOTE: some of the information needed to fully determine a frame's
// It is allowed to call web platform API's gated behind the // isolation status is currently not available in the browser process.
// crossOriginIsolated property. // Access to web platform API's must be checked in the renderer, with the
kIsolated, // WebExposedIsolationLevel on the browser side only used as a backup to
// catch misbehaving renderers.
// The frame is not in a cross-origin isolated agent cluster. It may be enum class WebExposedIsolationLevel {
// hosted in a cross-origin isolated process but it is not allowed to call // The frame is not in a cross-origin isolated agent cluster. It may not
// web platform API's gated behind the crossOriginIsolated property. // meet the requirements for such isolation in itself, or it may be
// hosted in a process capable of supporting cross-origin isolation or
// application isolation, but barred from using those capabilities by
// its embedder.
kNotIsolated, kNotIsolated,
// The frame is in a cross-origin isolated process, but it's not possible // The frame is in a cross-origin isolated process and agent cluster,
// to determine whether it's in a cross-origin isolated agent cluster. The // allowed to access web platform APIs gated on [CrossOriginIsolated].
// browser process should not prevent it from calling web platform API's //
// gated behind the crossOriginIsolated property because it may be allowed. // TODO(clamy): Remove this "maybe" status once it is possible to determine
// TODO(clamy): Remove this status once the document policy is available on // conclusively whether the document is capable of calling cross-origin
// the browser side. // isolated APIs by examining the active document policy.
kMaybeIsolated, kMaybeIsolated,
kIsolated,
// The frame is in a cross-origin isolated process and agent cluster that
// supports application isolation, allowing access to web platform APIs
// gated on both [CrossOriginIsolated] and [DirectSocketEnabled].
//
// TODO(clamy): Remove this "maybe" status once it is possible to determine
// conclusively whether the document is capable of calling cross-origin
// isolated APIs by examining the active document policy.
kMaybeIsolatedApplication,
kIsolatedApplication
}; };
// Returns whether the frame is in a cross-origin isolated agent cluster. // Returns the web-exposed isolation level of a frame's agent cluster.
// //
// Note that this is a property of the document so can change as the frame // Note that this is a property of the document so can change as the frame
// navigates. // navigates.
// //
// TODO(https://936696): Once RenderDocument ships this should be exposed as // TODO(https://936696): Once RenderDocument ships this should be exposed as
// an invariant of the document host. // an invariant of the document host.
virtual CrossOriginIsolationStatus GetCrossOriginIsolationStatus() = 0; virtual WebExposedIsolationLevel GetWebExposedIsolationLevel() = 0;
// Returns the last committed URL of this RenderFrameHost. This will be empty // Returns the last committed URL of this RenderFrameHost. This will be empty
// until the first commit in this RenderFrameHost. // until the first commit in this RenderFrameHost.