1 // Copyright 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "components/nacl/browser/nacl_browser.h"
7 #include "base/command_line.h"
8 #include "base/files/file_proxy.h"
9 #include "base/files/file_util.h"
10 #include "base/message_loop/message_loop.h"
11 #include "base/metrics/histogram.h"
12 #include "base/path_service.h"
13 #include "base/pickle.h"
14 #include "base/rand_util.h"
15 #include "base/time/time.h"
16 #include "base/win/windows_version.h"
17 #include "build/build_config.h"
18 #include "content/public/browser/browser_thread.h"
23 // An arbitrary delay to coalesce multiple writes to the cache.
24 const int kValidationCacheCoalescingTimeMS
= 6000;
25 const char kValidationCacheSequenceName
[] = "NaClValidationCache";
26 const base::FilePath::CharType kValidationCacheFileName
[] =
27 FILE_PATH_LITERAL("nacl_validation_cache.bin");
29 const bool kValidationCacheEnabledByDefault
= true;
31 // Keep the cache bounded to an arbitrary size. If it's too small, useful
32 // entries could be evicted when multiple .nexes are loaded at once. On the
33 // other hand, entries are not always claimed (and hence removed), so the size
34 // of the cache will likely saturate at its maximum size.
35 // Entries may not be claimed for two main reasons. 1) the NaCl process could
36 // be killed while it is loading. 2) the trusted NaCl plugin opens files using
37 // the code path but doesn't resolve them.
38 // TODO(ncbray) don't cache files that the plugin will not resolve.
39 const int kFilePathCacheSize
= 100;
41 const base::FilePath::StringType
NaClIrtName() {
42 base::FilePath::StringType
irt_name(FILE_PATH_LITERAL("nacl_irt_"));
44 #if defined(ARCH_CPU_X86_FAMILY)
45 #if defined(ARCH_CPU_X86_64)
48 bool is64
= (base::win::OSInfo::GetInstance()->wow64_status() ==
49 base::win::OSInfo::WOW64_ENABLED
);
54 irt_name
.append(FILE_PATH_LITERAL("x86_64"));
56 irt_name
.append(FILE_PATH_LITERAL("x86_32"));
58 #elif defined(ARCH_CPU_ARMEL)
59 irt_name
.append(FILE_PATH_LITERAL("arm"));
60 #elif defined(ARCH_CPU_MIPSEL)
61 irt_name
.append(FILE_PATH_LITERAL("mips32"));
63 #error Add support for your architecture to NaCl IRT file selection
65 irt_name
.append(FILE_PATH_LITERAL(".nexe"));
69 #if !defined(OS_ANDROID)
70 bool CheckEnvVar(const char* name
, bool default_value
) {
71 bool result
= default_value
;
72 const char* var
= getenv(name
);
73 if (var
&& strlen(var
) > 0) {
74 result
= var
[0] != '0';
80 void ReadCache(const base::FilePath
& filename
, std::string
* data
) {
81 if (!base::ReadFileToString(filename
, data
)) {
82 // Zero-size data used as an in-band error code.
87 void WriteCache(const base::FilePath
& filename
, const Pickle
* pickle
) {
88 base::WriteFile(filename
, static_cast<const char*>(pickle
->data()),
92 void RemoveCache(const base::FilePath
& filename
,
93 const base::Closure
& callback
) {
94 base::DeleteFile(filename
, false);
95 content::BrowserThread::PostTask(content::BrowserThread::IO
, FROM_HERE
,
99 void LogCacheQuery(nacl::NaClBrowser::ValidationCacheStatus status
) {
100 UMA_HISTOGRAM_ENUMERATION("NaCl.ValidationCache.Query", status
,
101 nacl::NaClBrowser::CACHE_MAX
);
104 void LogCacheSet(nacl::NaClBrowser::ValidationCacheStatus status
) {
105 // Bucket zero is reserved for future use.
106 UMA_HISTOGRAM_ENUMERATION("NaCl.ValidationCache.Set", status
,
107 nacl::NaClBrowser::CACHE_MAX
);
110 // Crash throttling parameters.
111 const size_t kMaxCrashesPerInterval
= 3;
112 const int64 kCrashesIntervalInSeconds
= 120;
118 base::File
OpenNaClReadExecImpl(const base::FilePath
& file_path
,
119 bool is_executable
) {
120 // Get a file descriptor. On Windows, we need 'GENERIC_EXECUTE' in order to
121 // memory map the executable.
122 // IMPORTANT: This file descriptor must not have write access - that could
123 // allow a NaCl inner sandbox escape.
124 uint32 flags
= base::File::FLAG_OPEN
| base::File::FLAG_READ
;
126 flags
|= base::File::FLAG_EXECUTE
; // Windows only flag.
127 base::File
file(file_path
, flags
);
131 // Check that the file does not reference a directory. Returning a descriptor
132 // to an extension directory could allow an outer sandbox escape. openat(...)
133 // could be used to traverse into the file system.
134 base::File::Info file_info
;
135 if (!file
.GetInfo(&file_info
) || file_info
.is_directory
)
141 NaClBrowser::NaClBrowser()
143 irt_state_(NaClResourceUninitialized
),
144 validation_cache_file_path_(),
145 validation_cache_is_enabled_(false),
146 validation_cache_is_modified_(false),
147 validation_cache_state_(NaClResourceUninitialized
),
148 path_cache_(kFilePathCacheSize
),
150 weak_factory_(this) {
151 #if !defined(OS_ANDROID)
152 validation_cache_is_enabled_
=
153 CheckEnvVar("NACL_VALIDATION_CACHE",
154 kValidationCacheEnabledByDefault
);
158 void NaClBrowser::SetDelegate(NaClBrowserDelegate
* delegate
) {
159 NaClBrowser
* nacl_browser
= NaClBrowser::GetInstance();
160 nacl_browser
->browser_delegate_
.reset(delegate
);
163 NaClBrowserDelegate
* NaClBrowser::GetDelegate() {
164 // The delegate is not owned by the IO thread. This accessor method can be
165 // called from other threads.
166 DCHECK(GetInstance()->browser_delegate_
.get() != NULL
);
167 return GetInstance()->browser_delegate_
.get();
170 void NaClBrowser::EarlyStartup() {
171 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO
));
173 InitValidationCacheFilePath();
176 NaClBrowser::~NaClBrowser() {
179 void NaClBrowser::InitIrtFilePath() {
180 // Allow the IRT library to be overridden via an environment
181 // variable. This allows the NaCl/Chromium integration bot to
182 // specify a newly-built IRT rather than using a prebuilt one
183 // downloaded via Chromium's DEPS file. We use the same environment
184 // variable that the standalone NaCl PPAPI plugin accepts.
185 const char* irt_path_var
= getenv("NACL_IRT_LIBRARY");
186 if (irt_path_var
!= NULL
) {
187 base::FilePath::StringType
path_string(
188 irt_path_var
, const_cast<const char*>(strchr(irt_path_var
, '\0')));
189 irt_filepath_
= base::FilePath(path_string
);
191 base::FilePath plugin_dir
;
192 if (!browser_delegate_
->GetPluginDirectory(&plugin_dir
)) {
193 DLOG(ERROR
) << "Failed to locate the plugins directory, NaCl disabled.";
197 irt_filepath_
= plugin_dir
.Append(NaClIrtName());
202 bool NaClBrowser::GetNaCl64ExePath(base::FilePath
* exe_path
) {
203 base::FilePath module_path
;
204 if (!PathService::Get(base::FILE_MODULE
, &module_path
)) {
205 LOG(ERROR
) << "NaCl process launch failed: could not resolve module";
208 *exe_path
= module_path
.DirName().Append(L
"nacl64");
213 NaClBrowser
* NaClBrowser::GetInstance() {
214 return Singleton
<NaClBrowser
>::get();
217 bool NaClBrowser::IsReady() const {
219 irt_state_
== NaClResourceReady
&&
220 validation_cache_state_
== NaClResourceReady
);
223 bool NaClBrowser::IsOk() const {
227 const base::File
& NaClBrowser::IrtFile() const {
228 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO
));
229 CHECK_EQ(irt_state_
, NaClResourceReady
);
230 CHECK(irt_file_
.IsValid());
234 void NaClBrowser::EnsureAllResourcesAvailable() {
235 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO
));
236 EnsureIrtAvailable();
237 EnsureValidationCacheAvailable();
240 // Load the IRT async.
241 void NaClBrowser::EnsureIrtAvailable() {
242 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO
));
243 if (IsOk() && irt_state_
== NaClResourceUninitialized
) {
244 irt_state_
= NaClResourceRequested
;
245 // TODO(ncbray) use blocking pool.
246 scoped_ptr
<base::FileProxy
> file_proxy(new base::FileProxy(
247 content::BrowserThread::GetMessageLoopProxyForThread(
248 content::BrowserThread::FILE).get()));
249 base::FileProxy
* proxy
= file_proxy
.get();
250 if (!proxy
->CreateOrOpen(irt_filepath_
,
251 base::File::FLAG_OPEN
| base::File::FLAG_READ
,
252 base::Bind(&NaClBrowser::OnIrtOpened
,
253 weak_factory_
.GetWeakPtr(),
254 Passed(&file_proxy
)))) {
255 LOG(ERROR
) << "Internal error, NaCl disabled.";
261 void NaClBrowser::OnIrtOpened(scoped_ptr
<base::FileProxy
> file_proxy
,
262 base::File::Error error_code
) {
263 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO
));
264 DCHECK_EQ(irt_state_
, NaClResourceRequested
);
265 if (file_proxy
->IsValid()) {
266 irt_file_
= file_proxy
->TakeFile();
268 LOG(ERROR
) << "Failed to open NaCl IRT file \""
269 << irt_filepath_
.LossyDisplayName()
270 << "\": " << error_code
;
273 irt_state_
= NaClResourceReady
;
277 void NaClBrowser::SetProcessGdbDebugStubPort(int process_id
, int port
) {
278 gdb_debug_stub_port_map_
[process_id
] = port
;
279 if (port
!= kGdbDebugStubPortUnknown
&&
280 !debug_stub_port_listener_
.is_null()) {
281 content::BrowserThread::PostTask(
282 content::BrowserThread::IO
,
284 base::Bind(debug_stub_port_listener_
, port
));
288 void NaClBrowser::SetGdbDebugStubPortListener(
289 base::Callback
<void(int)> listener
) {
290 debug_stub_port_listener_
= listener
;
293 void NaClBrowser::ClearGdbDebugStubPortListener() {
294 debug_stub_port_listener_
.Reset();
297 int NaClBrowser::GetProcessGdbDebugStubPort(int process_id
) {
298 GdbDebugStubPortMap::iterator i
= gdb_debug_stub_port_map_
.find(process_id
);
299 if (i
!= gdb_debug_stub_port_map_
.end()) {
302 return kGdbDebugStubPortUnused
;
305 void NaClBrowser::InitValidationCacheFilePath() {
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
309 // Start by finding the user data directory.
310 base::FilePath user_data_dir
;
311 if (!browser_delegate_
->GetUserDirectory(&user_data_dir
)) {
312 RunWithoutValidationCache();
315 // The cache directory may or may not be the user data directory.
316 base::FilePath cache_file_path
;
317 browser_delegate_
->GetCacheDirectory(&cache_file_path
);
318 // Append the base file name to the cache directory.
320 validation_cache_file_path_
=
321 cache_file_path
.Append(kValidationCacheFileName
);
324 void NaClBrowser::EnsureValidationCacheAvailable() {
325 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO
));
326 if (IsOk() && validation_cache_state_
== NaClResourceUninitialized
) {
327 if (ValidationCacheIsEnabled()) {
328 validation_cache_state_
= NaClResourceRequested
;
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
335 if (!content::BrowserThread::PostBlockingPoolTaskAndReply(
337 base::Bind(ReadCache
, validation_cache_file_path_
, data
),
338 base::Bind(&NaClBrowser::OnValidationCacheLoaded
,
339 weak_factory_
.GetWeakPtr(),
340 base::Owned(data
)))) {
341 RunWithoutValidationCache();
344 RunWithoutValidationCache();
349 void NaClBrowser::OnValidationCacheLoaded(const std::string
*data
) {
350 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO
));
351 // Did the cache get cleared before the load completed? If so, ignore the
353 if (validation_cache_state_
== NaClResourceReady
)
356 if (data
->size() == 0) {
358 validation_cache_
.Reset();
360 Pickle
pickle(data
->data(), data
->size());
361 validation_cache_
.Deserialize(&pickle
);
363 validation_cache_state_
= NaClResourceReady
;
367 void NaClBrowser::RunWithoutValidationCache() {
369 validation_cache_
.Reset();
370 validation_cache_is_enabled_
= false;
371 validation_cache_state_
= NaClResourceReady
;
375 void NaClBrowser::CheckWaiting() {
376 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO
));
377 if (!IsOk() || IsReady()) {
378 // Queue the waiting tasks into the message loop. This helps avoid
379 // re-entrancy problems that could occur if the closure was invoked
380 // directly. For example, this could result in use-after-free of the
382 for (std::vector
<base::Closure
>::iterator iter
= waiting_
.begin();
383 iter
!= waiting_
.end(); ++iter
) {
384 base::MessageLoop::current()->PostTask(FROM_HERE
, *iter
);
390 void NaClBrowser::MarkAsFailed() {
395 void NaClBrowser::WaitForResources(const base::Closure
& reply
) {
396 waiting_
.push_back(reply
);
397 EnsureAllResourcesAvailable();
401 const base::FilePath
& NaClBrowser::GetIrtFilePath() {
402 return irt_filepath_
;
405 void NaClBrowser::PutFilePath(const base::FilePath
& path
, uint64
* file_token_lo
,
406 uint64
* file_token_hi
) {
407 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO
));
409 uint64 file_token
[2] = {base::RandUint64(), base::RandUint64()};
410 // A zero file_token indicates there is no file_token, if we get zero, ask
411 // for another number.
412 if (file_token
[0] != 0 || file_token
[1] != 0) {
413 // If the file_token is in use, ask for another number.
414 std::string
key(reinterpret_cast<char*>(file_token
), sizeof(file_token
));
415 PathCacheType::iterator iter
= path_cache_
.Peek(key
);
416 if (iter
== path_cache_
.end()) {
417 path_cache_
.Put(key
, path
);
418 *file_token_lo
= file_token
[0];
419 *file_token_hi
= file_token
[1];
426 bool NaClBrowser::GetFilePath(uint64 file_token_lo
, uint64 file_token_hi
,
427 base::FilePath
* path
) {
428 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO
));
429 uint64 file_token
[2] = {file_token_lo
, file_token_hi
};
430 std::string
key(reinterpret_cast<char*>(file_token
), sizeof(file_token
));
431 PathCacheType::iterator iter
= path_cache_
.Peek(key
);
432 if (iter
== path_cache_
.end()) {
433 *path
= base::FilePath(FILE_PATH_LITERAL(""));
436 *path
= iter
->second
;
437 path_cache_
.Erase(iter
);
442 bool NaClBrowser::QueryKnownToValidate(const std::string
& signature
,
443 bool off_the_record
) {
444 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO
));
445 if (off_the_record
) {
446 // If we're off the record, don't reorder the main cache.
447 return validation_cache_
.QueryKnownToValidate(signature
, false) ||
448 off_the_record_validation_cache_
.QueryKnownToValidate(signature
, true);
450 bool result
= validation_cache_
.QueryKnownToValidate(signature
, true);
451 LogCacheQuery(result
? CACHE_HIT
: CACHE_MISS
);
452 // Queries can modify the MRU order of the cache.
453 MarkValidationCacheAsModified();
458 void NaClBrowser::SetKnownToValidate(const std::string
& signature
,
459 bool off_the_record
) {
460 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO
));
461 if (off_the_record
) {
462 off_the_record_validation_cache_
.SetKnownToValidate(signature
);
464 validation_cache_
.SetKnownToValidate(signature
);
465 // The number of sets should be equal to the number of cache misses, minus
466 // validation failures and successful validations where stubout occurs.
467 LogCacheSet(CACHE_HIT
);
468 MarkValidationCacheAsModified();
472 void NaClBrowser::ClearValidationCache(const base::Closure
& callback
) {
473 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO
));
474 // Note: this method may be called before EnsureValidationCacheAvailable has
475 // been invoked. In other words, this method may be called before any NaCl
476 // processes have been created. This method must succeed and invoke the
477 // callback in such a case. If it does not invoke the callback, Chrome's UI
478 // will hang in that case.
479 validation_cache_
.Reset();
480 off_the_record_validation_cache_
.Reset();
482 if (validation_cache_file_path_
.empty()) {
483 // Can't figure out what file to remove, but don't drop the callback.
484 content::BrowserThread::PostTask(content::BrowserThread::IO
, FROM_HERE
,
487 // Delegate the removal of the cache from the filesystem to another thread
488 // to avoid blocking the IO thread.
489 // This task is dispatched immediately, not delayed and coalesced, because
490 // the user interface for cache clearing is likely waiting for the callback.
491 // In addition, we need to make sure the cache is actually cleared before
492 // invoking the callback to meet the implicit guarantees of the UI.
493 content::BrowserThread::PostBlockingPoolSequencedTask(
494 kValidationCacheSequenceName
,
496 base::Bind(RemoveCache
, validation_cache_file_path_
, callback
));
499 // Make sure any delayed tasks to persist the cache to the filesystem are
501 validation_cache_is_modified_
= false;
503 // If the cache is cleared before it is loaded from the filesystem, act as if
504 // we just loaded an empty cache.
505 if (validation_cache_state_
!= NaClResourceReady
) {
506 validation_cache_state_
= NaClResourceReady
;
511 void NaClBrowser::MarkValidationCacheAsModified() {
512 if (!validation_cache_is_modified_
) {
513 // Wait before persisting to disk. This can coalesce multiple cache
514 // modifications info a single disk write.
515 base::MessageLoop::current()->PostDelayedTask(
517 base::Bind(&NaClBrowser::PersistValidationCache
,
518 weak_factory_
.GetWeakPtr()),
519 base::TimeDelta::FromMilliseconds(kValidationCacheCoalescingTimeMS
));
520 validation_cache_is_modified_
= true;
524 void NaClBrowser::PersistValidationCache() {
525 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO
));
526 // validation_cache_is_modified_ may be false if the cache was cleared while
527 // this delayed task was pending.
528 // validation_cache_file_path_ may be empty if something went wrong during
530 if (validation_cache_is_modified_
&& !validation_cache_file_path_
.empty()) {
531 Pickle
* pickle
= new Pickle();
532 validation_cache_
.Serialize(pickle
);
534 // Pass the serialized data to another thread to write to disk. File IO is
535 // not allowed on the IO thread (which is the thread this method runs on)
536 // because it can degrade the responsiveness of the browser.
537 // The task is sequenced so that multiple writes happen in order.
538 content::BrowserThread::PostBlockingPoolSequencedTask(
539 kValidationCacheSequenceName
,
541 base::Bind(WriteCache
, validation_cache_file_path_
,
542 base::Owned(pickle
)));
544 validation_cache_is_modified_
= false;
547 void NaClBrowser::OnProcessEnd(int process_id
) {
548 gdb_debug_stub_port_map_
.erase(process_id
);
551 void NaClBrowser::OnProcessCrashed() {
552 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO
));
553 if (crash_times_
.size() == kMaxCrashesPerInterval
) {
554 crash_times_
.pop_front();
556 base::Time time
= base::Time::Now();
557 crash_times_
.push_back(time
);
560 bool NaClBrowser::IsThrottled() {
561 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO
));
562 if (crash_times_
.size() != kMaxCrashesPerInterval
) {
565 base::TimeDelta delta
= base::Time::Now() - crash_times_
.front();
566 return delta
.InSeconds() <= kCrashesIntervalInSeconds
;