diff options
author | kerrnel <kerrnel@chromium.org> | 2015-08-14 11:15:56 -0700 |
---|---|---|
committer | Commit bot <commit-bot@chromium.org> | 2015-08-14 18:16:23 +0000 |
commit | 2bb08b2a5f6bcc382ebaccf1b50bd655592dfe51 (patch) | |
tree | d61a74f71fe4083a9b3bedbb74e1d58dee6c8b20 | |
parent | 8894e7a716085008961141302ecf6de887375ed7 (diff) | |
download | chromium_src-2bb08b2a5f6bcc382ebaccf1b50bd655592dfe51.zip chromium_src-2bb08b2a5f6bcc382ebaccf1b50bd655592dfe51.tar.gz chromium_src-2bb08b2a5f6bcc382ebaccf1b50bd655592dfe51.tar.bz2 |
Add support for Flash Player Component updates on Linux
This change adds support for Flash Player Component updates on Linux.
It enables the component updater on Linux, and creates a hints file that
the zygote will use to locate and preload the latest component updated flash.
This should not affect component update OS X and Windows, however, because
it forks the update path on Linux from those platforms, there is a risk of a bug
that could affect component update on OS X and Windows.
BUG=460595
Review URL: https://codereview.chromium.org/1261333004
Cr-Commit-Position: refs/heads/master@{#343435}
-rw-r--r-- | chrome/browser/component_updater/pepper_flash_component_installer.cc | 44 | ||||
-rw-r--r-- | chrome/chrome_common.gypi | 2 | ||||
-rw-r--r-- | chrome/chrome_tests_unit.gypi | 1 | ||||
-rw-r--r-- | chrome/common/chrome_content_client.cc | 109 | ||||
-rw-r--r-- | chrome/common/chrome_content_client.h | 7 | ||||
-rw-r--r-- | chrome/common/chrome_content_client_unittest.cc | 40 | ||||
-rw-r--r-- | chrome/common/chrome_paths.cc | 13 | ||||
-rw-r--r-- | chrome/common/chrome_paths.h | 4 | ||||
-rw-r--r-- | chrome/common/component_flash_hint_file_linux.cc | 225 | ||||
-rw-r--r-- | chrome/common/component_flash_hint_file_linux.h | 52 | ||||
-rw-r--r-- | chrome/common/component_flash_hint_file_linux_unittest.cc | 167 |
11 files changed, 647 insertions, 17 deletions
diff --git a/chrome/browser/component_updater/pepper_flash_component_installer.cc b/chrome/browser/component_updater/pepper_flash_component_installer.cc index 2ebbb31..dffeaed 100644 --- a/chrome/browser/component_updater/pepper_flash_component_installer.cc +++ b/chrome/browser/component_updater/pepper_flash_component_installer.cc @@ -15,6 +15,7 @@ #include "base/files/file_util.h" #include "base/logging.h" #include "base/path_service.h" +#include "base/stl_util.h" #include "base/strings/string_util.h" #include "base/strings/stringprintf.h" #include "base/strings/utf_string_conversions.h" @@ -25,6 +26,7 @@ #include "chrome/common/chrome_constants.h" #include "chrome/common/chrome_paths.h" #include "chrome/common/chrome_switches.h" +#include "chrome/common/component_flash_hint_file_linux.h" #include "chrome/common/pepper_flash.h" #include "chrome/common/ppapi_utils.h" #include "components/component_updater/component_updater_service.h" @@ -43,7 +45,7 @@ namespace component_updater { namespace { -#if defined(GOOGLE_CHROME_BUILD) && !defined(OS_LINUX) +#if defined(GOOGLE_CHROME_BUILD) // CRX hash. The extension id is: mimojjlkmoijpicakmndhoigimigcmbb. const uint8_t kSha2Hash[] = {0xc8, 0xce, 0x99, 0xba, 0xce, 0x89, 0xf8, 0x20, 0xac, 0xd3, 0x7e, 0x86, 0x8c, 0x86, 0x2c, 0x11, @@ -53,7 +55,7 @@ const uint8_t kSha2Hash[] = {0xc8, 0xce, 0x99, 0xba, 0xce, 0x89, 0xf8, 0x20, // If we don't have a Pepper Flash component, this is the version we claim. const char kNullVersion[] = "0.0.0.0"; -#endif // defined(GOOGLE_CHROME_BUILD) && !defined(OS_LINUX) +#endif // defined(GOOGLE_CHROME_BUILD) // The base directory on Windows looks like: // <profile>\AppData\Local\Google\Chrome\User Data\PepperFlash\. @@ -63,7 +65,7 @@ base::FilePath GetPepperFlashBaseDirectory() { return result; } -#if defined(GOOGLE_CHROME_BUILD) && !defined(OS_LINUX) +#if defined(GOOGLE_CHROME_BUILD) // Pepper Flash plugins have the version encoded in the path itself // so we need to enumerate the directories to find the full path. // On success, |latest_dir| returns something like: @@ -99,8 +101,9 @@ bool GetPepperFlashDirectory(base::FilePath* latest_dir, } return found; } -#endif // defined(GOOGLE_CHROME_BUILD) && !defined(OS_LINUX) +#endif // defined(GOOGLE_CHROME_BUILD) +#if !defined(OS_LINUX) || defined(GOOGLE_CHROME_BUILD) bool MakePepperFlashPluginInfo(const base::FilePath& flash_path, const Version& flash_version, bool out_of_process, @@ -177,6 +180,7 @@ void RegisterPepperFlashWithChrome(const base::FilePath& path, plugin_info.ToWebPluginInfo(), true); PluginService::GetInstance()->RefreshPlugins(); } +#endif // !defined(OS_LINUX) || defined(GOOGLE_CHROME_BUILD) } // namespace @@ -219,24 +223,42 @@ bool PepperFlashComponentInstaller::Install( return false; if (current_version_.CompareTo(version) > 0) return false; - if (!base::PathExists(unpack_path.Append(chrome::kPepperFlashPluginFilename))) + const base::FilePath unpacked_plugin = + unpack_path.Append(chrome::kPepperFlashPluginFilename); + if (!base::PathExists(unpacked_plugin)) return false; // Passed the basic tests. Time to install it. base::FilePath path = GetPepperFlashBaseDirectory().AppendASCII(version.GetString()); if (base::PathExists(path)) return false; + current_version_ = version; + if (!base::Move(unpack_path, path)) return false; +#if defined(OS_LINUX) + const base::FilePath flash_path = + path.Append(chrome::kPepperFlashPluginFilename); + // Populate the component updated flash hint file so that the zygote can + // locate and preload the latest version of flash. + if (!chrome::component_flash_hint_file::RecordFlashUpdate( + flash_path, flash_path, version.GetString())) { + if (!base::DeleteFile(path, true)) + LOG(ERROR) << "Hint file creation failed, but unable to delete " + "installed flash plugin."; + return false; + } +#else // Installation is done. Now tell the rest of chrome. Both the path service - // and to the plugin service. - current_version_ = version; + // and to the plugin service. On Linux, a restart is required to use the new + // Flash version, so we do not do this. PathService::Override(chrome::DIR_PEPPER_FLASH_PLUGIN, path); path = path.Append(chrome::kPepperFlashPluginFilename); BrowserThread::PostTask( BrowserThread::UI, FROM_HERE, base::Bind(&RegisterPepperFlashWithChrome, path, version)); +#endif // !defined(OS_LINUX) return true; } @@ -254,7 +276,7 @@ bool PepperFlashComponentInstaller::Uninstall() { namespace { -#if defined(GOOGLE_CHROME_BUILD) && !defined(OS_LINUX) +#if defined(GOOGLE_CHROME_BUILD) void FinishPepperFlashUpdateRegistration(ComponentUpdateService* cus, const Version& version) { DCHECK_CURRENTLY_ON(BrowserThread::UI); @@ -311,12 +333,12 @@ void StartPepperFlashUpdateRegistration(ComponentUpdateService* cus) { base::DeleteFile(*iter, true); } } -#endif // defined(GOOGLE_CHROME_BUILD) && !defined(OS_LINUX) +#endif // defined(GOOGLE_CHROME_BUILD) } // namespace void RegisterPepperFlashComponent(ComponentUpdateService* cus) { -#if defined(GOOGLE_CHROME_BUILD) && !defined(OS_LINUX) +#if defined(GOOGLE_CHROME_BUILD) // Component updated flash supersedes bundled flash therefore if that one // is disabled then this one should never install. base::CommandLine* cmd_line = base::CommandLine::ForCurrentProcess(); @@ -325,7 +347,7 @@ void RegisterPepperFlashComponent(ComponentUpdateService* cus) { BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE, base::Bind(&StartPepperFlashUpdateRegistration, cus)); -#endif +#endif // defined(GOOGLE_CHROME_BUILD) } } // namespace component_updater diff --git a/chrome/chrome_common.gypi b/chrome/chrome_common.gypi index ed1a287..603c9c2 100644 --- a/chrome/chrome_common.gypi +++ b/chrome/chrome_common.gypi @@ -43,6 +43,8 @@ 'common/common_param_traits.cc', 'common/common_param_traits.h', 'common/common_param_traits_macros.h', + 'common/component_flash_hint_file_linux.h', + 'common/component_flash_hint_file_linux.cc', 'common/content_restriction.h', 'common/content_settings_pattern_serializer.cc', 'common/content_settings_pattern_serializer.h', diff --git a/chrome/chrome_tests_unit.gypi b/chrome/chrome_tests_unit.gypi index c288dee..66d0ed8 100644 --- a/chrome/chrome_tests_unit.gypi +++ b/chrome/chrome_tests_unit.gypi @@ -517,6 +517,7 @@ 'common/chrome_content_client_unittest.cc', 'common/chrome_paths_unittest.cc', 'common/cloud_print/cloud_print_helpers_unittest.cc', + 'common/component_flash_hint_file_linux_unittest.cc', 'common/crash_keys_unittest.cc', 'common/ini_parser_unittest.cc', 'common/instant_types_unittest.cc', diff --git a/chrome/common/chrome_content_client.cc b/chrome/common/chrome_content_client.cc index 5fc72b7..f3b14b3 100644 --- a/chrome/common/chrome_content_client.cc +++ b/chrome/common/chrome_content_client.cc @@ -4,10 +4,15 @@ #include "chrome/common/chrome_content_client.h" +#if defined(OS_LINUX) +#include <fcntl.h> +#endif // defined(OS_LINUX) + #include "base/command_line.h" #include "base/debug/crash_logging.h" #include "base/files/file_util.h" #include "base/json/json_reader.h" +#include "base/memory/scoped_vector.h" #include "base/path_service.h" #include "base/strings/string16.h" #include "base/strings/string_number_conversions.h" @@ -20,6 +25,7 @@ #include "chrome/common/chrome_constants.h" #include "chrome/common/chrome_paths.h" #include "chrome/common/chrome_switches.h" +#include "chrome/common/component_flash_hint_file_linux.h" #include "chrome/common/crash_keys.h" #include "chrome/common/pepper_flash.h" #include "chrome/common/secure_origin_whitelist.h" @@ -233,6 +239,50 @@ void AddPepperFlashFromCommandLine( CreatePepperFlashInfo(base::FilePath(flash_path), flash_version)); } +#if defined(OS_LINUX) +bool IsUserDataDirAvailable() { + base::FilePath user_data_dir; + if (!PathService::Get(chrome::DIR_USER_DATA, &user_data_dir)) + return false; + return base::PathExists(user_data_dir); +} + +// This method is used on Linux only because of architectural differences in how +// it loads the component updated flash plugin, and not because the other +// platforms do not support component updated flash. On other platforms, the +// component updater sends an IPC message to all threads, at undefined points in +// time, with the URL of the component updated flash. Because the linux zygote +// thread has no access to the file system after it warms up, it must preload +// the component updated flash. +bool GetComponentUpdatedPepperFlash(content::PepperPluginInfo* plugin) { +#if defined(FLAPPER_AVAILABLE) + if (chrome::component_flash_hint_file::DoesHintFileExist()) { + base::FilePath flash_path; + std::string version; + if (chrome::component_flash_hint_file::VerifyAndReturnFlashLocation( + &flash_path, &version)) { + // Test if the file can be mapped as executable. If the user's home + // directory is mounted noexec, the component flash plugin will not load. + // By testing for this, Chrome can fallback to the bundled flash plugin. + if (!chrome::component_flash_hint_file::TestExecutableMapping( + flash_path)) { + LOG(WARNING) << "The component updated flash plugin could not be " + "mapped as executable. Attempting to fallback to the " + "bundled or system plugin."; + return false; + } + *plugin = CreatePepperFlashInfo(flash_path, version); + return true; + } else { + LOG(ERROR) + << "Failed to locate and load the component updated flash plugin."; + } + } +#endif // defined(FLAPPER_AVAILABLE) + return false; +} +#endif // defined(OS_LINUX) + bool GetBundledPepperFlash(content::PepperPluginInfo* plugin) { #if defined(FLAPPER_AVAILABLE) base::CommandLine* command_line = base::CommandLine::ForCurrentProcess(); @@ -397,18 +447,65 @@ void ChromeContentClient::SetGpuInfo(const gpu::GPUInfo& gpu_info) { #endif } +#if defined(ENABLE_PLUGINS) +// static +content::PepperPluginInfo* ChromeContentClient::FindMostRecentPlugin( + const std::vector<content::PepperPluginInfo*>& plugins) { + content::PepperPluginInfo* result = nullptr; + auto it = std::max_element( + plugins.begin(), plugins.end(), + [](content::PepperPluginInfo* x, content::PepperPluginInfo* y) { + Version version_x(x->version); + DCHECK(version_x.IsValid()); + return version_x.IsOlderThan(y->version); + }); + if (it != plugins.end()) + result = *it; + return result; +} +#endif // defined(ENABLE_PLUGINS) + void ChromeContentClient::AddPepperPlugins( std::vector<content::PepperPluginInfo>* plugins) { #if defined(ENABLE_PLUGINS) ComputeBuiltInPlugins(plugins); AddPepperFlashFromCommandLine(plugins); - content::PepperPluginInfo plugin; - if (GetBundledPepperFlash(&plugin)) - plugins->push_back(plugin); - if (GetSystemPepperFlash(&plugin)) - plugins->push_back(plugin); -#endif +#if defined(OS_LINUX) + // Depending on the sandbox configurtion, the user data directory + // is not always available. If it is not available, do not try and load any + // flash plugin. The flash player, if any, preloaded before the sandbox + // initialization will continue to be used. + if (!IsUserDataDirAvailable()) { + return; + } +#endif // defined(OS_LINUX) + + ScopedVector<content::PepperPluginInfo> flash_versions; + +#if defined(OS_LINUX) + scoped_ptr<content::PepperPluginInfo> component_flash( + new content::PepperPluginInfo); + if (GetComponentUpdatedPepperFlash(component_flash.get())) + flash_versions.push_back(component_flash.release()); +#endif // defined(OS_LINUX) + + scoped_ptr<content::PepperPluginInfo> bundled_flash( + new content::PepperPluginInfo); + if (GetBundledPepperFlash(bundled_flash.get())) + flash_versions.push_back(bundled_flash.release()); + + scoped_ptr<content::PepperPluginInfo> system_flash( + new content::PepperPluginInfo); + if (GetSystemPepperFlash(system_flash.get())) + flash_versions.push_back(system_flash.release()); + + // This function will return only the most recent version of the flash plugin. + content::PepperPluginInfo* max_flash = + FindMostRecentPlugin(flash_versions.get()); + if (max_flash != nullptr) + plugins->push_back(*max_flash); +#endif // defined(ENABLE_PLUGINS) } void ChromeContentClient::AddAdditionalSchemes( diff --git a/chrome/common/chrome_content_client.h b/chrome/common/chrome_content_client.h index 8b77d22..0b5d516 100644 --- a/chrome/common/chrome_content_client.h +++ b/chrome/common/chrome_content_client.h @@ -41,6 +41,13 @@ class ChromeContentClient : public content::ContentClient { content::PepperPluginInfo::GetInterfaceFunc get_interface, content::PepperPluginInfo::PPP_InitializeModuleFunc initialize_module, content::PepperPluginInfo::PPP_ShutdownModuleFunc shutdown_module); + + // This returns the most recent plugin based on the plugin versions. + // It does not make sense to call this on a vector that contains more than one + // plugin type. This function may return a nullptr if given an empty vector. + // The method is only visible for testing purposes. + static content::PepperPluginInfo* FindMostRecentPlugin( + const std::vector<content::PepperPluginInfo*>& plugins); #endif void SetActiveURL(const GURL& url) override; diff --git a/chrome/common/chrome_content_client_unittest.cc b/chrome/common/chrome_content_client_unittest.cc index 42514d7..5fa08bf 100644 --- a/chrome/common/chrome_content_client_unittest.cc +++ b/chrome/common/chrome_content_client_unittest.cc @@ -5,6 +5,8 @@ #include "chrome/common/chrome_content_client.h" #include "base/command_line.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/scoped_vector.h" #include "base/strings/string_split.h" #include "content/public/common/content_switches.h" #include "testing/gtest/include/gtest/gtest.h" @@ -81,4 +83,42 @@ TEST(ChromeContentClientTest, Basic) { #endif } +#if defined(ENABLE_PLUGINS) +TEST(ChromeContentClientTest, FindMostRecent) { + ScopedVector<content::PepperPluginInfo> vector1; + // Test an empty vector. + EXPECT_EQ(ChromeContentClient::FindMostRecentPlugin(vector1.get()), nullptr); + + // Now test the vector with one element. + content::PepperPluginInfo* info1 = new content::PepperPluginInfo(); + info1->version = "1.0.0.0"; + vector1.push_back(info1); + EXPECT_EQ(ChromeContentClient::FindMostRecentPlugin(vector1.get()), info1); + + // Now do the generic test of a complex vector. + content::PepperPluginInfo* info2 = new content::PepperPluginInfo(); + info2->version = "2.0.0.1"; + content::PepperPluginInfo* info3 = new content::PepperPluginInfo(); + info3->version = "3.5.6.7"; + content::PepperPluginInfo* info4 = new content::PepperPluginInfo(); + info4->version = "4.0.0.153"; + content::PepperPluginInfo* info5 = new content::PepperPluginInfo(); + info5->version = "5.0.12.1"; + content::PepperPluginInfo* info6_12 = new content::PepperPluginInfo(); + info6_12->version = "6.0.0.12"; + content::PepperPluginInfo* info6_13 = new content::PepperPluginInfo(); + info6_13->version = "6.0.0.13"; + + ScopedVector<content::PepperPluginInfo> vector2; + vector2.push_back(info4); + vector2.push_back(info2); + vector2.push_back(info6_13); + vector2.push_back(info3); + vector2.push_back(info5); + vector2.push_back(info6_12); + + EXPECT_EQ(ChromeContentClient::FindMostRecentPlugin(vector2.get()), info6_13); +} +#endif // defined(ENABLE_PLUGINS) + } // namespace chrome_common diff --git a/chrome/common/chrome_paths.cc b/chrome/common/chrome_paths.cc index 74bf041..0da194d 100644 --- a/chrome/common/chrome_paths.cc +++ b/chrome/common/chrome_paths.cc @@ -60,6 +60,11 @@ const base::FilePath::CharType kFilepathSinglePrefExtensions[] = #else FILE_PATH_LITERAL("/usr/share/chromium/extensions"); #endif // defined(GOOGLE_CHROME_BUILD) + +// The path to the hint file that tells the pepper plugin loader +// where it can find the latest component updated flash. +const base::FilePath::CharType kComponentUpdatedFlashHint[] = + FILE_PATH_LITERAL("latest-component-updated-flash"); #endif // defined(OS_LINUX) static base::LazyInstance<base::FilePath> @@ -566,6 +571,14 @@ bool PathProvider(int key, base::FilePath* result) { cur = cur.Append(kOfflinePageMetadataDirname); break; #endif // defined(OS_ANDROID) +#if defined(OS_LINUX) + case chrome::FILE_COMPONENT_FLASH_HINT: + if (!PathService::Get(chrome::DIR_COMPONENT_UPDATED_PEPPER_FLASH_PLUGIN, + &cur)) + return false; + cur = cur.Append(kComponentUpdatedFlashHint); + break; +#endif // defined(OS_LINUX) default: return false; diff --git a/chrome/common/chrome_paths.h b/chrome/common/chrome_paths.h index 95ef853..14fcc08 100644 --- a/chrome/common/chrome_paths.h +++ b/chrome/common/chrome_paths.h @@ -133,6 +133,10 @@ enum { DIR_GEN_TEST_DATA, // Directory where generated test data resides. DIR_TEST_DATA, // Directory where unit test data resides. DIR_TEST_TOOLS, // Directory where unit test tools reside. +#if defined(OS_LINUX) + FILE_COMPONENT_FLASH_HINT, // A file in a known location that points to + // the component updated flash plugin. +#endif // defined(OS_LINUX) PATH_END }; diff --git a/chrome/common/component_flash_hint_file_linux.cc b/chrome/common/component_flash_hint_file_linux.cc new file mode 100644 index 0000000..3d6c851 --- /dev/null +++ b/chrome/common/component_flash_hint_file_linux.cc @@ -0,0 +1,225 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/common/component_flash_hint_file_linux.h" + +#include <fcntl.h> +#include <sys/mman.h> + +#include "base/base64.h" +#include "base/files/file_path.h" +#include "base/files/file_util.h" +#include "base/files/important_file_writer.h" +#include "base/files/memory_mapped_file.h" +#include "base/files/scoped_file.h" +#include "base/json/json_string_value_serializer.h" +#include "base/path_service.h" +#include "base/posix/eintr_wrapper.h" +#include "base/stl_util.h" +#include "base/values.h" +#include "chrome/common/chrome_paths.h" +#include "crypto/secure_hash.h" +#include "crypto/secure_util.h" +#include "crypto/sha2.h" + +namespace chrome { + +namespace component_flash_hint_file { + +namespace { +// The current version of the hints file. +const int kCurrentHintFileVersion = 0x10; +// The earliest version of the hints file. +const int kEarliestHintFileVersion = 0x10; +// The Version field in the JSON encoded file. +const char kVersionField[] = "Version"; +// The HashAlgorithm field in the JSON encoded file. +const char kHashAlgoField[] = "HashAlgorithm"; +// The Hash field in the JSON encoded file. +const char kHashField[] = "Hash"; +// The PluginPath field in the JSON encoded file. +const char kPluginPath[] = "PluginPath"; +// The PluginVersion field in the JSON encoded file. +const char kPluginVersion[] = "PluginVersion"; +// For use with the scoped_ptr of an mmap-ed buffer +struct MmapDeleter { + explicit MmapDeleter(size_t map_size) { map_size = map_size_; } + inline void operator()(uint8_t* ptr) const { + if (ptr != MAP_FAILED) + munmap(ptr, map_size_); + } + + private: + size_t map_size_; +}; + +// Hashes the plugin file and returns the result in the out params. +// |mapped_file| is the file to be hashed. +// |result| is the buffer, which must be of size crypto::kSHA256Length, which +// will contain the hash. +// |len| is the size of the buffer, which must be crypto::kSHA256Length. +void SHA256Hash(const base::MemoryMappedFile& mapped_file, + void* result, + size_t len) { + CHECK_EQ(crypto::kSHA256Length, len); + scoped_ptr<crypto::SecureHash> secure_hash( + crypto::SecureHash::Create(crypto::SecureHash::SHA256)); + secure_hash->Update(mapped_file.data(), mapped_file.length()); + secure_hash->Finish(result, len); +} + +// This will serialize the file to disk as JSON. The format is: +// { +// "Version": 0x10, +// "HashAlgorithm": SecureHash::SHA256, +// "Hash": <Base64 Encoded Hash>, +// "PluginPath": /path/to/component/updated/flash.so, +// "PluginVersion": "1.0.0.1" +// } +bool WriteToDisk(const int version, + const crypto::SecureHash::Algorithm algorithm, + const std::string& hash, + const base::FilePath& plugin_path, + const std::string& flash_version) { + base::FilePath hint_file_path; + if (!PathService::Get(chrome::FILE_COMPONENT_FLASH_HINT, &hint_file_path)) + return false; + + std::string encoded_hash; + base::Base64Encode(hash, &encoded_hash); + + // Now construct a Value object to convert to JSON. + base::DictionaryValue dict; + dict.SetInteger(kVersionField, version); + dict.SetInteger(kHashAlgoField, crypto::SecureHash::SHA256); + dict.SetString(kHashField, encoded_hash); + dict.SetString(kPluginPath, plugin_path.value()); + dict.SetString(kPluginVersion, flash_version); + // Do the serialization of the DictionaryValue to JSON. + std::string json_string; + JSONStringValueSerializer serializer(&json_string); + if (!serializer.Serialize(dict)) + return false; + + return base::ImportantFileWriter::WriteFileAtomically(hint_file_path, + json_string); +} + +} // namespace + +bool TestExecutableMapping(const base::FilePath& path) { + const base::ScopedFD fd( + HANDLE_EINTR(open(path.value().c_str(), O_RDONLY | O_CLOEXEC))); + if (!fd.is_valid()) + return false; + const size_t map_size = sizeof(uint8_t); + const MmapDeleter deleter(map_size); + scoped_ptr<uint8_t, MmapDeleter> buf_ptr( + reinterpret_cast<uint8_t*>(mmap(nullptr, map_size, PROT_READ | PROT_EXEC, + MAP_PRIVATE, fd.get(), 0)), + deleter); + return buf_ptr.get() != MAP_FAILED; +} + +bool RecordFlashUpdate(const base::FilePath& unpacked_plugin, + const base::FilePath& moved_plugin, + const std::string& version) { + base::MemoryMappedFile mapped_file; + if (!mapped_file.Initialize(unpacked_plugin)) + return false; + + std::string hash(crypto::kSHA256Length, 0); + SHA256Hash(mapped_file, string_as_array(&hash), hash.size()); + + return WriteToDisk(kCurrentHintFileVersion, + crypto::SecureHash::Algorithm::SHA256, hash, moved_plugin, + version); +} + +bool DoesHintFileExist() { + base::FilePath hint_file_path; + if (!PathService::Get(chrome::FILE_COMPONENT_FLASH_HINT, &hint_file_path)) + return false; + return base::PathExists(hint_file_path); +} + +bool VerifyAndReturnFlashLocation(base::FilePath* path, + std::string* flash_version) { + base::FilePath hint_file_path; + if (!PathService::Get(chrome::FILE_COMPONENT_FLASH_HINT, &hint_file_path)) + return false; + + std::string json_string; + if (!base::ReadFileToString(hint_file_path, &json_string)) + return false; + + int error_code; + std::string error_message; + JSONStringValueDeserializer deserializer(json_string); + const scoped_ptr<base::Value> value( + deserializer.Deserialize(&error_code, &error_message)); + + if (!value) { + LOG(ERROR) + << "Could not deserialize the component updated flash hint file. Error " + << error_code << ": " << error_message; + return false; + } + + base::DictionaryValue* dict = nullptr; + if (!value->GetAsDictionary(&dict)) + return false; + + int version; + if (!dict->GetInteger(kVersionField, &version)) + return false; + if (version < kEarliestHintFileVersion || version > kCurrentHintFileVersion) + return false; + + int hash_algorithm; + if (!dict->GetInteger(kHashAlgoField, &hash_algorithm)) + return false; + if (hash_algorithm != crypto::SecureHash::SHA256) + return false; + + std::string hash; + if (!dict->GetString(kHashField, &hash)) + return false; + + std::string plugin_path_str; + if (!dict->GetString(kPluginPath, &plugin_path_str)) + return false; + + std::string plugin_version_str; + if (!dict->GetString(kPluginVersion, &plugin_version_str)) + return false; + + std::string decoded_hash; + if (!base::Base64Decode(hash, &decoded_hash)) + return false; + + const base::FilePath plugin_path(plugin_path_str); + base::MemoryMappedFile plugin_file; + if (!plugin_file.Initialize(plugin_path)) + return false; + + std::vector<uint8_t> file_hash(crypto::kSHA256Length, 0); + SHA256Hash(plugin_file, &file_hash[0], file_hash.size()); + if (!crypto::SecureMemEqual(&file_hash[0], string_as_array(&decoded_hash), + crypto::kSHA256Length)) { + LOG(ERROR) + << "The hash recorded in the component flash hint file does not " + "match the actual hash of the flash plugin found on disk. The " + "component flash plugin will not be loaded."; + return false; + } + + *path = plugin_path; + flash_version->assign(plugin_version_str); + return true; +} + +} // namespace component_flash_hint_file + +} // namespace chrome diff --git a/chrome/common/component_flash_hint_file_linux.h b/chrome/common/component_flash_hint_file_linux.h new file mode 100644 index 0000000..abe0701 --- /dev/null +++ b/chrome/common/component_flash_hint_file_linux.h @@ -0,0 +1,52 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_COMMON_COMPONENT_FLASH_HINT_FILE_LINUX_H_ +#define CHROME_COMMON_COMPONENT_FLASH_HINT_FILE_LINUX_H_ + +#include "build/build_config.h" + +#if defined(OS_LINUX) + +#include <string> + +namespace base { +class FilePath; +} + +namespace chrome { + +// The APIs in this namespace wraps the component updated flash hint file, which +// lives inside the PepperFlash folder of the user-data-dir, so that the Linux +// zygote process can preload the right version of flash. +namespace component_flash_hint_file { + +// Records a new flash update into the hint file. +// |unpacked_plugin| is the current location of the plugin. +// |moved_plugin| is the location where the plugin will be loaded from. +bool RecordFlashUpdate(const base::FilePath& unpacked_plugin, + const base::FilePath& moved_plugin, + const std::string& version); + +// Reports whether or not a hints file exists. +bool DoesHintFileExist(); + +// Return the path of the component updated flash plugin, only if the file has +// the correct hash sum. +// |path| will be populated with the path to the flash plugin. +// |version| will be populated with the version of the flash plugin. +bool VerifyAndReturnFlashLocation(base::FilePath* path, std::string* version); + +// Test if the specified plugin file can be mapped executable. +// This is useful to test if the flash plugin is in a directory mounted +// noexec, in which case Chrome will not be able to load and use the plugin. +// |path| is the path of the flash plugin that will mapped executable. +bool TestExecutableMapping(const base::FilePath& path); + +} // namespace component_flash_hint_file + +} // namespace chrome + +#endif // defined(OS_LINUX) +#endif // CHROME_COMMON_COMPONENT_FLASH_HINT_FILE_LINUX_H_ diff --git a/chrome/common/component_flash_hint_file_linux_unittest.cc b/chrome/common/component_flash_hint_file_linux_unittest.cc new file mode 100644 index 0000000..52a7a31 --- /dev/null +++ b/chrome/common/component_flash_hint_file_linux_unittest.cc @@ -0,0 +1,167 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/common/component_flash_hint_file_linux.h" + +#include <errno.h> +#include <stdlib.h> +#include <sys/mount.h> + +#include "base/files/file_util.h" +#include "base/files/scoped_temp_dir.h" +#include "base/path_service.h" +#include "base/process/kill.h" +#include "base/test/multiprocess_test.h" +#include "base/test/scoped_path_override.h" +#include "base/test/test_timeouts.h" +#include "chrome/common/chrome_paths.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "testing/multiprocess_func_list.h" + +namespace chrome { + +class ComponentFlashHintFileTest : public base::MultiProcessTest {}; + +TEST_F(ComponentFlashHintFileTest, ExistsTest) { + const base::ScopedPathOverride path_override(chrome::DIR_USER_DATA); + EXPECT_FALSE(component_flash_hint_file::DoesHintFileExist()); +} + +TEST_F(ComponentFlashHintFileTest, InstallTest) { + const base::ScopedPathOverride path_override(chrome::DIR_USER_DATA); + EXPECT_FALSE(component_flash_hint_file::DoesHintFileExist()); + + base::FilePath flash_dir; + ASSERT_TRUE(PathService::Get( + chrome::DIR_COMPONENT_UPDATED_PEPPER_FLASH_PLUGIN, &flash_dir)); + + base::File::Error error; + ASSERT_TRUE(base::CreateDirectoryAndGetError(flash_dir, &error)); + + // Write out a fixed byte array as the flash file. + uint8_t file[] = {0x4c, 0x65, 0x74, 0x20, 0x75, 0x73, + 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x67}; + flash_dir = flash_dir.Append("libflash.so"); + const std::string flash_version = "1.0.0.1"; + ASSERT_EQ(static_cast<int>(sizeof(file)), + base::WriteFile(flash_dir, reinterpret_cast<const char*>(file), + sizeof(file))); + ASSERT_TRUE(component_flash_hint_file::RecordFlashUpdate(flash_dir, flash_dir, + flash_version)); + ASSERT_TRUE(component_flash_hint_file::DoesHintFileExist()); + + // Confirm that the flash plugin can be verified and returned. + base::FilePath returned_flash_path; + std::string version; + ASSERT_TRUE(component_flash_hint_file::VerifyAndReturnFlashLocation( + &returned_flash_path, &version)); + ASSERT_EQ(returned_flash_path, flash_dir); + ASSERT_EQ(version, flash_version); + + // Now "corrupt" the flash file and make sure the checksum fails and nothing + // is returned. + file[0] = 0xAA; + ASSERT_TRUE(base::WriteFile(flash_dir, reinterpret_cast<const char*>(file), + sizeof(file)) == sizeof(file)); + base::FilePath empty_path; + std::string empty_version; + ASSERT_FALSE(component_flash_hint_file::VerifyAndReturnFlashLocation( + &empty_path, &empty_version)); + ASSERT_NE(empty_path, flash_dir); + ASSERT_FALSE(empty_version == flash_version); +} + +TEST_F(ComponentFlashHintFileTest, CorruptionTest) { + const base::ScopedPathOverride path_override(chrome::DIR_USER_DATA); + EXPECT_FALSE(component_flash_hint_file::DoesHintFileExist()); + + base::FilePath flash_dir; + ASSERT_TRUE(PathService::Get( + chrome::DIR_COMPONENT_UPDATED_PEPPER_FLASH_PLUGIN, &flash_dir)); + + base::File::Error error; + ASSERT_TRUE(base::CreateDirectoryAndGetError(flash_dir, &error)); + flash_dir = flash_dir.Append("libflash.so"); + + const uint8_t file[] = {0x56, 0x61, 0x20, 0x67, 0x75, 0x76, + 0x66, 0x20, 0x62, 0x61, 0x72, 0x20}; + ASSERT_TRUE(base::WriteFile(flash_dir, reinterpret_cast<const char*>(file), + sizeof(file)) == sizeof(file)); + const std::string flash_version = "1.0.0.1"; + ASSERT_TRUE(component_flash_hint_file::RecordFlashUpdate(flash_dir, flash_dir, + flash_version)); + ASSERT_TRUE(component_flash_hint_file::DoesHintFileExist()); + + // Now write out a new flash version that will not be moved into place. + const uint8_t updated_file[] = {0x43, 0x72, 0x62, 0x63, 0x79, 0x72, + 0x20, 0x66, 0x7a, 0x76, 0x79, 0x76}; + base::FilePath flash_dir_update; + ASSERT_TRUE(PathService::Get( + chrome::DIR_COMPONENT_UPDATED_PEPPER_FLASH_PLUGIN, &flash_dir_update)); + flash_dir_update = flash_dir_update.Append("other_flash.so"); + ASSERT_TRUE(base::WriteFile(flash_dir_update, + reinterpret_cast<const char*>(updated_file), + sizeof(updated_file)) == sizeof(updated_file)); + ASSERT_TRUE(component_flash_hint_file::RecordFlashUpdate( + flash_dir_update, flash_dir, flash_version)); + // |flash_dir_update| needs to be moved to |flash_dir|, but this test + // deliberately skips that step, so VerifyAndReturnFlashLocation should fail. + base::FilePath failed_flash_dir; + std::string failed_version; + ASSERT_FALSE(component_flash_hint_file::VerifyAndReturnFlashLocation( + &failed_flash_dir, &failed_version)); +} + +TEST_F(ComponentFlashHintFileTest, ExecTest1) { + base::ScopedTempDir temp_dir; + ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); + base::FilePath file_path = temp_dir.path().Append("plugin.so"); + const uint8_t file[] = {0x55, 0x62, 0x79, 0x71, 0x20, + 0x6c, 0x62, 0x68, 0x65, 0x20}; + + ASSERT_TRUE(base::WriteFile(file_path, reinterpret_cast<const char*>(file), + sizeof(file)) == sizeof(file)); + ASSERT_TRUE(component_flash_hint_file::TestExecutableMapping(file_path)); +} + +MULTIPROCESS_TEST_MAIN(NoExecMountTest) { + if (unshare(CLONE_NEWUSER | CLONE_NEWNS) != 0) { + LOG(ERROR) << "This kernel does not support unprivileged namespaces. " + "ExecTest2 will succeed without running."; + return 0; + } + // Now mount a NOEXEC fs. + const unsigned long tmpfs_flags = MS_NODEV | MS_NOSUID | MS_NOEXEC; + base::ScopedTempDir temp_dir; + CHECK(temp_dir.CreateUniqueTempDir()); + CHECK_EQ(0, mount("tmpfs", temp_dir.path().value().c_str(), "tmpfs", + tmpfs_flags, nullptr)); + const base::FilePath file_path = temp_dir.path().Append("plugin.so"); + const uint8_t file[] = {0x56, 0x61, 0x20, 0x67, 0x75, 0x72, + 0x20, 0x70, 0x76, 0x67, 0x6c, 0x20}; + bool test_exec = false; + bool file_written = + base::WriteFile(file_path, reinterpret_cast<const char*>(file), + sizeof(file)) == static_cast<int>(sizeof(file)); + if (file_written) + test_exec = component_flash_hint_file::TestExecutableMapping(file_path); + + if (umount(temp_dir.path().value().c_str()) != 0) + LOG(ERROR) << "Could not unmount directory " << temp_dir.path().value(); + + CHECK(file_written); + CHECK(!test_exec); + return 0; +} + +TEST_F(ComponentFlashHintFileTest, ExecTest2) { + base::Process process = SpawnChild("NoExecMountTest"); + ASSERT_TRUE(process.IsValid()); + int exit_code = 42; + ASSERT_TRUE(process.WaitForExitWithTimeout(TestTimeouts::action_max_timeout(), + &exit_code)); + EXPECT_EQ(0, exit_code); +} + +} // namespace chrome |