Ensure that HistoryController's current entry is updated on inert commits.
BUG=480201
TEST=See bug for repro steps.
Review URL: https://codereview.chromium.org/1112823005
Cr-Commit-Position: refs/heads/master@{#327806}
diff --git a/content/browser/bad_message.h b/content/browser/bad_message.h
index fe89e1c..8084d8b 100644
--- a/content/browser/bad_message.h
+++ b/content/browser/bad_message.h
@@ -39,6 +39,7 @@
RWHVM_UNEXPECTED_FRAME_TYPE,
RFPH_DETACH,
DFH_BAD_EMBEDDER_MESSAGE,
+ NC_AUTO_SUBFRAME,
// Please add new elements here. The naming convention is abbreviated class
// name (e.g. RenderFrameHost becomes RFH) plus a unique description of the
// reason.
diff --git a/content/browser/frame_host/navigation_controller_impl.cc b/content/browser/frame_host/navigation_controller_impl.cc
index 17f0007..f2850dc 100644
--- a/content/browser/frame_host/navigation_controller_impl.cc
+++ b/content/browser/frame_host/navigation_controller_impl.cc
@@ -1316,6 +1316,13 @@
// Update the current navigation entry in case we're going back/forward.
if (entry_index != last_committed_entry_index_) {
+ // Make sure that a subframe commit isn't changing the main frame URL.
+ // Otherwise the renderer process may be confused, leading to a URL spoof.
+ if (GetLastCommittedEntry()->GetURL() !=
+ GetEntryAtIndex(entry_index)->GetURL()) {
+ bad_message::ReceivedBadMessage(rfh->GetProcess(),
+ bad_message::NC_AUTO_SUBFRAME);
+ }
last_committed_entry_index_ = entry_index;
DiscardNonCommittedEntriesInternal();
return true;
diff --git a/content/browser/frame_host/navigation_controller_impl_browsertest.cc b/content/browser/frame_host/navigation_controller_impl_browsertest.cc
index 7a89b26c..57bbf7c 100644
--- a/content/browser/frame_host/navigation_controller_impl_browsertest.cc
+++ b/content/browser/frame_host/navigation_controller_impl_browsertest.cc
@@ -1070,4 +1070,104 @@
ResourceDispatcherHost::Get()->SetDelegate(nullptr);
}
+// Ensure the renderer process does not get confused about the current entry
+// due to subframes and replaced entries. See https://crbug.com/480201.
+IN_PROC_BROWSER_TEST_F(NavigationControllerBrowserTest,
+ PreventSpoofFromSubframeAndReplace) {
+ // Start at an initial URL.
+ GURL url1(embedded_test_server()->GetURL(
+ "/navigation_controller/simple_page_1.html"));
+ NavigateToURL(shell(), url1);
+
+ // Now go to a page with a real iframe.
+ GURL url2(embedded_test_server()->GetURL(
+ "/navigation_controller/page_with_data_iframe.html"));
+ NavigateToURL(shell(), url2);
+
+ // It is safe to obtain the root frame tree node here, as it doesn't change.
+ FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
+ ->GetFrameTree()
+ ->root();
+ ASSERT_EQ(1U, root->child_count());
+ ASSERT_NE(nullptr, root->child_at(0));
+
+ {
+ // Navigate in the iframe.
+ FrameNavigateParamsCapturer capturer(root->child_at(0));
+ GURL frame_url(embedded_test_server()->GetURL(
+ "/navigation_controller/simple_page_2.html"));
+ NavigateFrameToURL(root->child_at(0), frame_url);
+ capturer.Wait();
+ EXPECT_EQ(NAVIGATION_TYPE_NEW_SUBFRAME, capturer.details().type);
+ }
+
+ {
+ // Go back in the iframe.
+ TestNavigationObserver back_load_observer(shell()->web_contents());
+ shell()->web_contents()->GetController().GoBack();
+ back_load_observer.Wait();
+ }
+
+ {
+ // Go forward in the iframe.
+ TestNavigationObserver forward_load_observer(shell()->web_contents());
+ shell()->web_contents()->GetController().GoForward();
+ forward_load_observer.Wait();
+ }
+
+ GURL url3(embedded_test_server()->GetURL(
+ "/navigation_controller/page_with_iframe.html"));
+ {
+ // location.replace() to cause an inert commit.
+ TestNavigationObserver replace_load_observer(shell()->web_contents());
+ std::string script = "location.replace('" + url3.spec() + "')";
+ EXPECT_TRUE(content::ExecuteScript(root->current_frame_host(), script));
+ replace_load_observer.Wait();
+ }
+
+ {
+ // Go back to url2.
+ TestNavigationObserver back_load_observer(shell()->web_contents());
+ shell()->web_contents()->GetController().GoBack();
+ back_load_observer.Wait();
+
+ // Make sure the URL is correct for both the entry and the main frame, and
+ // that the process hasn't been killed for showing a spoof.
+ EXPECT_TRUE(root->current_frame_host()->IsRenderFrameLive());
+ EXPECT_EQ(url2, shell()->web_contents()->GetLastCommittedURL());
+ EXPECT_EQ(url2, root->current_url());
+ }
+
+ {
+ // Go back to reset main frame entirely.
+ TestNavigationObserver back_load_observer(shell()->web_contents());
+ shell()->web_contents()->GetController().GoBack();
+ back_load_observer.Wait();
+ EXPECT_EQ(url1, shell()->web_contents()->GetLastCommittedURL());
+ EXPECT_EQ(url1, root->current_url());
+ }
+
+ {
+ // Go forward.
+ TestNavigationObserver back_load_observer(shell()->web_contents());
+ shell()->web_contents()->GetController().GoForward();
+ back_load_observer.Wait();
+ EXPECT_EQ(url2, shell()->web_contents()->GetLastCommittedURL());
+ EXPECT_EQ(url2, root->current_url());
+ }
+
+ {
+ // Go forward to the replaced URL.
+ TestNavigationObserver forward_load_observer(shell()->web_contents());
+ shell()->web_contents()->GetController().GoForward();
+ forward_load_observer.Wait();
+
+ // Make sure the URL is correct for both the entry and the main frame, and
+ // that the process hasn't been killed for showing a spoof.
+ EXPECT_TRUE(root->current_frame_host()->IsRenderFrameLive());
+ EXPECT_EQ(url3, shell()->web_contents()->GetLastCommittedURL());
+ EXPECT_EQ(url3, root->current_url());
+ }
+}
+
} // namespace content
diff --git a/content/renderer/history_controller.cc b/content/renderer/history_controller.cc
index 35f072a..f2d3d96 100644
--- a/content/renderer/history_controller.cc
+++ b/content/renderer/history_controller.cc
@@ -161,14 +161,30 @@
const WebHistoryItem& item,
WebHistoryCommitType commit_type,
bool navigation_within_page) {
- if (commit_type == blink::WebBackForwardCommit) {
- if (!provisional_entry_)
- return;
- current_entry_.reset(provisional_entry_.release());
- } else if (commit_type == blink::WebStandardCommit) {
- CreateNewBackForwardItem(frame, item, navigation_within_page);
- } else if (commit_type == blink::WebInitialCommitInChildFrame) {
- UpdateForInitialLoadInChildFrame(frame, item);
+ switch (commit_type) {
+ case blink::WebBackForwardCommit:
+ if (!provisional_entry_)
+ return;
+ current_entry_.reset(provisional_entry_.release());
+ break;
+ case blink::WebStandardCommit:
+ CreateNewBackForwardItem(frame, item, navigation_within_page);
+ break;
+ case blink::WebInitialCommitInChildFrame:
+ UpdateForInitialLoadInChildFrame(frame, item);
+ break;
+ case blink::WebHistoryInertCommit:
+ // Even for inert commits (e.g., location.replace, client redirects), make
+ // sure the current entry gets updated, if there is one.
+ if (current_entry_) {
+ if (HistoryEntry::HistoryNode* node =
+ current_entry_->GetHistoryNodeForFrame(frame)) {
+ node->set_item(item);
+ }
+ }
+ break;
+ default:
+ NOTREACHED() << "Invalid commit type: " << commit_type;
}
}
diff --git a/content/test/data/navigation_controller/page_with_data_iframe.html b/content/test/data/navigation_controller/page_with_data_iframe.html
new file mode 100644
index 0000000..f91f8e0
--- /dev/null
+++ b/content/test/data/navigation_controller/page_with_data_iframe.html
@@ -0,0 +1,7 @@
+<html>
+<head></head>
+<body>
+ <p>This page has a data URL iframe.
+ <p><iframe src="data:text/html,Subframe"></iframe>
+</body>
+</html>