Commit 10111bd8 authored by Karandeep Bhatia's avatar Karandeep Bhatia Committed by Commit Bot

IsolatedWorldCSP: Enforce isolated world CSP checks for img resources.

This CL ensures that the isolated world CSP is used for CSP checks while
loading images from isolated worlds. It also simplifies the ImageLoader
code.

BUG=1099975

Change-Id: I9ea9be9c0b960e49b0420c1db17b7eed4a1ed47e
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2276816Reviewed-by: default avatarYutaka Hirano <yhirano@chromium.org>
Commit-Queue: Karan Bhatia <karandeepb@chromium.org>
Cr-Commit-Position: refs/heads/master@{#785608}
parent edb1628d
...@@ -232,19 +232,23 @@ ExecutionContext::GetContentSecurityPolicyDelegate() { ...@@ -232,19 +232,23 @@ ExecutionContext::GetContentSecurityPolicyDelegate() {
return *csp_delegate_; return *csp_delegate_;
} }
ContentSecurityPolicy* scoped_refptr<const DOMWrapperWorld> ExecutionContext::GetCurrentWorld() const {
ExecutionContext::GetContentSecurityPolicyForCurrentWorld() {
v8::Isolate* isolate = GetIsolate(); v8::Isolate* isolate = GetIsolate();
v8::HandleScope handle_scope(isolate); v8::HandleScope handle_scope(isolate);
v8::Local<v8::Context> v8_context = isolate->GetCurrentContext(); v8::Local<v8::Context> v8_context = isolate->GetCurrentContext();
// This can be called before we enter v8, hence the context might be empty, // This can be called before we enter v8, hence the context might be empty.
// which implies we are not in an isolated world.
if (v8_context.IsEmpty()) if (v8_context.IsEmpty())
return GetContentSecurityPolicy(); return nullptr;
return &DOMWrapperWorld::Current(isolate);
}
return GetContentSecurityPolicyForWorld( ContentSecurityPolicy*
DOMWrapperWorld::Current(GetIsolate())); ExecutionContext::GetContentSecurityPolicyForCurrentWorld() {
scoped_refptr<const DOMWrapperWorld> world = GetCurrentWorld();
return world ? GetContentSecurityPolicyForWorld(*world)
: GetContentSecurityPolicy();
} }
ContentSecurityPolicy* ExecutionContext::GetContentSecurityPolicyForWorld( ContentSecurityPolicy* ExecutionContext::GetContentSecurityPolicyForWorld(
......
...@@ -161,6 +161,10 @@ class CORE_EXPORT ExecutionContext : public Supplementable<ExecutionContext>, ...@@ -161,6 +161,10 @@ class CORE_EXPORT ExecutionContext : public Supplementable<ExecutionContext>,
network::mojom::blink::WebSandboxFlags GetSandboxFlags() const; network::mojom::blink::WebSandboxFlags GetSandboxFlags() const;
bool IsSandboxed(network::mojom::blink::WebSandboxFlags mask) const; bool IsSandboxed(network::mojom::blink::WebSandboxFlags mask) const;
// Returns a reference to the current world we are in. If the current v8
// context is empty, returns null.
scoped_refptr<const DOMWrapperWorld> GetCurrentWorld() const;
// Returns the content security policy to be used based on the current // Returns the content security policy to be used based on the current
// JavaScript world we are in. // JavaScript world we are in.
// Note: As part of crbug.com/896041, existing usages of // Note: As part of crbug.com/896041, existing usages of
......
...@@ -120,39 +120,17 @@ bool CanReuseFromListOfAvailableImages( ...@@ -120,39 +120,17 @@ bool CanReuseFromListOfAvailableImages(
} // namespace } // namespace
static ImageLoader::BypassMainWorldBehavior ShouldBypassMainWorldCSP(
ImageLoader* loader) {
DCHECK(loader);
DCHECK(loader->GetElement());
if (ContentSecurityPolicy::ShouldBypassMainWorld(
loader->GetElement()->GetExecutionContext())) {
return ImageLoader::kBypassMainWorldCSP;
}
return ImageLoader::kDoNotBypassMainWorldCSP;
}
class ImageLoader::Task { class ImageLoader::Task {
public: public:
Task(ImageLoader* loader, Task(ImageLoader* loader,
UpdateFromElementBehavior update_behavior, UpdateFromElementBehavior update_behavior,
network::mojom::ReferrerPolicy referrer_policy) network::mojom::ReferrerPolicy referrer_policy)
: loader_(loader), : loader_(loader),
should_bypass_main_world_csp_(ShouldBypassMainWorldCSP(loader)),
update_behavior_(update_behavior), update_behavior_(update_behavior),
referrer_policy_(referrer_policy) { referrer_policy_(referrer_policy) {
ExecutionContext* context = loader_->GetElement()->GetExecutionContext(); ExecutionContext* context = loader_->GetElement()->GetExecutionContext();
probe::AsyncTaskScheduled(context, "Image", &async_task_id_); probe::AsyncTaskScheduled(context, "Image", &async_task_id_);
v8::Isolate* isolate = V8PerIsolateData::MainThreadIsolate(); world_ = context->GetCurrentWorld();
v8::HandleScope scope(isolate);
// If we're invoked from C++ without a V8 context on the stack, we should
// run the microtask in the context of the element's document's main world.
if (!isolate->GetCurrentContext().IsEmpty()) {
script_state_ = ScriptState::Current(isolate);
} else {
script_state_ = ToScriptStateForMainWorld(
loader->GetElement()->GetDocument().GetFrame());
DCHECK(script_state_);
}
} }
void Run() { void Run() {
...@@ -160,29 +138,20 @@ class ImageLoader::Task { ...@@ -160,29 +138,20 @@ class ImageLoader::Task {
return; return;
ExecutionContext* context = loader_->GetElement()->GetExecutionContext(); ExecutionContext* context = loader_->GetElement()->GetExecutionContext();
probe::AsyncTask async_task(context, &async_task_id_); probe::AsyncTask async_task(context, &async_task_id_);
if (script_state_ && script_state_->ContextIsValid()) { loader_->DoUpdateFromElement(world_, update_behavior_, referrer_policy_);
ScriptState::Scope scope(script_state_);
loader_->DoUpdateFromElement(should_bypass_main_world_csp_,
update_behavior_, referrer_policy_);
} else {
// This call does not access v8::Context internally.
loader_->DoUpdateFromElement(should_bypass_main_world_csp_,
update_behavior_, referrer_policy_);
}
} }
void ClearLoader() { void ClearLoader() {
loader_ = nullptr; loader_ = nullptr;
script_state_ = nullptr; world_ = nullptr;
} }
base::WeakPtr<Task> GetWeakPtr() { return weak_factory_.GetWeakPtr(); } base::WeakPtr<Task> GetWeakPtr() { return weak_factory_.GetWeakPtr(); }
private: private:
WeakPersistent<ImageLoader> loader_; WeakPersistent<ImageLoader> loader_;
BypassMainWorldBehavior should_bypass_main_world_csp_;
UpdateFromElementBehavior update_behavior_; UpdateFromElementBehavior update_behavior_;
WeakPersistent<ScriptState> script_state_; scoped_refptr<const DOMWrapperWorld> world_;
network::mojom::ReferrerPolicy referrer_policy_; network::mojom::ReferrerPolicy referrer_policy_;
probe::AsyncTaskId async_task_id_; probe::AsyncTaskId async_task_id_;
...@@ -401,14 +370,8 @@ void ImageLoader::SetImageWithoutConsideringPendingLoadEvent( ...@@ -401,14 +370,8 @@ void ImageLoader::SetImageWithoutConsideringPendingLoadEvent(
static void ConfigureRequest( static void ConfigureRequest(
FetchParameters& params, FetchParameters& params,
ImageLoader::BypassMainWorldBehavior bypass_behavior,
Element& element, Element& element,
const ClientHintsPreferences& client_hints_preferences) { const ClientHintsPreferences& client_hints_preferences) {
if (bypass_behavior == ImageLoader::kBypassMainWorldCSP) {
params.SetContentSecurityCheck(
network::mojom::CSPDisposition::DO_NOT_CHECK);
}
CrossOriginAttributeValue cross_origin = GetCrossOriginAttributeValue( CrossOriginAttributeValue cross_origin = GetCrossOriginAttributeValue(
element.FastGetAttribute(html_names::kCrossoriginAttr)); element.FastGetAttribute(html_names::kCrossoriginAttr));
if (cross_origin != kCrossOriginAttributeNotSet) { if (cross_origin != kCrossOriginAttributeNotSet) {
...@@ -483,7 +446,7 @@ void ImageLoader::UpdateImageState(ImageResourceContent* new_image_content) { ...@@ -483,7 +446,7 @@ void ImageLoader::UpdateImageState(ImageResourceContent* new_image_content) {
} }
void ImageLoader::DoUpdateFromElement( void ImageLoader::DoUpdateFromElement(
BypassMainWorldBehavior bypass_behavior, scoped_refptr<const DOMWrapperWorld> world,
UpdateFromElementBehavior update_behavior, UpdateFromElementBehavior update_behavior,
network::mojom::ReferrerPolicy referrer_policy, network::mojom::ReferrerPolicy referrer_policy,
UpdateType update_type) { UpdateType update_type) {
...@@ -511,6 +474,7 @@ void ImageLoader::DoUpdateFromElement( ...@@ -511,6 +474,7 @@ void ImageLoader::DoUpdateFromElement(
// Unlike raw <img>, we block mixed content inside of <picture> or // Unlike raw <img>, we block mixed content inside of <picture> or
// <img srcset>. // <img srcset>.
ResourceLoaderOptions resource_loader_options; ResourceLoaderOptions resource_loader_options;
resource_loader_options.world = std::move(world);
resource_loader_options.initiator_info.name = GetElement()->localName(); resource_loader_options.initiator_info.name = GetElement()->localName();
ResourceRequest resource_request(url); ResourceRequest resource_request(url);
if (update_behavior == kUpdateForcedReload) { if (update_behavior == kUpdateForcedReload) {
...@@ -557,7 +521,7 @@ void ImageLoader::DoUpdateFromElement( ...@@ -557,7 +521,7 @@ void ImageLoader::DoUpdateFromElement(
DCHECK(document.GetFrame()); DCHECK(document.GetFrame());
FetchParameters params(std::move(resource_request), FetchParameters params(std::move(resource_request),
resource_loader_options); resource_loader_options);
ConfigureRequest(params, bypass_behavior, *element_, ConfigureRequest(params, *element_,
document.GetFrame()->GetClientHintsPreferences()); document.GetFrame()->GetClientHintsPreferences());
if (update_behavior != kUpdateForcedReload && if (update_behavior != kUpdateForcedReload &&
...@@ -712,8 +676,8 @@ void ImageLoader::UpdateFromElement( ...@@ -712,8 +676,8 @@ void ImageLoader::UpdateFromElement(
} }
if (ShouldLoadImmediately(ImageSourceToKURL(image_source_url))) { if (ShouldLoadImmediately(ImageSourceToKURL(image_source_url))) {
DoUpdateFromElement(kDoNotBypassMainWorldCSP, update_behavior, DoUpdateFromElement(element_->GetExecutionContext()->GetCurrentWorld(),
referrer_policy, UpdateType::kSync); update_behavior, referrer_policy, UpdateType::kSync);
return; return;
} }
// Allow the idiom "img.src=''; img.src='.." to clear down the image before an // Allow the idiom "img.src=''; img.src='.." to clear down the image before an
......
...@@ -40,6 +40,7 @@ ...@@ -40,6 +40,7 @@
namespace blink { namespace blink {
class ContainerNode; class ContainerNode;
class DOMWrapperWorld;
class Element; class Element;
class ExceptionState; class ExceptionState;
class IncrementLoadEventDelayCount; class IncrementLoadEventDelayCount;
...@@ -74,11 +75,6 @@ class CORE_EXPORT ImageLoader : public GarbageCollected<ImageLoader>, ...@@ -74,11 +75,6 @@ class CORE_EXPORT ImageLoader : public GarbageCollected<ImageLoader>,
kUpdateForcedReload kUpdateForcedReload
}; };
enum BypassMainWorldBehavior {
kBypassMainWorldCSP,
kDoNotBypassMainWorldCSP
};
void UpdateFromElement(UpdateFromElementBehavior = kUpdateNormal, void UpdateFromElement(UpdateFromElementBehavior = kUpdateNormal,
network::mojom::ReferrerPolicy = network::mojom::ReferrerPolicy =
network::mojom::ReferrerPolicy::kDefault); network::mojom::ReferrerPolicy::kDefault);
...@@ -170,7 +166,7 @@ class CORE_EXPORT ImageLoader : public GarbageCollected<ImageLoader>, ...@@ -170,7 +166,7 @@ class CORE_EXPORT ImageLoader : public GarbageCollected<ImageLoader>,
// Called from the task or from updateFromElement to initiate the load. // Called from the task or from updateFromElement to initiate the load.
void DoUpdateFromElement( void DoUpdateFromElement(
BypassMainWorldBehavior, scoped_refptr<const DOMWrapperWorld> world,
UpdateFromElementBehavior, UpdateFromElementBehavior,
network::mojom::ReferrerPolicy = network::mojom::ReferrerPolicy::kDefault, network::mojom::ReferrerPolicy = network::mojom::ReferrerPolicy::kDefault,
UpdateType = UpdateType::kAsync); UpdateType = UpdateType::kAsync);
......
ALERT: Running test #6 ALERT: Running test #4
CONSOLE ERROR: Refused to load the image 'http://127.0.0.1:8000/security/resources/abe.png?6' because it violates the following Content Security Policy directive: "img-src 'none'". ALERT: Test in main world.
CONSOLE ERROR: Refused to load the image 'http://127.0.0.1:8000/security/resources/abe.png?4' because it violates the following Content Security Policy directive: "img-src 'none'".
ALERT: BLOCKED ALERT: BLOCKED
ALERT: Running test #5 ALERT: Running test #3
CONSOLE ERROR: Refused to load the image 'http://127.0.0.1:8000/security/resources/abe.png?5' because it violates the following Content Security Policy directive: "img-src 'none'". ALERT: Test in isolated world without a CSP.
CONSOLE ERROR: Refused to load the image 'http://127.0.0.1:8000/security/resources/abe.png?3' because it violates the following Content Security Policy directive: "img-src 'none'".
ALERT: BLOCKED ALERT: BLOCKED
ALERT: Running test #4
ALERT: Starting to bypass main world's CSP:
ALERT: LOADED
ALERT: Running test #3
ALERT: LOADED
ALERT: Running test #2 ALERT: Running test #2
CONSOLE ERROR: Refused to load the image 'http://127.0.0.1:8000/security/resources/abe.png?2' because it violates the following Content Security Policy directive: "img-src 'none'". ALERT: Test in isolated world with lax CSP
ALERT: LOADED
ALERT: BLOCKED
ALERT: Running test #1 ALERT: Running test #1
CONSOLE ERROR: Refused to load the image 'http://127.0.0.1:8000/security/resources/abe.png?1' because it violates the following Content Security Policy directive: "img-src 'none'". ALERT: Test in isolated world with restrictive CSP
ALERT: LOADED
ALERT: BLOCKED
ALERT: Running test #0 ALERT: Running test #0
This test ensures that scripts run in isolated worlds marked with their own Content Security Policy aren't affected by the page's content security policy. Extensions, for example, should be able to load any resource they like. This test ensures that img-src checks respect the isolated world CSP when the IsolatedWorldCSP feature is enabled and bypass the main world CSP checks otherwise.
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
testRunner.waitUntilDone(); testRunner.waitUntilDone();
} }
tests = 6; tests = 4;
window.addEventListener("message", function(message) { window.addEventListener("message", function(message) {
tests -= 1; tests -= 1;
test(); test();
...@@ -32,36 +32,30 @@ ...@@ -32,36 +32,30 @@
} }
function test() { function test() {
function setImgSrc(isolated, num) { function setImgSrc(num) {
var img = document.getElementById('testimg'); var img = document.getElementById('testimg');
img.src = "../resources/abe.png?" + num; img.src = "../resources/abe.png?" + num;
} }
alert("Running test #" + tests + "\n"); alert("Running test #" + tests + "\n");
switch (tests) { switch (tests) {
case 6:
setImgSrc(false, 6);
break;
case 5:
testRunner.evaluateScriptInIsolatedWorld(1, String(eval("setImgSrc")) + "\nsetImgSrc(true, 5);");
break;
case 4: case 4:
alert("Starting to bypass main world's CSP:"); alert("Test in main world.");
testRunner.setIsolatedWorldInfo(1, 'chrome-extension://123', 'img-src *'); setImgSrc(4);
testRunner.evaluateScriptInIsolatedWorld(1, String(eval("setImgSrc")) + "\nsetImgSrc(true, 4);");
break; break;
case 3: case 3:
// Main world, then isolated world -> should load alert("Test in isolated world without a CSP.");
setImgSrc(false, 3); testRunner.evaluateScriptInIsolatedWorld(1, String(eval("setImgSrc")) + "\nsetImgSrc(3);");
testRunner.evaluateScriptInIsolatedWorld(1, String(eval("setImgSrc")) + "\nsetImgSrc(true, 3);");
break; break;
case 2: case 2:
// Isolated world, then main world -> should block alert("Test in isolated world with lax CSP");
testRunner.evaluateScriptInIsolatedWorld(1, String(eval("setImgSrc")) + "\nsetImgSrc(true, 2);"); testRunner.setIsolatedWorldInfo(1, 'chrome-extension://123', 'img-src *');
setImgSrc(false, 2); testRunner.evaluateScriptInIsolatedWorld(1, String(eval("setImgSrc")) + "\nsetImgSrc(2);");
break; break;
case 1: case 1:
setImgSrc(false, 1); alert("Test in isolated world with restrictive CSP");
testRunner.setIsolatedWorldInfo(1, 'chrome-extension://123', "img-src 'self'");
testRunner.evaluateScriptInIsolatedWorld(1, String(eval("setImgSrc")) + "\nsetImgSrc(0);");
break; break;
case 0: case 0:
testRunner.setIsolatedWorldInfo(1, null, null); testRunner.setIsolatedWorldInfo(1, null, null);
...@@ -74,10 +68,9 @@ ...@@ -74,10 +68,9 @@
<body onload='setup();'> <body onload='setup();'>
<p> <p>
<img id="testimg"> <img id="testimg">
This test ensures that scripts run in isolated worlds marked with their This test ensures that img-src checks respect the isolated world CSP
own Content Security Policy aren't affected by the page's content when the IsolatedWorldCSP feature is enabled and bypass the main world
security policy. Extensions, for example, should be able to load any CSP checks otherwise.
resource they like.
</p> </p>
</body> </body>
</html> </html>
ALERT: Running test #4
ALERT: Test in main world.
CONSOLE ERROR: Refused to load the image 'http://127.0.0.1:8000/security/resources/abe.png?4' because it violates the following Content Security Policy directive: "img-src 'none'".
ALERT: BLOCKED
ALERT: Running test #3
ALERT: Test in isolated world without a CSP.
CONSOLE ERROR: Refused to load the image 'http://127.0.0.1:8000/security/resources/abe.png?3' because it violates the following Content Security Policy directive: "img-src 'none'".
ALERT: BLOCKED
ALERT: Running test #2
ALERT: Test in isolated world with lax CSP
ALERT: LOADED
ALERT: Running test #1
ALERT: Test in isolated world with restrictive CSP
CONSOLE ERROR: Refused to load the image 'http://127.0.0.1:8000/security/resources/abe.png?0' because it violates the following Content Security Policy directive: "img-src 'self'".
ALERT: BLOCKED
ALERT: Running test #0
This test ensures that img-src checks respect the isolated world CSP when the IsolatedWorldCSP feature is enabled and bypass the main world CSP checks otherwise.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment