summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRaymes Khoury <raymes@chromium.org>2015-09-02 10:20:49 +1000
committerRaymes Khoury <raymes@chromium.org>2015-09-02 00:21:57 +0000
commit398696033a3f2e954aea68fdec10becb836a80b4 (patch)
tree31ae7b57d083a050c16628dd52d27cfcc1952b34
parenta9db9721cf2c1c1f7373e123718e1433419d3598 (diff)
downloadchromium_src-398696033a3f2e954aea68fdec10becb836a80b4.zip
chromium_src-398696033a3f2e954aea68fdec10becb836a80b4.tar.gz
chromium_src-398696033a3f2e954aea68fdec10becb836a80b4.tar.bz2
Prevent leaking PDF data cross-origin
BUG=520422 Review URL: https://codereview.chromium.org/1311973002 Cr-Commit-Position: refs/heads/master@{#345267} (cherry picked from commit fff450abc4e2fb330ba700547a8e6a7b0fb90a6e) Review URL: https://codereview.chromium.org/1316803003 . Cr-Commit-Position: refs/branch-heads/2454@{#446} Cr-Branched-From: 12bfc3360892ec53cd00fc239a47e5298beb063b-refs/heads/master@{#338390}
-rw-r--r--chrome/browser/pdf/pdf_extension_test.cc94
-rw-r--r--chrome/browser/resources/pdf/pdf.js19
-rw-r--r--pdf/out_of_process_instance.cc31
3 files changed, 126 insertions, 18 deletions
diff --git a/chrome/browser/pdf/pdf_extension_test.cc b/chrome/browser/pdf/pdf_extension_test.cc
index d6718e7..aec0754 100644
--- a/chrome/browser/pdf/pdf_extension_test.cc
+++ b/chrome/browser/pdf/pdf_extension_test.cc
@@ -26,6 +26,15 @@
const int kNumberLoadTestParts = 10;
+bool GetGuestCallback(content::WebContents** guest_out,
+ content::WebContents* guest) {
+ EXPECT_FALSE(*guest_out);
+ *guest_out = guest;
+ // Return false so that we iterate through all the guests and verify there is
+ // only one.
+ return false;
+}
+
class PDFExtensionTest : public ExtensionApiTest,
public testing::WithParamInterface<int> {
public:
@@ -156,6 +165,48 @@ class PDFExtensionTest : public ExtensionApiTest,
// someone deleting the directory and silently making the test pass.
ASSERT_GE(count, 1u);
}
+
+ void TestGetSelectedTextReply(GURL url, bool expect_success) {
+ ui_test_utils::NavigateToURL(browser(), url);
+ content::WebContents* web_contents =
+ browser()->tab_strip_model()->GetActiveWebContents();
+ ASSERT_TRUE(pdf_extension_test_util::EnsurePDFHasLoaded(web_contents));
+
+ // Reach into the guest and hook into it such that it posts back a 'flush'
+ // message after every getSelectedTextReply message sent.
+ content::BrowserPluginGuestManager* guest_manager =
+ web_contents->GetBrowserContext()->GetGuestManager();
+ content::WebContents* guest_contents = nullptr;
+ ASSERT_NO_FATAL_FAILURE(guest_manager->ForEachGuest(
+ web_contents, base::Bind(&GetGuestCallback, &guest_contents)));
+ ASSERT_TRUE(guest_contents);
+ ASSERT_TRUE(content::ExecuteScript(
+ guest_contents,
+ "var oldSendScriptingMessage = "
+ " PDFViewer.prototype.sendScriptingMessage_;"
+ "PDFViewer.prototype.sendScriptingMessage_ = function(message) {"
+ " oldSendScriptingMessage.bind(this)(message);"
+ " if (message.type == 'getSelectedTextReply')"
+ " this.parentWindow_.postMessage('flush', '*');"
+ "}"));
+
+ // Add an event listener for flush messages and request the selected text.
+ // If we get a flush message without receiving getSelectedText we know that
+ // the message didn't come through.
+ bool success = false;
+ ASSERT_TRUE(content::ExecuteScriptAndExtractBool(
+ web_contents,
+ "window.addEventListener('message', function(event) {"
+ " if (event.data == 'flush')"
+ " window.domAutomationController.send(false);"
+ " if (event.data.type == 'getSelectedTextReply')"
+ " window.domAutomationController.send(true);"
+ "});"
+ "document.getElementsByTagName('embed')[0].postMessage("
+ " {type: 'getSelectedText'});",
+ &success));
+ ASSERT_EQ(expect_success, success);
+ }
};
IN_PROC_BROWSER_TEST_P(PDFExtensionTest, Load) {
@@ -200,6 +251,49 @@ IN_PROC_BROWSER_TEST_F(PDFExtensionTest, ZoomManager) {
RunTestsInFile("zoom_manager_test.js", "test.pdf");
}
+// Ensure that the internal PDF plugin application/x-google-chrome-pdf won't be
+// loaded if it's not loaded in the chrome extension page.
+IN_PROC_BROWSER_TEST_F(PDFExtensionTest, EnsureInternalPluginDisabled) {
+ std::string url = embedded_test_server()->GetURL("/pdf/test.pdf").spec();
+ std::string data_url =
+ "data:text/html,"
+ "<html><body>"
+ "<embed type=\"application/x-google-chrome-pdf\" src=\"" +
+ url +
+ "\">"
+ "</body></html>";
+ ui_test_utils::NavigateToURL(browser(), GURL(data_url));
+ content::WebContents* web_contents =
+ browser()->tab_strip_model()->GetActiveWebContents();
+ bool plugin_loaded = false;
+ ASSERT_TRUE(content::ExecuteScriptAndExtractBool(
+ web_contents,
+ "var plugin_loaded = "
+ " document.getElementsByTagName('embed')[0].postMessage !== undefined;"
+ "window.domAutomationController.send(plugin_loaded);",
+ &plugin_loaded));
+ ASSERT_FALSE(plugin_loaded);
+}
+
+// Ensure cross-origin replies won't work for getSelectedText.
+IN_PROC_BROWSER_TEST_F(PDFExtensionTest, EnsureCrossOriginRepliesBlocked) {
+ std::string url = embedded_test_server()->GetURL("/pdf/test.pdf").spec();
+ std::string data_url =
+ "data:text/html,"
+ "<html><body>"
+ "<embed type=\"application/pdf\" src=\"" +
+ url +
+ "\">"
+ "</body></html>";
+ TestGetSelectedTextReply(GURL(data_url), false);
+}
+
+// Ensure same-origin replies do work for getSelectedText.
+IN_PROC_BROWSER_TEST_F(PDFExtensionTest, EnsureSameOriginRepliesAllowed) {
+ TestGetSelectedTextReply(embedded_test_server()->GetURL("/pdf/test.pdf"),
+ true);
+}
+
class MaterialPDFExtensionTest : public PDFExtensionTest {
void SetUpCommandLine(base::CommandLine* command_line) override {
command_line->AppendSwitch(switches::kEnablePdfMaterialUI);
diff --git a/chrome/browser/resources/pdf/pdf.js b/chrome/browser/resources/pdf/pdf.js
index 43e085e..c497574 100644
--- a/chrome/browser/resources/pdf/pdf.js
+++ b/chrome/browser/resources/pdf/pdf.js
@@ -84,6 +84,7 @@ function PDFViewer(browserApi) {
this.browserApi_ = browserApi;
this.loadState_ = LoadState.LOADING;
this.parentWindow_ = null;
+ this.parentOrigin_ = null;
this.delayedScriptingMessages_ = [];
@@ -674,6 +675,7 @@ PDFViewer.prototype = {
handleScriptingMessage: function(message) {
if (this.parentWindow_ != message.source) {
this.parentWindow_ = message.source;
+ this.parentOrigin_ = message.origin;
// Ensure that we notify the embedder if the document is loaded.
if (this.loadState_ != LoadState.LOADING)
this.sendDocumentLoadedMessage_();
@@ -760,11 +762,22 @@ PDFViewer.prototype = {
* @param {Object} message the message to send.
*/
sendScriptingMessage_: function(message) {
- if (this.parentWindow_)
- this.parentWindow_.postMessage(message, '*');
+ if (this.parentWindow_ && this.parentOrigin_) {
+ var targetOrigin;
+ // Only send data back to the embedder if it is from the same origin,
+ // unless we're sending it to ourselves (which could happen in the case
+ // of tests). We also allow documentLoaded messages through as this won't
+ // leak important information.
+ if (this.parentOrigin_ == window.location.origin)
+ targetOrigin = this.parentOrigin_;
+ else if (message.type == 'documentLoaded')
+ targetOrigin = '*';
+ else
+ targetOrigin = this.browserApi_.getStreamInfo().originalUrl;
+ this.parentWindow_.postMessage(message, targetOrigin);
+ }
},
-
/**
* @type {Viewport} the viewport of the PDF viewer.
*/
diff --git a/pdf/out_of_process_instance.cc b/pdf/out_of_process_instance.cc
index df881c5..808606b 100644
--- a/pdf/out_of_process_instance.cc
+++ b/pdf/out_of_process_instance.cc
@@ -303,23 +303,24 @@ bool OutOfProcessInstance::Init(uint32_t argc,
const char* argn[],
const char* argv[]) {
// Check if the PDF is being loaded in the PDF chrome extension. We only allow
- // the plugin to be put into "full frame" mode when it is being loaded in the
- // extension because this enables some features that we don't want pages
- // abusing outside of the extension.
+ // the plugin to be loaded in the extension and print preview to avoid
+ // exposing sensitive APIs directly to external websites.
pp::Var document_url_var = pp::URLUtil_Dev::Get()->GetDocumentURL(this);
- std::string document_url = document_url_var.is_string() ?
- document_url_var.AsString() : std::string();
+ if (!document_url_var.is_string())
+ return false;
+ std::string document_url = document_url_var.AsString();
std::string extension_url = std::string(kChromeExtension);
- bool in_extension =
- !document_url.compare(0, extension_url.size(), extension_url);
-
- if (in_extension) {
- // Check if the plugin is full frame. This is passed in from JS.
- for (uint32_t i = 0; i < argc; ++i) {
- if (strcmp(argn[i], "full-frame") == 0) {
- full_ = true;
- break;
- }
+ std::string print_preview_url = std::string(kChromePrint);
+ if (!base::StringPiece(document_url).starts_with(kChromeExtension) &&
+ !base::StringPiece(document_url).starts_with(kChromePrint)) {
+ return false;
+ }
+
+ // Check if the plugin is full frame. This is passed in from JS.
+ for (uint32_t i = 0; i < argc; ++i) {
+ if (strcmp(argn[i], "full-frame") == 0) {
+ full_ = true;
+ break;
}
}