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