// Copyright 2013 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/browser/component_updater/component_patcher_operation.h" #include #include #include "base/bind.h" #include "base/file_util.h" #include "base/files/memory_mapped_file.h" #include "base/json/json_file_value_serializer.h" #include "base/memory/scoped_handle.h" #include "base/path_service.h" #include "base/strings/string_number_conversions.h" #include "chrome/browser/component_updater/component_patcher.h" #include "chrome/browser/component_updater/component_updater_service.h" #include "chrome/common/chrome_utility_messages.h" #include "chrome/common/extensions/extension_constants.h" #include "content/public/browser/browser_thread.h" #include "content/public/browser/utility_process_host.h" #include "courgette/courgette.h" #include "courgette/third_party/bsdiff.h" #include "crypto/secure_hash.h" #include "crypto/sha2.h" #include "crypto/signature_verifier.h" #include "extensions/common/crx_file.h" #include "ipc/ipc_message_macros.h" #include "third_party/zlib/google/zip.h" using crypto::SecureHash; namespace component_updater { namespace { const char kInput[] = "input"; const char kOp[] = "op"; const char kOutput[] = "output"; const char kPatch[] = "patch"; const char kSha256[] = "sha256"; // The integer offset disambiguates between overlapping error ranges. const int kCourgetteErrorOffset = 300; const int kBsdiffErrorOffset = 600; class CourgetteTraits : public DeltaUpdateOpPatchStrategy { public: virtual int GetErrorOffset() const OVERRIDE; virtual int GetSuccessCode() const OVERRIDE; virtual scoped_ptr GetPatchMessage( base::FilePath input_abs_path, base::FilePath patch_abs_path, base::FilePath output_abs_path) OVERRIDE; virtual int Patch(base::FilePath input_abs_path, base::FilePath patch_abs_path, base::FilePath output_abs_path) OVERRIDE; }; int CourgetteTraits::GetErrorOffset() const { return kCourgetteErrorOffset; } int CourgetteTraits::GetSuccessCode() const { return courgette::C_OK; } scoped_ptr CourgetteTraits::GetPatchMessage( base::FilePath input_abs_path, base::FilePath patch_abs_path, base::FilePath output_abs_path) { return scoped_ptr( new ChromeUtilityMsg_PatchFileCourgette(input_abs_path, patch_abs_path, output_abs_path)); } int CourgetteTraits::Patch(base::FilePath input_abs_path, base::FilePath patch_abs_path, base::FilePath output_abs_path) { return courgette::ApplyEnsemblePatch(input_abs_path.value().c_str(), patch_abs_path.value().c_str(), output_abs_path.value().c_str()); } class BsdiffTraits : public DeltaUpdateOpPatchStrategy { public: virtual int GetErrorOffset() const OVERRIDE; virtual int GetSuccessCode() const OVERRIDE; virtual scoped_ptr GetPatchMessage( base::FilePath input_abs_path, base::FilePath patch_abs_path, base::FilePath output_abs_path) OVERRIDE; virtual int Patch(base::FilePath input_abs_path, base::FilePath patch_abs_path, base::FilePath output_abs_path) OVERRIDE; }; int BsdiffTraits::GetErrorOffset() const { return kBsdiffErrorOffset; } int BsdiffTraits::GetSuccessCode() const { return courgette::OK; } scoped_ptr BsdiffTraits::GetPatchMessage( base::FilePath input_abs_path, base::FilePath patch_abs_path, base::FilePath output_abs_path) { return scoped_ptr( new ChromeUtilityMsg_PatchFileBsdiff(input_abs_path, patch_abs_path, output_abs_path)); } int BsdiffTraits::Patch(base::FilePath input_abs_path, base::FilePath patch_abs_path, base::FilePath output_abs_path) { return courgette::ApplyBinaryPatch(input_abs_path, patch_abs_path, output_abs_path); } } // namespace DeltaUpdateOpPatchStrategy::~DeltaUpdateOpPatchStrategy() { } DeltaUpdateOp* CreateDeltaUpdateOp(const std::string& operation) { if (operation == "copy") { return new DeltaUpdateOpCopy(); } else if (operation == "create") { return new DeltaUpdateOpCreate(); } else if (operation == "bsdiff") { scoped_ptr strategy(new BsdiffTraits()); return new DeltaUpdateOpPatch(strategy.Pass()); } else if (operation == "courgette") { scoped_ptr strategy(new CourgetteTraits()); return new DeltaUpdateOpPatch(strategy.Pass()); } return NULL; } DeltaUpdateOp* CreateDeltaUpdateOp(const base::DictionaryValue& command) { std::string operation; if (!command.GetString(kOp, &operation)) return NULL; return CreateDeltaUpdateOp(operation); } DeltaUpdateOp::DeltaUpdateOp() : in_process_(false) {} DeltaUpdateOp::~DeltaUpdateOp() {} void DeltaUpdateOp::Run( const base::DictionaryValue* command_args, const base::FilePath& input_dir, const base::FilePath& unpack_dir, ComponentInstaller* installer, bool in_process, const ComponentUnpacker::Callback& callback, scoped_refptr task_runner) { callback_ = callback; in_process_ = in_process; task_runner_ = task_runner; std::string output_rel_path; if (!command_args->GetString(kOutput, &output_rel_path) || !command_args->GetString(kSha256, &output_sha256_)) { DoneRunning(ComponentUnpacker::kDeltaBadCommands, 0); return; } output_abs_path_ = unpack_dir.Append( base::FilePath::FromUTF8Unsafe(output_rel_path)); ComponentUnpacker::Error parse_result = DoParseArguments( command_args, input_dir, installer); if (parse_result != ComponentUnpacker::kNone) { DoneRunning(parse_result, 0); return; } const base::FilePath parent = output_abs_path_.DirName(); if (!base::DirectoryExists(parent)) { if (!base::CreateDirectory(parent)) { DoneRunning(ComponentUnpacker::kIoError, 0); return; } } DoRun(base::Bind(&DeltaUpdateOp::DoneRunning, scoped_refptr(this))); } void DeltaUpdateOp::DoneRunning(ComponentUnpacker::Error error, int extended_error) { if (error == ComponentUnpacker::kNone) error = CheckHash(); task_runner_->PostTask(FROM_HERE, base::Bind(callback_, error, extended_error)); callback_.Reset(); } // Uses the hash as a checksum to confirm that the file now residing in the // output directory probably has the contents it should. ComponentUnpacker::Error DeltaUpdateOp::CheckHash() { std::vector expected_hash; if (!base::HexStringToBytes(output_sha256_, &expected_hash) || expected_hash.size() != crypto::kSHA256Length) return ComponentUnpacker::kDeltaVerificationFailure; base::MemoryMappedFile output_file_mmapped; if (!output_file_mmapped.Initialize(output_abs_path_)) return ComponentUnpacker::kDeltaVerificationFailure; uint8 actual_hash[crypto::kSHA256Length] = {0}; const scoped_ptr hasher(SecureHash::Create(SecureHash::SHA256)); hasher->Update(output_file_mmapped.data(), output_file_mmapped.length()); hasher->Finish(actual_hash, sizeof(actual_hash)); if (memcmp(actual_hash, &expected_hash[0], sizeof(actual_hash))) return ComponentUnpacker::kDeltaVerificationFailure; return ComponentUnpacker::kNone; } bool DeltaUpdateOp::InProcess() { return in_process_; } DeltaUpdateOpCopy::DeltaUpdateOpCopy() {} DeltaUpdateOpCopy::~DeltaUpdateOpCopy() {} ComponentUnpacker::Error DeltaUpdateOpCopy::DoParseArguments( const base::DictionaryValue* command_args, const base::FilePath& input_dir, ComponentInstaller* installer) { std::string input_rel_path; if (!command_args->GetString(kInput, &input_rel_path)) return ComponentUnpacker::kDeltaBadCommands; if (!installer->GetInstalledFile(input_rel_path, &input_abs_path_)) return ComponentUnpacker::kDeltaMissingExistingFile; return ComponentUnpacker::kNone; } void DeltaUpdateOpCopy::DoRun(const ComponentUnpacker::Callback& callback) { if (!base::CopyFile(input_abs_path_, output_abs_path_)) callback.Run(ComponentUnpacker::kDeltaOperationFailure, 0); else callback.Run(ComponentUnpacker::kNone, 0); } DeltaUpdateOpCreate::DeltaUpdateOpCreate() {} DeltaUpdateOpCreate::~DeltaUpdateOpCreate() {} ComponentUnpacker::Error DeltaUpdateOpCreate::DoParseArguments( const base::DictionaryValue* command_args, const base::FilePath& input_dir, ComponentInstaller* installer) { std::string patch_rel_path; if (!command_args->GetString(kPatch, &patch_rel_path)) return ComponentUnpacker::kDeltaBadCommands; patch_abs_path_ = input_dir.Append( base::FilePath::FromUTF8Unsafe(patch_rel_path)); return ComponentUnpacker::kNone; } void DeltaUpdateOpCreate::DoRun(const ComponentUnpacker::Callback& callback) { if (!base::Move(patch_abs_path_, output_abs_path_)) callback.Run(ComponentUnpacker::kDeltaOperationFailure, 0); else callback.Run(ComponentUnpacker::kNone, 0); } DeltaUpdateOpPatchHost::DeltaUpdateOpPatchHost( scoped_refptr patcher) : patcher_(patcher) { } DeltaUpdateOpPatchHost::~DeltaUpdateOpPatchHost() { } void DeltaUpdateOpPatchHost::StartProcess(scoped_ptr message) { // The DeltaUpdateOpPatchHost is not responsible for deleting the // UtilityProcessHost object. content::UtilityProcessHost* host = content::UtilityProcessHost::Create( this, base::MessageLoopProxy::current().get()); host->DisableSandbox(); host->Send(message.release()); } bool DeltaUpdateOpPatchHost::OnMessageReceived(const IPC::Message& message) { bool handled = true; IPC_BEGIN_MESSAGE_MAP(DeltaUpdateOpPatchHost, message) IPC_MESSAGE_HANDLER(ChromeUtilityHostMsg_PatchFile_Succeeded, OnPatchSucceeded) IPC_MESSAGE_HANDLER(ChromeUtilityHostMsg_PatchFile_Failed, OnPatchFailed) IPC_MESSAGE_UNHANDLED(handled = false) IPC_END_MESSAGE_MAP() return handled; } void DeltaUpdateOpPatchHost::OnPatchSucceeded() { patcher_->DonePatching(ComponentUnpacker::kNone, 0); patcher_ = NULL; } void DeltaUpdateOpPatchHost::OnPatchFailed(int error_code) { patcher_->DonePatching(ComponentUnpacker::kDeltaOperationFailure, error_code); patcher_ = NULL; } void DeltaUpdateOpPatchHost::OnProcessCrashed(int exit_code) { patcher_->DonePatching(ComponentUnpacker::kDeltaPatchProcessFailure, exit_code); patcher_ = NULL; } DeltaUpdateOpPatch::DeltaUpdateOpPatch( scoped_ptr strategy) { strategy_ = strategy.Pass(); } DeltaUpdateOpPatch::~DeltaUpdateOpPatch() { } ComponentUnpacker::Error DeltaUpdateOpPatch::DoParseArguments( const base::DictionaryValue* command_args, const base::FilePath& input_dir, ComponentInstaller* installer) { std::string patch_rel_path; std::string input_rel_path; if (!command_args->GetString(kPatch, &patch_rel_path) || !command_args->GetString(kInput, &input_rel_path)) return ComponentUnpacker::kDeltaBadCommands; if (!installer->GetInstalledFile(input_rel_path, &input_abs_path_)) return ComponentUnpacker::kDeltaMissingExistingFile; patch_abs_path_ = input_dir.Append( base::FilePath::FromUTF8Unsafe(patch_rel_path)); return ComponentUnpacker::kNone; } void DeltaUpdateOpPatch::DoRun(const ComponentUnpacker::Callback& callback) { callback_ = callback; if (!InProcess()) { host_ = new DeltaUpdateOpPatchHost(scoped_refptr(this)); content::BrowserThread::PostTask( content::BrowserThread::IO, FROM_HERE, base::Bind(&DeltaUpdateOpPatchHost::StartProcess, host_, base::Passed(strategy_->GetPatchMessage(input_abs_path_, patch_abs_path_, output_abs_path_)))); return; } const int result = strategy_->Patch(input_abs_path_, patch_abs_path_, output_abs_path_); if (result == strategy_->GetSuccessCode()) DonePatching(ComponentUnpacker::kNone, 0); else DonePatching(ComponentUnpacker::kDeltaOperationFailure, result); } void DeltaUpdateOpPatch::DonePatching(ComponentUnpacker::Error error, int error_code) { host_ = NULL; if (error != ComponentUnpacker::kNone) { error_code += strategy_->GetErrorOffset(); } callback_.Run(error, error_code); // The callback is no longer needed - it is best to release it in case it // contains a reference to this object. callback_.Reset(); } } // namespace component_updater