Skip to content

Commit

Permalink
Drop CreateChildFrame messages when swapping out.
Browse files Browse the repository at this point in the history
There is a race condition in the current state of the code where in cross-process navigation we swap the existing RenderFrameHost with a new RenderFrameHost. If the existing host sends an IPC message to create a new child frame, it arrives on the IO thread, allocates a routing id based of the existing process (p1) and does a PostTask to the UI thread. If there is a CommitPending event either executing on the UI thread or in the task queue before the task posted from the IO thread, it will end up putting the existing RenderFrameHost in swapped out state (or waiting for swapped out). When the task to create a child frame is executed after that, it creates a new RenderFrameHost, but it uses the "current" process (p2), which is different than the process that sent the message (p1). This manifests sometimes as adding duplicate routing ids to RenderProcessHost and is in general really bad bug.

BUG=415059, 423691, 381990

Review URL: https://codereview.chromium.org/642813007

Cr-Commit-Position: refs/heads/master@{#299939}
  • Loading branch information
naskooskov authored and Commit bot committed Oct 16, 2014
1 parent 3183230 commit dcdb02f
Show file tree
Hide file tree
Showing 8 changed files with 131 additions and 26 deletions.
10 changes: 9 additions & 1 deletion content/browser/frame_host/frame_tree.cc
Original file line number Diff line number Diff line change
Expand Up @@ -143,8 +143,16 @@ void FrameTree::ForEach(
}

RenderFrameHostImpl* FrameTree::AddFrame(FrameTreeNode* parent,
int process_id,
int new_routing_id,
const std::string& frame_name) {
// A child frame always starts with an initial empty document, which means
// it is in the same SiteInstance as the parent frame. Ensure that the process
// which requested a child frame to be added is the same as the process of the
// parent node.
if (parent->current_frame_host()->GetProcess()->GetID() != process_id)
return nullptr;

scoped_ptr<FrameTreeNode> node(new FrameTreeNode(
this, parent->navigator(), render_frame_delegate_, render_view_delegate_,
render_widget_delegate_, manager_delegate_, frame_name));
Expand All @@ -154,7 +162,7 @@ RenderFrameHostImpl* FrameTree::AddFrame(FrameTreeNode* parent,
CHECK(result.second);
FrameTreeNode* node_ptr = node.get();
// AddChild is what creates the RenderFrameHost.
parent->AddChild(node.Pass(), new_routing_id);
parent->AddChild(node.Pass(), process_id, new_routing_id);
return node_ptr->current_frame_host();
}

Expand Down
1 change: 1 addition & 0 deletions content/browser/frame_host/frame_tree.h
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ class CONTENT_EXPORT FrameTree {

// Frame tree manipulation routines.
RenderFrameHostImpl* AddFrame(FrameTreeNode* parent,
int process_id,
int new_routing_id,
const std::string& frame_name);
void RemoveFrame(FrameTreeNode* child);
Expand Down
4 changes: 4 additions & 0 deletions content/browser/frame_host/frame_tree_node.cc
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,11 @@ bool FrameTreeNode::IsMainFrame() const {
}

void FrameTreeNode::AddChild(scoped_ptr<FrameTreeNode> child,
int process_id,
int frame_routing_id) {
// Child frame must always be created in the same process as the parent.
CHECK_EQ(process_id, render_manager_.current_host()->GetProcess()->GetID());

// Initialize the RenderFrameHost for the new node. We always create child
// frames in the same SiteInstance as the current frame, and they can swap to
// a different one if they navigate away.
Expand Down
4 changes: 3 additions & 1 deletion content/browser/frame_host/frame_tree_node.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,9 @@ class CONTENT_EXPORT FrameTreeNode {

bool IsMainFrame() const;

void AddChild(scoped_ptr<FrameTreeNode> child, int frame_routing_id);
void AddChild(scoped_ptr<FrameTreeNode> child,
int process_id,
int frame_routing_id);
void RemoveChild(FrameTreeNode* child);

// Clears process specific-state in this node to prepare for a new process.
Expand Down
50 changes: 34 additions & 16 deletions content/browser/frame_host/frame_tree_unittest.cc
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
#include "testing/gtest/include/gtest/gtest.h"

namespace content {

namespace {

// Appends a description of the structure of the frame tree to |result|.
Expand Down Expand Up @@ -102,6 +103,8 @@ class TreeWalkingWebContentsLogger : public WebContentsObserver {
DISALLOW_COPY_AND_ASSIGN(TreeWalkingWebContentsLogger);
};

} // namespace

class FrameTreeTest : public RenderViewHostImplTestHarness {
protected:
// Prints a FrameTree, for easy assertions of the tree hierarchy.
Expand All @@ -123,36 +126,38 @@ TEST_F(FrameTreeTest, Shape) {

std::string no_children_node("no children node");
std::string deep_subtree("node with deep subtree");
int process_id = root->current_frame_host()->GetProcess()->GetID();

ASSERT_EQ("1: []", GetTreeState(frame_tree));

// Simulate attaching a series of frames to build the frame tree.
frame_tree->AddFrame(root, 14, std::string());
frame_tree->AddFrame(root, 15, std::string());
frame_tree->AddFrame(root, 16, std::string());
frame_tree->AddFrame(root, process_id, 14, std::string());
frame_tree->AddFrame(root, process_id, 15, std::string());
frame_tree->AddFrame(root, process_id, 16, std::string());

frame_tree->AddFrame(root->child_at(0), 244, std::string());
frame_tree->AddFrame(root->child_at(1), 255, no_children_node);
frame_tree->AddFrame(root->child_at(0), 245, std::string());
frame_tree->AddFrame(root->child_at(0), process_id, 244, std::string());
frame_tree->AddFrame(root->child_at(1), process_id, 255, no_children_node);
frame_tree->AddFrame(root->child_at(0), process_id, 245, std::string());

ASSERT_EQ("1: [14: [244: [], 245: []], "
"15: [255 'no children node': []], "
"16: []]",
GetTreeState(frame_tree));

FrameTreeNode* child_16 = root->child_at(2);
frame_tree->AddFrame(child_16, 264, std::string());
frame_tree->AddFrame(child_16, 265, std::string());
frame_tree->AddFrame(child_16, 266, std::string());
frame_tree->AddFrame(child_16, 267, deep_subtree);
frame_tree->AddFrame(child_16, 268, std::string());
frame_tree->AddFrame(child_16, process_id, 264, std::string());
frame_tree->AddFrame(child_16, process_id, 265, std::string());
frame_tree->AddFrame(child_16, process_id, 266, std::string());
frame_tree->AddFrame(child_16, process_id, 267, deep_subtree);
frame_tree->AddFrame(child_16, process_id, 268, std::string());

FrameTreeNode* child_267 = child_16->child_at(3);
frame_tree->AddFrame(child_267, 365, std::string());
frame_tree->AddFrame(child_267->child_at(0), 455, std::string());
frame_tree->AddFrame(child_267->child_at(0)->child_at(0), 555, std::string());
frame_tree->AddFrame(child_267->child_at(0)->child_at(0)->child_at(0), 655,
frame_tree->AddFrame(child_267, process_id, 365, std::string());
frame_tree->AddFrame(child_267->child_at(0), process_id, 455, std::string());
frame_tree->AddFrame(child_267->child_at(0)->child_at(0), process_id, 555,
std::string());
frame_tree->AddFrame(child_267->child_at(0)->child_at(0)->child_at(0),
process_id, 655, std::string());

// Now that's it's fully built, verify the tree structure is as expected.
ASSERT_EQ("1: [14: [244: [], 245: []], "
Expand Down Expand Up @@ -227,5 +232,18 @@ TEST_F(FrameTreeTest, ObserverWalksTreeAfterCrash) {
activity.GetLog());
}

} // namespace
// Ensure that frames are not added to the tree, if the process passed in
// is different than the process of the parent node.
TEST_F(FrameTreeTest, FailAddFrameWithWrongProcessId) {
FrameTree* frame_tree = contents()->GetFrameTree();
FrameTreeNode* root = frame_tree->root();
int process_id = root->current_frame_host()->GetProcess()->GetID();

ASSERT_EQ("1: []", GetTreeState(frame_tree));

// Simulate attaching a frame from mismatched process id.
ASSERT_FALSE(frame_tree->AddFrame(root, process_id + 1, 1, std::string()));
ASSERT_EQ("1: []", GetTreeState(frame_tree));
}

} // namespace content
12 changes: 6 additions & 6 deletions content/browser/frame_host/navigator_impl_unittest.cc
Original file line number Diff line number Diff line change
Expand Up @@ -99,9 +99,12 @@ TEST_F(NavigatorTest, BrowserSideNavigationBeginNavigation) {
EnableBrowserSideNavigation();

// Add a subframe.
FrameTreeNode* root = contents()->GetFrameTree()->root();
TestRenderFrameHost* subframe_rfh = static_cast<TestRenderFrameHost*>(
contents()->GetFrameTree()->AddFrame(
contents()->GetFrameTree()->root(), 14, "Child"));
root, root->current_frame_host()->GetProcess()->GetID(), 14,
"Child"));
EXPECT_TRUE(subframe_rfh);

FrameTreeNode* subframe_node = subframe_rfh->frame_tree_node();
SendRequestNavigation(subframe_rfh->frame_tree_node(), kUrl2);
Expand All @@ -116,13 +119,10 @@ TEST_F(NavigatorTest, BrowserSideNavigationBeginNavigation) {
EXPECT_FALSE(subframe_request->info_for_test()->is_main_frame);
EXPECT_TRUE(subframe_request->info_for_test()->parent_is_main_frame);

FrameTreeNode* main_frame_node =
contents()->GetMainFrame()->frame_tree_node();
SendRequestNavigation(main_frame_node, kUrl3);
SendRequestNavigation(root, kUrl3);
// Simulate a BeginNavigation IPC on the main frame.
contents()->GetMainFrame()->SendBeginNavigationWithURL(kUrl3);
NavigationRequest* main_request =
GetNavigationRequestForFrameTreeNode(main_frame_node);
NavigationRequest* main_request = GetNavigationRequestForFrameTreeNode(root);
ASSERT_TRUE(main_request);
EXPECT_EQ(kUrl3, main_request->common_params().url);
EXPECT_EQ(kUrl3, main_request->info_for_test()->first_party_for_cookies);
Expand Down
11 changes: 10 additions & 1 deletion content/browser/frame_host/render_frame_host_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -611,8 +611,17 @@ void RenderFrameHostImpl::OnAddMessageToConsole(

void RenderFrameHostImpl::OnCreateChildFrame(int new_routing_id,
const std::string& frame_name) {
// It is possible that while a new RenderFrameHost was committed, the
// RenderFrame corresponding to this host sent an IPC message to create a
// frame and it is delivered after this host is swapped out.
// Ignore such messages, as we know this RenderFrameHost is going away.
if (rfh_state_ != RenderFrameHostImpl::STATE_DEFAULT)
return;

RenderFrameHostImpl* new_frame = frame_tree_->AddFrame(
frame_tree_node_, new_routing_id, frame_name);
frame_tree_node_, GetProcess()->GetID(), new_routing_id, frame_name);
if (!new_frame)
return;

// We know that the RenderFrame has been created in this case, immediately
// after the CreateChildFrame IPC was sent.
Expand Down
65 changes: 64 additions & 1 deletion content/browser/frame_host/render_frame_host_manager_unittest.cc
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,29 @@ class RenderViewHostDeletedObserver : public WebContentsObserver {
DISALLOW_COPY_AND_ASSIGN(RenderViewHostDeletedObserver);
};

// This observer keeps track of the last created RenderFrameHost to allow tests
// to ensure that no RenderFrameHost objects are created when not expected.
class RenderFrameHostCreatedObserver : public WebContentsObserver {
public:
RenderFrameHostCreatedObserver(WebContents* web_contents)
: WebContentsObserver(web_contents),
created_(false) {
}

virtual void RenderFrameCreated(RenderFrameHost* render_frame_host) override {
created_ = true;
}

bool created() {
return created_;
}

private:
bool created_;

DISALLOW_COPY_AND_ASSIGN(RenderFrameHostCreatedObserver);
};

// This observer keeps track of the last deleted RenderFrameHost to avoid
// accessing it and causing use-after-free condition.
class RenderFrameHostDeletedObserver : public WebContentsObserver {
Expand Down Expand Up @@ -178,7 +201,6 @@ class RenderFrameHostDeletedObserver : public WebContentsObserver {
DISALLOW_COPY_AND_ASSIGN(RenderFrameHostDeletedObserver);
};


// This observer is used to check whether IPC messages are being filtered for
// swapped out RenderFrameHost objects. It observes the plugin crash and favicon
// update events, which the FilterMessagesWhileSwappedOut test simulates being
Expand Down Expand Up @@ -567,6 +589,47 @@ TEST_F(RenderFrameHostManagerTest, FilterMessagesWhileSwappedOut) {
EXPECT_TRUE(ntp_process_host->sink().GetUniqueMessageMatching(IPC_REPLY_ID));
}

// Ensure that frames aren't added to the frame tree, if the message is coming
// from a process different than the parent frame's current RenderFrameHost
// process. Otherwise it is possible to have collisions of routing ids, as they
// are scoped per process. See https://crbug.com/415059.
TEST_F(RenderFrameHostManagerTest, DropCreateChildFrameWhileSwappedOut) {
const GURL kUrl1("http://foo.com");
const GURL kUrl2("http://www.google.com/");

// Navigate to the first site.
NavigateActiveAndCommit(kUrl1);
TestRenderFrameHost* initial_rfh = contents()->GetMainFrame();
{
RenderFrameHostCreatedObserver observer(contents());
initial_rfh->OnCreateChildFrame(
initial_rfh->GetProcess()->GetNextRoutingID(), std::string());
EXPECT_TRUE(observer.created());
}

// Create one more frame in the same SiteInstance where initial_rfh
// exists so that initial_rfh doesn't get deleted on navigation to another
// site.
initial_rfh->GetSiteInstance()->increment_active_frame_count();

// Navigate to a cross-site URL.
NavigateActiveAndCommit(kUrl2);
EXPECT_TRUE(initial_rfh->is_swapped_out());

TestRenderFrameHost* dest_rfh = contents()->GetMainFrame();
ASSERT_TRUE(dest_rfh);
EXPECT_NE(initial_rfh, dest_rfh);

{
// Since the old RFH is now swapped out, it shouldn't process any messages
// to create child frames.
RenderFrameHostCreatedObserver observer(contents());
initial_rfh->OnCreateChildFrame(
initial_rfh->GetProcess()->GetNextRoutingID(), std::string());
EXPECT_FALSE(observer.created());
}
}

TEST_F(RenderFrameHostManagerTest, WhiteListSwapCompositorFrame) {
TestRenderFrameHost* swapped_out_rfh = CreateSwappedOutRenderFrameHost();
TestRenderWidgetHostView* swapped_out_rwhv =
Expand Down

0 comments on commit dcdb02f

Please sign in to comment.