// Copyright (c) 2012 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 "extensions/browser/sandboxed_unpacker.h" #include #include #include #include #include #include #include "base/bind.h" #include "base/command_line.h" #include "base/files/file_enumerator.h" #include "base/files/file_util.h" #include "base/i18n/rtl.h" #include "base/json/json_string_value_serializer.h" #include "base/metrics/histogram_macros.h" #include "base/path_service.h" #include "base/sequenced_task_runner.h" #include "base/strings/string_number_conversions.h" #include "base/strings/utf_string_conversions.h" #include "base/task/post_task.h" #include "base/timer/elapsed_timer.h" #include "build/build_config.h" #include "components/crx_file/crx_verifier.h" #include "content/public/browser/browser_task_traits.h" #include "content/public/browser/browser_thread.h" #include "extensions/browser/api/declarative_net_request/index_helper.h" #include "extensions/browser/computed_hashes.h" #include "extensions/browser/extension_file_task_runner.h" #include "extensions/browser/install/crx_install_error.h" #include "extensions/browser/install/sandboxed_unpacker_failure_reason.h" #include "extensions/browser/zipfile_installer.h" #include "extensions/common/api/declarative_net_request/dnr_manifest_data.h" #include "extensions/common/constants.h" #include "extensions/common/extension.h" #include "extensions/common/extension_l10n_util.h" #include "extensions/common/extension_resource_path_normalizer.h" #include "extensions/common/extension_utility_types.h" #include "extensions/common/extensions_client.h" #include "extensions/common/features/feature_channel.h" #include "extensions/common/features/feature_session_type.h" #include "extensions/common/file_util.h" #include "extensions/common/manifest_constants.h" #include "extensions/common/manifest_handlers/default_locale_handler.h" #include "extensions/common/manifest_handlers/icons_handler.h" #include "extensions/common/switches.h" #include "extensions/strings/grit/extensions_strings.h" #include "third_party/skia/include/core/SkBitmap.h" #include "ui/base/l10n/l10n_util.h" #include "ui/gfx/codec/png_codec.h" using base::ASCIIToUTF16; using content::BrowserThread; namespace extensions { namespace { // Work horse for FindWritableTempLocation. Creates a temp file in the folder // and uses NormalizeFilePath to check if the path is junction free. bool VerifyJunctionFreeLocation(base::FilePath* temp_dir) { if (temp_dir->empty()) return false; base::FilePath temp_file; if (!base::CreateTemporaryFileInDir(*temp_dir, &temp_file)) { LOG(ERROR) << temp_dir->value() << " is not writable"; return false; } // NormalizeFilePath requires a non-empty file, so write some data. // If you change the exit points of this function please make sure all // exit points delete this temp file! if (base::WriteFile(temp_file, ".", 1) != 1) { base::DeleteFile(temp_file, false); return false; } base::FilePath normalized_temp_file; bool normalized = base::NormalizeFilePath(temp_file, &normalized_temp_file); if (!normalized) { // If |temp_file| contains a link, the sandbox will block all file // system operations, and the install will fail. LOG(ERROR) << temp_dir->value() << " seem to be on remote drive."; } else { *temp_dir = normalized_temp_file.DirName(); } // Clean up the temp file. base::DeleteFile(temp_file, false); return normalized; } // This function tries to find a location for unpacking the extension archive // that is writable and does not lie on a shared drive so that the sandboxed // unpacking process can write there. If no such location exists we can not // proceed and should fail. // The result will be written to |temp_dir|. The function will write to this // parameter even if it returns false. bool FindWritableTempLocation(const base::FilePath& extensions_dir, base::FilePath* temp_dir) { // On ChromeOS, we will only attempt to unpack extension in cryptohome (profile) // directory to provide additional security/privacy and speed up the rest of // the extension install process. #if !defined(OS_CHROMEOS) base::PathService::Get(base::DIR_TEMP, temp_dir); if (VerifyJunctionFreeLocation(temp_dir)) return true; #endif *temp_dir = file_util::GetInstallTempDir(extensions_dir); if (VerifyJunctionFreeLocation(temp_dir)) return true; // Neither paths is link free chances are good installation will fail. LOG(ERROR) << "Both the %TEMP% folder and the profile seem to be on " << "remote drives or read-only. Installation can not complete!"; return false; } std::set GetMessageCatalogPathsToBeSanitized( const base::FilePath& locales_path) { // Not all folders under _locales have to be valid locales. base::FileEnumerator locales(locales_path, /*recursive=*/false, base::FileEnumerator::DIRECTORIES); std::set message_catalog_paths; std::set all_locales; extension_l10n_util::GetAllLocales(&all_locales); base::FilePath locale_path; while (!(locale_path = locales.Next()).empty()) { if (!extension_l10n_util::ShouldSkipValidation(locales_path, locale_path, all_locales)) { message_catalog_paths.insert(locale_path.Append(kMessagesFilename)); } } return message_catalog_paths; } // Callback for ComputedHashes::Create, compute hashes for all files except // _metadata directory (e.g. computed_hashes.json itself). bool ShouldComputeHashesForResource( const base::FilePath& relative_resource_path) { std::vector components; relative_resource_path.GetComponents(&components); return !components.empty() && components[0] != kMetadataFolder; } base::Optional g_verifier_format_override_for_test; } // namespace SandboxedUnpackerClient::SandboxedUnpackerClient() : RefCountedDeleteOnSequence( content::GetUIThreadTaskRunner({})) { DCHECK_CURRENTLY_ON(BrowserThread::UI); } void SandboxedUnpackerClient::ShouldComputeHashesForOffWebstoreExtension( scoped_refptr extension, base::OnceCallback callback) { std::move(callback).Run(false); } SandboxedUnpacker::ScopedVerifierFormatOverrideForTest:: ScopedVerifierFormatOverrideForTest(crx_file::VerifierFormat format) { DCHECK(!g_verifier_format_override_for_test.has_value()); g_verifier_format_override_for_test = format; } SandboxedUnpacker::ScopedVerifierFormatOverrideForTest:: ~ScopedVerifierFormatOverrideForTest() { g_verifier_format_override_for_test.reset(); } SandboxedUnpacker::SandboxedUnpacker( Manifest::Location location, int creation_flags, const base::FilePath& extensions_dir, const scoped_refptr& unpacker_io_task_runner, SandboxedUnpackerClient* client) : client_(client), extensions_dir_(extensions_dir), location_(location), creation_flags_(creation_flags), unpacker_io_task_runner_(unpacker_io_task_runner) { // Tracking for crbug.com/692069. The location must be valid. If it's invalid, // the utility process kills itself for a bad IPC. CHECK_GT(location, Manifest::INVALID_LOCATION); CHECK_LT(location, Manifest::NUM_LOCATIONS); } bool SandboxedUnpacker::CreateTempDirectory() { DCHECK(unpacker_io_task_runner_->RunsTasksInCurrentSequence()); base::FilePath temp_dir; if (!FindWritableTempLocation(extensions_dir_, &temp_dir)) { ReportFailure(SandboxedUnpackerFailureReason::COULD_NOT_GET_TEMP_DIRECTORY, l10n_util::GetStringFUTF16( IDS_EXTENSION_PACKAGE_INSTALL_ERROR, ASCIIToUTF16("COULD_NOT_GET_TEMP_DIRECTORY"))); return false; } if (!temp_dir_.CreateUniqueTempDirUnderPath(temp_dir)) { ReportFailure( SandboxedUnpackerFailureReason::COULD_NOT_CREATE_TEMP_DIRECTORY, l10n_util::GetStringFUTF16( IDS_EXTENSION_PACKAGE_INSTALL_ERROR, ASCIIToUTF16("COULD_NOT_CREATE_TEMP_DIRECTORY"))); return false; } return true; } void SandboxedUnpacker::StartWithCrx(const CRXFileInfo& crx_info) { // We assume that we are started on the thread that the client wants us // to do file IO on. DCHECK(unpacker_io_task_runner_->RunsTasksInCurrentSequence()); std::string expected_hash; if (!crx_info.expected_hash.empty() && base::CommandLine::ForCurrentProcess()->HasSwitch( extensions::switches::kEnableCrxHashCheck)) { expected_hash = base::ToLowerASCII(crx_info.expected_hash); } if (!CreateTempDirectory()) return; // ReportFailure() already called. // Initialize the path that will eventually contain the unpacked extension. extension_root_ = temp_dir_.GetPath().AppendASCII(kTempExtensionName); // Extract the public key and validate the package. if (!ValidateSignature(crx_info.path, expected_hash, g_verifier_format_override_for_test.value_or( crx_info.required_format))) return; // ValidateSignature() already reported the error. // Copy the crx file into our working directory. base::FilePath temp_crx_path = temp_dir_.GetPath().Append(crx_info.path.BaseName()); if (!base::CopyFile(crx_info.path, temp_crx_path)) { // Failed to copy extension file to temporary directory. ReportFailure( SandboxedUnpackerFailureReason:: FAILED_TO_COPY_EXTENSION_FILE_TO_TEMP_DIRECTORY, l10n_util::GetStringFUTF16( IDS_EXTENSION_PACKAGE_INSTALL_ERROR, ASCIIToUTF16("FAILED_TO_COPY_EXTENSION_FILE_TO_TEMP_DIRECTORY"))); return; } // The utility process will have access to the directory passed to // SandboxedUnpacker. That directory should not contain a symlink or NTFS // reparse point. When the path is used, following the link/reparse point // will cause file system access outside the sandbox path, and the sandbox // will deny the operation. base::FilePath link_free_crx_path; if (!base::NormalizeFilePath(temp_crx_path, &link_free_crx_path)) { LOG(ERROR) << "Could not get the normalized path of " << temp_crx_path.value(); ReportFailure( SandboxedUnpackerFailureReason::COULD_NOT_GET_SANDBOX_FRIENDLY_PATH, l10n_util::GetStringUTF16(IDS_EXTENSION_UNPACK_FAILED)); return; } // Make sure to create the directory where the extension will be unzipped, as // the unzipper service requires it. base::FilePath unzipped_dir = link_free_crx_path.DirName().AppendASCII(kTempExtensionName); base::File::Error error; if (!base::CreateDirectoryAndGetError(unzipped_dir, &error)) { LOG(ERROR) << "Failed to created directory " << unzipped_dir.value() << " with error " << error; ReportFailure(SandboxedUnpackerFailureReason::UNZIP_FAILED, l10n_util::GetStringUTF16(IDS_EXTENSION_PACKAGE_UNZIP_ERROR)); return; } Unzip(link_free_crx_path, unzipped_dir); } void SandboxedUnpacker::StartWithDirectory(const std::string& extension_id, const std::string& public_key, const base::FilePath& directory) { // We assume that we are started on the thread that the client wants us // to do file IO on. DCHECK(unpacker_io_task_runner_->RunsTasksInCurrentSequence()); extension_id_ = extension_id; public_key_ = public_key; if (!CreateTempDirectory()) return; // ReportFailure() already called. extension_root_ = temp_dir_.GetPath().AppendASCII(kTempExtensionName); if (!base::Move(directory, extension_root_)) { LOG(ERROR) << "Could not move " << directory.value() << " to " << extension_root_.value(); ReportFailure( SandboxedUnpackerFailureReason::DIRECTORY_MOVE_FAILED, l10n_util::GetStringFUTF16(IDS_EXTENSION_PACKAGE_INSTALL_ERROR, ASCIIToUTF16("DIRECTORY_MOVE_FAILED"))); return; } Unpack(extension_root_); } SandboxedUnpacker::~SandboxedUnpacker() { // To avoid blocking shutdown, don't delete temporary directory here if it // hasn't been cleaned up or passed on to another owner yet. // This is OK because ExtensionGarbageCollector will take care of the leaked // |temp_dir_| eventually. temp_dir_.Take(); // Make sure that members get deleted on the thread they were created. if (image_sanitizer_) { unpacker_io_task_runner_->DeleteSoon(FROM_HERE, std::move(image_sanitizer_)); } if (json_file_sanitizer_) { unpacker_io_task_runner_->DeleteSoon(FROM_HERE, std::move(json_file_sanitizer_)); } } void SandboxedUnpacker::Unzip(const base::FilePath& crx_path, const base::FilePath& unzipped_dir) { DCHECK(unpacker_io_task_runner_->RunsTasksInCurrentSequence()); DCHECK(crx_path.DirName() == temp_dir_.GetPath()); ZipFileInstaller::Create(base::BindOnce(&SandboxedUnpacker::UnzipDone, this)) ->LoadFromZipFileInDir(crx_path, unzipped_dir); } void SandboxedUnpacker::UnzipDone(const base::FilePath& zip_file, const base::FilePath& unzip_dir, const std::string& error) { DCHECK(unpacker_io_task_runner_->RunsTasksInCurrentSequence()); if (!error.empty()) { ReportFailure(SandboxedUnpackerFailureReason::UNZIP_FAILED, l10n_util::GetStringUTF16(IDS_EXTENSION_PACKAGE_UNZIP_ERROR)); return; } Unpack(unzip_dir); } void SandboxedUnpacker::Unpack(const base::FilePath& directory) { DCHECK(unpacker_io_task_runner_->RunsTasksInCurrentSequence()); DCHECK(directory.DirName() == temp_dir_.GetPath()); base::FilePath manifest_path = extension_root_.Append(kManifestFilename); ParseJsonFile(manifest_path, base::BindOnce(&SandboxedUnpacker::ReadManifestDone, this)); } void SandboxedUnpacker::ReadManifestDone( base::Optional manifest, const base::Optional& error) { DCHECK(unpacker_io_task_runner_->RunsTasksInCurrentSequence()); if (error) { ReportUnpackExtensionFailed(*error); return; } if (!manifest || !manifest->is_dict()) { ReportUnpackExtensionFailed(manifest_errors::kInvalidManifest); return; } std::string error_msg; scoped_refptr extension( Extension::Create(extension_root_, location_, base::Value::AsDictionaryValue(manifest.value()), creation_flags_, extension_id_, &error_msg)); if (!extension) { ReportUnpackExtensionFailed(error_msg); return; } std::vector warnings; if (!file_util::ValidateExtension(extension.get(), &error_msg, &warnings)) { ReportUnpackExtensionFailed(error_msg); return; } extension->AddInstallWarnings(std::move(warnings)); UnpackExtensionSucceeded(std::move(manifest.value())); } void SandboxedUnpacker::UnpackExtensionSucceeded(base::Value manifest) { DCHECK(unpacker_io_task_runner_->RunsTasksInCurrentSequence()); base::Optional final_manifest(RewriteManifestFile(manifest)); if (!final_manifest) return; std::unique_ptr final_manifest_dict = base::DictionaryValue::From( base::Value::ToUniquePtrValue(std::move(final_manifest.value()))); // Create an extension object that refers to the temporary location the // extension was unpacked to. We use this until the extension is finally // installed. For example, the install UI shows images from inside the // extension. // Localize manifest now, so confirm UI gets correct extension name. // TODO(rdevlin.cronin): Continue removing std::string errors and replacing // with base::string16 std::string utf8_error; if (!extension_l10n_util::LocalizeExtension( extension_root_, final_manifest_dict.get(), extension_l10n_util::GzippedMessagesPermission::kDisallow, &utf8_error)) { ReportFailure( SandboxedUnpackerFailureReason::COULD_NOT_LOCALIZE_EXTENSION, l10n_util::GetStringFUTF16(IDS_EXTENSION_PACKAGE_ERROR_MESSAGE, base::UTF8ToUTF16(utf8_error))); return; } extension_ = Extension::Create(extension_root_, location_, *final_manifest_dict, Extension::REQUIRE_KEY | creation_flags_, &utf8_error); if (!extension_.get()) { ReportFailure(SandboxedUnpackerFailureReason::INVALID_MANIFEST, ASCIIToUTF16("Manifest is invalid: " + utf8_error)); return; } // The install icon path may be empty, which is OK, but if it is not it should // be normalized successfully. const std::string& original_install_icon_path = IconsInfo::GetIcons(extension_.get()) .Get(extension_misc::EXTENSION_ICON_LARGE, ExtensionIconSet::MATCH_BIGGER); if (!original_install_icon_path.empty() && !NormalizeExtensionResourcePath( base::FilePath::FromUTF8Unsafe(original_install_icon_path), &install_icon_path_)) { // Invalid path for browser image. ReportFailure( SandboxedUnpackerFailureReason::INVALID_PATH_FOR_BROWSER_IMAGE, l10n_util::GetStringFUTF16( IDS_EXTENSION_PACKAGE_INSTALL_ERROR, ASCIIToUTF16("INVALID_PATH_FOR_BROWSER_IMAGE"))); return; } manifest_ = std::move(manifest); DCHECK(!image_sanitizer_); std::set image_paths = ExtensionsClient::Get()->GetBrowserImagePaths(extension_.get()); image_sanitizer_ = ImageSanitizer::CreateAndStart( &data_decoder_, extension_root_, image_paths, base::BindRepeating(&SandboxedUnpacker::ImageSanitizerDecodedImage, this), base::BindOnce(&SandboxedUnpacker::ImageSanitizationDone, this)); } void SandboxedUnpacker::ImageSanitizerDecodedImage(const base::FilePath& path, SkBitmap image) { if (path == install_icon_path_) install_icon_ = image; } void SandboxedUnpacker::ImageSanitizationDone( ImageSanitizer::Status status, const base::FilePath& file_path_for_error) { if (status == ImageSanitizer::Status::kSuccess) { // Next step is to sanitize the message catalogs. ReadMessageCatalogs(); return; } SandboxedUnpackerFailureReason failure_reason = SandboxedUnpackerFailureReason::UNPACKER_CLIENT_FAILED; base::string16 error; switch (status) { case ImageSanitizer::Status::kImagePathError: failure_reason = SandboxedUnpackerFailureReason::INVALID_PATH_FOR_BROWSER_IMAGE; error = l10n_util::GetStringFUTF16( IDS_EXTENSION_PACKAGE_INSTALL_ERROR, ASCIIToUTF16("INVALID_PATH_FOR_BROWSER_IMAGE")); break; case ImageSanitizer::Status::kFileReadError: case ImageSanitizer::Status::kDecodingError: error = l10n_util::GetStringFUTF16( IDS_EXTENSION_PACKAGE_IMAGE_ERROR, base::i18n::GetDisplayStringInLTRDirectionality( file_path_for_error.BaseName().LossyDisplayName())); break; case ImageSanitizer::Status::kFileDeleteError: failure_reason = SandboxedUnpackerFailureReason::ERROR_REMOVING_OLD_IMAGE_FILE; error = l10n_util::GetStringFUTF16( IDS_EXTENSION_PACKAGE_INSTALL_ERROR, ASCIIToUTF16("ERROR_REMOVING_OLD_IMAGE_FILE")); break; case ImageSanitizer::Status::kEncodingError: failure_reason = SandboxedUnpackerFailureReason::ERROR_RE_ENCODING_THEME_IMAGE; error = l10n_util::GetStringFUTF16( IDS_EXTENSION_PACKAGE_INSTALL_ERROR, ASCIIToUTF16("ERROR_RE_ENCODING_THEME_IMAGE")); break; case ImageSanitizer::Status::kFileWriteError: failure_reason = SandboxedUnpackerFailureReason::ERROR_SAVING_THEME_IMAGE; error = l10n_util::GetStringFUTF16(IDS_EXTENSION_PACKAGE_INSTALL_ERROR, ASCIIToUTF16("ERROR_SAVING_THEME_IMAGE")); break; case ImageSanitizer::Status::kServiceError: failure_reason = SandboxedUnpackerFailureReason:: UTILITY_PROCESS_CRASHED_WHILE_TRYING_TO_INSTALL; error = l10n_util::GetStringFUTF16( IDS_EXTENSION_PACKAGE_INSTALL_ERROR, ASCIIToUTF16("ERROR_UTILITY_PROCESS_CRASH")); break; default: NOTREACHED(); break; } ReportFailure(failure_reason, error); } void SandboxedUnpacker::ReadMessageCatalogs() { DCHECK(unpacker_io_task_runner_->RunsTasksInCurrentSequence()); if (LocaleInfo::GetDefaultLocale(extension_.get()).empty()) { MessageCatalogsSanitized(JsonFileSanitizer::Status::kSuccess, std::string()); return; } // Get the paths to the message catalogs we should sanitize on the file task // runner. base::FilePath locales_path = extension_root_.Append(kLocaleFolder); base::PostTaskAndReplyWithResult( extensions::GetExtensionFileTaskRunner().get(), FROM_HERE, base::BindOnce(&GetMessageCatalogPathsToBeSanitized, locales_path), base::BindOnce(&SandboxedUnpacker::SanitizeMessageCatalogs, this)); } void SandboxedUnpacker::SanitizeMessageCatalogs( const std::set& message_catalog_paths) { DCHECK(unpacker_io_task_runner_->RunsTasksInCurrentSequence()); json_file_sanitizer_ = JsonFileSanitizer::CreateAndStart( &data_decoder_, message_catalog_paths, base::BindOnce(&SandboxedUnpacker::MessageCatalogsSanitized, this)); } void SandboxedUnpacker::MessageCatalogsSanitized( JsonFileSanitizer::Status status, const std::string& error_msg) { DCHECK(unpacker_io_task_runner_->RunsTasksInCurrentSequence()); if (status == JsonFileSanitizer::Status::kSuccess) { IndexAndPersistJSONRulesetsIfNeeded(); return; } SandboxedUnpackerFailureReason failure_reason = SandboxedUnpackerFailureReason::UNPACKER_CLIENT_FAILED; base::string16 error; switch (status) { case JsonFileSanitizer::Status::kFileReadError: case JsonFileSanitizer::Status::kDecodingError: failure_reason = SandboxedUnpackerFailureReason::INVALID_CATALOG_DATA; error = l10n_util::GetStringFUTF16(IDS_EXTENSION_PACKAGE_INSTALL_ERROR, ASCIIToUTF16("INVALID_CATALOG_DATA")); break; case JsonFileSanitizer::Status::kSerializingError: failure_reason = SandboxedUnpackerFailureReason::ERROR_SERIALIZING_CATALOG; error = l10n_util::GetStringFUTF16(IDS_EXTENSION_PACKAGE_INSTALL_ERROR, ASCIIToUTF16("ERROR_SERIALIZING_CATALOG")); break; case JsonFileSanitizer::Status::kFileDeleteError: case JsonFileSanitizer::Status::kFileWriteError: failure_reason = SandboxedUnpackerFailureReason::ERROR_SAVING_CATALOG; error = l10n_util::GetStringFUTF16(IDS_EXTENSION_PACKAGE_INSTALL_ERROR, ASCIIToUTF16("ERROR_SAVING_CATALOG")); break; default: NOTREACHED(); break; } ReportFailure(failure_reason, error); } void SandboxedUnpacker::IndexAndPersistJSONRulesetsIfNeeded() { DCHECK(unpacker_io_task_runner_->RunsTasksInCurrentSequence()); DCHECK(extension_); declarative_net_request::IndexHelper::IndexStaticRulesets( *extension_, base::BindOnce(&SandboxedUnpacker::OnJSONRulesetsIndexed, this)); } void SandboxedUnpacker::OnJSONRulesetsIndexed( declarative_net_request::IndexHelper::Result result) { if (result.error) { ReportFailure( SandboxedUnpackerFailureReason::ERROR_INDEXING_DNR_RULESET, l10n_util::GetStringFUTF16(IDS_EXTENSION_PACKAGE_ERROR_MESSAGE, base::UTF8ToUTF16(*result.error))); return; } if (!result.warnings.empty()) extension_->AddInstallWarnings(std::move(result.warnings)); ruleset_checksums_ = std::move(result.ruleset_checksums); CheckComputeHashes(); } void SandboxedUnpacker::CheckComputeHashes() { DCHECK(unpacker_io_task_runner_->RunsTasksInCurrentSequence()); client_->ShouldComputeHashesForOffWebstoreExtension( extension_, base::BindOnce(&SandboxedUnpacker::MaybeComputeHashes, this)); } void SandboxedUnpacker::MaybeComputeHashes(bool should_compute) { DCHECK(unpacker_io_task_runner_->RunsTasksInCurrentSequence()); if (!should_compute) { ReportSuccess(); return; } base::ElapsedTimer timer; base::Optional computed_hashes_data = ComputedHashes::Compute( extension_->path(), extension_misc::kContentVerificationDefaultBlockSize, IsCancelledCallback(), base::BindRepeating(&ShouldComputeHashesForResource)); bool success = computed_hashes_data && ComputedHashes(std::move(*computed_hashes_data)) .WriteToFile(file_util::GetComputedHashesPath(extension_->path())); UMA_HISTOGRAM_BOOLEAN( "Extensions.ContentVerification.ComputeHashesOnInstallResult", success); if (success) { UMA_HISTOGRAM_TIMES( "Extensions.ContentVerification.ComputeHashesOnInstallTime", timer.Elapsed()); } else { LOG(ERROR) << "[extension " << extension_->id() << "] Failed to create computed_hashes.json"; } ReportSuccess(); } data_decoder::mojom::JsonParser* SandboxedUnpacker::GetJsonParserPtr() { DCHECK(unpacker_io_task_runner_->RunsTasksInCurrentSequence()); if (!json_parser_) { data_decoder_.GetService()->BindJsonParser( json_parser_.BindNewPipeAndPassReceiver()); json_parser_.set_disconnect_handler(base::BindOnce( &SandboxedUnpacker::ReportFailure, this, SandboxedUnpackerFailureReason:: UTILITY_PROCESS_CRASHED_WHILE_TRYING_TO_INSTALL, l10n_util::GetStringFUTF16( IDS_EXTENSION_PACKAGE_INSTALL_ERROR, ASCIIToUTF16("UTILITY_PROCESS_CRASHED_WHILE_TRYING_TO_INSTALL")) + ASCIIToUTF16(". ") + l10n_util::GetStringUTF16(IDS_EXTENSION_INSTALL_PROCESS_CRASHED))); } return json_parser_.get(); } void SandboxedUnpacker::ReportUnpackExtensionFailed(base::StringPiece error) { DCHECK(unpacker_io_task_runner_->RunsTasksInCurrentSequence()); ReportFailure(SandboxedUnpackerFailureReason::UNPACKER_CLIENT_FAILED, l10n_util::GetStringFUTF16(IDS_EXTENSION_PACKAGE_ERROR_MESSAGE, base::UTF8ToUTF16(error))); } base::string16 SandboxedUnpacker::FailureReasonToString16( const SandboxedUnpackerFailureReason reason) { switch (reason) { case SandboxedUnpackerFailureReason::COULD_NOT_GET_TEMP_DIRECTORY: return ASCIIToUTF16("COULD_NOT_GET_TEMP_DIRECTORY"); case SandboxedUnpackerFailureReason::COULD_NOT_CREATE_TEMP_DIRECTORY: return ASCIIToUTF16("COULD_NOT_CREATE_TEMP_DIRECTORY"); case SandboxedUnpackerFailureReason:: FAILED_TO_COPY_EXTENSION_FILE_TO_TEMP_DIRECTORY: return ASCIIToUTF16("FAILED_TO_COPY_EXTENSION_FILE_TO_TEMP_DIRECTORY"); case SandboxedUnpackerFailureReason::COULD_NOT_GET_SANDBOX_FRIENDLY_PATH: return ASCIIToUTF16("COULD_NOT_GET_SANDBOX_FRIENDLY_PATH"); case SandboxedUnpackerFailureReason::COULD_NOT_LOCALIZE_EXTENSION: return ASCIIToUTF16("COULD_NOT_LOCALIZE_EXTENSION"); case SandboxedUnpackerFailureReason::INVALID_MANIFEST: return ASCIIToUTF16("INVALID_MANIFEST"); case SandboxedUnpackerFailureReason::UNPACKER_CLIENT_FAILED: return ASCIIToUTF16("UNPACKER_CLIENT_FAILED"); case SandboxedUnpackerFailureReason:: UTILITY_PROCESS_CRASHED_WHILE_TRYING_TO_INSTALL: return ASCIIToUTF16("UTILITY_PROCESS_CRASHED_WHILE_TRYING_TO_INSTALL"); case SandboxedUnpackerFailureReason::CRX_FILE_NOT_READABLE: return ASCIIToUTF16("CRX_FILE_NOT_READABLE"); case SandboxedUnpackerFailureReason::CRX_HEADER_INVALID: return ASCIIToUTF16("CRX_HEADER_INVALID"); case SandboxedUnpackerFailureReason::CRX_MAGIC_NUMBER_INVALID: return ASCIIToUTF16("CRX_MAGIC_NUMBER_INVALID"); case SandboxedUnpackerFailureReason::CRX_VERSION_NUMBER_INVALID: return ASCIIToUTF16("CRX_VERSION_NUMBER_INVALID"); case SandboxedUnpackerFailureReason::CRX_EXCESSIVELY_LARGE_KEY_OR_SIGNATURE: return ASCIIToUTF16("CRX_EXCESSIVELY_LARGE_KEY_OR_SIGNATURE"); case SandboxedUnpackerFailureReason::CRX_ZERO_KEY_LENGTH: return ASCIIToUTF16("CRX_ZERO_KEY_LENGTH"); case SandboxedUnpackerFailureReason::CRX_ZERO_SIGNATURE_LENGTH: return ASCIIToUTF16("CRX_ZERO_SIGNATURE_LENGTH"); case SandboxedUnpackerFailureReason::CRX_PUBLIC_KEY_INVALID: return ASCIIToUTF16("CRX_PUBLIC_KEY_INVALID"); case SandboxedUnpackerFailureReason::CRX_SIGNATURE_INVALID: return ASCIIToUTF16("CRX_SIGNATURE_INVALID"); case SandboxedUnpackerFailureReason:: CRX_SIGNATURE_VERIFICATION_INITIALIZATION_FAILED: return ASCIIToUTF16("CRX_SIGNATURE_VERIFICATION_INITIALIZATION_FAILED"); case SandboxedUnpackerFailureReason::CRX_SIGNATURE_VERIFICATION_FAILED: return ASCIIToUTF16("CRX_SIGNATURE_VERIFICATION_FAILED"); case SandboxedUnpackerFailureReason::CRX_FILE_IS_DELTA_UPDATE: return ASCIIToUTF16("CRX_FILE_IS_DELTA_UPDATE"); case SandboxedUnpackerFailureReason::CRX_EXPECTED_HASH_INVALID: return ASCIIToUTF16("CRX_EXPECTED_HASH_INVALID"); case SandboxedUnpackerFailureReason::ERROR_SERIALIZING_MANIFEST_JSON: return ASCIIToUTF16("ERROR_SERIALIZING_MANIFEST_JSON"); case SandboxedUnpackerFailureReason::ERROR_SAVING_MANIFEST_JSON: return ASCIIToUTF16("ERROR_SAVING_MANIFEST_JSON"); case SandboxedUnpackerFailureReason::INVALID_PATH_FOR_BROWSER_IMAGE: return ASCIIToUTF16("INVALID_PATH_FOR_BROWSER_IMAGE"); case SandboxedUnpackerFailureReason::ERROR_REMOVING_OLD_IMAGE_FILE: return ASCIIToUTF16("ERROR_REMOVING_OLD_IMAGE_FILE"); case SandboxedUnpackerFailureReason::INVALID_PATH_FOR_BITMAP_IMAGE: return ASCIIToUTF16("INVALID_PATH_FOR_BITMAP_IMAGE"); case SandboxedUnpackerFailureReason::ERROR_RE_ENCODING_THEME_IMAGE: return ASCIIToUTF16("ERROR_RE_ENCODING_THEME_IMAGE"); case SandboxedUnpackerFailureReason::ERROR_SAVING_THEME_IMAGE: return ASCIIToUTF16("ERROR_SAVING_THEME_IMAGE"); case SandboxedUnpackerFailureReason::INVALID_CATALOG_DATA: return ASCIIToUTF16("INVALID_CATALOG_DATA"); case SandboxedUnpackerFailureReason::ERROR_SERIALIZING_CATALOG: return ASCIIToUTF16("ERROR_SERIALIZING_CATALOG"); case SandboxedUnpackerFailureReason::ERROR_SAVING_CATALOG: return ASCIIToUTF16("ERROR_SAVING_CATALOG"); case SandboxedUnpackerFailureReason::CRX_HASH_VERIFICATION_FAILED: return ASCIIToUTF16("CRX_HASH_VERIFICATION_FAILED"); case SandboxedUnpackerFailureReason::UNZIP_FAILED: return ASCIIToUTF16("UNZIP_FAILED"); case SandboxedUnpackerFailureReason::DIRECTORY_MOVE_FAILED: return ASCIIToUTF16("DIRECTORY_MOVE_FAILED"); case SandboxedUnpackerFailureReason::ERROR_INDEXING_DNR_RULESET: return ASCIIToUTF16("ERROR_INDEXING_DNR_RULESET"); case SandboxedUnpackerFailureReason::CRX_REQUIRED_PROOF_MISSING: return ASCIIToUTF16("CRX_REQUIRED_PROOF_MISSING"); case SandboxedUnpackerFailureReason::DEPRECATED_ABORTED_DUE_TO_SHUTDOWN: case SandboxedUnpackerFailureReason::DEPRECATED_ERROR_PARSING_DNR_RULESET: case SandboxedUnpackerFailureReason::NUM_FAILURE_REASONS: default: NOTREACHED(); return base::string16(); } } void SandboxedUnpacker::FailWithPackageError( const SandboxedUnpackerFailureReason reason) { ReportFailure(reason, l10n_util::GetStringFUTF16(IDS_EXTENSION_PACKAGE_ERROR_CODE, FailureReasonToString16(reason))); } bool SandboxedUnpacker::ValidateSignature( const base::FilePath& crx_path, const std::string& expected_hash, const crx_file::VerifierFormat required_format) { std::vector hash; if (!expected_hash.empty()) { if (!base::HexStringToBytes(expected_hash, &hash)) { FailWithPackageError( SandboxedUnpackerFailureReason::CRX_EXPECTED_HASH_INVALID); return false; } } const crx_file::VerifierResult result = crx_file::Verify( crx_path, required_format, std::vector>(), hash, &public_key_, &extension_id_); switch (result) { case crx_file::VerifierResult::OK_FULL: { if (!expected_hash.empty()) UMA_HISTOGRAM_BOOLEAN("Extensions.SandboxUnpackHashCheck", true); return true; } case crx_file::VerifierResult::OK_DELTA: FailWithPackageError( SandboxedUnpackerFailureReason::CRX_FILE_IS_DELTA_UPDATE); break; case crx_file::VerifierResult::ERROR_FILE_NOT_READABLE: FailWithPackageError( SandboxedUnpackerFailureReason::CRX_FILE_NOT_READABLE); break; case crx_file::VerifierResult::ERROR_HEADER_INVALID: FailWithPackageError(SandboxedUnpackerFailureReason::CRX_HEADER_INVALID); break; case crx_file::VerifierResult::ERROR_SIGNATURE_INITIALIZATION_FAILED: FailWithPackageError( SandboxedUnpackerFailureReason:: CRX_SIGNATURE_VERIFICATION_INITIALIZATION_FAILED); break; case crx_file::VerifierResult::ERROR_SIGNATURE_VERIFICATION_FAILED: FailWithPackageError( SandboxedUnpackerFailureReason::CRX_SIGNATURE_VERIFICATION_FAILED); break; case crx_file::VerifierResult::ERROR_EXPECTED_HASH_INVALID: FailWithPackageError( SandboxedUnpackerFailureReason::CRX_EXPECTED_HASH_INVALID); break; case crx_file::VerifierResult::ERROR_REQUIRED_PROOF_MISSING: FailWithPackageError( SandboxedUnpackerFailureReason::CRX_REQUIRED_PROOF_MISSING); break; case crx_file::VerifierResult::ERROR_FILE_HASH_FAILED: // We should never get this result unless we had specifically asked for // verification of the crx file's hash. CHECK(!expected_hash.empty()); UMA_HISTOGRAM_BOOLEAN("Extensions.SandboxUnpackHashCheck", false); FailWithPackageError( SandboxedUnpackerFailureReason::CRX_HASH_VERIFICATION_FAILED); break; } return false; } void SandboxedUnpacker::ReportFailure( const SandboxedUnpackerFailureReason reason, const base::string16& error) { DCHECK(unpacker_io_task_runner_->RunsTasksInCurrentSequence()); UMA_HISTOGRAM_ENUMERATION( "Extensions.SandboxUnpackFailureReason", reason, SandboxedUnpackerFailureReason::NUM_FAILURE_REASONS); Cleanup(); client_->OnUnpackFailure(CrxInstallError(reason, error)); } void SandboxedUnpacker::ReportSuccess() { DCHECK(unpacker_io_task_runner_->RunsTasksInCurrentSequence()); UMA_HISTOGRAM_COUNTS_1M("Extensions.SandboxUnpackSuccess", 1); DCHECK(!temp_dir_.GetPath().empty()); // Client takes ownership of temporary directory, manifest, and extension. client_->OnUnpackSuccess( temp_dir_.Take(), extension_root_, base::DictionaryValue::From( base::Value::ToUniquePtrValue(std::move(manifest_.value()))), extension_.get(), install_icon_, std::move(ruleset_checksums_)); // Interestingly, the C++ standard doesn't guarantee that a moved-from vector // is empty. ruleset_checksums_.clear(); extension_.reset(); Cleanup(); } base::Optional SandboxedUnpacker::RewriteManifestFile( const base::Value& manifest) { constexpr int64_t kMaxFingerprintSize = 1024; // Add the public key extracted earlier to the parsed manifest and overwrite // the original manifest. We do this to ensure the manifest doesn't contain an // exploitable bug that could be used to compromise the browser. DCHECK(!public_key_.empty()); base::Value final_manifest = manifest.Clone(); final_manifest.SetStringKey(manifest_keys::kPublicKey, public_key_); { std::string differential_fingerprint; if (base::ReadFileToStringWithMaxSize( extension_root_.Append(kDifferentialFingerprintFilename), &differential_fingerprint, kMaxFingerprintSize)) { final_manifest.SetStringKey(manifest_keys::kDifferentialFingerprint, std::move(differential_fingerprint)); } } std::string manifest_json; JSONStringValueSerializer serializer(&manifest_json); serializer.set_pretty_print(true); if (!serializer.Serialize(final_manifest)) { // Error serializing manifest.json. ReportFailure( SandboxedUnpackerFailureReason::ERROR_SERIALIZING_MANIFEST_JSON, l10n_util::GetStringFUTF16( IDS_EXTENSION_PACKAGE_INSTALL_ERROR, ASCIIToUTF16("ERROR_SERIALIZING_MANIFEST_JSON"))); return base::nullopt; } base::FilePath manifest_path = extension_root_.Append(kManifestFilename); int size = base::checked_cast(manifest_json.size()); if (base::WriteFile(manifest_path, manifest_json.data(), size) != size) { // Error saving manifest.json. ReportFailure( SandboxedUnpackerFailureReason::ERROR_SAVING_MANIFEST_JSON, l10n_util::GetStringFUTF16(IDS_EXTENSION_PACKAGE_INSTALL_ERROR, ASCIIToUTF16("ERROR_SAVING_MANIFEST_JSON"))); return base::nullopt; } return std::move(final_manifest); } void SandboxedUnpacker::Cleanup() { DCHECK(unpacker_io_task_runner_->RunsTasksInCurrentSequence()); if (temp_dir_.IsValid() && !temp_dir_.Delete()) { LOG(WARNING) << "Can not delete temp directory at " << temp_dir_.GetPath().value(); } image_sanitizer_.reset(); json_file_sanitizer_.reset(); json_parser_.reset(); } void SandboxedUnpacker::ParseJsonFile( const base::FilePath& path, data_decoder::mojom::JsonParser::ParseCallback callback) { DCHECK(unpacker_io_task_runner_->RunsTasksInCurrentSequence()); std::string contents; if (!base::ReadFileToString(path, &contents)) { std::move(callback).Run( /*value=*/base::nullopt, /*error=*/base::Optional("File doesn't exist.")); return; } GetJsonParserPtr()->Parse(contents, std::move(callback)); } } // namespace extensions