• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2013 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #ifdef UNSAFE_BUFFERS_BUILD
6 // TODO(crbug.com/40285824): Remove this and convert code to safer constructs.
7 #pragma allow_unsafe_buffers
8 #endif
9 
10 #include "components/nacl/browser/nacl_browser.h"
11 
12 #include <stddef.h>
13 #include <utility>
14 
15 #include "base/command_line.h"
16 #include "base/containers/span.h"
17 #include "base/files/file_proxy.h"
18 #include "base/files/file_util.h"
19 #include "base/lazy_instance.h"
20 #include "base/location.h"
21 #include "base/metrics/histogram_macros.h"
22 #include "base/path_service.h"
23 #include "base/pickle.h"
24 #include "base/rand_util.h"
25 #include "base/task/single_thread_task_runner.h"
26 #include "base/time/time.h"
27 #include "build/build_config.h"
28 #include "content/public/browser/browser_task_traits.h"
29 #include "content/public/browser/browser_thread.h"
30 #include "url/gurl.h"
31 
32 namespace {
33 
34 // Tasks posted in this file are on the critical path of displaying the official
35 // virtual keyboard on Chrome OS. https://crbug.com/976542
36 constexpr base::TaskPriority kUserBlocking = base::TaskPriority::USER_BLOCKING;
37 
38 // An arbitrary delay to coalesce multiple writes to the cache.
39 const int kValidationCacheCoalescingTimeMS = 6000;
40 const base::FilePath::CharType kValidationCacheFileName[] =
41     FILE_PATH_LITERAL("nacl_validation_cache.bin");
42 
43 const bool kValidationCacheEnabledByDefault = true;
44 
NaClIrtName()45 const base::FilePath::StringType NaClIrtName() {
46   base::FilePath::StringType irt_name(FILE_PATH_LITERAL("nacl_irt_"));
47 
48 #if defined(ARCH_CPU_X86_FAMILY)
49 #if defined(ARCH_CPU_X86_64)
50   irt_name.append(FILE_PATH_LITERAL("x86_64"));
51 #else
52   irt_name.append(FILE_PATH_LITERAL("x86_32"));
53 #endif
54 #elif defined(ARCH_CPU_ARM_FAMILY)
55   irt_name.append(FILE_PATH_LITERAL("arm"));
56 #elif defined(ARCH_CPU_MIPSEL)
57   irt_name.append(FILE_PATH_LITERAL("mips32"));
58 #else
59 #error Add support for your architecture to NaCl IRT file selection
60 #endif
61   irt_name.append(FILE_PATH_LITERAL(".nexe"));
62   return irt_name;
63 }
64 
65 #if !BUILDFLAG(IS_ANDROID)
CheckEnvVar(const char * name,bool default_value)66 bool CheckEnvVar(const char* name, bool default_value) {
67   bool result = default_value;
68   const char* var = getenv(name);
69   if (var && strlen(var) > 0) {
70     result = var[0] != '0';
71   }
72   return result;
73 }
74 #endif
75 
ReadCache(const base::FilePath & filename,std::string * data)76 void ReadCache(const base::FilePath& filename, std::string* data) {
77   if (!base::ReadFileToString(filename, data)) {
78     // Zero-size data used as an in-band error code.
79     data->clear();
80   }
81 }
82 
WriteCache(const base::FilePath & filename,const base::Pickle * pickle)83 void WriteCache(const base::FilePath& filename, const base::Pickle* pickle) {
84   base::WriteFile(filename, base::span(*pickle));
85 }
86 
RemoveCache(const base::FilePath & filename,base::OnceClosure callback)87 void RemoveCache(const base::FilePath& filename, base::OnceClosure callback) {
88   base::DeleteFile(filename);
89   content::GetIOThreadTaskRunner({})->PostTask(FROM_HERE, std::move(callback));
90 }
91 
LogCacheQuery(nacl::NaClBrowser::ValidationCacheStatus status)92 void LogCacheQuery(nacl::NaClBrowser::ValidationCacheStatus status) {
93   UMA_HISTOGRAM_ENUMERATION("NaCl.ValidationCache.Query", status,
94                             nacl::NaClBrowser::CACHE_MAX);
95 }
96 
LogCacheSet(nacl::NaClBrowser::ValidationCacheStatus status)97 void LogCacheSet(nacl::NaClBrowser::ValidationCacheStatus status) {
98   // Bucket zero is reserved for future use.
99   UMA_HISTOGRAM_ENUMERATION("NaCl.ValidationCache.Set", status,
100                             nacl::NaClBrowser::CACHE_MAX);
101 }
102 
103 // Crash throttling parameters.
104 const size_t kMaxCrashesPerInterval = 3;
105 const int64_t kCrashesIntervalInSeconds = 120;
106 
107 // Holds the NaClBrowserDelegate, which is leaked on shutdown.
108 NaClBrowserDelegate* g_browser_delegate = nullptr;
109 
110 }  // namespace
111 
112 namespace nacl {
113 
OpenNaClReadExecImpl(const base::FilePath & file_path,bool is_executable)114 base::File OpenNaClReadExecImpl(const base::FilePath& file_path,
115                                 bool is_executable) {
116   // Get a file descriptor. On Windows, we need 'GENERIC_EXECUTE' in order to
117   // memory map the executable.
118   // IMPORTANT: This file descriptor must not have write access - that could
119   // allow a NaCl inner sandbox escape.
120   uint32_t flags = base::File::FLAG_OPEN | base::File::FLAG_READ;
121   if (is_executable)
122     flags |= base::File::FLAG_WIN_EXECUTE;  // Windows only flag.
123   base::File file(file_path, flags);
124   if (!file.IsValid())
125     return file;
126 
127   // Check that the file does not reference a directory. Returning a descriptor
128   // to an extension directory could allow an outer sandbox escape. openat(...)
129   // could be used to traverse into the file system.
130   base::File::Info file_info;
131   if (!file.GetInfo(&file_info) || file_info.is_directory)
132     return base::File();
133 
134   return file;
135 }
136 
NaClBrowser()137 NaClBrowser::NaClBrowser() {
138 #if !BUILDFLAG(IS_ANDROID)
139   validation_cache_is_enabled_ =
140       CheckEnvVar("NACL_VALIDATION_CACHE", kValidationCacheEnabledByDefault);
141 #endif
142       DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
143 }
144 
SetDelegate(std::unique_ptr<NaClBrowserDelegate> delegate)145 void NaClBrowser::SetDelegate(std::unique_ptr<NaClBrowserDelegate> delegate) {
146   // In the browser SetDelegate is called after threads are initialized.
147   // In tests it is called before initializing BrowserThreads.
148   if (content::BrowserThread::IsThreadInitialized(content::BrowserThread::UI)) {
149     DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
150   }
151   DCHECK(delegate);
152   DCHECK(!g_browser_delegate);
153   g_browser_delegate = delegate.release();
154 }
155 
GetDelegate()156 NaClBrowserDelegate* NaClBrowser::GetDelegate() {
157   // NaClBrowser calls this on the IO thread, not the UI thread.
158   DCHECK(g_browser_delegate);
159   return g_browser_delegate;
160 }
161 
ClearAndDeleteDelegate()162 void NaClBrowser::ClearAndDeleteDelegate() {
163   DCHECK(g_browser_delegate);
164   delete g_browser_delegate;
165   g_browser_delegate = nullptr;
166 }
167 
EarlyStartup()168 void NaClBrowser::EarlyStartup() {
169   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
170   InitIrtFilePath();
171   InitValidationCacheFilePath();
172 }
173 
~NaClBrowser()174 NaClBrowser::~NaClBrowser() {
175   NOTREACHED();
176 }
177 
InitIrtFilePath()178 void NaClBrowser::InitIrtFilePath() {
179   // Allow the IRT library to be overridden via an environment
180   // variable.  This allows the NaCl/Chromium integration bot to
181   // specify a newly-built IRT rather than using a prebuilt one
182   // downloaded via Chromium's DEPS file.  We use the same environment
183   // variable that the standalone NaCl PPAPI plugin accepts.
184   const char* irt_path_var = getenv("NACL_IRT_LIBRARY");
185   if (irt_path_var != NULL) {
186     base::FilePath::StringType path_string(
187         irt_path_var, const_cast<const char*>(strchr(irt_path_var, '\0')));
188     irt_filepath_ = base::FilePath(path_string);
189   } else {
190     base::FilePath plugin_dir;
191     if (!GetDelegate()->GetPluginDirectory(&plugin_dir)) {
192       DLOG(ERROR) << "Failed to locate the plugins directory, NaCl disabled.";
193       MarkAsFailed();
194       return;
195     }
196     irt_filepath_ = plugin_dir.Append(NaClIrtName());
197   }
198 }
199 
200 // static
GetInstanceInternal()201 NaClBrowser* NaClBrowser::GetInstanceInternal() {
202   static NaClBrowser* g_instance = nullptr;
203   if (!g_instance)
204     g_instance = new NaClBrowser();
205   return g_instance;
206 }
207 
GetInstance()208 NaClBrowser* NaClBrowser::GetInstance() {
209   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
210   return GetInstanceInternal();
211 }
212 
IsReady() const213 bool NaClBrowser::IsReady() const {
214   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
215   return (IsOk() &&
216           irt_state_ == NaClResourceReady &&
217           validation_cache_state_ == NaClResourceReady);
218 }
219 
IsOk() const220 bool NaClBrowser::IsOk() const {
221   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
222   return !has_failed_;
223 }
224 
IrtFile() const225 const base::File& NaClBrowser::IrtFile() const {
226   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
227   CHECK_EQ(irt_state_, NaClResourceReady);
228   CHECK(irt_file_.IsValid());
229   return irt_file_;
230 }
231 
EnsureAllResourcesAvailable()232 void NaClBrowser::EnsureAllResourcesAvailable() {
233   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
234   EnsureIrtAvailable();
235   EnsureValidationCacheAvailable();
236 }
237 
238 // Load the IRT async.
EnsureIrtAvailable()239 void NaClBrowser::EnsureIrtAvailable() {
240   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
241   if (IsOk() && irt_state_ == NaClResourceUninitialized) {
242     irt_state_ = NaClResourceRequested;
243     auto task_runner = base::ThreadPool::CreateTaskRunner(
244         {base::MayBlock(), kUserBlocking,
245          base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN});
246     std::unique_ptr<base::FileProxy> file_proxy(
247         new base::FileProxy(task_runner.get()));
248     base::FileProxy* proxy = file_proxy.get();
249     if (!proxy->CreateOrOpen(
250             irt_filepath_, base::File::FLAG_OPEN | base::File::FLAG_READ,
251             base::BindOnce(&NaClBrowser::OnIrtOpened, base::Unretained(this),
252                            std::move(file_proxy)))) {
253       LOG(ERROR) << "Internal error, NaCl disabled.";
254       MarkAsFailed();
255     }
256   }
257 }
258 
OnIrtOpened(std::unique_ptr<base::FileProxy> file_proxy,base::File::Error error_code)259 void NaClBrowser::OnIrtOpened(std::unique_ptr<base::FileProxy> file_proxy,
260                               base::File::Error error_code) {
261   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
262   DCHECK_EQ(irt_state_, NaClResourceRequested);
263   if (file_proxy->IsValid()) {
264     irt_file_ = file_proxy->TakeFile();
265   } else {
266     LOG(ERROR) << "Failed to open NaCl IRT file \""
267                << irt_filepath_.LossyDisplayName()
268                << "\": " << error_code;
269     MarkAsFailed();
270   }
271   irt_state_ = NaClResourceReady;
272   CheckWaiting();
273 }
274 
SetProcessGdbDebugStubPort(int process_id,int port)275 void NaClBrowser::SetProcessGdbDebugStubPort(int process_id, int port) {
276   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
277   gdb_debug_stub_port_map_[process_id] = port;
278   if (port != kGdbDebugStubPortUnknown &&
279       !debug_stub_port_listener_.is_null()) {
280     content::GetIOThreadTaskRunner({})->PostTask(
281         FROM_HERE, base::BindOnce(debug_stub_port_listener_, port));
282   }
283 }
284 
285 // static
SetGdbDebugStubPortListenerForTest(base::RepeatingCallback<void (int)> listener)286 void NaClBrowser::SetGdbDebugStubPortListenerForTest(
287     base::RepeatingCallback<void(int)> listener) {
288   GetInstanceInternal()->debug_stub_port_listener_ = listener;
289 }
290 
291 // static
ClearGdbDebugStubPortListenerForTest()292 void NaClBrowser::ClearGdbDebugStubPortListenerForTest() {
293   GetInstanceInternal()->debug_stub_port_listener_.Reset();
294 }
295 
GetProcessGdbDebugStubPort(int process_id)296 int NaClBrowser::GetProcessGdbDebugStubPort(int process_id) {
297   // Called from TaskManager TaskGroup impl, on CrBrowserMain.
298   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
299   auto i = gdb_debug_stub_port_map_.find(process_id);
300   if (i != gdb_debug_stub_port_map_.end()) {
301     return i->second;
302   }
303   return kGdbDebugStubPortUnused;
304 }
305 
InitValidationCacheFilePath()306 void NaClBrowser::InitValidationCacheFilePath() {
307   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
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 (!GetDelegate()->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   GetDelegate()->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_CURRENTLY_ON(content::BrowserThread::UI);
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       base::ThreadPool::PostTaskAndReply(
338           FROM_HERE, {base::MayBlock(), base::TaskPriority::BEST_EFFORT},
339           base::BindOnce(ReadCache, validation_cache_file_path_, data),
340           base::BindOnce(&NaClBrowser::OnValidationCacheLoaded,
341                          base::Unretained(this), base::Owned(data)));
342     } else {
343       RunWithoutValidationCache();
344     }
345   }
346 }
347 
OnValidationCacheLoaded(const std::string * data)348 void NaClBrowser::OnValidationCacheLoaded(const std::string *data) {
349   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
350   // Did the cache get cleared before the load completed?  If so, ignore the
351   // incoming data.
352   if (validation_cache_state_ == NaClResourceReady)
353     return;
354 
355   if (data->size() == 0) {
356     // No file found.
357     validation_cache_.Reset();
358   } else {
359     base::Pickle pickle =
360         base::Pickle::WithUnownedBuffer(base::as_byte_span(*data));
361     validation_cache_.Deserialize(&pickle);
362   }
363   validation_cache_state_ = NaClResourceReady;
364   CheckWaiting();
365 }
366 
RunWithoutValidationCache()367 void NaClBrowser::RunWithoutValidationCache() {
368   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
369   // Be paranoid.
370   validation_cache_.Reset();
371   validation_cache_is_enabled_ = false;
372   validation_cache_state_ = NaClResourceReady;
373   CheckWaiting();
374 }
375 
CheckWaiting()376 void NaClBrowser::CheckWaiting() {
377   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
378   if (!IsOk() || IsReady()) {
379     // Queue the waiting tasks into the message loop.  This helps avoid
380     // re-entrancy problems that could occur if the closure was invoked
381     // directly.  For example, this could result in use-after-free of the
382     // process host.
383     for (auto iter = waiting_.begin(); iter != waiting_.end(); ++iter) {
384       base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
385           FROM_HERE, std::move(*iter));
386     }
387     waiting_.clear();
388   }
389 }
390 
MarkAsFailed()391 void NaClBrowser::MarkAsFailed() {
392   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
393   has_failed_ = true;
394   CheckWaiting();
395 }
396 
WaitForResources(base::OnceClosure reply)397 void NaClBrowser::WaitForResources(base::OnceClosure reply) {
398   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
399   waiting_.push_back(std::move(reply));
400   EnsureAllResourcesAvailable();
401   CheckWaiting();
402 }
403 
GetIrtFilePath()404 const base::FilePath& NaClBrowser::GetIrtFilePath() {
405   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
406   return irt_filepath_;
407 }
408 
PutFilePath(const base::FilePath & path,uint64_t * file_token_lo,uint64_t * file_token_hi)409 void NaClBrowser::PutFilePath(const base::FilePath& path,
410                               uint64_t* file_token_lo,
411                               uint64_t* file_token_hi) {
412   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
413   while (true) {
414     uint64_t file_token[2] = {base::RandUint64(), base::RandUint64()};
415     // A zero file_token indicates there is no file_token, if we get zero, ask
416     // for another number.
417     if (file_token[0] != 0 || file_token[1] != 0) {
418       // If the file_token is in use, ask for another number.
419       std::string key(reinterpret_cast<char*>(file_token), sizeof(file_token));
420       auto iter = path_cache_.Peek(key);
421       if (iter == path_cache_.end()) {
422         path_cache_.Put(key, path);
423         *file_token_lo = file_token[0];
424         *file_token_hi = file_token[1];
425         break;
426       }
427     }
428   }
429 }
430 
GetFilePath(uint64_t file_token_lo,uint64_t file_token_hi,base::FilePath * path)431 bool NaClBrowser::GetFilePath(uint64_t file_token_lo,
432                               uint64_t file_token_hi,
433                               base::FilePath* path) {
434   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
435   uint64_t file_token[2] = {file_token_lo, file_token_hi};
436   std::string key(reinterpret_cast<char*>(file_token), sizeof(file_token));
437   auto iter = path_cache_.Peek(key);
438   if (iter == path_cache_.end()) {
439     *path = base::FilePath(FILE_PATH_LITERAL(""));
440     return false;
441   }
442   *path = iter->second;
443   path_cache_.Erase(iter);
444   return true;
445 }
446 
447 
QueryKnownToValidate(const std::string & signature,bool off_the_record)448 bool NaClBrowser::QueryKnownToValidate(const std::string& signature,
449                                        bool off_the_record) {
450   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
451   if (off_the_record) {
452     // If we're off the record, don't reorder the main cache.
453     return validation_cache_.QueryKnownToValidate(signature, false) ||
454         off_the_record_validation_cache_.QueryKnownToValidate(signature, true);
455   } else {
456     bool result = validation_cache_.QueryKnownToValidate(signature, true);
457     LogCacheQuery(result ? CACHE_HIT : CACHE_MISS);
458     // Queries can modify the MRU order of the cache.
459     MarkValidationCacheAsModified();
460     return result;
461   }
462 }
463 
SetKnownToValidate(const std::string & signature,bool off_the_record)464 void NaClBrowser::SetKnownToValidate(const std::string& signature,
465                                      bool off_the_record) {
466   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
467   if (off_the_record) {
468     off_the_record_validation_cache_.SetKnownToValidate(signature);
469   } else {
470     validation_cache_.SetKnownToValidate(signature);
471     // The number of sets should be equal to the number of cache misses, minus
472     // validation failures and successful validations where stubout occurs.
473     LogCacheSet(CACHE_HIT);
474     MarkValidationCacheAsModified();
475   }
476 }
477 
ClearValidationCache(base::OnceClosure callback)478 void NaClBrowser::ClearValidationCache(base::OnceClosure callback) {
479   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
480   // Note: this method may be called before EnsureValidationCacheAvailable has
481   // been invoked.  In other words, this method may be called before any NaCl
482   // processes have been created.  This method must succeed and invoke the
483   // callback in such a case.  If it does not invoke the callback, Chrome's UI
484   // will hang in that case.
485   validation_cache_.Reset();
486   off_the_record_validation_cache_.Reset();
487 
488   if (validation_cache_file_path_.empty()) {
489     // Can't figure out what file to remove, but don't drop the callback.
490     content::GetIOThreadTaskRunner({})->PostTask(FROM_HERE,
491                                                  std::move(callback));
492   } else {
493     // Delegate the removal of the cache from the filesystem to another thread
494     // to avoid blocking the IO thread.
495     // This task is dispatched immediately, not delayed and coalesced, because
496     // the user interface for cache clearing is likely waiting for the callback.
497     // In addition, we need to make sure the cache is actually cleared before
498     // invoking the callback to meet the implicit guarantees of the UI.
499     file_task_runner_->PostTask(
500         FROM_HERE, base::BindOnce(RemoveCache, validation_cache_file_path_,
501                                   std::move(callback)));
502   }
503 
504   // Make sure any delayed tasks to persist the cache to the filesystem are
505   // squelched.
506   validation_cache_is_modified_ = false;
507 
508   // If the cache is cleared before it is loaded from the filesystem, act as if
509   // we just loaded an empty cache.
510   if (validation_cache_state_ != NaClResourceReady) {
511     validation_cache_state_ = NaClResourceReady;
512     CheckWaiting();
513   }
514 }
515 
MarkValidationCacheAsModified()516 void NaClBrowser::MarkValidationCacheAsModified() {
517   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
518   if (!validation_cache_is_modified_) {
519     // Wait before persisting to disk.  This can coalesce multiple cache
520     // modifications info a single disk write.
521     base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
522         FROM_HERE,
523         base::BindOnce(&NaClBrowser::PersistValidationCache,
524                        base::Unretained(this)),
525         base::Milliseconds(kValidationCacheCoalescingTimeMS));
526     validation_cache_is_modified_ = true;
527   }
528 }
529 
PersistValidationCache()530 void NaClBrowser::PersistValidationCache() {
531   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
532   // validation_cache_is_modified_ may be false if the cache was cleared while
533   // this delayed task was pending.
534   // validation_cache_file_path_ may be empty if something went wrong during
535   // initialization.
536   if (validation_cache_is_modified_ && !validation_cache_file_path_.empty()) {
537     base::Pickle* pickle = new base::Pickle();
538     validation_cache_.Serialize(pickle);
539 
540     // Pass the serialized data to another thread to write to disk.  File IO is
541     // not allowed on the IO thread (which is the thread this method runs on)
542     // because it can degrade the responsiveness of the browser.
543     // The task is sequenced so that multiple writes happen in order.
544     file_task_runner_->PostTask(
545         FROM_HERE, base::BindOnce(WriteCache, validation_cache_file_path_,
546                                   base::Owned(pickle)));
547   }
548   validation_cache_is_modified_ = false;
549 }
550 
OnProcessEnd(int process_id)551 void NaClBrowser::OnProcessEnd(int process_id) {
552   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
553   gdb_debug_stub_port_map_.erase(process_id);
554 }
555 
OnProcessCrashed()556 void NaClBrowser::OnProcessCrashed() {
557   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
558   if (crash_times_.size() == kMaxCrashesPerInterval) {
559     crash_times_.pop_front();
560   }
561   base::Time time = base::Time::Now();
562   crash_times_.push_back(time);
563 }
564 
IsThrottled()565 bool NaClBrowser::IsThrottled() {
566   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
567   if (crash_times_.size() != kMaxCrashesPerInterval) {
568     return false;
569   }
570   base::TimeDelta delta = base::Time::Now() - crash_times_.front();
571   return delta.InSeconds() <= kCrashesIntervalInSeconds;
572 }
573 
574 }  // namespace nacl
575