diff --git a/components/performance_manager/v8_memory/web_memory_impl.cc b/components/performance_manager/v8_memory/web_memory_impl.cc index 44e3d379c95d7..07a4d31561d55 100644 --- a/components/performance_manager/v8_memory/web_memory_impl.cc +++ b/components/performance_manager/v8_memory/web_memory_impl.cc @@ -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) diff --git a/content/browser/direct_sockets/direct_sockets_service_impl.cc b/content/browser/direct_sockets/direct_sockets_service_impl.cc index d18ca03d3c359..2977f51c4ee3d 100644 --- a/content/browser/direct_sockets/direct_sockets_service_impl.cc +++ b/content/browser/direct_sockets/direct_sockets_service_impl.cc @@ -287,6 +287,12 @@ void DirectSocketsServiceImpl::OpenTcpSocket( mojo::PendingReceiver receiver, mojo::PendingRemote 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; @@ -314,6 +320,12 @@ void DirectSocketsServiceImpl::OpenUdpSocket( mojo::PendingReceiver receiver, mojo::PendingRemote 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; diff --git a/content/browser/renderer_host/render_frame_host_impl.cc b/content/browser/renderer_host/render_frame_host_impl.cc index ef9aac14d612f..b1647921ee6ed 100644 --- a/content/browser/renderer_host/render_frame_host_impl.cc +++ b/content/browser/renderer_host/render_frame_host_impl.cc @@ -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() { diff --git a/content/browser/renderer_host/render_frame_host_impl.h b/content/browser/renderer_host/render_frame_host_impl.h index 410ba79caf4f3..ec2afec05ebd2 100644 --- a/content/browser/renderer_host/render_frame_host_impl.h +++ b/content/browser/renderer_host/render_frame_host_impl.h @@ -336,7 +336,7 @@ class CONTENT_EXPORT RenderFrameHostImpl const base::Optional& 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; diff --git a/content/browser/renderer_host/render_frame_host_impl_browsertest.cc b/content/browser/renderer_host/render_frame_host_impl_browsertest.cc index a4276f2d36aa0..33681aa4b800d 100644 --- a/content/browser/renderer_host/render_frame_host_impl_browsertest.cc +++ b/content/browser/renderer_host/render_frame_host_impl_browsertest.cc @@ -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, diff --git a/content/browser/worker_host/worker_browsertest.cc b/content/browser/worker_host/worker_browsertest.cc index 5fc9b38933637..16a4a747feb27 100644 --- a/content/browser/worker_host/worker_browsertest.cc +++ b/content/browser/worker_host/worker_browsertest.cc @@ -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 diff --git a/content/public/browser/render_frame_host.h b/content/public/browser/render_frame_host.h index 9bf669e25e8fb..f387432a31250 100644 --- a/content/public/browser/render_frame_host.h +++ b/content/public/browser/render_frame_host.h @@ -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.