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.
5 #include "content/common/plugin_list.h"
9 #if defined(OS_OPENBSD)
10 #include <sys/exec_elf.h>
16 #include <sys/types.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"
34 // These headers must be included in this order to make the declaration gods
36 #include "base/third_party/nspr/prcpucfg_linux.h"
42 // We build up a list of files and mtimes so we can sort them.
43 typedef std::pair
<base::FilePath
, base::Time
> FileAndTime
;
44 typedef std::vector
<FileAndTime
> FileTimeList
;
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,
54 // Copied from nsplugindefs.h instead of including the file since it has a bunch
56 enum nsPluginVariable
{
57 nsPluginVariable_NameString
= 1,
58 nsPluginVariable_DescriptionString
= 2
61 // Comparator used to sort by descending mtime then ascending filename.
62 bool 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
;
68 // Sort by mtime, descending.
69 return a
.second
> b
.second
;
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
75 bool CheckQuirks(PluginQuirk quirks
) {
76 if (quirks
== PLUGIN_QUIRK_NONE
)
79 if ((quirks
& PLUGIN_QUIRK_MISSING_SSE2_CHECK
) != 0) {
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.
92 bool IsBlacklistedBySha1sumAndQuirks(const base::FilePath
& path
) {
93 const struct BadEntry
{
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
},
111 if (!base::GetFileSize(path
, &size
))
113 for (size_t i
= 0; i
< ARRAYSIZE_UNSAFE(bad_entries
); i
++) {
114 if (bad_entries
[i
].size
!= size
)
117 std::string file_content
;
118 if (!base::ReadFileToString(path
, &file_content
))
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
);
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.
134 bool IsUndesirablePlugin(const WebPluginInfo
& info
) {
135 std::string filename
= info
.path
.BaseName().value();
136 const char* kUndesiredPlugins
[] = {
137 "npcxoffice", // Crossover
138 "npwrapper", // nspluginwrapper
140 for (size_t i
= 0; i
< arraysize(kUndesiredPlugins
); i
++) {
141 if (filename
.find(kUndesiredPlugins
[i
]) != std::string::npos
) {
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
152 bool IsBlacklistedPlugin(const base::FilePath
& path
) {
153 const char* kBlackListedPlugins
[] = {
154 "nppdf.so", // Adobe PDF
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
) {
162 return IsBlacklistedBySha1sumAndQuirks(path
);
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.
168 bool 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
);
175 bool ret
= (fstat(fd
, &stat_buf
) >= 0 && S_ISREG(stat_buf
.st_mode
));
176 if (IGNORE_EINTR(close(fd
)) < 0)
181 const size_t kELFBufferSize
= 5;
182 char buffer
[kELFBufferSize
];
183 if (!base::ReadFile(filename
, buffer
, kELFBufferSize
))
186 if (buffer
[0] != ELFMAG0
||
187 buffer
[1] != ELFMAG1
||
188 buffer
[2] != ELFMAG2
||
189 buffer
[3] != ELFMAG3
) {
190 // Not an ELF file, perhaps?
194 int elf_class
= buffer
[EI_CLASS
];
195 #if defined(ARCH_CPU_32_BITS)
196 if (elf_class
== ELFCLASS32
)
198 #elif defined(ARCH_CPU_64_BITS)
199 if (elf_class
== ELFCLASS64
)
206 // This structure matches enough of nspluginwrapper's NPW_PluginInfo
207 // for us to extract the real plugin path.
208 struct __attribute__((packed
)) NSPluginWrapperInfo
{
209 char ident
[32]; // NSPluginWrapper magic identifier (includes version).
210 char path
[PATH_MAX
]; // Path to wrapped plugin.
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.
217 void UnwrapNSPluginWrapper(void **dl
, base::FilePath
* unwrapped_path
) {
218 NSPluginWrapperInfo
* info
=
219 reinterpret_cast<NSPluginWrapperInfo
*>(dlsym(*dl
, "NPW_Plugin"));
221 return; // Not a NSPW plugin.
223 // Here we could check the NSPW ident field for the versioning
224 // information, but the path field is available in all versions
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
)));
232 path_end
= info
->path
+ sizeof(info
->path
);
233 base::FilePath path
= base::FilePath(
234 std::string(info
->path
, path_end
- info
->path
));
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.";
244 void* newdl
= base::LoadNativeLibrary(path
, &error
);
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.";
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
);
261 *unwrapped_path
= path
;
266 bool 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
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.";
280 void* dl
= base::LoadNativeLibrary(filename
, &error
);
282 LOG_IF(ERROR
, PluginList::DebugPluginLoading())
283 << "While reading plugin info, unable to load library "
284 << filename
.value() << " (" << error
<< "), skipping.";
288 info
->path
= filename
;
290 // Attempt to swap in the wrapped plugin if this is nspluginwrapper.
291 UnwrapNSPluginWrapper(&dl
, &info
->path
);
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";
305 mime_description
= NP_GetMIMEDescription();
307 if (!mime_description
) {
308 LOG_IF(ERROR
, PluginList::DebugPluginLoading())
309 << "MIME description for " << filename
.value() << " is empty";
312 ParseMIMEDescription(mime_description
, &info
->mime_types
);
314 // The plugin name and description live behind NP_GetValue calls.
315 typedef NPError (*NP_GetValueType
)(void* unused
,
316 nsPluginVariable variable
,
318 NP_GetValueType NP_GetValue
=
319 reinterpret_cast<NP_GetValueType
>(dlsym(dl
, "NP_GetValue"));
321 const char* name
= NULL
;
322 NP_GetValue(NULL
, nsPluginVariable_NameString
, &name
);
324 info
->name
= base::UTF8ToUTF16(name
);
325 ExtractVersionString(name
, info
);
328 const char* description
= NULL
;
329 NP_GetValue(NULL
, nsPluginVariable_DescriptionString
, &description
);
331 info
->desc
= base::UTF8ToUTF16(description
);
332 if (info
->version
.empty())
333 ExtractVersionString(description
, info
);
336 LOG_IF(ERROR
, PluginList::DebugPluginLoading())
337 << "Got info for plugin " << filename
.value()
338 << " Name = \"" << base::UTF16ToUTF8(info
->name
)
339 << "\", Description = \"" << base::UTF16ToUTF8(info
->desc
)
340 << "\", Version = \"" << base::UTF16ToUTF8(info
->version
)
343 LOG_IF(ERROR
, PluginList::DebugPluginLoading())
344 << "Plugin " << filename
.value()
345 << " has no GetValue() and probably won't work.";
348 // Intentionally not unloading the plugin here, it can lead to crashes.
354 void 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.
362 // See ParsePluginMimeDescription near
363 // http://mxr.mozilla.org/firefox/source/modules/plugin/base/src/nsPluginsDirUtils.h#53
365 std::string::size_type ofs
= 0;
367 WebPluginMimeType mime_type
;
369 std::string::size_type end
= description
.find(':', ofs
);
370 if (end
== std::string::npos
)
372 mime_type
.mime_type
= description
.substr(ofs
, end
- ofs
);
375 end
= description
.find(':', ofs
);
376 if (end
== std::string::npos
)
378 const std::string extensions
= description
.substr(ofs
, end
- ofs
);
379 base::SplitString(extensions
, ',', &mime_type
.file_extensions
);
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
=
387 base::UTF8ToUTF16(description
.substr(ofs
, end
- ofs
));
389 mime_type
.description
= base::UTF8ToUTF16(description
.substr(ofs
));
391 mime_types
->push_back(mime_type
);
392 if (end
== std::string::npos
)
399 void PluginList::ExtractVersionString(const std::string
& desc
,
400 WebPluginInfo
* info
) {
401 // This matching works by extracting a version substring, along the lines of:
402 // No postfix: second match in .*<prefix>.*$
403 // With postfix: second match .*<prefix>.*<postfix>
404 static const struct {
406 const char* kPostfix
;
407 } kPrePostFixes
[] = {
408 { "Shockwave Flash ", 0 },
409 { "Java(TM) Plug-in ", 0 },
410 { "(using IcedTea-Web ", " " },
414 for (size_t i
= 0; kPrePostFixes
[i
].kPrefix
; ++i
) {
416 if ((pos
= desc
.find(kPrePostFixes
[i
].kPrefix
)) != std::string::npos
) {
417 version
= desc
.substr(pos
+ strlen(kPrePostFixes
[i
].kPrefix
));
418 pos
= std::string::npos
;
419 if (kPrePostFixes
[i
].kPostfix
)
420 pos
= version
.find(kPrePostFixes
[i
].kPostfix
);
421 if (pos
!= std::string::npos
)
422 version
= version
.substr(0, pos
);
426 if (!version
.empty()) {
427 info
->version
= base::UTF8ToUTF16(version
);
431 void PluginList::GetPluginDirectories(std::vector
<base::FilePath
>* plugin_dirs
) {
432 // See http://groups.google.com/group/chromium-dev/browse_thread/thread/7a70e5fcbac786a9
434 // We first consult Chrome-specific dirs, then fall back on the logic
437 if (PluginList::plugins_discovery_disabled_
)
440 // Note: "extra" plugin dirs and paths are examined before these.
441 // "Extra" are those added by PluginList::AddExtraPluginDir() and
442 // PluginList::AddExtraPluginPath().
444 // The Chrome binary dir + "plugins/".
446 PathService::Get(base::DIR_EXE
, &dir
);
447 plugin_dirs
->push_back(dir
.Append("plugins"));
449 // Chrome OS only loads plugins from /opt/google/chrome/plugins.
450 #if !defined(OS_CHROMEOS)
451 // Mozilla code to reference:
452 // http://mxr.mozilla.org/firefox/ident?i=NS_APP_PLUGINS_DIR_LIST
453 // and tens of accompanying files (mxr is very helpful).
454 // This code carefully matches their behavior for compat reasons.
456 // 1) MOZ_PLUGIN_PATH env variable.
457 const char* moz_plugin_path
= getenv("MOZ_PLUGIN_PATH");
458 if (moz_plugin_path
) {
459 std::vector
<std::string
> paths
;
460 base::SplitString(moz_plugin_path
, ':', &paths
);
461 for (size_t i
= 0; i
< paths
.size(); ++i
)
462 plugin_dirs
->push_back(base::FilePath(paths
[i
]));
465 // 2) NS_USER_PLUGINS_DIR: ~/.mozilla/plugins.
466 // This is a de-facto standard, so even though we're not Mozilla, let's
467 // look in there too.
468 base::FilePath home
= base::GetHomeDir();
470 plugin_dirs
->push_back(home
.Append(".mozilla/plugins"));
472 // 3) NS_SYSTEM_PLUGINS_DIR:
473 // This varies across different browsers and versions, so check 'em all.
474 plugin_dirs
->push_back(base::FilePath("/usr/lib/browser-plugins"));
475 plugin_dirs
->push_back(base::FilePath("/usr/lib/mozilla/plugins"));
476 plugin_dirs
->push_back(base::FilePath("/usr/lib/firefox/plugins"));
477 plugin_dirs
->push_back(base::FilePath("/usr/lib/xulrunner-addons/plugins"));
479 #if defined(ARCH_CPU_64_BITS)
480 // On my Ubuntu system, /usr/lib64 is a symlink to /usr/lib.
481 // But a user reported on their Fedora system they are separate.
482 plugin_dirs
->push_back(base::FilePath("/usr/lib64/browser-plugins"));
483 plugin_dirs
->push_back(base::FilePath("/usr/lib64/mozilla/plugins"));
484 plugin_dirs
->push_back(base::FilePath("/usr/lib64/firefox/plugins"));
485 plugin_dirs
->push_back(base::FilePath("/usr/lib64/xulrunner-addons/plugins"));
486 #endif // defined(ARCH_CPU_64_BITS)
487 #endif // !defined(OS_CHROMEOS)
490 void PluginList::GetPluginsInDir(
491 const base::FilePath
& dir_path
, std::vector
<base::FilePath
>* plugins
) {
492 // See ScanPluginsDirectory near
493 // http://mxr.mozilla.org/firefox/source/modules/plugin/base/src/nsPluginHostImpl.cpp#5052
495 // Construct and stat a list of all filenames under consideration, for
496 // later sorting by mtime.
498 base::FileEnumerator
enumerator(dir_path
,
499 false, // not recursive
500 base::FileEnumerator::FILES
);
501 for (base::FilePath path
= enumerator
.Next(); !path
.value().empty();
502 path
= enumerator
.Next()) {
503 // Skip over Mozilla .xpt files.
504 if (path
.MatchesExtension(FILE_PATH_LITERAL(".xpt")))
507 // Java doesn't like being loaded through a symlink, since it uses
508 // its path to find dependent data files.
509 // MakeAbsoluteFilePath calls through to realpath(), which resolves
511 base::FilePath orig_path
= path
;
512 path
= base::MakeAbsoluteFilePath(path
);
515 LOG_IF(ERROR
, PluginList::DebugPluginLoading())
516 << "Resolved " << orig_path
.value() << " -> " << path
.value();
518 if (std::find(plugins
->begin(), plugins
->end(), path
) != plugins
->end()) {
519 LOG_IF(ERROR
, PluginList::DebugPluginLoading())
520 << "Skipping duplicate instance of " << path
.value();
524 if (IsBlacklistedPlugin(path
)) {
525 LOG_IF(ERROR
, PluginList::DebugPluginLoading())
526 << "Skipping blacklisted plugin " << path
.value();
530 // Flash stops working if the containing directory involves 'netscape'.
531 // No joke. So use the other path if it's better.
532 static const char kFlashPlayerFilename
[] = "libflashplayer.so";
533 static const char kNetscapeInPath
[] = "/netscape/";
534 if (path
.BaseName().value() == kFlashPlayerFilename
&&
535 path
.value().find(kNetscapeInPath
) != std::string::npos
) {
536 if (orig_path
.value().find(kNetscapeInPath
) == std::string::npos
) {
537 // Go back to the old path.
540 LOG_IF(ERROR
, PluginList::DebugPluginLoading())
541 << "Flash misbehaves when used from a directory containing "
542 << kNetscapeInPath
<< ", so skipping " << orig_path
.value();
548 base::File::Info info
;
549 if (!base::GetFileInfo(path
, &info
))
552 files
.push_back(std::make_pair(path
, info
.last_modified
));
555 // Sort the file list by time (and filename).
556 std::sort(files
.begin(), files
.end(), CompareTime
);
558 // Load the files in order.
559 for (FileTimeList::const_iterator i
= files
.begin(); i
!= files
.end(); ++i
) {
560 plugins
->push_back(i
->first
);
564 bool PluginList::ShouldLoadPluginUsingPluginList(
565 const WebPluginInfo
& info
, std::vector
<WebPluginInfo
>* plugins
) {
566 LOG_IF(ERROR
, PluginList::DebugPluginLoading())
567 << "Considering " << info
.path
.value() << " (" << info
.name
<< ")";
569 if (IsUndesirablePlugin(info
)) {
570 LOG_IF(ERROR
, PluginList::DebugPluginLoading())
571 << info
.path
.value() << " is undesirable.";
573 // See if we have a better version of this plugin.
574 for (size_t j
= 0; j
< plugins
->size(); ++j
) {
575 if ((*plugins
)[j
].name
== info
.name
&&
576 !IsUndesirablePlugin((*plugins
)[j
])) {
577 // Skip the current undesirable one so we can use the better one
579 LOG_IF(ERROR
, PluginList::DebugPluginLoading())
580 << "Skipping " << info
.path
.value() << ", preferring "
581 << (*plugins
)[j
].path
.value();
587 // TODO(evanm): prefer the newest version of flash, etc. here?
589 VLOG_IF(1, PluginList::DebugPluginLoading()) << "Using " << info
.path
.value();
594 } // namespace content