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:

committed by
Chromium LUCI CQ

parent
ecf9aff0d7
commit
1df5150128
components/performance_manager/v8_memory
content
browser
direct_sockets
renderer_host
worker_host
public
browser
@@ -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.
|
||||||
|
Reference in New Issue
Block a user