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