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.
4
5 #include "components/nacl/browser/nacl_browser.h"
6
7 #include "base/command_line.h"
8 #include "base/file_util.h"
9 #include "base/files/file_proxy.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"
19 #include "url/gurl.h"
20
21 namespace {
22
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");
28
29 const bool kValidationCacheEnabledByDefault = true;
30
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;
40
NaClIrtName()41 const base::FilePath::StringType NaClIrtName() {
42 base::FilePath::StringType irt_name(FILE_PATH_LITERAL("nacl_irt_"));
43
44 #if defined(ARCH_CPU_X86_FAMILY)
45 #if defined(ARCH_CPU_X86_64)
46 bool is64 = true;
47 #elif defined(OS_WIN)
48 bool is64 = (base::win::OSInfo::GetInstance()->wow64_status() ==
49 base::win::OSInfo::WOW64_ENABLED);
50 #else
51 bool is64 = false;
52 #endif
53 if (is64)
54 irt_name.append(FILE_PATH_LITERAL("x86_64"));
55 else
56 irt_name.append(FILE_PATH_LITERAL("x86_32"));
57
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"));
62 #else
63 #error Add support for your architecture to NaCl IRT file selection
64 #endif
65 irt_name.append(FILE_PATH_LITERAL(".nexe"));
66 return irt_name;
67 }
68
CheckEnvVar(const char * name,bool default_value)69 bool CheckEnvVar(const char* name, bool default_value) {
70 bool result = default_value;
71 const char* var = getenv(name);
72 if (var && strlen(var) > 0) {
73 result = var[0] != '0';
74 }
75 return result;
76 }
77
ReadCache(const base::FilePath & filename,std::string * data)78 void ReadCache(const base::FilePath& filename, std::string* data) {
79 if (!base::ReadFileToString(filename, data)) {
80 // Zero-size data used as an in-band error code.
81 data->clear();
82 }
83 }
84
WriteCache(const base::FilePath & filename,const Pickle * pickle)85 void WriteCache(const base::FilePath& filename, const Pickle* pickle) {
86 base::WriteFile(filename, static_cast<const char*>(pickle->data()),
87 pickle->size());
88 }
89
RemoveCache(const base::FilePath & filename,const base::Closure & callback)90 void RemoveCache(const base::FilePath& filename,
91 const base::Closure& callback) {
92 base::DeleteFile(filename, false);
93 content::BrowserThread::PostTask(content::BrowserThread::IO, FROM_HERE,
94 callback);
95 }
96
LogCacheQuery(nacl::NaClBrowser::ValidationCacheStatus status)97 void LogCacheQuery(nacl::NaClBrowser::ValidationCacheStatus status) {
98 UMA_HISTOGRAM_ENUMERATION("NaCl.ValidationCache.Query", status,
99 nacl::NaClBrowser::CACHE_MAX);
100 }
101
LogCacheSet(nacl::NaClBrowser::ValidationCacheStatus status)102 void LogCacheSet(nacl::NaClBrowser::ValidationCacheStatus status) {
103 // Bucket zero is reserved for future use.
104 UMA_HISTOGRAM_ENUMERATION("NaCl.ValidationCache.Set", status,
105 nacl::NaClBrowser::CACHE_MAX);
106 }
107
108 // Crash throttling parameters.
109 const size_t kMaxCrashesPerInterval = 3;
110 const int64 kCrashesIntervalInSeconds = 120;
111
112 } // namespace
113
114 namespace nacl {
115
OpenNaClExecutableImpl(const base::FilePath & file_path)116 base::File OpenNaClExecutableImpl(const base::FilePath& file_path) {
117 // Get a file descriptor. On Windows, we need 'GENERIC_EXECUTE' in order to
118 // memory map the executable.
119 // IMPORTANT: This file descriptor must not have write access - that could
120 // allow a NaCl inner sandbox escape.
121 base::File file(file_path,
122 (base::File::FLAG_OPEN |
123 base::File::FLAG_READ |
124 base::File::FLAG_EXECUTE)); // Windows only flag.
125 if (!file.IsValid())
126 return file.Pass();
127
128 // Check that the file does not reference a directory. Returning a descriptor
129 // to an extension directory could allow an outer sandbox escape. openat(...)
130 // could be used to traverse into the file system.
131 base::File::Info file_info;
132 if (!file.GetInfo(&file_info) || file_info.is_directory)
133 return base::File();
134
135 return file.Pass();
136 }
137
NaClBrowser()138 NaClBrowser::NaClBrowser()
139 : weak_factory_(this),
140 irt_filepath_(),
141 irt_state_(NaClResourceUninitialized),
142 validation_cache_file_path_(),
143 validation_cache_is_enabled_(
144 CheckEnvVar("NACL_VALIDATION_CACHE",
145 kValidationCacheEnabledByDefault)),
146 validation_cache_is_modified_(false),
147 validation_cache_state_(NaClResourceUninitialized),
148 path_cache_(kFilePathCacheSize),
149 ok_(true) {
150 }
151
SetDelegate(NaClBrowserDelegate * delegate)152 void NaClBrowser::SetDelegate(NaClBrowserDelegate* delegate) {
153 NaClBrowser* nacl_browser = NaClBrowser::GetInstance();
154 nacl_browser->browser_delegate_.reset(delegate);
155 }
156
GetDelegate()157 NaClBrowserDelegate* NaClBrowser::GetDelegate() {
158 // The delegate is not owned by the IO thread. This accessor method can be
159 // called from other threads.
160 DCHECK(GetInstance()->browser_delegate_.get() != NULL);
161 return GetInstance()->browser_delegate_.get();
162 }
163
EarlyStartup()164 void NaClBrowser::EarlyStartup() {
165 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
166 InitIrtFilePath();
167 InitValidationCacheFilePath();
168 }
169
~NaClBrowser()170 NaClBrowser::~NaClBrowser() {
171 }
172
InitIrtFilePath()173 void NaClBrowser::InitIrtFilePath() {
174 // Allow the IRT library to be overridden via an environment
175 // variable. This allows the NaCl/Chromium integration bot to
176 // specify a newly-built IRT rather than using a prebuilt one
177 // downloaded via Chromium's DEPS file. We use the same environment
178 // variable that the standalone NaCl PPAPI plugin accepts.
179 const char* irt_path_var = getenv("NACL_IRT_LIBRARY");
180 if (irt_path_var != NULL) {
181 base::FilePath::StringType path_string(
182 irt_path_var, const_cast<const char*>(strchr(irt_path_var, '\0')));
183 irt_filepath_ = base::FilePath(path_string);
184 } else {
185 base::FilePath plugin_dir;
186 if (!browser_delegate_->GetPluginDirectory(&plugin_dir)) {
187 DLOG(ERROR) << "Failed to locate the plugins directory, NaCl disabled.";
188 MarkAsFailed();
189 return;
190 }
191 irt_filepath_ = plugin_dir.Append(NaClIrtName());
192 }
193 }
194
195 #if defined(OS_WIN)
GetNaCl64ExePath(base::FilePath * exe_path)196 bool NaClBrowser::GetNaCl64ExePath(base::FilePath* exe_path) {
197 base::FilePath module_path;
198 if (!PathService::Get(base::FILE_MODULE, &module_path)) {
199 LOG(ERROR) << "NaCl process launch failed: could not resolve module";
200 return false;
201 }
202 *exe_path = module_path.DirName().Append(L"nacl64");
203 return true;
204 }
205 #endif
206
GetInstance()207 NaClBrowser* NaClBrowser::GetInstance() {
208 return Singleton<NaClBrowser>::get();
209 }
210
IsReady() const211 bool NaClBrowser::IsReady() const {
212 return (IsOk() &&
213 irt_state_ == NaClResourceReady &&
214 validation_cache_state_ == NaClResourceReady);
215 }
216
IsOk() const217 bool NaClBrowser::IsOk() const {
218 return ok_;
219 }
220
IrtFile() const221 const base::File& NaClBrowser::IrtFile() const {
222 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
223 CHECK_EQ(irt_state_, NaClResourceReady);
224 CHECK(irt_file_.IsValid());
225 return irt_file_;
226 }
227
EnsureAllResourcesAvailable()228 void NaClBrowser::EnsureAllResourcesAvailable() {
229 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
230 EnsureIrtAvailable();
231 EnsureValidationCacheAvailable();
232 }
233
234 // Load the IRT async.
EnsureIrtAvailable()235 void NaClBrowser::EnsureIrtAvailable() {
236 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
237 if (IsOk() && irt_state_ == NaClResourceUninitialized) {
238 irt_state_ = NaClResourceRequested;
239 // TODO(ncbray) use blocking pool.
240 scoped_ptr<base::FileProxy> file_proxy(new base::FileProxy(
241 content::BrowserThread::GetMessageLoopProxyForThread(
242 content::BrowserThread::FILE).get()));
243 base::FileProxy* proxy = file_proxy.get();
244 if (!proxy->CreateOrOpen(irt_filepath_,
245 base::File::FLAG_OPEN | base::File::FLAG_READ,
246 base::Bind(&NaClBrowser::OnIrtOpened,
247 weak_factory_.GetWeakPtr(),
248 Passed(&file_proxy)))) {
249 LOG(ERROR) << "Internal error, NaCl disabled.";
250 MarkAsFailed();
251 }
252 }
253 }
254
OnIrtOpened(scoped_ptr<base::FileProxy> file_proxy,base::File::Error error_code)255 void NaClBrowser::OnIrtOpened(scoped_ptr<base::FileProxy> file_proxy,
256 base::File::Error error_code) {
257 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
258 DCHECK_EQ(irt_state_, NaClResourceRequested);
259 if (file_proxy->IsValid()) {
260 irt_file_ = file_proxy->TakeFile();
261 } else {
262 LOG(ERROR) << "Failed to open NaCl IRT file \""
263 << irt_filepath_.LossyDisplayName()
264 << "\": " << error_code;
265 MarkAsFailed();
266 }
267 irt_state_ = NaClResourceReady;
268 CheckWaiting();
269 }
270
SetProcessGdbDebugStubPort(int process_id,int port)271 void NaClBrowser::SetProcessGdbDebugStubPort(int process_id, int port) {
272 gdb_debug_stub_port_map_[process_id] = port;
273 if (port != kGdbDebugStubPortUnknown &&
274 !debug_stub_port_listener_.is_null()) {
275 content::BrowserThread::PostTask(
276 content::BrowserThread::IO,
277 FROM_HERE,
278 base::Bind(debug_stub_port_listener_, port));
279 }
280 }
281
SetGdbDebugStubPortListener(base::Callback<void (int)> listener)282 void NaClBrowser::SetGdbDebugStubPortListener(
283 base::Callback<void(int)> listener) {
284 debug_stub_port_listener_ = listener;
285 }
286
ClearGdbDebugStubPortListener()287 void NaClBrowser::ClearGdbDebugStubPortListener() {
288 debug_stub_port_listener_.Reset();
289 }
290
GetProcessGdbDebugStubPort(int process_id)291 int NaClBrowser::GetProcessGdbDebugStubPort(int process_id) {
292 GdbDebugStubPortMap::iterator i = gdb_debug_stub_port_map_.find(process_id);
293 if (i != gdb_debug_stub_port_map_.end()) {
294 return i->second;
295 }
296 return kGdbDebugStubPortUnused;
297 }
298
InitValidationCacheFilePath()299 void NaClBrowser::InitValidationCacheFilePath() {
300 // Determine where the validation cache resides in the file system. It
301 // exists in Chrome's cache directory and is not tied to any specific
302 // profile.
303 // Start by finding the user data directory.
304 base::FilePath user_data_dir;
305 if (!browser_delegate_->GetUserDirectory(&user_data_dir)) {
306 RunWithoutValidationCache();
307 return;
308 }
309 // The cache directory may or may not be the user data directory.
310 base::FilePath cache_file_path;
311 browser_delegate_->GetCacheDirectory(&cache_file_path);
312 // Append the base file name to the cache directory.
313
314 validation_cache_file_path_ =
315 cache_file_path.Append(kValidationCacheFileName);
316 }
317
EnsureValidationCacheAvailable()318 void NaClBrowser::EnsureValidationCacheAvailable() {
319 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
320 if (IsOk() && validation_cache_state_ == NaClResourceUninitialized) {
321 if (ValidationCacheIsEnabled()) {
322 validation_cache_state_ = NaClResourceRequested;
323
324 // Structure for carrying data between the callbacks.
325 std::string* data = new std::string();
326 // We can get away not giving this a sequence ID because this is the first
327 // task and further file access will not occur until after we get a
328 // response.
329 if (!content::BrowserThread::PostBlockingPoolTaskAndReply(
330 FROM_HERE,
331 base::Bind(ReadCache, validation_cache_file_path_, data),
332 base::Bind(&NaClBrowser::OnValidationCacheLoaded,
333 weak_factory_.GetWeakPtr(),
334 base::Owned(data)))) {
335 RunWithoutValidationCache();
336 }
337 } else {
338 RunWithoutValidationCache();
339 }
340 }
341 }
342
OnValidationCacheLoaded(const std::string * data)343 void NaClBrowser::OnValidationCacheLoaded(const std::string *data) {
344 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
345 // Did the cache get cleared before the load completed? If so, ignore the
346 // incoming data.
347 if (validation_cache_state_ == NaClResourceReady)
348 return;
349
350 if (data->size() == 0) {
351 // No file found.
352 validation_cache_.Reset();
353 } else {
354 Pickle pickle(data->data(), data->size());
355 validation_cache_.Deserialize(&pickle);
356 }
357 validation_cache_state_ = NaClResourceReady;
358 CheckWaiting();
359 }
360
RunWithoutValidationCache()361 void NaClBrowser::RunWithoutValidationCache() {
362 // Be paranoid.
363 validation_cache_.Reset();
364 validation_cache_is_enabled_ = false;
365 validation_cache_state_ = NaClResourceReady;
366 CheckWaiting();
367 }
368
CheckWaiting()369 void NaClBrowser::CheckWaiting() {
370 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
371 if (!IsOk() || IsReady()) {
372 // Queue the waiting tasks into the message loop. This helps avoid
373 // re-entrancy problems that could occur if the closure was invoked
374 // directly. For example, this could result in use-after-free of the
375 // process host.
376 for (std::vector<base::Closure>::iterator iter = waiting_.begin();
377 iter != waiting_.end(); ++iter) {
378 base::MessageLoop::current()->PostTask(FROM_HERE, *iter);
379 }
380 waiting_.clear();
381 }
382 }
383
MarkAsFailed()384 void NaClBrowser::MarkAsFailed() {
385 ok_ = false;
386 CheckWaiting();
387 }
388
WaitForResources(const base::Closure & reply)389 void NaClBrowser::WaitForResources(const base::Closure& reply) {
390 waiting_.push_back(reply);
391 EnsureAllResourcesAvailable();
392 CheckWaiting();
393 }
394
GetIrtFilePath()395 const base::FilePath& NaClBrowser::GetIrtFilePath() {
396 return irt_filepath_;
397 }
398
PutFilePath(const base::FilePath & path,uint64 * file_token_lo,uint64 * file_token_hi)399 void NaClBrowser::PutFilePath(const base::FilePath& path, uint64* file_token_lo,
400 uint64* file_token_hi) {
401 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
402 while (true) {
403 uint64 file_token[2] = {base::RandUint64(), base::RandUint64()};
404 // A zero file_token indicates there is no file_token, if we get zero, ask
405 // for another number.
406 if (file_token[0] != 0 || file_token[1] != 0) {
407 // If the file_token is in use, ask for another number.
408 std::string key(reinterpret_cast<char*>(file_token), sizeof(file_token));
409 PathCacheType::iterator iter = path_cache_.Peek(key);
410 if (iter == path_cache_.end()) {
411 path_cache_.Put(key, path);
412 *file_token_lo = file_token[0];
413 *file_token_hi = file_token[1];
414 break;
415 }
416 }
417 }
418 }
419
GetFilePath(uint64 file_token_lo,uint64 file_token_hi,base::FilePath * path)420 bool NaClBrowser::GetFilePath(uint64 file_token_lo, uint64 file_token_hi,
421 base::FilePath* path) {
422 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
423 uint64 file_token[2] = {file_token_lo, file_token_hi};
424 std::string key(reinterpret_cast<char*>(file_token), sizeof(file_token));
425 PathCacheType::iterator iter = path_cache_.Peek(key);
426 if (iter == path_cache_.end()) {
427 *path = base::FilePath(FILE_PATH_LITERAL(""));
428 return false;
429 }
430 *path = iter->second;
431 path_cache_.Erase(iter);
432 return true;
433 }
434
435
QueryKnownToValidate(const std::string & signature,bool off_the_record)436 bool NaClBrowser::QueryKnownToValidate(const std::string& signature,
437 bool off_the_record) {
438 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
439 if (off_the_record) {
440 // If we're off the record, don't reorder the main cache.
441 return validation_cache_.QueryKnownToValidate(signature, false) ||
442 off_the_record_validation_cache_.QueryKnownToValidate(signature, true);
443 } else {
444 bool result = validation_cache_.QueryKnownToValidate(signature, true);
445 LogCacheQuery(result ? CACHE_HIT : CACHE_MISS);
446 // Queries can modify the MRU order of the cache.
447 MarkValidationCacheAsModified();
448 return result;
449 }
450 }
451
SetKnownToValidate(const std::string & signature,bool off_the_record)452 void NaClBrowser::SetKnownToValidate(const std::string& signature,
453 bool off_the_record) {
454 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
455 if (off_the_record) {
456 off_the_record_validation_cache_.SetKnownToValidate(signature);
457 } else {
458 validation_cache_.SetKnownToValidate(signature);
459 // The number of sets should be equal to the number of cache misses, minus
460 // validation failures and successful validations where stubout occurs.
461 LogCacheSet(CACHE_HIT);
462 MarkValidationCacheAsModified();
463 }
464 }
465
ClearValidationCache(const base::Closure & callback)466 void NaClBrowser::ClearValidationCache(const base::Closure& callback) {
467 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
468 // Note: this method may be called before EnsureValidationCacheAvailable has
469 // been invoked. In other words, this method may be called before any NaCl
470 // processes have been created. This method must succeed and invoke the
471 // callback in such a case. If it does not invoke the callback, Chrome's UI
472 // will hang in that case.
473 validation_cache_.Reset();
474 off_the_record_validation_cache_.Reset();
475
476 if (validation_cache_file_path_.empty()) {
477 // Can't figure out what file to remove, but don't drop the callback.
478 content::BrowserThread::PostTask(content::BrowserThread::IO, FROM_HERE,
479 callback);
480 } else {
481 // Delegate the removal of the cache from the filesystem to another thread
482 // to avoid blocking the IO thread.
483 // This task is dispatched immediately, not delayed and coalesced, because
484 // the user interface for cache clearing is likely waiting for the callback.
485 // In addition, we need to make sure the cache is actually cleared before
486 // invoking the callback to meet the implicit guarantees of the UI.
487 content::BrowserThread::PostBlockingPoolSequencedTask(
488 kValidationCacheSequenceName,
489 FROM_HERE,
490 base::Bind(RemoveCache, validation_cache_file_path_, callback));
491 }
492
493 // Make sure any delayed tasks to persist the cache to the filesystem are
494 // squelched.
495 validation_cache_is_modified_ = false;
496
497 // If the cache is cleared before it is loaded from the filesystem, act as if
498 // we just loaded an empty cache.
499 if (validation_cache_state_ != NaClResourceReady) {
500 validation_cache_state_ = NaClResourceReady;
501 CheckWaiting();
502 }
503 }
504
MarkValidationCacheAsModified()505 void NaClBrowser::MarkValidationCacheAsModified() {
506 if (!validation_cache_is_modified_) {
507 // Wait before persisting to disk. This can coalesce multiple cache
508 // modifications info a single disk write.
509 base::MessageLoop::current()->PostDelayedTask(
510 FROM_HERE,
511 base::Bind(&NaClBrowser::PersistValidationCache,
512 weak_factory_.GetWeakPtr()),
513 base::TimeDelta::FromMilliseconds(kValidationCacheCoalescingTimeMS));
514 validation_cache_is_modified_ = true;
515 }
516 }
517
PersistValidationCache()518 void NaClBrowser::PersistValidationCache() {
519 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
520 // validation_cache_is_modified_ may be false if the cache was cleared while
521 // this delayed task was pending.
522 // validation_cache_file_path_ may be empty if something went wrong during
523 // initialization.
524 if (validation_cache_is_modified_ && !validation_cache_file_path_.empty()) {
525 Pickle* pickle = new Pickle();
526 validation_cache_.Serialize(pickle);
527
528 // Pass the serialized data to another thread to write to disk. File IO is
529 // not allowed on the IO thread (which is the thread this method runs on)
530 // because it can degrade the responsiveness of the browser.
531 // The task is sequenced so that multiple writes happen in order.
532 content::BrowserThread::PostBlockingPoolSequencedTask(
533 kValidationCacheSequenceName,
534 FROM_HERE,
535 base::Bind(WriteCache, validation_cache_file_path_,
536 base::Owned(pickle)));
537 }
538 validation_cache_is_modified_ = false;
539 }
540
OnProcessEnd(int process_id)541 void NaClBrowser::OnProcessEnd(int process_id) {
542 gdb_debug_stub_port_map_.erase(process_id);
543 }
544
OnProcessCrashed()545 void NaClBrowser::OnProcessCrashed() {
546 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
547 if (crash_times_.size() == kMaxCrashesPerInterval) {
548 crash_times_.pop_front();
549 }
550 base::Time time = base::Time::Now();
551 crash_times_.push_back(time);
552 }
553
IsThrottled()554 bool NaClBrowser::IsThrottled() {
555 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
556 if (crash_times_.size() != kMaxCrashesPerInterval) {
557 return false;
558 }
559 base::TimeDelta delta = base::Time::Now() - crash_times_.front();
560 return delta.InSeconds() <= kCrashesIntervalInSeconds;
561 }
562
563 } // namespace nacl
564