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.
|
||||
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)
|
||||
|
@ -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;
|
||||
@ -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;
|
||||
|
@ -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;
|
||||
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;
|
||||
}
|
||||
// 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;
|
||||
return RenderFrameHost::WebExposedIsolationLevel::kNotIsolated;
|
||||
}
|
||||
|
||||
const GURL& RenderFrameHostImpl::GetLastCommittedURL() {
|
||||
|
@ -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;
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
// 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.
|
||||
//
|
||||
// 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.
|
||||
// Three broad categories are possible:
|
||||
//
|
||||
// Certain advanced web platform APIs are gated behind this property. It will
|
||||
// correspond to the value returned by accessing
|
||||
// "WindowOrWorkerGlobalScope.crossOriginIsolated" in Javascript.
|
||||
// 1. The frame may not be isolated in a web-facing way.
|
||||
//
|
||||
// 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.
|
||||
// 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.
|
||||
|
Reference in New Issue
Block a user