• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2013 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 "components/nacl/browser/nacl_browser.h"
6 
7 #include "base/command_line.h"
8 #include "base/file_util.h"
9 #include "base/message_loop/message_loop.h"
10 #include "base/metrics/histogram.h"
11 #include "base/path_service.h"
12 #include "base/pickle.h"
13 #include "base/rand_util.h"
14 #include "base/time/time.h"
15 #include "base/win/windows_version.h"
16 #include "build/build_config.h"
17 #include "content/public/browser/browser_thread.h"
18 #include "url/gurl.h"
19 
20 namespace {
21 
22 // An arbitrary delay to coalesce multiple writes to the cache.
23 const int kValidationCacheCoalescingTimeMS = 6000;
24 const char kValidationCacheSequenceName[] = "NaClValidationCache";
25 const base::FilePath::CharType kValidationCacheFileName[] =
26     FILE_PATH_LITERAL("nacl_validation_cache.bin");
27 
28 const bool kValidationCacheEnabledByDefault = true;
29 
30 enum ValidationCacheStatus {
31   CACHE_MISS = 0,
32   CACHE_HIT,
33   CACHE_MAX
34 };
35 
36 // Keep the cache bounded to an arbitrary size.  If it's too small, useful
37 // entries could be evicted when multiple .nexes are loaded at once.  On the
38 // other hand, entries are not always claimed (and hence removed), so the size
39 // of the cache will likely saturate at its maximum size.
40 // Entries may not be claimed for two main reasons. 1) the NaCl process could
41 // be killed while it is loading.  2) the trusted NaCl plugin opens files using
42 // the code path but doesn't resolve them.
43 // TODO(ncbray) don't cache files that the plugin will not resolve.
44 const int kFilePathCacheSize = 100;
45 
NaClIrtName()46 const base::FilePath::StringType NaClIrtName() {
47   base::FilePath::StringType irt_name(FILE_PATH_LITERAL("nacl_irt_"));
48 
49 #if defined(ARCH_CPU_X86_FAMILY)
50 #if defined(ARCH_CPU_X86_64)
51   bool is64 = true;
52 #elif defined(OS_WIN)
53   bool is64 = (base::win::OSInfo::GetInstance()->wow64_status() ==
54                base::win::OSInfo::WOW64_ENABLED);
55 #else
56   bool is64 = false;
57 #endif
58   if (is64)
59     irt_name.append(FILE_PATH_LITERAL("x86_64"));
60   else
61     irt_name.append(FILE_PATH_LITERAL("x86_32"));
62 
63 #elif defined(ARCH_CPU_ARMEL)
64   // TODO(mcgrathr): Eventually we'll need to distinguish arm32 vs thumb2.
65   // That may need to be based on the actual nexe rather than a static
66   // choice, which would require substantial refactoring.
67   irt_name.append(FILE_PATH_LITERAL("arm"));
68 #elif defined(ARCH_CPU_MIPSEL)
69   irt_name.append(FILE_PATH_LITERAL("mips32"));
70 #else
71 #error Add support for your architecture to NaCl IRT file selection
72 #endif
73   irt_name.append(FILE_PATH_LITERAL(".nexe"));
74   return irt_name;
75 }
76 
CheckEnvVar(const char * name,bool default_value)77 bool CheckEnvVar(const char* name, bool default_value) {
78   bool result = default_value;
79   const char* var = getenv(name);
80   if (var && strlen(var) > 0) {
81     result = var[0] != '0';
82   }
83   return result;
84 }
85 
ReadCache(const base::FilePath & filename,std::string * data)86 void ReadCache(const base::FilePath& filename, std::string* data) {
87   if (!base::ReadFileToString(filename, data)) {
88     // Zero-size data used as an in-band error code.
89     data->clear();
90   }
91 }
92 
WriteCache(const base::FilePath & filename,const Pickle * pickle)93 void WriteCache(const base::FilePath& filename, const Pickle* pickle) {
94   file_util::WriteFile(filename, static_cast<const char*>(pickle->data()),
95                        pickle->size());
96 }
97 
RemoveCache(const base::FilePath & filename,const base::Closure & callback)98 void RemoveCache(const base::FilePath& filename,
99                  const base::Closure& callback) {
100   base::DeleteFile(filename, false);
101   content::BrowserThread::PostTask(content::BrowserThread::IO, FROM_HERE,
102                                    callback);
103 }
104 
LogCacheQuery(ValidationCacheStatus status)105 void LogCacheQuery(ValidationCacheStatus status) {
106   UMA_HISTOGRAM_ENUMERATION("NaCl.ValidationCache.Query", status, CACHE_MAX);
107 }
108 
LogCacheSet(ValidationCacheStatus status)109 void LogCacheSet(ValidationCacheStatus status) {
110   // Bucket zero is reserved for future use.
111   UMA_HISTOGRAM_ENUMERATION("NaCl.ValidationCache.Set", status, CACHE_MAX);
112 }
113 
114 // Crash throttling parameters.
115 const size_t kMaxCrashesPerInterval = 3;
116 const int64 kCrashesIntervalInSeconds = 120;
117 
118 }  // namespace
119 
120 namespace nacl {
121 
OpenNaClExecutableImpl(const base::FilePath & file_path)122 base::PlatformFile OpenNaClExecutableImpl(const base::FilePath& file_path) {
123   // Get a file descriptor. On Windows, we need 'GENERIC_EXECUTE' in order to
124   // memory map the executable.
125   // IMPORTANT: This file descriptor must not have write access - that could
126   // allow a NaCl inner sandbox escape.
127   base::PlatformFile file;
128   base::PlatformFileError error_code;
129   file = base::CreatePlatformFile(
130       file_path,
131       (base::PLATFORM_FILE_OPEN |
132        base::PLATFORM_FILE_READ |
133        base::PLATFORM_FILE_EXECUTE),  // Windows only flag.
134       NULL,
135       &error_code);
136   if (error_code != base::PLATFORM_FILE_OK)
137     return base::kInvalidPlatformFileValue;
138 
139   // Check that the file does not reference a directory. Returning a descriptor
140   // to an extension directory could allow an outer sandbox escape. openat(...)
141   // could be used to traverse into the file system.
142   base::PlatformFileInfo file_info;
143   if (!base::GetPlatformFileInfo(file, &file_info) || file_info.is_directory) {
144     base::ClosePlatformFile(file);
145     return base::kInvalidPlatformFileValue;
146   }
147   return file;
148 }
149 
NaClBrowser()150 NaClBrowser::NaClBrowser()
151     : weak_factory_(this),
152       irt_platform_file_(base::kInvalidPlatformFileValue),
153       irt_filepath_(),
154       irt_state_(NaClResourceUninitialized),
155       validation_cache_file_path_(),
156       validation_cache_is_enabled_(
157           CheckEnvVar("NACL_VALIDATION_CACHE",
158                       kValidationCacheEnabledByDefault)),
159       validation_cache_is_modified_(false),
160       validation_cache_state_(NaClResourceUninitialized),
161       path_cache_(kFilePathCacheSize),
162       ok_(true) {
163 }
164 
SetDelegate(NaClBrowserDelegate * delegate)165 void NaClBrowser::SetDelegate(NaClBrowserDelegate* delegate) {
166   NaClBrowser* nacl_browser = NaClBrowser::GetInstance();
167   nacl_browser->browser_delegate_.reset(delegate);
168 }
169 
GetDelegate()170 NaClBrowserDelegate* NaClBrowser::GetDelegate() {
171   // The delegate is not owned by the IO thread.  This accessor method can be
172   // called from other threads.
173   DCHECK(GetInstance()->browser_delegate_.get() != NULL);
174   return GetInstance()->browser_delegate_.get();
175 }
176 
EarlyStartup()177 void NaClBrowser::EarlyStartup() {
178   DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
179   InitIrtFilePath();
180   InitValidationCacheFilePath();
181 }
182 
~NaClBrowser()183 NaClBrowser::~NaClBrowser() {
184   if (irt_platform_file_ != base::kInvalidPlatformFileValue)
185     base::ClosePlatformFile(irt_platform_file_);
186 }
187 
InitIrtFilePath()188 void NaClBrowser::InitIrtFilePath() {
189   // Allow the IRT library to be overridden via an environment
190   // variable.  This allows the NaCl/Chromium integration bot to
191   // specify a newly-built IRT rather than using a prebuilt one
192   // downloaded via Chromium's DEPS file.  We use the same environment
193   // variable that the standalone NaCl PPAPI plugin accepts.
194   const char* irt_path_var = getenv("NACL_IRT_LIBRARY");
195   if (irt_path_var != NULL) {
196     base::FilePath::StringType path_string(
197         irt_path_var, const_cast<const char*>(strchr(irt_path_var, '\0')));
198     irt_filepath_ = base::FilePath(path_string);
199   } else {
200     base::FilePath plugin_dir;
201     if (!browser_delegate_->GetPluginDirectory(&plugin_dir)) {
202       DLOG(ERROR) << "Failed to locate the plugins directory, NaCl disabled.";
203       MarkAsFailed();
204       return;
205     }
206     irt_filepath_ = plugin_dir.Append(NaClIrtName());
207   }
208 }
209 
210 #if defined(OS_WIN)
GetNaCl64ExePath(base::FilePath * exe_path)211 bool NaClBrowser::GetNaCl64ExePath(base::FilePath* exe_path) {
212   base::FilePath module_path;
213   if (!PathService::Get(base::FILE_MODULE, &module_path)) {
214     LOG(ERROR) << "NaCl process launch failed: could not resolve module";
215     return false;
216   }
217   *exe_path = module_path.DirName().Append(L"nacl64");
218   return true;
219 }
220 #endif
221 
GetInstance()222 NaClBrowser* NaClBrowser::GetInstance() {
223   return Singleton<NaClBrowser>::get();
224 }
225 
IsReady() const226 bool NaClBrowser::IsReady() const {
227   return (IsOk() &&
228           irt_state_ == NaClResourceReady &&
229           validation_cache_state_ == NaClResourceReady);
230 }
231 
IsOk() const232 bool NaClBrowser::IsOk() const {
233   return ok_;
234 }
235 
IrtFile() const236 base::PlatformFile NaClBrowser::IrtFile() const {
237   DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
238   CHECK_EQ(irt_state_, NaClResourceReady);
239   CHECK_NE(irt_platform_file_, base::kInvalidPlatformFileValue);
240   return irt_platform_file_;
241 }
242 
EnsureAllResourcesAvailable()243 void NaClBrowser::EnsureAllResourcesAvailable() {
244   DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
245   EnsureIrtAvailable();
246   EnsureValidationCacheAvailable();
247 }
248 
249 // Load the IRT async.
EnsureIrtAvailable()250 void NaClBrowser::EnsureIrtAvailable() {
251   DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
252   if (IsOk() && irt_state_ == NaClResourceUninitialized) {
253     irt_state_ = NaClResourceRequested;
254     // TODO(ncbray) use blocking pool.
255     if (!base::FileUtilProxy::CreateOrOpen(
256             content::BrowserThread::GetMessageLoopProxyForThread(
257                 content::BrowserThread::FILE)
258                 .get(),
259             irt_filepath_,
260             base::PLATFORM_FILE_OPEN | base::PLATFORM_FILE_READ,
261             base::Bind(&NaClBrowser::OnIrtOpened,
262                        weak_factory_.GetWeakPtr()))) {
263       LOG(ERROR) << "Internal error, NaCl disabled.";
264       MarkAsFailed();
265     }
266   }
267 }
268 
OnIrtOpened(base::PlatformFileError error_code,base::PassPlatformFile file,bool created)269 void NaClBrowser::OnIrtOpened(base::PlatformFileError error_code,
270                               base::PassPlatformFile file,
271                               bool created) {
272   DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
273   DCHECK_EQ(irt_state_, NaClResourceRequested);
274   DCHECK(!created);
275   if (error_code == base::PLATFORM_FILE_OK) {
276     irt_platform_file_ = file.ReleaseValue();
277   } else {
278     LOG(ERROR) << "Failed to open NaCl IRT file \""
279                << irt_filepath_.LossyDisplayName()
280                << "\": " << error_code;
281     MarkAsFailed();
282   }
283   irt_state_ = NaClResourceReady;
284   CheckWaiting();
285 }
286 
FireGdbDebugStubPortOpened(int port)287 void NaClBrowser::FireGdbDebugStubPortOpened(int port) {
288   content::BrowserThread::PostTask(
289       content::BrowserThread::IO,
290       FROM_HERE,
291       base::Bind(debug_stub_port_listener_, port));
292 }
293 
HasGdbDebugStubPortListener()294 bool NaClBrowser::HasGdbDebugStubPortListener() {
295   return !debug_stub_port_listener_.is_null();
296 }
297 
SetGdbDebugStubPortListener(base::Callback<void (int)> listener)298 void NaClBrowser::SetGdbDebugStubPortListener(
299     base::Callback<void(int)> listener) {
300   debug_stub_port_listener_ = listener;
301 }
302 
ClearGdbDebugStubPortListener()303 void NaClBrowser::ClearGdbDebugStubPortListener() {
304   debug_stub_port_listener_.Reset();
305 }
306 
InitValidationCacheFilePath()307 void NaClBrowser::InitValidationCacheFilePath() {
308   // Determine where the validation cache resides in the file system.  It
309   // exists in Chrome's cache directory and is not tied to any specific
310   // profile.
311   // Start by finding the user data directory.
312   base::FilePath user_data_dir;
313   if (!browser_delegate_->GetUserDirectory(&user_data_dir)) {
314     RunWithoutValidationCache();
315     return;
316   }
317   // The cache directory may or may not be the user data directory.
318   base::FilePath cache_file_path;
319   browser_delegate_->GetCacheDirectory(&cache_file_path);
320   // Append the base file name to the cache directory.
321 
322   validation_cache_file_path_ =
323       cache_file_path.Append(kValidationCacheFileName);
324 }
325 
EnsureValidationCacheAvailable()326 void NaClBrowser::EnsureValidationCacheAvailable() {
327   DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
328   if (IsOk() && validation_cache_state_ == NaClResourceUninitialized) {
329     if (ValidationCacheIsEnabled()) {
330       validation_cache_state_ = NaClResourceRequested;
331 
332       // Structure for carrying data between the callbacks.
333       std::string* data = new std::string();
334       // We can get away not giving this a sequence ID because this is the first
335       // task and further file access will not occur until after we get a
336       // response.
337       if (!content::BrowserThread::PostBlockingPoolTaskAndReply(
338               FROM_HERE,
339               base::Bind(ReadCache, validation_cache_file_path_, data),
340               base::Bind(&NaClBrowser::OnValidationCacheLoaded,
341                          weak_factory_.GetWeakPtr(),
342                          base::Owned(data)))) {
343         RunWithoutValidationCache();
344       }
345     } else {
346       RunWithoutValidationCache();
347     }
348   }
349 }
350 
OnValidationCacheLoaded(const std::string * data)351 void NaClBrowser::OnValidationCacheLoaded(const std::string *data) {
352   DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
353   // Did the cache get cleared before the load completed?  If so, ignore the
354   // incoming data.
355   if (validation_cache_state_ == NaClResourceReady)
356     return;
357 
358   if (data->size() == 0) {
359     // No file found.
360     validation_cache_.Reset();
361   } else {
362     Pickle pickle(data->data(), data->size());
363     validation_cache_.Deserialize(&pickle);
364   }
365   validation_cache_state_ = NaClResourceReady;
366   CheckWaiting();
367 }
368 
RunWithoutValidationCache()369 void NaClBrowser::RunWithoutValidationCache() {
370   // Be paranoid.
371   validation_cache_.Reset();
372   validation_cache_is_enabled_ = false;
373   validation_cache_state_ = NaClResourceReady;
374   CheckWaiting();
375 }
376 
CheckWaiting()377 void NaClBrowser::CheckWaiting() {
378   DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
379   if (!IsOk() || IsReady()) {
380     // Queue the waiting tasks into the message loop.  This helps avoid
381     // re-entrancy problems that could occur if the closure was invoked
382     // directly.  For example, this could result in use-after-free of the
383     // process host.
384     for (std::vector<base::Closure>::iterator iter = waiting_.begin();
385          iter != waiting_.end(); ++iter) {
386       base::MessageLoop::current()->PostTask(FROM_HERE, *iter);
387     }
388     waiting_.clear();
389   }
390 }
391 
MarkAsFailed()392 void NaClBrowser::MarkAsFailed() {
393   ok_ = false;
394   CheckWaiting();
395 }
396 
WaitForResources(const base::Closure & reply)397 void NaClBrowser::WaitForResources(const base::Closure& reply) {
398   waiting_.push_back(reply);
399   EnsureAllResourcesAvailable();
400   CheckWaiting();
401 }
402 
GetIrtFilePath()403 const base::FilePath& NaClBrowser::GetIrtFilePath() {
404   return irt_filepath_;
405 }
406 
PutFilePath(const base::FilePath & path,uint64 * file_token_lo,uint64 * file_token_hi)407 void NaClBrowser::PutFilePath(const base::FilePath& path, uint64* file_token_lo,
408                               uint64* file_token_hi) {
409   DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
410   while (true) {
411     uint64 file_token[2] = {base::RandUint64(), base::RandUint64()};
412     // A zero file_token indicates there is no file_token, if we get zero, ask
413     // for another number.
414     if (file_token[0] != 0 || file_token[1] != 0) {
415       // If the file_token is in use, ask for another number.
416       std::string key(reinterpret_cast<char*>(file_token), sizeof(file_token));
417       PathCacheType::iterator iter = path_cache_.Peek(key);
418       if (iter == path_cache_.end()) {
419         path_cache_.Put(key, path);
420         *file_token_lo = file_token[0];
421         *file_token_hi = file_token[1];
422         break;
423       }
424     }
425   }
426 }
427 
GetFilePath(uint64 file_token_lo,uint64 file_token_hi,base::FilePath * path)428 bool NaClBrowser::GetFilePath(uint64 file_token_lo, uint64 file_token_hi,
429                               base::FilePath* path) {
430   DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
431   uint64 file_token[2] = {file_token_lo, file_token_hi};
432   std::string key(reinterpret_cast<char*>(file_token), sizeof(file_token));
433   PathCacheType::iterator iter = path_cache_.Peek(key);
434   if (iter == path_cache_.end()) {
435     *path = base::FilePath(FILE_PATH_LITERAL(""));
436     return false;
437   }
438   *path = iter->second;
439   path_cache_.Erase(iter);
440   return true;
441 }
442 
443 
QueryKnownToValidate(const std::string & signature,bool off_the_record)444 bool NaClBrowser::QueryKnownToValidate(const std::string& signature,
445                                        bool off_the_record) {
446   DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
447   if (off_the_record) {
448     // If we're off the record, don't reorder the main cache.
449     return validation_cache_.QueryKnownToValidate(signature, false) ||
450         off_the_record_validation_cache_.QueryKnownToValidate(signature, true);
451   } else {
452     bool result = validation_cache_.QueryKnownToValidate(signature, true);
453     LogCacheQuery(result ? CACHE_HIT : CACHE_MISS);
454     // Queries can modify the MRU order of the cache.
455     MarkValidationCacheAsModified();
456     return result;
457   }
458 }
459 
SetKnownToValidate(const std::string & signature,bool off_the_record)460 void NaClBrowser::SetKnownToValidate(const std::string& signature,
461                                      bool off_the_record) {
462   DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
463   if (off_the_record) {
464     off_the_record_validation_cache_.SetKnownToValidate(signature);
465   } else {
466     validation_cache_.SetKnownToValidate(signature);
467     // The number of sets should be equal to the number of cache misses, minus
468     // validation failures and successful validations where stubout occurs.
469     LogCacheSet(CACHE_HIT);
470     MarkValidationCacheAsModified();
471   }
472 }
473 
ClearValidationCache(const base::Closure & callback)474 void NaClBrowser::ClearValidationCache(const base::Closure& callback) {
475   DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
476   // Note: this method may be called before EnsureValidationCacheAvailable has
477   // been invoked.  In other words, this method may be called before any NaCl
478   // processes have been created.  This method must succeed and invoke the
479   // callback in such a case.  If it does not invoke the callback, Chrome's UI
480   // will hang in that case.
481   validation_cache_.Reset();
482   off_the_record_validation_cache_.Reset();
483 
484   if (validation_cache_file_path_.empty()) {
485     // Can't figure out what file to remove, but don't drop the callback.
486     content::BrowserThread::PostTask(content::BrowserThread::IO, FROM_HERE,
487                                      callback);
488   } else {
489     // Delegate the removal of the cache from the filesystem to another thread
490     // to avoid blocking the IO thread.
491     // This task is dispatched immediately, not delayed and coalesced, because
492     // the user interface for cache clearing is likely waiting for the callback.
493     // In addition, we need to make sure the cache is actually cleared before
494     // invoking the callback to meet the implicit guarantees of the UI.
495     content::BrowserThread::PostBlockingPoolSequencedTask(
496         kValidationCacheSequenceName,
497         FROM_HERE,
498         base::Bind(RemoveCache, validation_cache_file_path_, callback));
499   }
500 
501   // Make sure any delayed tasks to persist the cache to the filesystem are
502   // squelched.
503   validation_cache_is_modified_ = false;
504 
505   // If the cache is cleared before it is loaded from the filesystem, act as if
506   // we just loaded an empty cache.
507   if (validation_cache_state_ != NaClResourceReady) {
508     validation_cache_state_ = NaClResourceReady;
509     CheckWaiting();
510   }
511 }
512 
MarkValidationCacheAsModified()513 void NaClBrowser::MarkValidationCacheAsModified() {
514   if (!validation_cache_is_modified_) {
515     // Wait before persisting to disk.  This can coalesce multiple cache
516     // modifications info a single disk write.
517     base::MessageLoop::current()->PostDelayedTask(
518          FROM_HERE,
519          base::Bind(&NaClBrowser::PersistValidationCache,
520                     weak_factory_.GetWeakPtr()),
521          base::TimeDelta::FromMilliseconds(kValidationCacheCoalescingTimeMS));
522     validation_cache_is_modified_ = true;
523   }
524 }
525 
PersistValidationCache()526 void NaClBrowser::PersistValidationCache() {
527   DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
528   // validation_cache_is_modified_ may be false if the cache was cleared while
529   // this delayed task was pending.
530   // validation_cache_file_path_ may be empty if something went wrong during
531   // initialization.
532   if (validation_cache_is_modified_ && !validation_cache_file_path_.empty()) {
533     Pickle* pickle = new Pickle();
534     validation_cache_.Serialize(pickle);
535 
536     // Pass the serialized data to another thread to write to disk.  File IO is
537     // not allowed on the IO thread (which is the thread this method runs on)
538     // because it can degrade the responsiveness of the browser.
539     // The task is sequenced so that multiple writes happen in order.
540     content::BrowserThread::PostBlockingPoolSequencedTask(
541         kValidationCacheSequenceName,
542         FROM_HERE,
543         base::Bind(WriteCache, validation_cache_file_path_,
544                    base::Owned(pickle)));
545   }
546   validation_cache_is_modified_ = false;
547 }
548 
OnProcessCrashed()549 void NaClBrowser::OnProcessCrashed() {
550   DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
551   if (crash_times_.size() == kMaxCrashesPerInterval) {
552     crash_times_.pop_front();
553   }
554   base::Time time = base::Time::Now();
555   crash_times_.push_back(time);
556 }
557 
IsThrottled()558 bool NaClBrowser::IsThrottled() {
559   DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
560   if (crash_times_.size() != kMaxCrashesPerInterval) {
561     return false;
562   }
563   base::TimeDelta delta = base::Time::Now() - crash_times_.front();
564   return delta.InSeconds() <= kCrashesIntervalInSeconds;
565 }
566 
567 }  // namespace nacl
568