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