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/extensions/api/runtime/chrome_runtime_api_delegate.h"
6 
7 #include <memory>
8 #include <string>
9 #include <utility>
10 #include <vector>
11 
12 #include "base/bind.h"
13 #include "base/lazy_instance.h"
14 #include "base/location.h"
15 #include "base/metrics/histogram_macros.h"
16 #include "base/single_thread_task_runner.h"
17 #include "base/threading/thread_task_runner_handle.h"
18 #include "base/time/tick_clock.h"
19 #include "base/time/time.h"
20 #include "build/build_config.h"
21 #include "chrome/browser/extensions/extension_service.h"
22 #include "chrome/browser/extensions/extension_tab_util.h"
23 #include "chrome/browser/extensions/updater/extension_updater.h"
24 #include "chrome/browser/profiles/profile.h"
25 #include "chrome/browser/ui/browser_finder.h"
26 #include "chrome/browser/ui/browser_navigator.h"
27 #include "chrome/browser/ui/browser_navigator_params.h"
28 #include "chrome/browser/ui/browser_window.h"
29 #include "components/update_client/update_query_params.h"
30 #include "content/public/browser/notification_service.h"
31 #include "extensions/browser/extension_system.h"
32 #include "extensions/browser/notification_types.h"
33 #include "extensions/browser/warning_service.h"
34 #include "extensions/browser/warning_set.h"
35 #include "extensions/common/api/runtime.h"
36 #include "extensions/common/constants.h"
37 #include "extensions/common/manifest.h"
38 #include "net/base/backoff_entry.h"
39 
40 #if defined(OS_CHROMEOS)
41 #include "chromeos/dbus/dbus_thread_manager.h"
42 #include "chromeos/dbus/power/power_manager_client.h"
43 #include "components/user_manager/user_manager.h"
44 #include "third_party/cros_system_api/dbus/service_constants.h"
45 #endif
46 
47 using extensions::Extension;
48 using extensions::ExtensionSystem;
49 using extensions::ExtensionUpdater;
50 
51 using extensions::api::runtime::PlatformInfo;
52 
53 namespace {
54 
55 const char kUpdateThrottled[] = "throttled";
56 const char kUpdateNotFound[] = "no_update";
57 const char kUpdateFound[] = "update_available";
58 
59 // If an extension reloads itself within this many miliseconds of reloading
60 // itself, the reload is considered suspiciously fast.
61 const int kFastReloadTime = 10000;
62 
63 // Same as above, but we shorten the fast reload interval for unpacked
64 // extensions for ease of testing.
65 const int kUnpackedFastReloadTime = 1000;
66 
67 // After this many suspiciously fast consecutive reloads, an extension will get
68 // disabled.
69 const int kFastReloadCount = 5;
70 
71 // Same as above, but we increase the fast reload count for unpacked extensions.
72 const int kUnpackedFastReloadCount = 30;
73 
74 // A holder class for the policy we use for exponential backoff of update check
75 // requests.
76 class BackoffPolicy {
77  public:
78   BackoffPolicy();
79   ~BackoffPolicy();
80 
81   // Returns the actual policy to use.
82   static const net::BackoffEntry::Policy* Get();
83 
84  private:
85   net::BackoffEntry::Policy policy_;
86 };
87 
88 // We use a LazyInstance since one of the the policy values references an
89 // extern symbol, which would cause a static initializer to be generated if we
90 // just declared the policy struct as a static variable.
91 base::LazyInstance<BackoffPolicy>::DestructorAtExit g_backoff_policy =
92     LAZY_INSTANCE_INITIALIZER;
93 
BackoffPolicy()94 BackoffPolicy::BackoffPolicy() {
95   policy_ = {
96       // num_errors_to_ignore
97       0,
98 
99       // initial_delay_ms (note that we set 'always_use_initial_delay' to false
100       // below)
101       1000 * extensions::kDefaultUpdateFrequencySeconds,
102 
103       // multiply_factor
104       1,
105 
106       // jitter_factor
107       0.1,
108 
109       // maximum_backoff_ms (-1 means no maximum)
110       -1,
111 
112       // entry_lifetime_ms (-1 means never discard)
113       -1,
114 
115       // always_use_initial_delay
116       false,
117   };
118 }
119 
~BackoffPolicy()120 BackoffPolicy::~BackoffPolicy() {}
121 
122 // static
Get()123 const net::BackoffEntry::Policy* BackoffPolicy::Get() {
124   return &g_backoff_policy.Get().policy_;
125 }
126 
127 const base::TickClock* g_test_clock = nullptr;
128 
129 }  // namespace
130 
131 struct ChromeRuntimeAPIDelegate::UpdateCheckInfo {
132  public:
UpdateCheckInfoChromeRuntimeAPIDelegate::UpdateCheckInfo133   UpdateCheckInfo() {
134     if (g_test_clock)
135       backoff.reset(
136           new net::BackoffEntry(BackoffPolicy::Get(), g_test_clock));
137     else
138       backoff.reset(new net::BackoffEntry(BackoffPolicy::Get()));
139   }
140 
141   std::unique_ptr<net::BackoffEntry> backoff;
142   std::vector<UpdateCheckCallback> callbacks;
143 };
144 
ChromeRuntimeAPIDelegate(content::BrowserContext * context)145 ChromeRuntimeAPIDelegate::ChromeRuntimeAPIDelegate(
146     content::BrowserContext* context)
147     : browser_context_(context), registered_for_updates_(false) {
148   registrar_.Add(this,
149                  extensions::NOTIFICATION_EXTENSION_UPDATE_FOUND,
150                  content::NotificationService::AllSources());
151   extension_registry_observer_.Add(
152       extensions::ExtensionRegistry::Get(browser_context_));
153 }
154 
~ChromeRuntimeAPIDelegate()155 ChromeRuntimeAPIDelegate::~ChromeRuntimeAPIDelegate() {
156 }
157 
158 // static
set_tick_clock_for_tests(const base::TickClock * clock)159 void ChromeRuntimeAPIDelegate::set_tick_clock_for_tests(
160     const base::TickClock* clock) {
161   g_test_clock = clock;
162 }
163 
AddUpdateObserver(extensions::UpdateObserver * observer)164 void ChromeRuntimeAPIDelegate::AddUpdateObserver(
165     extensions::UpdateObserver* observer) {
166   registered_for_updates_ = true;
167   ExtensionSystem::Get(browser_context_)
168       ->extension_service()
169       ->AddUpdateObserver(observer);
170 }
171 
RemoveUpdateObserver(extensions::UpdateObserver * observer)172 void ChromeRuntimeAPIDelegate::RemoveUpdateObserver(
173     extensions::UpdateObserver* observer) {
174   if (registered_for_updates_) {
175     ExtensionSystem::Get(browser_context_)
176         ->extension_service()
177         ->RemoveUpdateObserver(observer);
178   }
179 }
180 
ReloadExtension(const std::string & extension_id)181 void ChromeRuntimeAPIDelegate::ReloadExtension(
182     const std::string& extension_id) {
183   const Extension* extension =
184       extensions::ExtensionRegistry::Get(browser_context_)
185           ->GetInstalledExtension(extension_id);
186   int fast_reload_time = kFastReloadTime;
187   int fast_reload_count = kFastReloadCount;
188 
189   // If an extension is unpacked, we allow for a faster reload interval
190   // and more fast reload attempts before terminating the extension.
191   // This is intended to facilitate extension testing for developers.
192   if (extensions::Manifest::IsUnpackedLocation(extension->location())) {
193     fast_reload_time = kUnpackedFastReloadTime;
194     fast_reload_count = kUnpackedFastReloadCount;
195   }
196 
197   std::pair<base::TimeTicks, int>& reload_info =
198       last_reload_time_[extension_id];
199   base::TimeTicks now =
200       g_test_clock ? g_test_clock->NowTicks() : base::TimeTicks::Now();
201   if (reload_info.first.is_null() ||
202       (now - reload_info.first).InMilliseconds() > fast_reload_time) {
203     reload_info.second = 0;
204   } else {
205     reload_info.second++;
206   }
207   if (!reload_info.first.is_null()) {
208     UMA_HISTOGRAM_LONG_TIMES("Extensions.RuntimeReloadTime",
209                              now - reload_info.first);
210   }
211   UMA_HISTOGRAM_COUNTS_100("Extensions.RuntimeReloadFastCount",
212                            reload_info.second);
213   reload_info.first = now;
214 
215   extensions::ExtensionService* service =
216       ExtensionSystem::Get(browser_context_)->extension_service();
217 
218   if (reload_info.second >= fast_reload_count) {
219     // Unloading an extension clears all warnings, so first terminate the
220     // extension, and then add the warning. Since this is called from an
221     // extension function unloading the extension has to be done
222     // asynchronously. Fortunately PostTask guarentees FIFO order so just
223     // post both tasks.
224     base::ThreadTaskRunnerHandle::Get()->PostTask(
225         FROM_HERE,
226         base::BindOnce(&extensions::ExtensionService::TerminateExtension,
227                        service->AsWeakPtr(), extension_id));
228     extensions::WarningSet warnings;
229     warnings.insert(
230         extensions::Warning::CreateReloadTooFrequentWarning(
231             extension_id));
232     base::ThreadTaskRunnerHandle::Get()->PostTask(
233         FROM_HERE,
234         base::BindOnce(&extensions::WarningService::NotifyWarningsOnUI,
235                        browser_context_, warnings));
236   } else {
237     // We can't call ReloadExtension directly, since when this method finishes
238     // it tries to decrease the reference count for the extension, which fails
239     // if the extension has already been reloaded; so instead we post a task.
240     base::ThreadTaskRunnerHandle::Get()->PostTask(
241         FROM_HERE,
242         base::BindOnce(&extensions::ExtensionService::ReloadExtension,
243                        service->AsWeakPtr(), extension_id));
244   }
245 }
246 
CheckForUpdates(const std::string & extension_id,const UpdateCheckCallback & callback)247 bool ChromeRuntimeAPIDelegate::CheckForUpdates(
248     const std::string& extension_id,
249     const UpdateCheckCallback& callback) {
250   ExtensionSystem* system = ExtensionSystem::Get(browser_context_);
251   extensions::ExtensionService* service = system->extension_service();
252   ExtensionUpdater* updater = service->updater();
253   if (!updater) {
254     return false;
255   }
256 
257   UpdateCheckInfo& info = update_check_info_[extension_id];
258 
259   // If not enough time has elapsed, or we have 10 or more outstanding calls,
260   // return a status of throttled.
261   if (info.backoff->ShouldRejectRequest() || info.callbacks.size() >= 10) {
262     base::ThreadTaskRunnerHandle::Get()->PostTask(
263         FROM_HERE, base::BindOnce(callback, UpdateCheckResult(
264                                                 true, kUpdateThrottled, "")));
265   } else {
266     info.callbacks.push_back(callback);
267 
268     extensions::ExtensionUpdater::CheckParams params;
269     params.ids = {extension_id};
270     params.callback = base::Bind(&ChromeRuntimeAPIDelegate::UpdateCheckComplete,
271                                  base::Unretained(this), extension_id);
272     updater->CheckNow(std::move(params));
273   }
274   return true;
275 }
276 
OpenURL(const GURL & uninstall_url)277 void ChromeRuntimeAPIDelegate::OpenURL(const GURL& uninstall_url) {
278   Profile* profile = Profile::FromBrowserContext(browser_context_);
279   Browser* browser = chrome::FindLastActiveWithProfile(profile);
280   if (!browser)
281     browser = Browser::Create(Browser::CreateParams(profile, false));
282   if (!browser)
283     return;
284 
285   NavigateParams params(browser, uninstall_url,
286                         ui::PAGE_TRANSITION_CLIENT_REDIRECT);
287   params.disposition = WindowOpenDisposition::NEW_FOREGROUND_TAB;
288   params.user_gesture = false;
289   Navigate(&params);
290 }
291 
GetPlatformInfo(PlatformInfo * info)292 bool ChromeRuntimeAPIDelegate::GetPlatformInfo(PlatformInfo* info) {
293   const char* os = update_client::UpdateQueryParams::GetOS();
294   if (strcmp(os, "mac") == 0) {
295     info->os = extensions::api::runtime::PLATFORM_OS_MAC;
296   } else if (strcmp(os, "win") == 0) {
297     info->os = extensions::api::runtime::PLATFORM_OS_WIN;
298   } else if (strcmp(os, "cros") == 0) {
299     info->os = extensions::api::runtime::PLATFORM_OS_CROS;
300   } else if (strcmp(os, "linux") == 0) {
301     info->os = extensions::api::runtime::PLATFORM_OS_LINUX;
302   } else if (strcmp(os, "freebsd") == 0) {
303     info->os = extensions::api::runtime::PLATFORM_OS_FREEBSD;
304   } else if (strcmp(os, "openbsd") == 0) {
305     info->os = extensions::api::runtime::PLATFORM_OS_OPENBSD;
306   } else {
307     NOTREACHED();
308     return false;
309   }
310 
311   const char* arch = update_client::UpdateQueryParams::GetArch();
312   if (strcmp(arch, "arm") == 0) {
313     info->arch = extensions::api::runtime::PLATFORM_ARCH_ARM;
314   } else if (strcmp(arch, "arm64") == 0) {
315     info->arch = extensions::api::runtime::PLATFORM_ARCH_ARM64;
316   } else if (strcmp(arch, "x86") == 0) {
317     info->arch = extensions::api::runtime::PLATFORM_ARCH_X86_32;
318   } else if (strcmp(arch, "x64") == 0) {
319     info->arch = extensions::api::runtime::PLATFORM_ARCH_X86_64;
320   } else if (strcmp(arch, "mipsel") == 0) {
321     info->arch = extensions::api::runtime::PLATFORM_ARCH_MIPS;
322   } else if (strcmp(arch, "mips64el") == 0) {
323     info->arch = extensions::api::runtime::PLATFORM_ARCH_MIPS64;
324   } else {
325     NOTREACHED();
326     return false;
327   }
328 
329   const char* nacl_arch = update_client::UpdateQueryParams::GetNaclArch();
330   if (strcmp(nacl_arch, "arm") == 0) {
331     info->nacl_arch = extensions::api::runtime::PLATFORM_NACL_ARCH_ARM;
332   } else if (strcmp(nacl_arch, "arm64") == 0) {
333     // Use ARM for ARM64 NaCl, as ARM64 NaCl is not available.
334     info->nacl_arch = extensions::api::runtime::PLATFORM_NACL_ARCH_ARM;
335   } else if (strcmp(nacl_arch, "x86-32") == 0) {
336     info->nacl_arch = extensions::api::runtime::PLATFORM_NACL_ARCH_X86_32;
337   } else if (strcmp(nacl_arch, "x86-64") == 0) {
338     info->nacl_arch = extensions::api::runtime::PLATFORM_NACL_ARCH_X86_64;
339   } else if (strcmp(nacl_arch, "mips32") == 0) {
340     info->nacl_arch = extensions::api::runtime::PLATFORM_NACL_ARCH_MIPS;
341   } else if (strcmp(nacl_arch, "mips64") == 0) {
342     info->nacl_arch = extensions::api::runtime::PLATFORM_NACL_ARCH_MIPS64;
343   } else {
344     NOTREACHED();
345     return false;
346   }
347 
348   return true;
349 }
350 
RestartDevice(std::string * error_message)351 bool ChromeRuntimeAPIDelegate::RestartDevice(std::string* error_message) {
352 #if defined(OS_CHROMEOS)
353   if (user_manager::UserManager::Get()->IsLoggedInAsKioskApp()) {
354     chromeos::PowerManagerClient::Get()->RequestRestart(
355         power_manager::REQUEST_RESTART_OTHER, "chrome.runtime API");
356     return true;
357   }
358 #endif
359   *error_message = "Function available only for ChromeOS kiosk mode.";
360   return false;
361 }
362 
OpenOptionsPage(const Extension * extension,content::BrowserContext * browser_context)363 bool ChromeRuntimeAPIDelegate::OpenOptionsPage(
364     const Extension* extension,
365     content::BrowserContext* browser_context) {
366   return extensions::ExtensionTabUtil::OpenOptionsPageFromAPI(extension,
367                                                               browser_context);
368 }
369 
Observe(int type,const content::NotificationSource & source,const content::NotificationDetails & details)370 void ChromeRuntimeAPIDelegate::Observe(
371     int type,
372     const content::NotificationSource& source,
373     const content::NotificationDetails& details) {
374   DCHECK_EQ(extensions::NOTIFICATION_EXTENSION_UPDATE_FOUND, type);
375   using UpdateDetails = const std::pair<std::string, base::Version>;
376   const std::string& id = content::Details<UpdateDetails>(details)->first;
377   const base::Version& version =
378       content::Details<UpdateDetails>(details)->second;
379   if (version.IsValid()) {
380     CallUpdateCallbacks(
381         id, UpdateCheckResult(true, kUpdateFound, version.GetString()));
382   }
383 }
384 
OnExtensionInstalled(content::BrowserContext * browser_context,const Extension * extension,bool is_update)385 void ChromeRuntimeAPIDelegate::OnExtensionInstalled(
386     content::BrowserContext* browser_context,
387     const Extension* extension,
388     bool is_update) {
389   if (!is_update)
390     return;
391   auto info = update_check_info_.find(extension->id());
392   if (info != update_check_info_.end()) {
393     info->second.backoff->Reset();
394   }
395 }
396 
UpdateCheckComplete(const std::string & extension_id)397 void ChromeRuntimeAPIDelegate::UpdateCheckComplete(
398     const std::string& extension_id) {
399   ExtensionSystem* system = ExtensionSystem::Get(browser_context_);
400   extensions::ExtensionService* service = system->extension_service();
401   const Extension* update = service->GetPendingExtensionUpdate(extension_id);
402   UpdateCheckInfo& info = update_check_info_[extension_id];
403 
404   // We always inform the BackoffEntry of a "failure" here, because we only
405   // want to consider an update check request a success from a throttling
406   // standpoint once the extension goes on to actually update to a new
407   // version. See OnExtensionInstalled for where we reset the BackoffEntry.
408   info.backoff->InformOfRequest(false);
409 
410   if (update) {
411     CallUpdateCallbacks(
412         extension_id,
413         UpdateCheckResult(true, kUpdateFound, update->VersionString()));
414   } else {
415     CallUpdateCallbacks(extension_id,
416                         UpdateCheckResult(true, kUpdateNotFound, ""));
417   }
418 }
419 
CallUpdateCallbacks(const std::string & extension_id,const UpdateCheckResult & result)420 void ChromeRuntimeAPIDelegate::CallUpdateCallbacks(
421     const std::string& extension_id,
422     const UpdateCheckResult& result) {
423   auto it = update_check_info_.find(extension_id);
424   if (it == update_check_info_.end())
425     return;
426   std::vector<UpdateCheckCallback> callbacks;
427   it->second.callbacks.swap(callbacks);
428   for (const auto& callback : callbacks) {
429     callback.Run(result);
430   }
431 }
432