Skip to content

Commit

Permalink
Enforce Direct Socket isolation in the browser.
Browse files Browse the repository at this point in the history
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}
  • Loading branch information
mikewest authored and Chromium LUCI CQ committed May 8, 2021
1 parent ecf9aff commit 1df5150
Show file tree
Hide file tree
Showing 7 changed files with 129 additions and 59 deletions.
4 changes: 2 additions & 2 deletions components/performance_manager/v8_memory/web_memory_impl.cc
Expand Up @@ -78,8 +78,8 @@ void CheckIsCrossOriginIsolatedOnUISeq(
// Frame was deleted before the task ran.
return;
}
if (rfh->GetCrossOriginIsolationStatus() ==
content::RenderFrameHost::CrossOriginIsolationStatus::kNotIsolated &&
if (rfh->GetWebExposedIsolationLevel() ==
content::RenderFrameHost::WebExposedIsolationLevel::kNotIsolated &&
!base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kDisableWebSecurity)) {
std::move(bad_message_callback)
Expand Down
12 changes: 12 additions & 0 deletions content/browser/direct_sockets/direct_sockets_service_impl.cc
Expand Up @@ -287,6 +287,12 @@ void DirectSocketsServiceImpl::OpenTcpSocket(
mojo::PendingReceiver<network::mojom::TCPConnectedSocket> receiver,
mojo::PendingRemote<network::mojom::SocketObserver> observer,
OpenTcpSocketCallback callback) {
if (!frame_host_ || frame_host_->GetWebExposedIsolationLevel() <
RenderFrameHost::WebExposedIsolationLevel::
kMaybeIsolatedApplication) {
mojo::ReportBadMessage("Insufficient isolation to open socket.");
return;
}
if (!options) {
mojo::ReportBadMessage("Invalid request to open socket");
return;
Expand Down Expand Up @@ -314,6 +320,12 @@ void DirectSocketsServiceImpl::OpenUdpSocket(
mojo::PendingReceiver<network::mojom::UDPSocket> receiver,
mojo::PendingRemote<network::mojom::UDPSocketListener> listener,
OpenUdpSocketCallback callback) {
if (!frame_host_ || frame_host_->GetWebExposedIsolationLevel() <
RenderFrameHost::WebExposedIsolationLevel::
kMaybeIsolatedApplication) {
mojo::ReportBadMessage("Insufficient isolation to open socket.");
return;
}
if (!options) {
mojo::ReportBadMessage("Invalid request to open socket");
return;
Expand Down
27 changes: 16 additions & 11 deletions content/browser/renderer_host/render_frame_host_impl.cc
Expand Up @@ -1875,18 +1875,23 @@ bool RenderFrameHostImpl::RequiresProxyToParent() {
return GetSiteInstance() != parent_->GetSiteInstance();
}

RenderFrameHost::CrossOriginIsolationStatus
RenderFrameHostImpl::GetCrossOriginIsolationStatus() {
RenderFrameHost::WebExposedIsolationLevel
RenderFrameHostImpl::GetWebExposedIsolationLevel() {
ProcessLock process_lock = GetSiteInstance()->GetProcessLock();
if (process_lock.is_invalid() ||
!process_lock.web_exposed_isolation_info().is_isolated()) {
// Cross-origin isolated frames must be hosted in cross-origin isolated
// processes.
return RenderFrameHost::CrossOriginIsolationStatus::kNotIsolated;
}
// TODO(crbug.com/1159832): Check the document policy once it's available to
// find out if this frame is actually isolated.
return RenderFrameHost::CrossOriginIsolationStatus::kMaybeIsolated;
if (process_lock.is_invalid())
return RenderFrameHost::WebExposedIsolationLevel::kNotIsolated;

WebExposedIsolationInfo info = process_lock.web_exposed_isolation_info();
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;
}
return RenderFrameHost::WebExposedIsolationLevel::kNotIsolated;
}

const GURL& RenderFrameHostImpl::GetLastCommittedURL() {
Expand Down
2 changes: 1 addition & 1 deletion content/browser/renderer_host/render_frame_host_impl.h
Expand Up @@ -336,7 +336,7 @@ class CONTENT_EXPORT RenderFrameHostImpl
const base::Optional<gfx::Size>& GetFrameSize() override;
size_t GetFrameDepth() override;
bool IsCrossProcessSubframe() override;
CrossOriginIsolationStatus GetCrossOriginIsolationStatus() override;
WebExposedIsolationLevel GetWebExposedIsolationLevel() override;
const GURL& GetLastCommittedURL() override;
const url::Origin& GetLastCommittedOrigin() override;
const net::NetworkIsolationKey& GetNetworkIsolationKey() override;
Expand Down
Expand Up @@ -5139,20 +5139,56 @@ IN_PROC_BROWSER_TEST_F(RenderFrameHostImplBrowserTest,
}

IN_PROC_BROWSER_TEST_F(RenderFrameHostImplBrowserTest,
GetCrossOriginIsolationStatus) {
GetWebExposedIsolationLevel) {
// Not isolated:
EXPECT_TRUE(
NavigateToURL(shell(), embedded_test_server()->GetURL("/empty.html")));
EXPECT_EQ(RenderFrameHost::CrossOriginIsolationStatus::kNotIsolated,
root_frame_host()->GetCrossOriginIsolationStatus());
EXPECT_EQ(RenderFrameHost::WebExposedIsolationLevel::kNotIsolated,
root_frame_host()->GetWebExposedIsolationLevel());

// Cross-Origin Isolated:
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 kIsolated or kMaybeIsolated.
EXPECT_NE(RenderFrameHost::CrossOriginIsolationStatus::kNotIsolated,
root_frame_host()->GetCrossOriginIsolationStatus());
EXPECT_LT(RenderFrameHost::WebExposedIsolationLevel::kNotIsolated,
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,
Expand Down
4 changes: 2 additions & 2 deletions content/browser/worker_host/worker_browsertest.cc
Expand Up @@ -338,8 +338,8 @@ IN_PROC_BROWSER_TEST_P(WorkerTest, SharedWorkerWithoutCoepInDifferentProcess) {
shell()->web_contents()->GetMainFrame());
auto page_lock = page_rfh->GetSiteInstance()->GetProcessLock();
EXPECT_TRUE(page_lock.web_exposed_isolation_info().is_isolated());
EXPECT_NE(page_rfh->GetCrossOriginIsolationStatus(),
RenderFrameHost::CrossOriginIsolationStatus::kNotIsolated);
EXPECT_GT(page_rfh->GetWebExposedIsolationLevel(),
RenderFrameHost::WebExposedIsolationLevel::kNotIsolated);

// Create a shared worker from the cross-origin-isolated page.
// The worker must be in a different process because shared workers isn't
Expand Down
93 changes: 55 additions & 38 deletions content/public/browser/render_frame_host.h
Expand Up @@ -334,60 +334,77 @@ class CONTENT_EXPORT RenderFrameHost : public IPC::Listener,
// Returns true if the frame is out of process relative to its parent.
virtual bool IsCrossProcessSubframe() = 0;

// Indicates whether this frame is in a cross-origin isolated agent cluster.
// See [1] and [2] for a description of what this means for web content.
// Specifically, an agent cluster may be cross-origin isolated if:
// - 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
// 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
// correspond to the value returned by accessing
// "WindowOrWorkerGlobalScope.crossOriginIsolated" in Javascript.
// Reflects the web-exposed isolation properties of a given frame, which
// depends both on the process in which the frame lives, as well as the agent
// cluster into which it has been placed.
//
// NOTE: some of the information needed to fully determine a frame's
// cross-isolation status is currently not available in the browser process.
// Access to web platform API's must be checked in the renderer, with the
// CrossOriginIsolationStatus on the browser side only used as a backup to
// catch misbehaving renderers.
// Three broad categories are possible:
//
// 1. The frame may not be isolated in a web-facing way.
//
// 2. The frame may be "cross-origin isolated", corresponding to the value
// returned by `WorkerOrWindowGlobalScope.crossOriginIsolated`, and gating
// the set of APIs which specify [CrossOriginIsolated] attributes. The
// requirements for this level of isolation are described in [1] and [2]
// 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]
// https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/crossOriginIsolated
// [2] https://w3c.github.io/webappsec-permissions-policy/
enum class CrossOriginIsolationStatus {
// The frame is in a cross-origin isolated process and agent cluster.
// It is allowed to call web platform API's gated behind the
// crossOriginIsolated property.
kIsolated,

// The frame is not in a cross-origin isolated agent cluster. It may be
// hosted in a cross-origin isolated process but it is not allowed to call
// web platform API's gated behind the crossOriginIsolated property.
//
// NOTE: some of the information needed to fully determine a frame's
// isolation status is currently not available in the browser process.
// Access to web platform API's must be checked in the renderer, with the
// WebExposedIsolationLevel on the browser side only used as a backup to
// catch misbehaving renderers.
enum class WebExposedIsolationLevel {
// The frame is not in a cross-origin isolated agent cluster. It may not
// 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,

// The frame is in a cross-origin isolated process, but it's not possible
// to determine whether it's in a cross-origin isolated agent cluster. The
// 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 status once the document policy is available on
// the browser side.
// The frame is in a cross-origin isolated process and agent cluster,
// allowed to access web platform APIs gated on [CrossOriginIsolated].
//
// 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.
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
// navigates.
//
// TODO(https://936696): Once RenderDocument ships this should be exposed as
// 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
// until the first commit in this RenderFrameHost.
Expand Down

0 comments on commit 1df5150

Please sign in to comment.