Check permissions for chrome.tabs.executeScript() in the
renderer just before injection to avoid races.
BUG=30937
Review URL: http://codereview.chromium.org/509032
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@35176 0039d316-1c4b-4281-b951-d872f2087c98
diff --git a/chrome/browser/extensions/execute_code_in_tab_function.cc b/chrome/browser/extensions/execute_code_in_tab_function.cc
index 98757d2..5dc6690 100644
--- a/chrome/browser/extensions/execute_code_in_tab_function.cc
+++ b/chrome/browser/extensions/execute_code_in_tab_function.cc
@@ -68,6 +68,8 @@
DCHECK(browser);
DCHECK(contents);
+ // NOTE: This can give the wrong answer due to race conditions, but it is OK,
+ // we check again in the renderer.
if (!GetExtension()->CanAccessHost(contents->GetURL())) {
error_ = ExtensionErrorUtils::FormatErrorMessage(
keys::kCannotAccessPageError, contents->GetURL().spec());
@@ -135,6 +137,12 @@
return;
}
+ Extension* extension = GetExtension();
+ if (!extension) {
+ SendResponse(false);
+ return;
+ }
+
bool is_js_code = true;
std::string function_name = name();
if (function_name == TabsInsertCSSFunction::function_name()) {
@@ -145,8 +153,9 @@
registrar_.Add(this, NotificationType::TAB_CODE_EXECUTED,
NotificationService::AllSources());
AddRef(); // balanced in Observe()
- contents->ExecuteCode(request_id(), extension_id(), is_js_code,
- code_string, all_frames_);
+ contents->ExecuteCode(request_id(), extension->id(),
+ extension->host_permissions(), is_js_code, code_string,
+ all_frames_);
}
void ExecuteCodeInTabFunction::Observe(NotificationType type,
diff --git a/chrome/browser/extensions/execute_script_apitest.cc b/chrome/browser/extensions/execute_script_apitest.cc
index 902ffe6..07662b6c 100644
--- a/chrome/browser/extensions/execute_script_apitest.cc
+++ b/chrome/browser/extensions/execute_script_apitest.cc
@@ -7,10 +7,13 @@
// This test failed at times on the Vista dbg builder and has been marked as
// flaky for now. Bug http://code.google.com/p/chromium/issues/detail?id=28630
IN_PROC_BROWSER_TEST_F(ExtensionApiTest, FLAKY_ExecuteScript) {
- host_resolver()->AddRule("a.com", "127.0.0.1");
+ // We need a.com to be a little bit slow to trigger a race condition.
+ host_resolver()->AddRuleWithLatency("a.com", "127.0.0.1", 500);
host_resolver()->AddRule("b.com", "127.0.0.1");
+ host_resolver()->AddRule("c.com", "127.0.0.1");
StartHTTPServer();
ASSERT_TRUE(RunExtensionTest("executescript/basic")) << message_;
ASSERT_TRUE(RunExtensionTest("executescript/in_frame")) << message_;
+ ASSERT_TRUE(RunExtensionTest("executescript/permissions")) << message_;
}
diff --git a/chrome/browser/tab_contents/tab_contents.cc b/chrome/browser/tab_contents/tab_contents.cc
index 06801fd..39a25f2 100644
--- a/chrome/browser/tab_contents/tab_contents.cc
+++ b/chrome/browser/tab_contents/tab_contents.cc
@@ -815,12 +815,13 @@
}
void TabContents::ExecuteCode(int request_id, const std::string& extension_id,
+ const std::vector<URLPattern>& host_permissions,
bool is_js_code, const std::string& code_string,
bool all_frames) {
render_view_host()->Send(
- new ViewMsg_ExecuteCode(render_view_host()->routing_id(), request_id,
- extension_id, is_js_code, code_string,
- all_frames));
+ new ViewMsg_ExecuteCode(render_view_host()->routing_id(),
+ ViewMsg_ExecuteCode_Params(request_id, extension_id, host_permissions,
+ is_js_code, code_string, all_frames)));
}
void TabContents::PopupNotificationVisibilityChanged(bool visible) {
diff --git a/chrome/browser/tab_contents/tab_contents.h b/chrome/browser/tab_contents/tab_contents.h
index 2a84b8c..112675d 100644
--- a/chrome/browser/tab_contents/tab_contents.h
+++ b/chrome/browser/tab_contents/tab_contents.h
@@ -32,6 +32,7 @@
#include "chrome/browser/tab_contents/navigation_entry.h"
#include "chrome/browser/tab_contents/page_navigator.h"
#include "chrome/browser/tab_contents/render_view_host_manager.h"
+#include "chrome/common/extensions/url_pattern.h"
#include "chrome/common/navigation_types.h"
#include "chrome/common/notification_registrar.h"
#include "chrome/common/property_bag.h"
@@ -368,6 +369,7 @@
// Execute code in this tab.
void ExecuteCode(int request_id, const std::string& extension_id,
+ const std::vector<URLPattern>& host_permissions,
bool is_js_code, const std::string& code_string,
bool all_frames);
diff --git a/chrome/common/render_messages.h b/chrome/common/render_messages.h
index c886522e..555ebd06 100644
--- a/chrome/common/render_messages.h
+++ b/chrome/common/render_messages.h
@@ -501,6 +501,39 @@
DOMStorageType storage_type_;
};
+// Allows an extension to execute code in a tab.
+struct ViewMsg_ExecuteCode_Params {
+ ViewMsg_ExecuteCode_Params(){}
+ ViewMsg_ExecuteCode_Params(int request_id, const std::string& extension_id,
+ const std::vector<URLPattern>& host_permissions,
+ bool is_javascript, const std::string& code,
+ bool all_frames)
+ : request_id(request_id), extension_id(extension_id),
+ host_permissions(host_permissions), is_javascript(is_javascript),
+ code(code), all_frames(all_frames) {
+ }
+
+ // The extension API request id, for responding.
+ int request_id;
+
+ // The ID of the requesting extension. To know which isolated world to
+ // execute the code inside of.
+ std::string extension_id;
+
+ // The host permissions of the requesting extension. So that we can check them
+ // right before injecting, to avoid any race conditions.
+ std::vector<URLPattern> host_permissions;
+
+ // Whether the code is JavaScript or CSS.
+ bool is_javascript;
+
+ // String of code to execute.
+ std::string code;
+
+ // Whether to inject into all frames, or only the root frame.
+ bool all_frames;
+};
+
namespace IPC {
template <>
@@ -2243,6 +2276,32 @@
}
};
+template<>
+struct ParamTraits<ViewMsg_ExecuteCode_Params> {
+ typedef ViewMsg_ExecuteCode_Params param_type;
+ static void Write(Message* m, const param_type& p) {
+ WriteParam(m, p.request_id);
+ WriteParam(m, p.extension_id);
+ WriteParam(m, p.host_permissions);
+ WriteParam(m, p.is_javascript);
+ WriteParam(m, p.code);
+ WriteParam(m, p.all_frames);
+ }
+
+ static bool Read(const Message* m, void** iter, param_type* p) {
+ return
+ ReadParam(m, iter, &p->request_id) &&
+ ReadParam(m, iter, &p->extension_id) &&
+ ReadParam(m, iter, &p->host_permissions) &&
+ ReadParam(m, iter, &p->is_javascript) &&
+ ReadParam(m, iter, &p->code) &&
+ ReadParam(m, iter, &p->all_frames);
+ }
+ static void Log(const param_type& p, std::wstring* l) {
+ l->append(L"<ViewMsg_ExecuteCode_Params>");
+ }
+};
+
} // namespace IPC
diff --git a/chrome/common/render_messages_internal.h b/chrome/common/render_messages_internal.h
index ca3e80b..453cdf38 100644
--- a/chrome/common/render_messages_internal.h
+++ b/chrome/common/render_messages_internal.h
@@ -745,15 +745,8 @@
ViewType::Type /* view_type */)
// Notification that renderer should run some JavaScript code.
- IPC_MESSAGE_ROUTED5(ViewMsg_ExecuteCode,
- int, /* request id */
- std::string, /* id of extension which runs the scripts */
- bool, /* It's true if the code is JavaScript; Otherwise
- the code is CSS text. */
- std::string, /* code would be executed */
- bool /* It's true if the code would be injected into all
- frames; Otherwise the code is only injected into
- top main frame */)
+ IPC_MESSAGE_ROUTED1(ViewMsg_ExecuteCode,
+ ViewMsg_ExecuteCode_Params)
// Returns a file handle
IPC_MESSAGE_CONTROL2(ViewMsg_DatabaseOpenFileResponse,
diff --git a/chrome/renderer/render_view.cc b/chrome/renderer/render_view.cc
index baac373..c4f7483 100644
--- a/chrome/renderer/render_view.cc
+++ b/chrome/renderer/render_view.cc
@@ -214,6 +214,16 @@
result->push_back(urls[i]);
}
+static bool UrlMatchesPermissions(
+ const GURL& url, const std::vector<URLPattern>& host_permissions) {
+ for (size_t i = 0; i < host_permissions.size(); ++i) {
+ if (host_permissions[i].MatchesUrl(url))
+ return true;
+ }
+
+ return false;
+}
+
///////////////////////////////////////////////////////////////////////////////
int32 RenderView::next_page_id_ = 1;
@@ -2358,10 +2368,9 @@
WebFrame* main_frame = webview()->mainFrame();
if (frame == main_frame) {
while (!pending_code_execution_queue_.empty()) {
- scoped_refptr<CodeExecutionInfo> info =
+ linked_ptr<ViewMsg_ExecuteCode_Params>& params =
pending_code_execution_queue_.front();
- ExecuteCodeImpl(main_frame, info->request_id, info->extension_id,
- info->is_js_code, info->code_string, info->all_frames);
+ ExecuteCodeImpl(main_frame, *params);
pending_code_execution_queue_.pop();
}
}
@@ -3807,57 +3816,53 @@
edit_commands_ = edit_commands;
}
-void RenderView::OnExecuteCode(int request_id, const std::string& extension_id,
- bool is_js_code,
- const std::string& code_string,
- bool all_frames) {
+void RenderView::OnExecuteCode(const ViewMsg_ExecuteCode_Params& params) {
WebFrame* main_frame = webview() ? webview()->mainFrame() : NULL;
if (!main_frame) {
- Send(new ViewMsg_ExecuteCodeFinished(routing_id_, request_id, false));
+ Send(new ViewMsg_ExecuteCodeFinished(routing_id_, params.request_id,
+ false));
return;
}
WebDataSource* ds = main_frame->dataSource();
NavigationState* navigation_state = NavigationState::FromDataSource(ds);
if (!navigation_state->user_script_idle_scheduler()->has_run()) {
- scoped_refptr<CodeExecutionInfo> info = new CodeExecutionInfo(
- request_id, extension_id, is_js_code, code_string, all_frames);
- pending_code_execution_queue_.push(info);
+ pending_code_execution_queue_.push(
+ linked_ptr<ViewMsg_ExecuteCode_Params>(
+ new ViewMsg_ExecuteCode_Params(params)));
return;
}
- ExecuteCodeImpl(main_frame, request_id, extension_id, is_js_code,
- code_string, all_frames);
+ ExecuteCodeImpl(main_frame, params);
}
void RenderView::ExecuteCodeImpl(WebFrame* frame,
- int request_id,
- const std::string& extension_id,
- bool is_js_code,
- const std::string& code_string,
- bool all_frames) {
+ const ViewMsg_ExecuteCode_Params& params) {
std::vector<WebFrame*> frame_vector;
frame_vector.push_back(frame);
- if (all_frames)
+ if (params.all_frames)
GetAllChildFrames(frame, &frame_vector);
for (std::vector<WebFrame*>::iterator frame_it = frame_vector.begin();
frame_it != frame_vector.end(); ++frame_it) {
WebFrame* frame = *frame_it;
- if (is_js_code) {
+ if (params.is_javascript) {
+ if (!UrlMatchesPermissions(frame->url(), params.host_permissions))
+ continue;
+
std::vector<WebScriptSource> sources;
sources.push_back(
- WebScriptSource(WebString::fromUTF8(code_string)));
- UserScriptSlave::InsertInitExtensionCode(&sources, extension_id);
+ WebScriptSource(WebString::fromUTF8(params.code)));
+ UserScriptSlave::InsertInitExtensionCode(&sources, params.extension_id);
frame->executeScriptInIsolatedWorld(
- UserScriptSlave::GetIsolatedWorldId(extension_id),
+ UserScriptSlave::GetIsolatedWorldId(params.extension_id),
&sources.front(), sources.size(), EXTENSION_GROUP_CONTENT_SCRIPTS);
} else {
- frame->insertStyleText(WebString::fromUTF8(code_string), WebString());
+ frame->insertStyleText(WebString::fromUTF8(params.code), WebString());
}
}
- Send(new ViewMsg_ExecuteCodeFinished(routing_id_, request_id, true));
+ Send(new ViewMsg_ExecuteCodeFinished(routing_id_, params.request_id, true));
}
void RenderView::Close() {
diff --git a/chrome/renderer/render_view.h b/chrome/renderer/render_view.h
index c69a601..60bf6cb 100644
--- a/chrome/renderer/render_view.h
+++ b/chrome/renderer/render_view.h
@@ -16,6 +16,7 @@
#include "base/gfx/point.h"
#include "base/gfx/rect.h"
#include "base/id_map.h"
+#include "base/linked_ptr.h"
#include "base/shared_memory.h"
#include "base/timer.h"
#include "base/values.h"
@@ -25,6 +26,7 @@
#include "chrome/common/navigation_gesture.h"
#include "chrome/common/notification_type.h"
#include "chrome/common/page_zoom.h"
+#include "chrome/common/render_messages.h"
#include "chrome/common/renderer_preferences.h"
#include "chrome/common/view_types.h"
#include "chrome/renderer/automation/dom_automation_controller.h"
@@ -611,17 +613,9 @@
const WebKit::WebMediaPlayerAction& action);
void OnNotifyRendererViewType(ViewType::Type view_type);
void OnUpdateBrowserWindowId(int window_id);
- void OnExecuteCode(int request_id,
- const std::string& extension_id,
- bool is_js_code,
- const std::string& code_string,
- bool all_frames);
+ void OnExecuteCode(const ViewMsg_ExecuteCode_Params& params);
void ExecuteCodeImpl(WebKit::WebFrame* frame,
- int request_id,
- const std::string& extension_id,
- bool is_js_code,
- const std::string& code_string,
- bool all_frames);
+ const ViewMsg_ExecuteCode_Params& params);
void OnUpdateBackForwardListCount(int back_list_count,
int forward_list_count);
void OnGetAccessibilityInfo(
@@ -961,34 +955,8 @@
// Id number of browser window which RenderView is attached to.
int browser_window_id_;
- // If page is loading, we can't run code, just create CodeExecutionInfo
- // objects store pending execution information and delay the execution until
- // page is loaded.
- struct CodeExecutionInfo : public base::RefCounted<CodeExecutionInfo> {
- CodeExecutionInfo(int id_of_request, const std::string& id_of_extension,
- bool is_js, const std::string& code,
- bool inject_to_all_frames)
- : request_id(id_of_request),
- extension_id(id_of_extension),
- code_string(code),
- is_js_code(is_js),
- all_frames(inject_to_all_frames) {}
- int request_id;
-
- // The id of extension who issues the pending executeScript API call.
- std::string extension_id;
-
- // The code which would be executed.
- std::string code_string;
-
- // It's true if |code_string| is JavaScript; otherwise |code_string| is
- // CSS text.
- bool is_js_code;
- // It's true if the code_string would be injected into all frames.
- bool all_frames;
- };
-
- std::queue<scoped_refptr<CodeExecutionInfo> > pending_code_execution_queue_;
+ std::queue<linked_ptr<ViewMsg_ExecuteCode_Params> >
+ pending_code_execution_queue_;
// page id for the last navigation sent to the browser.
int32 last_top_level_navigation_page_id_;
diff --git a/chrome/test/data/extensions/api_test/executescript/permissions/empty.html b/chrome/test/data/extensions/api_test/executescript/permissions/empty.html
new file mode 100644
index 0000000..90531a4b
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/executescript/permissions/empty.html
@@ -0,0 +1,2 @@
+<html>
+</html>
diff --git a/chrome/test/data/extensions/api_test/executescript/permissions/frames.html b/chrome/test/data/extensions/api_test/executescript/permissions/frames.html
new file mode 100644
index 0000000..85ba47b3f
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/executescript/permissions/frames.html
@@ -0,0 +1,10 @@
+<html>
+<iframe src="http://a.com:1337/files/extensions/api_test/executescript/permissions/empty.html">
+</iframe>
+
+<iframe src="http://b.com:1337/files/extensions/api_test/executescript/permissions/empty.html">
+</iframe>
+
+<iframe src="http://c.com:1337/files/extensions/api_test/executescript/permissions/empty.html">
+</iframe>
+</html>
diff --git a/chrome/test/data/extensions/api_test/executescript/permissions/manifest.json b/chrome/test/data/extensions/api_test/executescript/permissions/manifest.json
new file mode 100644
index 0000000..50c7de35
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/executescript/permissions/manifest.json
@@ -0,0 +1,6 @@
+{
+ "version": "1.0.0.0",
+ "name": "Tests edge cases in chrome.tabs.executeScript() with permissions",
+ "background_page": "test.html",
+ "permissions": ["tabs", "http://a.com/", "http://b.com/"]
+}
diff --git a/chrome/test/data/extensions/api_test/executescript/permissions/script.js b/chrome/test/data/extensions/api_test/executescript/permissions/script.js
new file mode 100644
index 0000000..b73bce3
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/executescript/permissions/script.js
@@ -0,0 +1 @@
+chrome.extension.sendRequest(location.href);
diff --git a/chrome/test/data/extensions/api_test/executescript/permissions/test.html b/chrome/test/data/extensions/api_test/executescript/permissions/test.html
new file mode 100644
index 0000000..74c6e32
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/executescript/permissions/test.html
@@ -0,0 +1,55 @@
+<script>
+var assertEq = chrome.test.assertEq;
+var assertTrue = chrome.test.assertTrue;
+var numReceivedRequests = 0;
+var relativePath = 'files/extensions/api_test/executescript/permissions/';
+var testFile = relativePath + 'empty.html';
+var testFileFrames = relativePath + 'frames.html';
+var onTabLoaded;
+
+chrome.extension.onRequest.addListener(function(req) {
+ numReceivedRequests++;
+});
+
+chrome.tabs.onUpdated.addListener(function(tabId, changeInfo, tab) {
+ if (tab.status == 'complete' && onTabLoaded)
+ onTabLoaded(tab);
+});
+
+chrome.test.runTests([
+ // Test a race that used to occur here (see bug 30937).
+ // Open a tab that we're not allowed to execute in (c.com), then navigate it
+ // to a tab we *are* allowed to execute in (a.com), then quickly run script in
+ // the tab before it navigates. It should appear to work (no error -- it could
+ // have been a developer mistake), but not actually do anything.
+ function() {
+ chrome.tabs.create({url: 'http://c.com:1337/' + testFile});
+ onTabLoaded = function(tab) {
+ onTabLoaded = null;
+ numReceivedRequests = 0;
+ chrome.tabs.update(tab.id, {url: 'http://a.com:1337/' + testFile});
+ chrome.tabs.executeScript(tab.id, {file: 'script.js'});
+ window.setTimeout(function() {
+ assertEq(0, numReceivedRequests);
+ chrome.test.runNextTest();
+ }, 4000);
+ };
+ },
+
+ // Inject into all frames. This should only affect frames we have access to.
+ // This page has three subframes, one each from a.com, b.com, and c.com. We
+ // have access to two of those, plus the root frame, so we should get three
+ // responses.
+ function() {
+ chrome.tabs.create({url: 'http://a.com:1337/' + testFileFrames});
+ onTabLoaded = function(tab) {
+ numReceivedRequests = 0;
+ chrome.tabs.executeScript(tab.id, {file: 'script.js', allFrames: true});
+ window.setTimeout(function() {
+ chrome.test.assertEq(3, numReceivedRequests);
+ chrome.test.runNextTest();
+ }, 4000);
+ };
+ }
+]);
+</script>