1// Copyright (c) 2011 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 "content/common/plugin_list.h" 6 7#include <algorithm> 8#include <dlfcn.h> 9#if defined(OS_OPENBSD) 10#include <sys/exec_elf.h> 11#else 12#include <elf.h> 13#endif 14#include <fcntl.h> 15#include <sys/stat.h> 16#include <sys/types.h> 17#include <unistd.h> 18 19#include "base/cpu.h" 20#include "base/file_util.h" 21#include "base/files/file_enumerator.h" 22#include "base/native_library.h" 23#include "base/path_service.h" 24#include "base/posix/eintr_wrapper.h" 25#include "base/sha1.h" 26#include "base/strings/string_split.h" 27#include "base/strings/string_util.h" 28#include "base/strings/stringprintf.h" 29#include "base/strings/sys_string_conversions.h" 30#include "base/strings/utf_string_conversions.h" 31#include "build/build_config.h" 32#include "third_party/npapi/bindings/nphostapi.h" 33 34// These headers must be included in this order to make the declaration gods 35// happy. 36#include "base/third_party/nspr/prcpucfg_linux.h" 37 38namespace content { 39 40namespace { 41 42// We build up a list of files and mtimes so we can sort them. 43typedef std::pair<base::FilePath, base::Time> FileAndTime; 44typedef std::vector<FileAndTime> FileTimeList; 45 46enum PluginQuirk { 47 // No quirks - plugin is outright banned. 48 PLUGIN_QUIRK_NONE = 0, 49 // Plugin is using SSE2 instructions without checking for SSE2 instruction 50 // support. Ban the plugin if the system has no SSE2 support. 51 PLUGIN_QUIRK_MISSING_SSE2_CHECK = 1 << 0, 52}; 53 54// Copied from nsplugindefs.h instead of including the file since it has a bunch 55// of dependencies. 56enum nsPluginVariable { 57 nsPluginVariable_NameString = 1, 58 nsPluginVariable_DescriptionString = 2 59}; 60 61// Comparator used to sort by descending mtime then ascending filename. 62bool CompareTime(const FileAndTime& a, const FileAndTime& b) { 63 if (a.second == b.second) { 64 // Fall back on filename sorting, just to make the predicate valid. 65 return a.first < b.first; 66 } 67 68 // Sort by mtime, descending. 69 return a.second > b.second; 70} 71 72// Checks to see if the current environment meets any of the condtions set in 73// |quirks|. Returns true if any of the conditions are met, or if |quirks| is 74// PLUGIN_QUIRK_NONE. 75bool CheckQuirks(PluginQuirk quirks) { 76 if (quirks == PLUGIN_QUIRK_NONE) 77 return true; 78 79 if ((quirks & PLUGIN_QUIRK_MISSING_SSE2_CHECK) != 0) { 80 base::CPU cpu; 81 if (!cpu.has_sse2()) 82 return true; 83 } 84 85 return false; 86} 87 88// Return true if |path| matches a known (file size, sha1sum) pair. 89// Also check against any PluginQuirks the bad plugin may have. 90// The use of the file size is an optimization so we don't have to read in 91// the entire file unless we have to. 92bool IsBlacklistedBySha1sumAndQuirks(const base::FilePath& path) { 93 const struct BadEntry { 94 int64 size; 95 std::string sha1; 96 PluginQuirk quirks; 97 } bad_entries[] = { 98 // Flash 9 r31 - http://crbug.com/29237 99 { 7040080, "fa5803061125ca47846713b34a26a42f1f1e98bb", PLUGIN_QUIRK_NONE }, 100 // Flash 9 r48 - http://crbug.com/29237 101 { 7040036, "0c4b3768a6d4bfba003088e4b9090d381de1af2b", PLUGIN_QUIRK_NONE }, 102 // Flash 11.2.202.236, 32-bit - http://crbug.com/140086 103 { 17406436, "1e07eac912faf9426c52a288c76c3b6238f90b6b", 104 PLUGIN_QUIRK_MISSING_SSE2_CHECK }, 105 // Flash 11.2.202.238, 32-bit - http://crbug.com/140086 106 { 17410532, "e9401097e97c8443a7d9156be62184ffe1addd5c", 107 PLUGIN_QUIRK_MISSING_SSE2_CHECK }, 108 }; 109 110 int64 size; 111 if (!base::GetFileSize(path, &size)) 112 return false; 113 for (size_t i = 0; i < ARRAYSIZE_UNSAFE(bad_entries); i++) { 114 if (bad_entries[i].size != size) 115 continue; 116 117 std::string file_content; 118 if (!base::ReadFileToString(path, &file_content)) 119 continue; 120 std::string sha1 = base::SHA1HashString(file_content); 121 std::string sha1_readable; 122 for (size_t j = 0; j < sha1.size(); j++) 123 base::StringAppendF(&sha1_readable, "%02x", sha1[j] & 0xFF); 124 if (bad_entries[i].sha1 == sha1_readable) 125 return CheckQuirks(bad_entries[i].quirks); 126 } 127 return false; 128} 129 130// Some plugins are shells around other plugins; we prefer to use the 131// real plugin directly, if it's available. This function returns 132// true if we should prefer other plugins over this one. We'll still 133// use a "undesirable" plugin if no other option is available. 134bool IsUndesirablePlugin(const WebPluginInfo& info) { 135 std::string filename = info.path.BaseName().value(); 136 const char* kUndesiredPlugins[] = { 137 "npcxoffice", // Crossover 138 "npwrapper", // nspluginwrapper 139 }; 140 for (size_t i = 0; i < arraysize(kUndesiredPlugins); i++) { 141 if (filename.find(kUndesiredPlugins[i]) != std::string::npos) { 142 return true; 143 } 144 } 145 return false; 146} 147 148// Return true if we shouldn't load a plugin at all. 149// This is an ugly hack to blacklist Adobe Acrobat due to not supporting 150// its Xt-based mainloop. 151// http://code.google.com/p/chromium/issues/detail?id=38229 152bool IsBlacklistedPlugin(const base::FilePath& path) { 153 const char* kBlackListedPlugins[] = { 154 "nppdf.so", // Adobe PDF 155 }; 156 std::string filename = path.BaseName().value(); 157 for (size_t i = 0; i < arraysize(kBlackListedPlugins); i++) { 158 if (filename.find(kBlackListedPlugins[i]) != std::string::npos) { 159 return true; 160 } 161 } 162 return IsBlacklistedBySha1sumAndQuirks(path); 163} 164 165// Read the ELF header and return true if it is usable on 166// the current architecture (e.g. 32-bit ELF on 32-bit build). 167// Returns false on other errors as well. 168bool ELFMatchesCurrentArchitecture(const base::FilePath& filename) { 169 // First make sure we can open the file and it is in fact, a regular file. 170 struct stat stat_buf; 171 // Open with O_NONBLOCK so we don't block on pipes. 172 int fd = open(filename.value().c_str(), O_RDONLY|O_NONBLOCK); 173 if (fd < 0) 174 return false; 175 bool ret = (fstat(fd, &stat_buf) >= 0 && S_ISREG(stat_buf.st_mode)); 176 if (IGNORE_EINTR(close(fd)) < 0) 177 return false; 178 if (!ret) 179 return false; 180 181 const size_t kELFBufferSize = 5; 182 char buffer[kELFBufferSize]; 183 if (!base::ReadFile(filename, buffer, kELFBufferSize)) 184 return false; 185 186 if (buffer[0] != ELFMAG0 || 187 buffer[1] != ELFMAG1 || 188 buffer[2] != ELFMAG2 || 189 buffer[3] != ELFMAG3) { 190 // Not an ELF file, perhaps? 191 return false; 192 } 193 194 int elf_class = buffer[EI_CLASS]; 195#if defined(ARCH_CPU_32_BITS) 196 if (elf_class == ELFCLASS32) 197 return true; 198#elif defined(ARCH_CPU_64_BITS) 199 if (elf_class == ELFCLASS64) 200 return true; 201#endif 202 203 return false; 204} 205 206// This structure matches enough of nspluginwrapper's NPW_PluginInfo 207// for us to extract the real plugin path. 208struct __attribute__((packed)) NSPluginWrapperInfo { 209 char ident[32]; // NSPluginWrapper magic identifier (includes version). 210 char path[PATH_MAX]; // Path to wrapped plugin. 211}; 212 213// Test a plugin for whether it's been wrapped by NSPluginWrapper, and 214// if so attempt to unwrap it. Pass in an opened plugin handle; on 215// success, |dl| and |unwrapped_path| will be filled in with the newly 216// opened plugin. On failure, params are left unmodified. 217void UnwrapNSPluginWrapper(void **dl, base::FilePath* unwrapped_path) { 218 NSPluginWrapperInfo* info = 219 reinterpret_cast<NSPluginWrapperInfo*>(dlsym(*dl, "NPW_Plugin")); 220 if (!info) 221 return; // Not a NSPW plugin. 222 223 // Here we could check the NSPW ident field for the versioning 224 // information, but the path field is available in all versions 225 // anyway. 226 227 // Grab the path to the wrapped plugin. Just in case the structure 228 // format changes, protect against the path not being null-terminated. 229 char* path_end = static_cast<char*>(memchr(info->path, '\0', 230 sizeof(info->path))); 231 if (!path_end) 232 path_end = info->path + sizeof(info->path); 233 base::FilePath path = base::FilePath( 234 std::string(info->path, path_end - info->path)); 235 236 if (!ELFMatchesCurrentArchitecture(path)) { 237 LOG(WARNING) << path.value() << " is nspluginwrapper wrapping a " 238 << "plugin for a different architecture; it will " 239 << "work better if you instead use a native plugin."; 240 return; 241 } 242 243 std::string error; 244 void* newdl = base::LoadNativeLibrary(path, &error); 245 if (!newdl) { 246 // We couldn't load the unwrapped plugin for some reason, despite 247 // being able to load the wrapped one. Just use the wrapped one. 248 LOG_IF(ERROR, PluginList::DebugPluginLoading()) 249 << "Could not use unwrapped nspluginwrapper plugin " 250 << unwrapped_path->value() << " (" << error << "), " 251 << "using the wrapped one."; 252 return; 253 } 254 255 // Unload the wrapped plugin, and use the wrapped plugin instead. 256 LOG_IF(ERROR, PluginList::DebugPluginLoading()) 257 << "Using unwrapped version " << unwrapped_path->value() 258 << " of nspluginwrapper-wrapped plugin."; 259 base::UnloadNativeLibrary(*dl); 260 *dl = newdl; 261 *unwrapped_path = path; 262} 263 264} // namespace 265 266bool PluginList::ReadWebPluginInfo(const base::FilePath& filename, 267 WebPluginInfo* info) { 268 // The file to reference is: 269 // http://mxr.mozilla.org/firefox/source/modules/plugin/base/src/nsPluginsDirUnix.cpp 270 271 // Skip files that aren't appropriate for our architecture. 272 if (!ELFMatchesCurrentArchitecture(filename)) { 273 LOG_IF(ERROR, PluginList::DebugPluginLoading()) 274 << "Skipping plugin " << filename.value() 275 << " because it doesn't match the current architecture."; 276 return false; 277 } 278 279 std::string error; 280 void* dl = base::LoadNativeLibrary(filename, &error); 281 if (!dl) { 282 LOG_IF(ERROR, PluginList::DebugPluginLoading()) 283 << "While reading plugin info, unable to load library " 284 << filename.value() << " (" << error << "), skipping."; 285 return false; 286 } 287 288 info->path = filename; 289 290 // Attempt to swap in the wrapped plugin if this is nspluginwrapper. 291 UnwrapNSPluginWrapper(&dl, &info->path); 292 293 // See comments in plugin_lib_mac regarding this symbol. 294 typedef const char* (*NP_GetMimeDescriptionType)(); 295 NP_GetMimeDescriptionType NP_GetMIMEDescription = 296 reinterpret_cast<NP_GetMimeDescriptionType>( 297 dlsym(dl, "NP_GetMIMEDescription")); 298 const char* mime_description = NULL; 299 if (!NP_GetMIMEDescription) { 300 LOG_IF(ERROR, PluginList::DebugPluginLoading()) 301 << "Plugin " << filename.value() << " doesn't have a " 302 << "NP_GetMIMEDescription symbol"; 303 return false; 304 } 305 mime_description = NP_GetMIMEDescription(); 306 307 if (!mime_description) { 308 LOG_IF(ERROR, PluginList::DebugPluginLoading()) 309 << "MIME description for " << filename.value() << " is empty"; 310 return false; 311 } 312 ParseMIMEDescription(mime_description, &info->mime_types); 313 314 // The plugin name and description live behind NP_GetValue calls. 315 typedef NPError (*NP_GetValueType)(void* unused, 316 nsPluginVariable variable, 317 void* value_out); 318 NP_GetValueType NP_GetValue = 319 reinterpret_cast<NP_GetValueType>(dlsym(dl, "NP_GetValue")); 320 if (NP_GetValue) { 321 const char* name = NULL; 322 NP_GetValue(NULL, nsPluginVariable_NameString, &name); 323 if (name) { 324 info->name = UTF8ToUTF16(name); 325 ExtractVersionString(name, info); 326 } 327 328 const char* description = NULL; 329 NP_GetValue(NULL, nsPluginVariable_DescriptionString, &description); 330 if (description) { 331 info->desc = UTF8ToUTF16(description); 332 if (info->version.empty()) 333 ExtractVersionString(description, info); 334 } 335 336 LOG_IF(ERROR, PluginList::DebugPluginLoading()) 337 << "Got info for plugin " << filename.value() 338 << " Name = \"" << UTF16ToUTF8(info->name) 339 << "\", Description = \"" << UTF16ToUTF8(info->desc) 340 << "\", Version = \"" << UTF16ToUTF8(info->version) 341 << "\"."; 342 } else { 343 LOG_IF(ERROR, PluginList::DebugPluginLoading()) 344 << "Plugin " << filename.value() 345 << " has no GetValue() and probably won't work."; 346 } 347 348 // Intentionally not unloading the plugin here, it can lead to crashes. 349 350 return true; 351} 352 353// static 354void PluginList::ParseMIMEDescription( 355 const std::string& description, 356 std::vector<WebPluginMimeType>* mime_types) { 357 // We parse the description here into WebPluginMimeType structures. 358 // Naively from the NPAPI docs you'd think you could use 359 // string-splitting, but the Firefox parser turns out to do something 360 // different: find the first colon, then the second, then a semi. 361 // 362 // See ParsePluginMimeDescription near 363 // http://mxr.mozilla.org/firefox/source/modules/plugin/base/src/nsPluginsDirUtils.h#53 364 365 std::string::size_type ofs = 0; 366 for (;;) { 367 WebPluginMimeType mime_type; 368 369 std::string::size_type end = description.find(':', ofs); 370 if (end == std::string::npos) 371 break; 372 mime_type.mime_type = description.substr(ofs, end - ofs); 373 ofs = end + 1; 374 375 end = description.find(':', ofs); 376 if (end == std::string::npos) 377 break; 378 const std::string extensions = description.substr(ofs, end - ofs); 379 base::SplitString(extensions, ',', &mime_type.file_extensions); 380 ofs = end + 1; 381 382 end = description.find(';', ofs); 383 // It's ok for end to run off the string here. If there's no 384 // trailing semicolon we consume the remainder of the string. 385 if (end != std::string::npos) { 386 mime_type.description = UTF8ToUTF16(description.substr(ofs, end - ofs)); 387 } else { 388 mime_type.description = UTF8ToUTF16(description.substr(ofs)); 389 } 390 mime_types->push_back(mime_type); 391 if (end == std::string::npos) 392 break; 393 ofs = end + 1; 394 } 395} 396 397// static 398void PluginList::ExtractVersionString(const std::string& desc, 399 WebPluginInfo* info) { 400 // This matching works by extracting a version substring, along the lines of: 401 // No postfix: second match in .*<prefix>.*$ 402 // With postfix: second match .*<prefix>.*<postfix> 403 static const struct { 404 const char* kPrefix; 405 const char* kPostfix; 406 } kPrePostFixes[] = { 407 { "Shockwave Flash ", 0 }, 408 { "Java(TM) Plug-in ", 0 }, 409 { "(using IcedTea-Web ", " " }, 410 { 0, 0 } 411 }; 412 std::string version; 413 for (size_t i = 0; kPrePostFixes[i].kPrefix; ++i) { 414 size_t pos; 415 if ((pos = desc.find(kPrePostFixes[i].kPrefix)) != std::string::npos) { 416 version = desc.substr(pos + strlen(kPrePostFixes[i].kPrefix)); 417 pos = std::string::npos; 418 if (kPrePostFixes[i].kPostfix) 419 pos = version.find(kPrePostFixes[i].kPostfix); 420 if (pos != std::string::npos) 421 version = version.substr(0, pos); 422 break; 423 } 424 } 425 if (!version.empty()) { 426 info->version = UTF8ToUTF16(version); 427 } 428} 429 430void PluginList::GetPluginDirectories(std::vector<base::FilePath>* plugin_dirs) { 431 // See http://groups.google.com/group/chromium-dev/browse_thread/thread/7a70e5fcbac786a9 432 // for discussion. 433 // We first consult Chrome-specific dirs, then fall back on the logic 434 // Mozilla uses. 435 436 if (PluginList::plugins_discovery_disabled_) 437 return; 438 439 // Note: "extra" plugin dirs and paths are examined before these. 440 // "Extra" are those added by PluginList::AddExtraPluginDir() and 441 // PluginList::AddExtraPluginPath(). 442 443 // The Chrome binary dir + "plugins/". 444 base::FilePath dir; 445 PathService::Get(base::DIR_EXE, &dir); 446 plugin_dirs->push_back(dir.Append("plugins")); 447 448 // Chrome OS only loads plugins from /opt/google/chrome/plugins. 449#if !defined(OS_CHROMEOS) 450 // Mozilla code to reference: 451 // http://mxr.mozilla.org/firefox/ident?i=NS_APP_PLUGINS_DIR_LIST 452 // and tens of accompanying files (mxr is very helpful). 453 // This code carefully matches their behavior for compat reasons. 454 455 // 1) MOZ_PLUGIN_PATH env variable. 456 const char* moz_plugin_path = getenv("MOZ_PLUGIN_PATH"); 457 if (moz_plugin_path) { 458 std::vector<std::string> paths; 459 base::SplitString(moz_plugin_path, ':', &paths); 460 for (size_t i = 0; i < paths.size(); ++i) 461 plugin_dirs->push_back(base::FilePath(paths[i])); 462 } 463 464 // 2) NS_USER_PLUGINS_DIR: ~/.mozilla/plugins. 465 // This is a de-facto standard, so even though we're not Mozilla, let's 466 // look in there too. 467 base::FilePath home = base::GetHomeDir(); 468 if (!home.empty()) 469 plugin_dirs->push_back(home.Append(".mozilla/plugins")); 470 471 // 3) NS_SYSTEM_PLUGINS_DIR: 472 // This varies across different browsers and versions, so check 'em all. 473 plugin_dirs->push_back(base::FilePath("/usr/lib/browser-plugins")); 474 plugin_dirs->push_back(base::FilePath("/usr/lib/mozilla/plugins")); 475 plugin_dirs->push_back(base::FilePath("/usr/lib/firefox/plugins")); 476 plugin_dirs->push_back(base::FilePath("/usr/lib/xulrunner-addons/plugins")); 477 478#if defined(ARCH_CPU_64_BITS) 479 // On my Ubuntu system, /usr/lib64 is a symlink to /usr/lib. 480 // But a user reported on their Fedora system they are separate. 481 plugin_dirs->push_back(base::FilePath("/usr/lib64/browser-plugins")); 482 plugin_dirs->push_back(base::FilePath("/usr/lib64/mozilla/plugins")); 483 plugin_dirs->push_back(base::FilePath("/usr/lib64/firefox/plugins")); 484 plugin_dirs->push_back(base::FilePath("/usr/lib64/xulrunner-addons/plugins")); 485#endif // defined(ARCH_CPU_64_BITS) 486#endif // !defined(OS_CHROMEOS) 487} 488 489void PluginList::GetPluginsInDir( 490 const base::FilePath& dir_path, std::vector<base::FilePath>* plugins) { 491 // See ScanPluginsDirectory near 492 // http://mxr.mozilla.org/firefox/source/modules/plugin/base/src/nsPluginHostImpl.cpp#5052 493 494 // Construct and stat a list of all filenames under consideration, for 495 // later sorting by mtime. 496 FileTimeList files; 497 base::FileEnumerator enumerator(dir_path, 498 false, // not recursive 499 base::FileEnumerator::FILES); 500 for (base::FilePath path = enumerator.Next(); !path.value().empty(); 501 path = enumerator.Next()) { 502 // Skip over Mozilla .xpt files. 503 if (path.MatchesExtension(FILE_PATH_LITERAL(".xpt"))) 504 continue; 505 506 // Java doesn't like being loaded through a symlink, since it uses 507 // its path to find dependent data files. 508 // MakeAbsoluteFilePath calls through to realpath(), which resolves 509 // symlinks. 510 base::FilePath orig_path = path; 511 path = base::MakeAbsoluteFilePath(path); 512 if (path.empty()) 513 path = orig_path; 514 LOG_IF(ERROR, PluginList::DebugPluginLoading()) 515 << "Resolved " << orig_path.value() << " -> " << path.value(); 516 517 if (std::find(plugins->begin(), plugins->end(), path) != plugins->end()) { 518 LOG_IF(ERROR, PluginList::DebugPluginLoading()) 519 << "Skipping duplicate instance of " << path.value(); 520 continue; 521 } 522 523 if (IsBlacklistedPlugin(path)) { 524 LOG_IF(ERROR, PluginList::DebugPluginLoading()) 525 << "Skipping blacklisted plugin " << path.value(); 526 continue; 527 } 528 529 // Flash stops working if the containing directory involves 'netscape'. 530 // No joke. So use the other path if it's better. 531 static const char kFlashPlayerFilename[] = "libflashplayer.so"; 532 static const char kNetscapeInPath[] = "/netscape/"; 533 if (path.BaseName().value() == kFlashPlayerFilename && 534 path.value().find(kNetscapeInPath) != std::string::npos) { 535 if (orig_path.value().find(kNetscapeInPath) == std::string::npos) { 536 // Go back to the old path. 537 path = orig_path; 538 } else { 539 LOG_IF(ERROR, PluginList::DebugPluginLoading()) 540 << "Flash misbehaves when used from a directory containing " 541 << kNetscapeInPath << ", so skipping " << orig_path.value(); 542 continue; 543 } 544 } 545 546 // Get mtime. 547 base::PlatformFileInfo info; 548 if (!base::GetFileInfo(path, &info)) 549 continue; 550 551 files.push_back(std::make_pair(path, info.last_modified)); 552 } 553 554 // Sort the file list by time (and filename). 555 std::sort(files.begin(), files.end(), CompareTime); 556 557 // Load the files in order. 558 for (FileTimeList::const_iterator i = files.begin(); i != files.end(); ++i) { 559 plugins->push_back(i->first); 560 } 561} 562 563bool PluginList::ShouldLoadPluginUsingPluginList( 564 const WebPluginInfo& info, std::vector<WebPluginInfo>* plugins) { 565 LOG_IF(ERROR, PluginList::DebugPluginLoading()) 566 << "Considering " << info.path.value() << " (" << info.name << ")"; 567 568 if (IsUndesirablePlugin(info)) { 569 LOG_IF(ERROR, PluginList::DebugPluginLoading()) 570 << info.path.value() << " is undesirable."; 571 572 // See if we have a better version of this plugin. 573 for (size_t j = 0; j < plugins->size(); ++j) { 574 if ((*plugins)[j].name == info.name && 575 !IsUndesirablePlugin((*plugins)[j])) { 576 // Skip the current undesirable one so we can use the better one 577 // we just found. 578 LOG_IF(ERROR, PluginList::DebugPluginLoading()) 579 << "Skipping " << info.path.value() << ", preferring " 580 << (*plugins)[j].path.value(); 581 return false; 582 } 583 } 584 } 585 586 // TODO(evanm): prefer the newest version of flash, etc. here? 587 588 VLOG_IF(1, PluginList::DebugPluginLoading()) << "Using " << info.path.value(); 589 590 return true; 591} 592 593} // namespace content 594