Commit 91f655b1 authored by asargent's avatar asargent Committed by Commit bot

Fix extension bindings injection for iframes

For iframes, we don't want to use the source url for determining the
associated extension because it starts out with an about:blank context
that is scriptable by its parent.

BUG=573131

Review-Url: https://codereview.chromium.org/2151693002
Cr-Commit-Position: refs/heads/master@{#407214}
parent 0a30b3b1
......@@ -9,6 +9,7 @@
#include "chrome/browser/net/url_request_mock_util.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/test/base/ui_test_utils.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/test/browser_test_utils.h"
......@@ -208,5 +209,65 @@ IN_PROC_BROWSER_TEST_F(ExtensionBindingsApiTest,
EXPECT_EQ("success", result);
}
class FramesExtensionBindingsApiTest : public ExtensionBindingsApiTest {
public:
void SetUpCommandLine(base::CommandLine* command_line) override {
ExtensionBindingsApiTest::SetUpCommandLine(command_line);
command_line->AppendSwitch(switches::kDisablePopupBlocking);
}
};
// This tests that web pages with iframes or child windows pointing at
// chrome-extenison:// urls, both web_accessible and nonexistent pages, don't
// get improper extensions bindings injected while they briefly still point at
// about:blank and are still scriptable by their parent.
//
// The general idea is to load up 2 extensions, one which listens for external
// messages ("receiver") and one which we'll try first faking messages from in
// the web page's iframe, as well as actually send a message from later
// ("sender").
IN_PROC_BROWSER_TEST_F(FramesExtensionBindingsApiTest, FramesBeforeNavigation) {
// Load the sender and receiver extensions, and make sure they are ready.
ExtensionTestMessageListener sender_ready("sender_ready", true);
const Extension* sender = LoadExtension(
test_data_dir_.AppendASCII("bindings").AppendASCII("message_sender"));
ASSERT_NE(nullptr, sender);
ASSERT_TRUE(sender_ready.WaitUntilSatisfied());
ExtensionTestMessageListener receiver_ready("receiver_ready", false);
const Extension* receiver =
LoadExtension(test_data_dir_.AppendASCII("bindings")
.AppendASCII("external_message_listener"));
ASSERT_NE(nullptr, receiver);
ASSERT_TRUE(receiver_ready.WaitUntilSatisfied());
// Load the web page which tries to impersonate the sender extension via
// scripting iframes/child windows before they finish navigating to pages
// within the sender extension.
ASSERT_TRUE(embedded_test_server()->Start());
ui_test_utils::NavigateToURL(
browser(),
embedded_test_server()->GetURL(
"/extensions/api_test/bindings/frames_before_navigation.html"));
bool page_success = false;
ASSERT_TRUE(content::ExecuteScriptAndExtractBool(
browser()->tab_strip_model()->GetWebContentsAt(0), "getResult()",
&page_success));
EXPECT_TRUE(page_success);
// Reply to |sender|, causing it to send a message over to |receiver|, and
// then ask |receiver| for the total message count. It should be 1 since
// |receiver| should not have received any impersonated messages.
sender_ready.Reply(receiver->id());
int message_count = 0;
ASSERT_TRUE(content::ExecuteScriptAndExtractInt(
ProcessManager::Get(profile())
->GetBackgroundHostForExtension(receiver->id())
->host_contents(),
"getMessageCountAfterReceivingRealSenderMessage()", &message_count));
EXPECT_EQ(1, message_count);
}
} // namespace
} // namespace extensions
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
var messagesReceived = [];
// Have we received the real message from the sender extension?
var receivedRealSenderMessage = false;
// Has the c++ code in the browser test asked us for the total count of messages
// we've received?
var sendCountAfterSenderMessage = false;
function getMessageCountAfterReceivingRealSenderMessage() {
if (receivedRealSenderMessage) {
window.domAutomationController.send(messagesReceived.length);
} else {
sendCountAfterSenderMessage = true;
}
}
chrome.runtime.onMessageExternal.addListener(function(msg, sender, respond) {
messagesReceived.push({msg:msg, sender:sender});
if (msg == 'from_sender') {
receivedRealSenderMessage = true;
if (sendCountAfterSenderMessage) {
window.domAutomationController.send(messagesReceived.length);
}
}
});
chrome.test.sendMessage('receiver_ready');
\ No newline at end of file
{
"name": "External Message Listener (id mlmdejkkkhmhchpmepehbcncoalclded)",
"version": "0.1",
"key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxvZCeDtPe+NIpnq7VtLOb/0bs+QlmF7cOvDzqgwniGEla8Z903Pq/oV8f649IQrvqfz+osvJxvqODzKiGU7a9g1cLu/fSVZXyhlGf7fCQjBYdPC7iFbFkTqSoeUOLCZzXYmxg/2t5WIWq8pLZgz043KjuzuS68pLRltHBRGeG+SWPu+cyfeNcFNPwlOjk6QCD/EUUG/QVeobACekF2/hh4TkRW5iU/vcJ+84aygr3r4hsvIoIEVMtlatHpzqAt/X01zsXJ5d9jH5DI5wnjUVPJKpA31QLkKu0Rq3s/79eTEEiZqSAFFA/1mMPs90u5iUyiYx3U5vNml/P+r6b31f0QIDAQAB",
"manifest_version": 2,
"permissions": ["tabs"],
"background": {
"persistent": true,
"scripts": ["background.js"]
}
}
<!doctype html>
<html>
<body>
<iframe id="frame1" src="chrome-extension://ficgdghpakbhhkmdjamiedmcoobamkoo/public.html"></iframe>
<iframe id="frame2" src="chrome-extension://ficgdghpakbhhkmdjamiedmcoobamkoo/nonexistent.html"></iframe>
<script>
// We expect that chrome.runtime.id will not be set in this frame, and that
// either chrome.runtime.sendMessage is not defined or we're able to call it
// (but elsewhere in the browser_test that uses this page we check that the
// message was not actually sent, or at least didn't incorrectly seem to come
// from the extension ficgdghpakbhhkmdjamiedmcoobamkoo).
function testFrame(win) {
var haveId = true;
var didRun = false;
try {
haveId = win.eval('typeof chrome.runtime != "undefined" && ' +
'typeof chrome.runtime.id != "undefined"');
win.eval('typeof chrome.runtime != "undefined" ' +
'&& chrome.runtime.sendMessage(' +
'"mlmdejkkkhmhchpmepehbcncoalclded", "evil")');
didRun = true;
} catch (e) {
console.log('caught exception: ' + e);
}
return didRun && !haveId;
}
// Test the two frames (actual page and nonexistent page) that were included in
// the original html document.
var frame1Success = testFrame(document.getElementById('frame1').contentWindow);
var frame2Success = testFrame(document.getElementById('frame2').contentWindow);
// Now test two frames that get dynamically created and added to the DOM, again
// one actual page and one nonexistent.
var frame3 = document.createElement('iframe');
frame3.src = 'chrome-extension://ficgdghpakbhhkmdjamiedmcoobamkoo/public.html';
document.body.appendChild(frame3);
var frame3Success = testFrame(frame3.contentWindow);
var frame4 = document.createElement('iframe');
frame4.src =
'chrome-extension://ficgdghpakbhhkmdjamiedmcoobamkoo/nonexistent.html';
document.body.appendChild(frame4);
var frame4Success = testFrame(frame4.contentWindow);
// Finally, test against two newly opened windows.
var newWin1 = window.open(
'chrome-extension://ficgdghpakbhhkmdjamiedmcoobamkoo/public.html');
var newWindow1Success = testFrame(newWin1);
var newWin2 = window.open(
'chrome-extension://ficgdghpakbhhkmdjamiedmcoobamkoo/nonexistent.html');
var newWindow2Success = testFrame(newWin2);
function getResult() {
window.domAutomationController.send(
frame1Success && frame2Success && frame3Success &&
frame4Success && newWindow1Success && newWindow2Success);
}
</script>
</body>
</html>
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
chrome.test.sendMessage('sender_ready', function(id) {
chrome.runtime.sendMessage(id, 'from_sender');
});
{
"name": "Message Sender (id ficgdghpakbhhkmdjamiedmcoobamkoo)",
"version": "0.1",
"key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAydrsgsTfyyY3zLW+j9ZNb0/Ej0yIKlysp5Scz549wyvpTZIeNqYrq9wt5W6Lcfe9jthuxlLRzKiPP/kk/B8CZCsyet9XbLxnKCJUJUMv/8iih0wC+7tmzqfcDPjBrjv5VT0IAGBaupIDvJbSnf8efGI1U2c3teXZrpk320asfL26x+xezQOPWTcbrY4KINH11AZK8slB0WsWy2pxoel8O7uPRDDy4uqGX27J6oU3WdU5A4/L8j8UQCFcgAuQu/0Cu8WoLuJ4z3CdObQCBXCnmvyIKMpgmqa3Yk5oJRPBqOFkwMnJRFivnTjrBss/M9Jft3WzkHx4Wn9mDzDXQLltOQIDAQAB",
"manifest_version": 2,
"background": {
"persistent": true,
"scripts": ["background.js"]
},
"web_accessible_resources": [
"public.html"
]
}
......@@ -108,7 +108,7 @@ ScriptContext::ScriptContext(const v8::Local<v8::Context>& v8_context,
effective_context_type_(effective_context_type),
safe_builtins_(this),
isolate_(v8_context->GetIsolate()),
url_(web_frame_ ? GetDataSourceURLForFrame(web_frame_) : GURL()),
url_(web_frame_ ? GURL(web_frame_->document().url()) : GURL()),
runner_(new Runner(this)) {
VLOG(1) << "Created context:\n" << GetDebugString();
gin::PerContextData* gin_data = gin::PerContextData::From(v8_context);
......
......@@ -140,8 +140,21 @@ const Extension* ScriptContextSet::GetExtensionFromFrameAndWorld(
// Isolated worlds (content script).
extension_id = ScriptInjection::GetHostIdForIsolatedWorld(world_id);
} else {
// Extension pages (chrome-extension:// URLs).
GURL frame_url = ScriptContext::GetDataSourceURLForFrame(frame);
// For looking up the extension associated with this frame, we either want
// to use the current url or possibly the data source url (which this frame
// may be navigating to shortly), depending on the security origin of the
// frame. We don't always want to use the data source url because some
// frames (eg iframes and windows created via window.open) briefly contain
// an about:blank script context that is scriptable by their parent/opener
// before they finish navigating.
GURL frame_url(frame->document().url());
GURL data_src_url = ScriptContext::GetDataSourceURLForFrame(frame);
if (frame_url.is_empty() && data_src_url.is_valid() &&
frame->getSecurityOrigin().canAccess(
blink::WebSecurityOrigin::create(data_src_url))) {
frame_url = data_src_url;
}
frame_url = ScriptContext::GetEffectiveDocumentURL(frame, frame_url,
use_effective_url);
extension_id =
......
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