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