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/files/file_proxy.h"
12 #include "base/files/file_util.h"
13 #include "base/lazy_instance.h"
14 #include "base/location.h"
15 #include "base/metrics/histogram_macros.h"
16 #include "base/path_service.h"
17 #include "base/pickle.h"
18 #include "base/rand_util.h"
19 #include "base/task/single_thread_task_runner.h"
20 #include "base/time/time.h"
21 #include "build/build_config.h"
22 #include "content/public/browser/browser_task_traits.h"
23 #include "content/public/browser/browser_thread.h"
24 #include "url/gurl.h"
25
26 namespace {
27
28 // Tasks posted in this file are on the critical path of displaying the official
29 // virtual keyboard on Chrome OS. https://crbug.com/976542
30 constexpr base::TaskPriority kUserBlocking = base::TaskPriority::USER_BLOCKING;
31
32 // An arbitrary delay to coalesce multiple writes to the cache.
33 const int kValidationCacheCoalescingTimeMS = 6000;
34 const base::FilePath::CharType kValidationCacheFileName[] =
35 FILE_PATH_LITERAL("nacl_validation_cache.bin");
36
37 const bool kValidationCacheEnabledByDefault = true;
38
NaClIrtName()39 const base::FilePath::StringType NaClIrtName() {
40 base::FilePath::StringType irt_name(FILE_PATH_LITERAL("nacl_irt_"));
41
42 #if defined(ARCH_CPU_X86_FAMILY)
43 #if defined(ARCH_CPU_X86_64)
44 irt_name.append(FILE_PATH_LITERAL("x86_64"));
45 #else
46 irt_name.append(FILE_PATH_LITERAL("x86_32"));
47 #endif
48 #elif defined(ARCH_CPU_ARM_FAMILY)
49 irt_name.append(FILE_PATH_LITERAL("arm"));
50 #elif defined(ARCH_CPU_MIPSEL)
51 irt_name.append(FILE_PATH_LITERAL("mips32"));
52 #else
53 #error Add support for your architecture to NaCl IRT file selection
54 #endif
55 irt_name.append(FILE_PATH_LITERAL(".nexe"));
56 return irt_name;
57 }
58
59 #if !BUILDFLAG(IS_ANDROID)
CheckEnvVar(const char * name,bool default_value)60 bool CheckEnvVar(const char* name, bool default_value) {
61 bool result = default_value;
62 const char* var = getenv(name);
63 if (var && strlen(var) > 0) {
64 result = var[0] != '0';
65 }
66 return result;
67 }
68 #endif
69
ReadCache(const base::FilePath & filename,std::string * data)70 void ReadCache(const base::FilePath& filename, std::string* data) {
71 if (!base::ReadFileToString(filename, data)) {
72 // Zero-size data used as an in-band error code.
73 data->clear();
74 }
75 }
76
WriteCache(const base::FilePath & filename,const base::Pickle * pickle)77 void WriteCache(const base::FilePath& filename, const base::Pickle* pickle) {
78 base::WriteFile(filename,
79 base::make_span(static_cast<const uint8_t*>(pickle->data()),
80 pickle->size()));
81 }
82
RemoveCache(const base::FilePath & filename,base::OnceClosure callback)83 void RemoveCache(const base::FilePath& filename, base::OnceClosure callback) {
84 base::DeleteFile(filename);
85 content::GetIOThreadTaskRunner({})->PostTask(FROM_HERE, std::move(callback));
86 }
87
LogCacheQuery(nacl::NaClBrowser::ValidationCacheStatus status)88 void LogCacheQuery(nacl::NaClBrowser::ValidationCacheStatus status) {
89 UMA_HISTOGRAM_ENUMERATION("NaCl.ValidationCache.Query", status,
90 nacl::NaClBrowser::CACHE_MAX);
91 }
92
LogCacheSet(nacl::NaClBrowser::ValidationCacheStatus status)93 void LogCacheSet(nacl::NaClBrowser::ValidationCacheStatus status) {
94 // Bucket zero is reserved for future use.
95 UMA_HISTOGRAM_ENUMERATION("NaCl.ValidationCache.Set", status,
96 nacl::NaClBrowser::CACHE_MAX);
97 }
98
99 // Crash throttling parameters.
100 const size_t kMaxCrashesPerInterval = 3;
101 const int64_t kCrashesIntervalInSeconds = 120;
102
103 // Holds the NaClBrowserDelegate, which is leaked on shutdown.
104 NaClBrowserDelegate* g_browser_delegate = nullptr;
105
106 } // namespace
107
108 namespace nacl {
109
OpenNaClReadExecImpl(const base::FilePath & file_path,bool is_executable)110 base::File OpenNaClReadExecImpl(const base::FilePath& file_path,
111 bool is_executable) {
112 // Get a file descriptor. On Windows, we need 'GENERIC_EXECUTE' in order to
113 // memory map the executable.
114 // IMPORTANT: This file descriptor must not have write access - that could
115 // allow a NaCl inner sandbox escape.
116 uint32_t flags = base::File::FLAG_OPEN | base::File::FLAG_READ;
117 if (is_executable)
118 flags |= base::File::FLAG_WIN_EXECUTE; // Windows only flag.
119 base::File file(file_path, flags);
120 if (!file.IsValid())
121 return file;
122
123 // Check that the file does not reference a directory. Returning a descriptor
124 // to an extension directory could allow an outer sandbox escape. openat(...)
125 // could be used to traverse into the file system.
126 base::File::Info file_info;
127 if (!file.GetInfo(&file_info) || file_info.is_directory)
128 return base::File();
129
130 return file;
131 }
132
NaClBrowser()133 NaClBrowser::NaClBrowser() {
134 #if !BUILDFLAG(IS_ANDROID)
135 validation_cache_is_enabled_ =
136 CheckEnvVar("NACL_VALIDATION_CACHE", kValidationCacheEnabledByDefault);
137 #endif
138 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
139 }
140
SetDelegate(std::unique_ptr<NaClBrowserDelegate> delegate)141 void NaClBrowser::SetDelegate(std::unique_ptr<NaClBrowserDelegate> delegate) {
142 // In the browser SetDelegate is called after threads are initialized.
143 // In tests it is called before initializing BrowserThreads.
144 if (content::BrowserThread::IsThreadInitialized(content::BrowserThread::UI)) {
145 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
146 }
147 DCHECK(delegate);
148 DCHECK(!g_browser_delegate);
149 g_browser_delegate = delegate.release();
150 }
151
GetDelegate()152 NaClBrowserDelegate* NaClBrowser::GetDelegate() {
153 // NaClBrowser calls this on the IO thread, not the UI thread.
154 DCHECK(g_browser_delegate);
155 return g_browser_delegate;
156 }
157
ClearAndDeleteDelegateForTest()158 void NaClBrowser::ClearAndDeleteDelegateForTest() {
159 DCHECK(
160 !content::BrowserThread::IsThreadInitialized(content::BrowserThread::UI));
161 DCHECK(g_browser_delegate);
162 delete g_browser_delegate;
163 g_browser_delegate = nullptr;
164 }
165
EarlyStartup()166 void NaClBrowser::EarlyStartup() {
167 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
168 InitIrtFilePath();
169 InitValidationCacheFilePath();
170 }
171
~NaClBrowser()172 NaClBrowser::~NaClBrowser() {
173 NOTREACHED();
174 }
175
InitIrtFilePath()176 void NaClBrowser::InitIrtFilePath() {
177 // Allow the IRT library to be overridden via an environment
178 // variable. This allows the NaCl/Chromium integration bot to
179 // specify a newly-built IRT rather than using a prebuilt one
180 // downloaded via Chromium's DEPS file. We use the same environment
181 // variable that the standalone NaCl PPAPI plugin accepts.
182 const char* irt_path_var = getenv("NACL_IRT_LIBRARY");
183 if (irt_path_var != NULL) {
184 base::FilePath::StringType path_string(
185 irt_path_var, const_cast<const char*>(strchr(irt_path_var, '\0')));
186 irt_filepath_ = base::FilePath(path_string);
187 } else {
188 base::FilePath plugin_dir;
189 if (!GetDelegate()->GetPluginDirectory(&plugin_dir)) {
190 DLOG(ERROR) << "Failed to locate the plugins directory, NaCl disabled.";
191 MarkAsFailed();
192 return;
193 }
194 irt_filepath_ = plugin_dir.Append(NaClIrtName());
195 }
196 }
197
198 // static
GetInstanceInternal()199 NaClBrowser* NaClBrowser::GetInstanceInternal() {
200 static NaClBrowser* g_instance = nullptr;
201 if (!g_instance)
202 g_instance = new NaClBrowser();
203 return g_instance;
204 }
205
GetInstance()206 NaClBrowser* NaClBrowser::GetInstance() {
207 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
208 return GetInstanceInternal();
209 }
210
IsReady() const211 bool NaClBrowser::IsReady() const {
212 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
213 return (IsOk() &&
214 irt_state_ == NaClResourceReady &&
215 validation_cache_state_ == NaClResourceReady);
216 }
217
IsOk() const218 bool NaClBrowser::IsOk() const {
219 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
220 return !has_failed_;
221 }
222
IrtFile() const223 const base::File& NaClBrowser::IrtFile() const {
224 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
225 CHECK_EQ(irt_state_, NaClResourceReady);
226 CHECK(irt_file_.IsValid());
227 return irt_file_;
228 }
229
EnsureAllResourcesAvailable()230 void NaClBrowser::EnsureAllResourcesAvailable() {
231 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
232 EnsureIrtAvailable();
233 EnsureValidationCacheAvailable();
234 }
235
236 // Load the IRT async.
EnsureIrtAvailable()237 void NaClBrowser::EnsureIrtAvailable() {
238 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
239 if (IsOk() && irt_state_ == NaClResourceUninitialized) {
240 irt_state_ = NaClResourceRequested;
241 auto task_runner = base::ThreadPool::CreateTaskRunner(
242 {base::MayBlock(), kUserBlocking,
243 base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN});
244 std::unique_ptr<base::FileProxy> file_proxy(
245 new base::FileProxy(task_runner.get()));
246 base::FileProxy* proxy = file_proxy.get();
247 if (!proxy->CreateOrOpen(
248 irt_filepath_, base::File::FLAG_OPEN | base::File::FLAG_READ,
249 base::BindOnce(&NaClBrowser::OnIrtOpened, base::Unretained(this),
250 std::move(file_proxy)))) {
251 LOG(ERROR) << "Internal error, NaCl disabled.";
252 MarkAsFailed();
253 }
254 }
255 }
256
OnIrtOpened(std::unique_ptr<base::FileProxy> file_proxy,base::File::Error error_code)257 void NaClBrowser::OnIrtOpened(std::unique_ptr<base::FileProxy> file_proxy,
258 base::File::Error error_code) {
259 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
260 DCHECK_EQ(irt_state_, NaClResourceRequested);
261 if (file_proxy->IsValid()) {
262 irt_file_ = file_proxy->TakeFile();
263 } else {
264 LOG(ERROR) << "Failed to open NaCl IRT file \""
265 << irt_filepath_.LossyDisplayName()
266 << "\": " << error_code;
267 MarkAsFailed();
268 }
269 irt_state_ = NaClResourceReady;
270 CheckWaiting();
271 }
272
SetProcessGdbDebugStubPort(int process_id,int port)273 void NaClBrowser::SetProcessGdbDebugStubPort(int process_id, int port) {
274 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
275 gdb_debug_stub_port_map_[process_id] = port;
276 if (port != kGdbDebugStubPortUnknown &&
277 !debug_stub_port_listener_.is_null()) {
278 content::GetIOThreadTaskRunner({})->PostTask(
279 FROM_HERE, base::BindOnce(debug_stub_port_listener_, port));
280 }
281 }
282
283 // static
SetGdbDebugStubPortListenerForTest(base::RepeatingCallback<void (int)> listener)284 void NaClBrowser::SetGdbDebugStubPortListenerForTest(
285 base::RepeatingCallback<void(int)> listener) {
286 GetInstanceInternal()->debug_stub_port_listener_ = listener;
287 }
288
289 // static
ClearGdbDebugStubPortListenerForTest()290 void NaClBrowser::ClearGdbDebugStubPortListenerForTest() {
291 GetInstanceInternal()->debug_stub_port_listener_.Reset();
292 }
293
GetProcessGdbDebugStubPort(int process_id)294 int NaClBrowser::GetProcessGdbDebugStubPort(int process_id) {
295 // Called from TaskManager TaskGroup impl, on CrBrowserMain.
296 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
297 auto i = gdb_debug_stub_port_map_.find(process_id);
298 if (i != gdb_debug_stub_port_map_.end()) {
299 return i->second;
300 }
301 return kGdbDebugStubPortUnused;
302 }
303
InitValidationCacheFilePath()304 void NaClBrowser::InitValidationCacheFilePath() {
305 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
306 // Determine where the validation cache resides in the file system. It
307 // exists in Chrome's cache directory and is not tied to any specific
308 // profile.
309 // Start by finding the user data directory.
310 base::FilePath user_data_dir;
311 if (!GetDelegate()->GetUserDirectory(&user_data_dir)) {
312 RunWithoutValidationCache();
313 return;
314 }
315 // The cache directory may or may not be the user data directory.
316 base::FilePath cache_file_path;
317 GetDelegate()->GetCacheDirectory(&cache_file_path);
318 // Append the base file name to the cache directory.
319
320 validation_cache_file_path_ =
321 cache_file_path.Append(kValidationCacheFileName);
322 }
323
EnsureValidationCacheAvailable()324 void NaClBrowser::EnsureValidationCacheAvailable() {
325 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
326 if (IsOk() && validation_cache_state_ == NaClResourceUninitialized) {
327 if (ValidationCacheIsEnabled()) {
328 validation_cache_state_ = NaClResourceRequested;
329
330 // Structure for carrying data between the callbacks.
331 std::string* data = new std::string();
332 // We can get away not giving this a sequence ID because this is the first
333 // task and further file access will not occur until after we get a
334 // response.
335 base::ThreadPool::PostTaskAndReply(
336 FROM_HERE, {base::MayBlock(), base::TaskPriority::BEST_EFFORT},
337 base::BindOnce(ReadCache, validation_cache_file_path_, data),
338 base::BindOnce(&NaClBrowser::OnValidationCacheLoaded,
339 base::Unretained(this), base::Owned(data)));
340 } else {
341 RunWithoutValidationCache();
342 }
343 }
344 }
345
OnValidationCacheLoaded(const std::string * data)346 void NaClBrowser::OnValidationCacheLoaded(const std::string *data) {
347 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
348 // Did the cache get cleared before the load completed? If so, ignore the
349 // incoming data.
350 if (validation_cache_state_ == NaClResourceReady)
351 return;
352
353 if (data->size() == 0) {
354 // No file found.
355 validation_cache_.Reset();
356 } else {
357 base::Pickle pickle(data->data(), data->size());
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