1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "chrome/browser/guest_view/guest_view_manager.h"
6
7 #include "base/strings/stringprintf.h"
8 #include "chrome/browser/extensions/extension_service.h"
9 #include "chrome/browser/guest_view/guest_view_base.h"
10 #include "chrome/browser/guest_view/guest_view_constants.h"
11 #include "chrome/browser/guest_view/guest_view_manager_factory.h"
12 #include "chrome/browser/guest_view/web_view/web_view_guest.h"
13 #include "chrome/browser/profiles/profile.h"
14 #include "content/public/browser/browser_context.h"
15 #include "content/public/browser/render_process_host.h"
16 #include "content/public/browser/user_metrics.h"
17 #include "content/public/browser/web_contents_observer.h"
18 #include "content/public/common/result_codes.h"
19 #include "content/public/common/url_constants.h"
20 #include "extensions/browser/extension_system.h"
21 #include "net/base/escape.h"
22 #include "url/gurl.h"
23
24 using content::BrowserContext;
25 using content::SiteInstance;
26 using content::WebContents;
27
28 // static
29 GuestViewManagerFactory* GuestViewManager::factory_ = NULL;
30
GuestViewManager(content::BrowserContext * context)31 GuestViewManager::GuestViewManager(content::BrowserContext* context)
32 : current_instance_id_(0), last_instance_id_removed_(0), context_(context) {
33 }
34
~GuestViewManager()35 GuestViewManager::~GuestViewManager() {}
36
37 // static.
FromBrowserContext(BrowserContext * context)38 GuestViewManager* GuestViewManager::FromBrowserContext(
39 BrowserContext* context) {
40 GuestViewManager* guest_manager =
41 static_cast<GuestViewManager*>(context->GetUserData(
42 guestview::kGuestViewManagerKeyName));
43 if (!guest_manager) {
44 if (factory_) {
45 guest_manager = factory_->CreateGuestViewManager(context);
46 } else {
47 guest_manager = new GuestViewManager(context);
48 }
49 context->SetUserData(guestview::kGuestViewManagerKeyName, guest_manager);
50 }
51 return guest_manager;
52 }
53
GetGuestByInstanceIDSafely(int guest_instance_id,int embedder_render_process_id)54 content::WebContents* GuestViewManager::GetGuestByInstanceIDSafely(
55 int guest_instance_id,
56 int embedder_render_process_id) {
57 if (!CanEmbedderAccessInstanceIDMaybeKill(embedder_render_process_id,
58 guest_instance_id)) {
59 return NULL;
60 }
61 return GetGuestByInstanceID(guest_instance_id, embedder_render_process_id);
62 }
63
GetNextInstanceID()64 int GuestViewManager::GetNextInstanceID() {
65 return ++current_instance_id_;
66 }
67
CreateGuest(content::SiteInstance * embedder_site_instance,int instance_id,scoped_ptr<base::DictionaryValue> extra_params)68 content::WebContents* GuestViewManager::CreateGuest(
69 content::SiteInstance* embedder_site_instance,
70 int instance_id,
71 scoped_ptr<base::DictionaryValue> extra_params) {
72 std::string storage_partition_id;
73 bool persist_storage = false;
74 std::string storage_partition_string;
75 WebViewGuest::ParsePartitionParam(
76 extra_params.get(), &storage_partition_id, &persist_storage);
77
78 content::RenderProcessHost* embedder_process_host =
79 embedder_site_instance->GetProcess();
80 // Validate that the partition id coming from the renderer is valid UTF-8,
81 // since we depend on this in other parts of the code, such as FilePath
82 // creation. If the validation fails, treat it as a bad message and kill the
83 // renderer process.
84 if (!base::IsStringUTF8(storage_partition_id)) {
85 content::RecordAction(
86 base::UserMetricsAction("BadMessageTerminate_BPGM"));
87 base::KillProcess(
88 embedder_process_host->GetHandle(),
89 content::RESULT_CODE_KILLED_BAD_MESSAGE, false);
90 return NULL;
91 }
92
93 const GURL& embedder_site_url = embedder_site_instance->GetSiteURL();
94 const std::string& host = embedder_site_url.host();
95
96 std::string url_encoded_partition = net::EscapeQueryParamValue(
97 storage_partition_id, false);
98 // The SiteInstance of a given webview tag is based on the fact that it's
99 // a guest process in addition to which platform application the tag
100 // belongs to and what storage partition is in use, rather than the URL
101 // that the tag is being navigated to.
102 GURL guest_site(base::StringPrintf("%s://%s/%s?%s",
103 content::kGuestScheme,
104 host.c_str(),
105 persist_storage ? "persist" : "",
106 url_encoded_partition.c_str()));
107
108 // If we already have a webview tag in the same app using the same storage
109 // partition, we should use the same SiteInstance so the existing tag and
110 // the new tag can script each other.
111 SiteInstance* guest_site_instance = GetGuestSiteInstance(guest_site);
112 if (!guest_site_instance) {
113 // Create the SiteInstance in a new BrowsingInstance, which will ensure
114 // that webview tags are also not allowed to send messages across
115 // different partitions.
116 guest_site_instance = SiteInstance::CreateForURL(
117 embedder_site_instance->GetBrowserContext(), guest_site);
118 }
119 WebContents::CreateParams create_params(
120 embedder_site_instance->GetBrowserContext(),
121 guest_site_instance);
122 create_params.guest_instance_id = instance_id;
123 create_params.guest_extra_params.reset(extra_params.release());
124 return WebContents::Create(create_params);
125 }
126
MaybeGetGuestByInstanceIDOrKill(int guest_instance_id,int embedder_render_process_id,const GuestByInstanceIDCallback & callback)127 void GuestViewManager::MaybeGetGuestByInstanceIDOrKill(
128 int guest_instance_id,
129 int embedder_render_process_id,
130 const GuestByInstanceIDCallback& callback) {
131 if (!CanEmbedderAccessInstanceIDMaybeKill(embedder_render_process_id,
132 guest_instance_id)) {
133 // If we kill the embedder, then don't bother calling back.
134 return;
135 }
136 content::WebContents* guest_web_contents =
137 GetGuestByInstanceID(guest_instance_id, embedder_render_process_id);
138 callback.Run(guest_web_contents);
139 }
140
GetGuestSiteInstance(const GURL & guest_site)141 SiteInstance* GuestViewManager::GetGuestSiteInstance(
142 const GURL& guest_site) {
143 for (GuestInstanceMap::const_iterator it =
144 guest_web_contents_by_instance_id_.begin();
145 it != guest_web_contents_by_instance_id_.end(); ++it) {
146 if (it->second->GetSiteInstance()->GetSiteURL() == guest_site)
147 return it->second->GetSiteInstance();
148 }
149 return NULL;
150 }
151
ForEachGuest(WebContents * embedder_web_contents,const GuestCallback & callback)152 bool GuestViewManager::ForEachGuest(WebContents* embedder_web_contents,
153 const GuestCallback& callback) {
154 for (GuestInstanceMap::iterator it =
155 guest_web_contents_by_instance_id_.begin();
156 it != guest_web_contents_by_instance_id_.end(); ++it) {
157 WebContents* guest = it->second;
158 GuestViewBase* guest_view = GuestViewBase::FromWebContents(guest);
159 if (embedder_web_contents != guest_view->embedder_web_contents())
160 continue;
161
162 if (callback.Run(guest))
163 return true;
164 }
165 return false;
166 }
167
AddGuest(int guest_instance_id,WebContents * guest_web_contents)168 void GuestViewManager::AddGuest(int guest_instance_id,
169 WebContents* guest_web_contents) {
170 CHECK(!ContainsKey(guest_web_contents_by_instance_id_, guest_instance_id));
171 CHECK(CanUseGuestInstanceID(guest_instance_id));
172 guest_web_contents_by_instance_id_[guest_instance_id] = guest_web_contents;
173 }
174
RemoveGuest(int guest_instance_id)175 void GuestViewManager::RemoveGuest(int guest_instance_id) {
176 GuestInstanceMap::iterator it =
177 guest_web_contents_by_instance_id_.find(guest_instance_id);
178 DCHECK(it != guest_web_contents_by_instance_id_.end());
179 guest_web_contents_by_instance_id_.erase(it);
180
181 // All the instance IDs that lie within [0, last_instance_id_removed_]
182 // are invalid.
183 // The remaining sparse invalid IDs are kept in |removed_instance_ids_| set.
184 // The following code compacts the set by incrementing
185 // |last_instance_id_removed_|.
186 if (guest_instance_id == last_instance_id_removed_ + 1) {
187 ++last_instance_id_removed_;
188 // Compact.
189 std::set<int>::iterator iter = removed_instance_ids_.begin();
190 while (iter != removed_instance_ids_.end()) {
191 int instance_id = *iter;
192 // The sparse invalid IDs must not lie within
193 // [0, last_instance_id_removed_]
194 DCHECK(instance_id > last_instance_id_removed_);
195 if (instance_id != last_instance_id_removed_ + 1)
196 break;
197 ++last_instance_id_removed_;
198 removed_instance_ids_.erase(iter++);
199 }
200 } else {
201 removed_instance_ids_.insert(guest_instance_id);
202 }
203 }
204
GetGuestByInstanceID(int guest_instance_id,int embedder_render_process_id)205 content::WebContents* GuestViewManager::GetGuestByInstanceID(
206 int guest_instance_id,
207 int embedder_render_process_id) {
208 GuestInstanceMap::const_iterator it =
209 guest_web_contents_by_instance_id_.find(guest_instance_id);
210 if (it == guest_web_contents_by_instance_id_.end())
211 return NULL;
212 return it->second;
213 }
214
CanEmbedderAccessInstanceIDMaybeKill(int embedder_render_process_id,int guest_instance_id)215 bool GuestViewManager::CanEmbedderAccessInstanceIDMaybeKill(
216 int embedder_render_process_id,
217 int guest_instance_id) {
218 if (!CanEmbedderAccessInstanceID(embedder_render_process_id,
219 guest_instance_id)) {
220 // The embedder process is trying to access a guest it does not own.
221 content::RecordAction(
222 base::UserMetricsAction("BadMessageTerminate_BPGM"));
223 base::KillProcess(
224 content::RenderProcessHost::FromID(embedder_render_process_id)->
225 GetHandle(),
226 content::RESULT_CODE_KILLED_BAD_MESSAGE, false);
227 return false;
228 }
229 return true;
230 }
231
CanUseGuestInstanceID(int guest_instance_id)232 bool GuestViewManager::CanUseGuestInstanceID(int guest_instance_id) {
233 if (guest_instance_id <= last_instance_id_removed_)
234 return false;
235 return !ContainsKey(removed_instance_ids_, guest_instance_id);
236 }
237
CanEmbedderAccessInstanceID(int embedder_render_process_id,int guest_instance_id)238 bool GuestViewManager::CanEmbedderAccessInstanceID(
239 int embedder_render_process_id,
240 int guest_instance_id) {
241 // The embedder is trying to access a guest with a negative or zero
242 // instance ID.
243 if (guest_instance_id <= guestview::kInstanceIDNone)
244 return false;
245
246 // The embedder is trying to access an instance ID that has not yet been
247 // allocated by GuestViewManager. This could cause instance ID
248 // collisions in the future, and potentially give one embedder access to a
249 // guest it does not own.
250 if (guest_instance_id > current_instance_id_)
251 return false;
252
253 GuestInstanceMap::const_iterator it =
254 guest_web_contents_by_instance_id_.find(guest_instance_id);
255 if (it == guest_web_contents_by_instance_id_.end())
256 return true;
257
258 GuestViewBase* guest_view = GuestViewBase::FromWebContents(it->second);
259 if (!guest_view)
260 return false;
261
262 return CanEmbedderAccessGuest(embedder_render_process_id, guest_view);
263 }
264
CanEmbedderAccessGuest(int embedder_render_process_id,GuestViewBase * guest)265 bool GuestViewManager::CanEmbedderAccessGuest(int embedder_render_process_id,
266 GuestViewBase* guest) {
267 // The embedder can access the guest if it has not been attached and its
268 // opener's embedder lives in the same process as the given embedder.
269 if (!guest->attached()) {
270 if (!guest->GetOpener())
271 return false;
272
273 return embedder_render_process_id ==
274 guest->GetOpener()->embedder_web_contents()->GetRenderProcessHost()->
275 GetID();
276 }
277
278 return embedder_render_process_id ==
279 guest->embedder_web_contents()->GetRenderProcessHost()->GetID();
280 }
281