summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--chrome/browser/chromeos/app_mode/kiosk_app_data.cc7
-rw-r--r--chrome/browser/chromeos/app_mode/kiosk_external_update_validator.cc9
-rw-r--r--chrome/browser/extensions/crx_installer.cc15
-rw-r--r--chrome/browser/extensions/startup_helper.cc12
-rw-r--r--extensions/browser/DEPS1
-rw-r--r--extensions/browser/sandboxed_unpacker.cc202
-rw-r--r--extensions/browser/sandboxed_unpacker.h102
-rw-r--r--extensions/browser/sandboxed_unpacker_unittest.cc58
-rw-r--r--extensions/common/extension_utility_messages.h6
-rw-r--r--extensions/utility/utility_handler.cc38
-rw-r--r--extensions/utility/utility_handler.h5
11 files changed, 319 insertions, 136 deletions
diff --git a/chrome/browser/chromeos/app_mode/kiosk_app_data.cc b/chrome/browser/chromeos/app_mode/kiosk_app_data.cc
index 22d73d3..1f88f65 100644
--- a/chrome/browser/chromeos/app_mode/kiosk_app_data.cc
+++ b/chrome/browser/chromeos/app_mode/kiosk_app_data.cc
@@ -147,10 +147,9 @@ class KioskAppData::CrxLoader : public extensions::SandboxedUnpackerClient {
scoped_refptr<extensions::SandboxedUnpacker> unpacker(
new extensions::SandboxedUnpacker(
- extensions::CRXFileInfo(crx_file_), extensions::Manifest::INTERNAL,
- extensions::Extension::NO_FLAGS, temp_dir_.path(),
- task_runner_.get(), this));
- unpacker->Start();
+ extensions::Manifest::INTERNAL, extensions::Extension::NO_FLAGS,
+ temp_dir_.path(), task_runner_.get(), this));
+ unpacker->StartWithCrx(extensions::CRXFileInfo(crx_file_));
}
void NotifyFinishedOnBlockingPool() {
diff --git a/chrome/browser/chromeos/app_mode/kiosk_external_update_validator.cc b/chrome/browser/chromeos/app_mode/kiosk_external_update_validator.cc
index b71ab94..d4409a9 100644
--- a/chrome/browser/chromeos/app_mode/kiosk_external_update_validator.cc
+++ b/chrome/browser/chromeos/app_mode/kiosk_external_update_validator.cc
@@ -29,12 +29,11 @@ KioskExternalUpdateValidator::~KioskExternalUpdateValidator() {
void KioskExternalUpdateValidator::Start() {
scoped_refptr<extensions::SandboxedUnpacker> unpacker(
new extensions::SandboxedUnpacker(
- crx_file_, extensions::Manifest::EXTERNAL_PREF,
- extensions::Extension::NO_FLAGS, crx_unpack_dir_,
- backend_task_runner_.get(), this));
+ extensions::Manifest::EXTERNAL_PREF, extensions::Extension::NO_FLAGS,
+ crx_unpack_dir_, backend_task_runner_.get(), this));
if (!backend_task_runner_->PostTask(
- FROM_HERE,
- base::Bind(&extensions::SandboxedUnpacker::Start, unpacker.get()))) {
+ FROM_HERE, base::Bind(&extensions::SandboxedUnpacker::StartWithCrx,
+ unpacker.get(), crx_file_))) {
NOTREACHED();
}
}
diff --git a/chrome/browser/extensions/crx_installer.cc b/chrome/browser/extensions/crx_installer.cc
index 2effb1a..295fe34 100644
--- a/chrome/browser/extensions/crx_installer.cc
+++ b/chrome/browser/extensions/crx_installer.cc
@@ -183,18 +183,15 @@ void CrxInstaller::InstallCrxFile(const CRXFileInfo& source_file) {
source_file_ = source_file.path;
- scoped_refptr<SandboxedUnpacker> unpacker(
- new SandboxedUnpacker(source_file,
- install_source_,
- creation_flags_,
- install_directory_,
- installer_task_runner_.get(),
- this));
+ scoped_refptr<SandboxedUnpacker> unpacker(new SandboxedUnpacker(
+ install_source_, creation_flags_, install_directory_,
+ installer_task_runner_.get(), this));
if (!installer_task_runner_->PostTask(
- FROM_HERE,
- base::Bind(&SandboxedUnpacker::Start, unpacker.get())))
+ FROM_HERE, base::Bind(&SandboxedUnpacker::StartWithCrx,
+ unpacker.get(), source_file))) {
NOTREACHED();
+ }
}
void CrxInstaller::InstallUserScript(const base::FilePath& source_file,
diff --git a/chrome/browser/extensions/startup_helper.cc b/chrome/browser/extensions/startup_helper.cc
index 49068c4..53cd444 100644
--- a/chrome/browser/extensions/startup_helper.cc
+++ b/chrome/browser/extensions/startup_helper.cc
@@ -133,14 +133,10 @@ class ValidateCrxHelper : public SandboxedUnpackerClient {
scoped_refptr<base::SingleThreadTaskRunner> file_task_runner =
BrowserThread::GetMessageLoopProxyForThread(BrowserThread::FILE);
- scoped_refptr<SandboxedUnpacker> unpacker(
- new SandboxedUnpacker(crx_file_,
- Manifest::INTERNAL,
- 0, /* no special creation flags */
- temp_dir_,
- file_task_runner.get(),
- this));
- unpacker->Start();
+ scoped_refptr<SandboxedUnpacker> unpacker(new SandboxedUnpacker(
+ Manifest::INTERNAL, 0, /* no special creation flags */
+ temp_dir_, file_task_runner.get(), this));
+ unpacker->StartWithCrx(crx_file_);
}
// The file being validated.
diff --git a/extensions/browser/DEPS b/extensions/browser/DEPS
index b20ccf8..abf85d9 100644
--- a/extensions/browser/DEPS
+++ b/extensions/browser/DEPS
@@ -24,6 +24,7 @@ include_rules = [
"+third_party/leveldatabase",
"+third_party/re2",
"+third_party/WebKit/public/web",
+ "+third_party/zlib/google",
]
specific_include_rules = {
diff --git a/extensions/browser/sandboxed_unpacker.cc b/extensions/browser/sandboxed_unpacker.cc
index 7b27e85..965de48 100644
--- a/extensions/browser/sandboxed_unpacker.cc
+++ b/extensions/browser/sandboxed_unpacker.cc
@@ -212,26 +212,18 @@ SandboxedUnpackerClient::SandboxedUnpackerClient()
}
SandboxedUnpacker::SandboxedUnpacker(
- const CRXFileInfo& file,
Manifest::Location location,
int creation_flags,
const base::FilePath& extensions_dir,
const scoped_refptr<base::SequencedTaskRunner>& unpacker_io_task_runner,
SandboxedUnpackerClient* client)
- : crx_path_(file.path),
- package_hash_(base::ToLowerASCII(file.expected_hash)),
- check_crx_hash_(false),
- client_(client),
+ : client_(client),
extensions_dir_(extensions_dir),
got_response_(false),
location_(location),
creation_flags_(creation_flags),
- unpacker_io_task_runner_(unpacker_io_task_runner) {
- if (!package_hash_.empty()) {
- check_crx_hash_ = base::CommandLine::ForCurrentProcess()->HasSwitch(
- extensions::switches::kEnableCrxHashCheck);
- }
-}
+ unpacker_io_task_runner_(unpacker_io_task_runner),
+ utility_wrapper_(new UtilityHostWrapper) {}
bool SandboxedUnpacker::CreateTempDirectory() {
CHECK(unpacker_io_task_runner_->RunsTasksOnCurrentThread());
@@ -256,15 +248,21 @@ bool SandboxedUnpacker::CreateTempDirectory() {
return true;
}
-void SandboxedUnpacker::Start() {
+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.
CHECK(unpacker_io_task_runner_->RunsTasksOnCurrentThread());
- unpack_start_time_ = base::TimeTicks::Now();
+ crx_unpack_start_time_ = base::TimeTicks::Now();
+ 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);
+ }
PATH_LENGTH_HISTOGRAM("Extensions.SandboxUnpackInitialCrxPathLength",
- crx_path_);
+ crx_info.path);
if (!CreateTempDirectory())
return; // ReportFailure() already called.
@@ -274,15 +272,16 @@ void SandboxedUnpacker::Start() {
extension_root_);
// Extract the public key and validate the package.
- if (!ValidateSignature())
+ if (!ValidateSignature(crx_info.path, expected_hash))
return; // ValidateSignature() already reported the error.
// Copy the crx file into our working directory.
- base::FilePath temp_crx_path = temp_dir_.path().Append(crx_path_.BaseName());
+ base::FilePath temp_crx_path =
+ temp_dir_.path().Append(crx_info.path.BaseName());
PATH_LENGTH_HISTOGRAM("Extensions.SandboxUnpackTempCrxPathLength",
temp_crx_path);
- if (!base::CopyFile(crx_path_, temp_crx_path)) {
+ if (!base::CopyFile(crx_info.path, temp_crx_path)) {
// Failed to copy extension file to temporary directory.
ReportFailure(
FAILED_TO_COPY_EXTENSION_FILE_TO_TEMP_DIRECTORY,
@@ -309,10 +308,35 @@ void SandboxedUnpacker::Start() {
link_free_crx_path);
BrowserThread::PostTask(BrowserThread::IO, FROM_HERE,
- base::Bind(&SandboxedUnpacker::StartProcessOnIOThread,
+ base::Bind(&SandboxedUnpacker::StartUnzipOnIOThread,
this, link_free_crx_path));
}
+void SandboxedUnpacker::StartWithDirectory(const std::string& extension_id,
+ const std::string& public_key,
+ const base::FilePath& directory) {
+ extension_id_ = extension_id;
+ public_key_ = public_key;
+ if (!CreateTempDirectory())
+ return; // ReportFailure() already called.
+
+ extension_root_ = temp_dir_.path().AppendASCII(kTempExtensionName);
+
+ if (!base::Move(directory, extension_root_)) {
+ LOG(ERROR) << "Could not move " << directory.value() << " to "
+ << extension_root_.value();
+ ReportFailure(
+ DIRECTORY_MOVE_FAILED,
+ l10n_util::GetStringFUTF16(IDS_EXTENSION_PACKAGE_INSTALL_ERROR,
+ ASCIIToUTF16("DIRECTORY_MOVE_FAILED")));
+ return;
+ }
+
+ BrowserThread::PostTask(BrowserThread::IO, FROM_HERE,
+ base::Bind(&SandboxedUnpacker::StartUnpackOnIOThread,
+ this, 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.
@@ -322,11 +346,15 @@ SandboxedUnpacker::~SandboxedUnpacker() {
bool SandboxedUnpacker::OnMessageReceived(const IPC::Message& message) {
bool handled = true;
IPC_BEGIN_MESSAGE_MAP(SandboxedUnpacker, message)
- IPC_MESSAGE_HANDLER(ExtensionUtilityHostMsg_UnpackExtension_Succeeded,
- OnUnpackExtensionSucceeded)
- IPC_MESSAGE_HANDLER(ExtensionUtilityHostMsg_UnpackExtension_Failed,
- OnUnpackExtensionFailed)
- IPC_MESSAGE_UNHANDLED(handled = false)
+ IPC_MESSAGE_HANDLER(ExtensionUtilityHostMsg_UnzipToDir_Succeeded,
+ OnUnzipToDirSucceeded)
+ IPC_MESSAGE_HANDLER(ExtensionUtilityHostMsg_UnzipToDir_Failed,
+ OnUnzipToDirFailed)
+ IPC_MESSAGE_HANDLER(ExtensionUtilityHostMsg_UnpackExtension_Succeeded,
+ OnUnpackExtensionSucceeded)
+ IPC_MESSAGE_HANDLER(ExtensionUtilityHostMsg_UnpackExtension_Failed,
+ OnUnpackExtensionFailed)
+ IPC_MESSAGE_UNHANDLED(handled = false)
IPC_END_MESSAGE_MAP()
return handled;
}
@@ -346,23 +374,57 @@ void SandboxedUnpacker::OnProcessCrashed(int exit_code) {
l10n_util::GetStringUTF16(IDS_EXTENSION_INSTALL_PROCESS_CRASHED));
}
-void SandboxedUnpacker::StartProcessOnIOThread(
- const base::FilePath& temp_crx_path) {
- UtilityProcessHost* host =
- UtilityProcessHost::Create(this, unpacker_io_task_runner_.get());
- host->SetName(
- l10n_util::GetStringUTF16(IDS_UTILITY_PROCESS_EXTENSION_UNPACKER_NAME));
- // Grant the subprocess access to the entire subdir the extension file is
- // in, so that it can unpack to that dir.
- host->SetExposedDir(temp_crx_path.DirName());
- host->Send(new ExtensionUtilityMsg_UnpackExtension(
- temp_crx_path, extension_id_, location_, creation_flags_));
+void SandboxedUnpacker::StartUnzipOnIOThread(const base::FilePath& crx_path) {
+ if (!utility_wrapper_->StartIfNeeded(temp_dir_.path(), this,
+ unpacker_io_task_runner_)) {
+ ReportFailure(
+ COULD_NOT_START_UTILITY_PROCESS,
+ l10n_util::GetStringFUTF16(
+ IDS_EXTENSION_PACKAGE_INSTALL_ERROR,
+ FailureReasonToString16(COULD_NOT_START_UTILITY_PROCESS)));
+ return;
+ }
+ DCHECK(crx_path.DirName() == temp_dir_.path());
+ base::FilePath unzipped_dir =
+ crx_path.DirName().AppendASCII(kTempExtensionName);
+ utility_wrapper_->host()->Send(
+ new ExtensionUtilityMsg_UnzipToDir(crx_path, unzipped_dir));
+}
+
+void SandboxedUnpacker::StartUnpackOnIOThread(
+ const base::FilePath& directory_path) {
+ if (!utility_wrapper_->StartIfNeeded(temp_dir_.path(), this,
+ unpacker_io_task_runner_)) {
+ ReportFailure(
+ COULD_NOT_START_UTILITY_PROCESS,
+ l10n_util::GetStringFUTF16(
+ IDS_EXTENSION_PACKAGE_INSTALL_ERROR,
+ FailureReasonToString16(COULD_NOT_START_UTILITY_PROCESS)));
+ return;
+ }
+ DCHECK(directory_path.DirName() == temp_dir_.path());
+ utility_wrapper_->host()->Send(new ExtensionUtilityMsg_UnpackExtension(
+ directory_path, extension_id_, location_, creation_flags_));
+}
+
+void SandboxedUnpacker::OnUnzipToDirSucceeded(const base::FilePath& directory) {
+ BrowserThread::PostTask(
+ BrowserThread::IO, FROM_HERE,
+ base::Bind(&SandboxedUnpacker::StartUnpackOnIOThread, this, directory));
+}
+
+void SandboxedUnpacker::OnUnzipToDirFailed(const std::string& error) {
+ got_response_ = true;
+ utility_wrapper_ = nullptr;
+ ReportFailure(UNZIP_FAILED,
+ l10n_util::GetStringUTF16(IDS_EXTENSION_PACKAGE_UNZIP_ERROR));
}
void SandboxedUnpacker::OnUnpackExtensionSucceeded(
const base::DictionaryValue& manifest) {
CHECK(unpacker_io_task_runner_->RunsTasksOnCurrentThread());
got_response_ = true;
+ utility_wrapper_ = nullptr;
scoped_ptr<base::DictionaryValue> final_manifest(
RewriteManifestFile(manifest));
@@ -411,6 +473,7 @@ void SandboxedUnpacker::OnUnpackExtensionSucceeded(
void SandboxedUnpacker::OnUnpackExtensionFailed(const base::string16& error) {
CHECK(unpacker_io_task_runner_->RunsTasksOnCurrentThread());
got_response_ = true;
+ utility_wrapper_ = nullptr;
ReportFailure(
UNPACKER_CLIENT_FAILED,
l10n_util::GetStringFUTF16(IDS_EXTENSION_PACKAGE_ERROR_MESSAGE, error));
@@ -495,6 +558,13 @@ base::string16 SandboxedUnpacker::FailureReasonToString16(
case CRX_HASH_VERIFICATION_FAILED:
return ASCIIToUTF16("CRX_HASH_VERIFICATION_FAILED");
+ case UNZIP_FAILED:
+ return ASCIIToUTF16("UNZIP_FAILED");
+ case DIRECTORY_MOVE_FAILED:
+ return ASCIIToUTF16("DIRECTORY_MOVE_FAILED");
+ case COULD_NOT_START_UTILITY_PROCESS:
+ return ASCIIToUTF16("COULD_NOT_START_UTILITY_PROCESS");
+
case NUM_FAILURE_REASONS:
NOTREACHED();
return base::string16();
@@ -509,14 +579,14 @@ void SandboxedUnpacker::FailWithPackageError(FailureReason reason) {
FailureReasonToString16(reason)));
}
-bool SandboxedUnpacker::ValidateSignature() {
+bool SandboxedUnpacker::ValidateSignature(const base::FilePath& crx_path,
+ const std::string& expected_hash) {
CrxFile::ValidateError error = CrxFile::ValidateSignature(
- crx_path_, check_crx_hash_ ? package_hash_ : std::string(), &public_key_,
- &extension_id_, nullptr);
+ crx_path, expected_hash, &public_key_, &extension_id_, nullptr);
switch (error) {
case CrxFile::ValidateError::NONE: {
- if (check_crx_hash_)
+ if (!expected_hash.empty())
UMA_HISTOGRAM_BOOLEAN("Extensions.SandboxUnpackHashCheck", true);
return true;
}
@@ -558,7 +628,7 @@ bool SandboxedUnpacker::ValidateSignature() {
case CrxFile::ValidateError::CRX_HASH_VERIFICATION_FAILED:
// We should never get this result unless we had specifically asked for
// verification of the crx file's hash.
- CHECK(check_crx_hash_ && !package_hash_.empty());
+ CHECK(!expected_hash.empty());
UMA_HISTOGRAM_BOOLEAN("Extensions.SandboxUnpackHashCheck", false);
FailWithPackageError(CRX_HASH_VERIFICATION_FAILED);
break;
@@ -568,10 +638,12 @@ bool SandboxedUnpacker::ValidateSignature() {
void SandboxedUnpacker::ReportFailure(FailureReason reason,
const base::string16& error) {
+ utility_wrapper_ = nullptr;
UMA_HISTOGRAM_ENUMERATION("Extensions.SandboxUnpackFailureReason", reason,
NUM_FAILURE_REASONS);
- UMA_HISTOGRAM_TIMES("Extensions.SandboxUnpackFailureTime",
- base::TimeTicks::Now() - unpack_start_time_);
+ if (!crx_unpack_start_time_.is_null())
+ UMA_HISTOGRAM_TIMES("Extensions.SandboxUnpackFailureTime",
+ base::TimeTicks::Now() - crx_unpack_start_time_);
Cleanup();
CrxInstallError error_info(reason == CRX_HASH_VERIFICATION_FAILED
@@ -585,10 +657,14 @@ void SandboxedUnpacker::ReportFailure(FailureReason reason,
void SandboxedUnpacker::ReportSuccess(
const base::DictionaryValue& original_manifest,
const SkBitmap& install_icon) {
+ utility_wrapper_ = nullptr;
UMA_HISTOGRAM_COUNTS("Extensions.SandboxUnpackSuccess", 1);
- RecordSuccessfulUnpackTimeHistograms(
- crx_path_, base::TimeTicks::Now() - unpack_start_time_);
+ if (!crx_unpack_start_time_.is_null())
+ RecordSuccessfulUnpackTimeHistograms(
+ crx_path_for_histograms_,
+ base::TimeTicks::Now() - crx_unpack_start_time_);
+ DCHECK(!temp_dir_.path().empty());
// Client takes ownership of temporary directory and extension.
client_->OnUnpackSuccess(temp_dir_.Take(), extension_root_,
@@ -601,6 +677,7 @@ base::DictionaryValue* SandboxedUnpacker::RewriteManifestFile(
// 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());
scoped_ptr<base::DictionaryValue> final_manifest(manifest.DeepCopy());
final_manifest->SetString(manifest_keys::kPublicKey, public_key_);
@@ -631,6 +708,7 @@ base::DictionaryValue* SandboxedUnpacker::RewriteManifestFile(
}
bool SandboxedUnpacker::RewriteImageFiles(SkBitmap* install_icon) {
+ DCHECK(!temp_dir_.path().empty());
DecodedImages images;
if (!ReadImagesFromFile(temp_dir_.path(), &images)) {
// Couldn't read image data from disk.
@@ -810,4 +888,42 @@ void SandboxedUnpacker::Cleanup() {
}
}
+SandboxedUnpacker::UtilityHostWrapper::UtilityHostWrapper() {}
+
+bool SandboxedUnpacker::UtilityHostWrapper::StartIfNeeded(
+ const base::FilePath& exposed_dir,
+ const scoped_refptr<UtilityProcessHostClient>& client,
+ const scoped_refptr<base::SequencedTaskRunner>& client_task_runner) {
+ DCHECK_CURRENTLY_ON(BrowserThread::IO);
+ if (!utility_host_) {
+ utility_host_ =
+ UtilityProcessHost::Create(client, client_task_runner)->AsWeakPtr();
+ utility_host_->SetName(
+ l10n_util::GetStringUTF16(IDS_UTILITY_PROCESS_EXTENSION_UNPACKER_NAME));
+
+ // Grant the subprocess access to our temp dir so it can write out files.
+ DCHECK(!exposed_dir.empty());
+ utility_host_->SetExposedDir(exposed_dir);
+ if (!utility_host_->StartBatchMode()) {
+ utility_host_.reset();
+ return false;
+ }
+ }
+ return true;
+}
+
+content::UtilityProcessHost* SandboxedUnpacker::UtilityHostWrapper::host()
+ const {
+ DCHECK_CURRENTLY_ON(BrowserThread::IO);
+ return utility_host_.get();
+}
+
+SandboxedUnpacker::UtilityHostWrapper::~UtilityHostWrapper() {
+ DCHECK_CURRENTLY_ON(BrowserThread::IO);
+ if (utility_host_) {
+ utility_host_->EndBatchMode();
+ utility_host_.reset();
+ }
+}
+
} // namespace extensions
diff --git a/extensions/browser/sandboxed_unpacker.h b/extensions/browser/sandboxed_unpacker.h
index 17156df..826d939 100644
--- a/extensions/browser/sandboxed_unpacker.h
+++ b/extensions/browser/sandboxed_unpacker.h
@@ -2,14 +2,15 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-#ifndef CHROME_BROWSER_EXTENSIONS_SANDBOXED_UNPACKER_H_
-#define CHROME_BROWSER_EXTENSIONS_SANDBOXED_UNPACKER_H_
+#ifndef EXTENSIONS_BROWSER_SANDBOXED_UNPACKER_H_
+#define EXTENSIONS_BROWSER_SANDBOXED_UNPACKER_H_
#include <string>
#include "base/files/file_path.h"
#include "base/files/scoped_temp_dir.h"
#include "base/memory/ref_counted_delete_on_message_loop.h"
+#include "base/memory/weak_ptr.h"
#include "base/time/time.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/utility_process_host_client.h"
@@ -24,6 +25,10 @@ class DictionaryValue;
class SequencedTaskRunner;
}
+namespace content {
+class UtilityProcessHost;
+}
+
namespace crypto {
class SecureHash;
}
@@ -64,10 +69,11 @@ class SandboxedUnpackerClient
virtual ~SandboxedUnpackerClient() {}
};
-// SandboxedUnpacker unpacks extensions from the CRX format into a
-// directory. This is done in a sandboxed subprocess to protect the browser
-// process from parsing complex formats like JPEG or JSON from untrusted
-// sources.
+// SandboxedUnpacker does work to optionally unpack and then validate/sanitize
+// an extension, either starting from a crx file or an already unzipped
+// directory (eg from differential update). This is done in a sandboxed
+// subprocess to protect the browser process from parsing complex formats like
+// JPEG or JSON from untrusted sources.
//
// Unpacking an extension using this class makes minor changes to its source,
// such as transcoding all images to PNG, parsing all message catalogs
@@ -87,19 +93,25 @@ class SandboxedUnpackerClient
// NOTE: This class should only be used on the file thread.
class SandboxedUnpacker : public content::UtilityProcessHostClient {
public:
- // Unpacks the extension in |crx_path| into a temporary directory and calls
- // |client| with the result. If |run_out_of_process| is provided, unpacking
- // is done in a sandboxed subprocess. Otherwise, it is done in-process.
+ // Creates a SanboxedUnpacker that will do work to unpack an extension,
+ // passing the |location| and |creation_flags| to Extension::Create. The
+ // |extensions_dir| parameter should specify the directory under which we'll
+ // create a subdirectory to write the unpacked extension contents.
SandboxedUnpacker(
- const CRXFileInfo& file,
Manifest::Location location,
int creation_flags,
const base::FilePath& extensions_dir,
const scoped_refptr<base::SequencedTaskRunner>& unpacker_io_task_runner,
SandboxedUnpackerClient* client);
- // Start unpacking the extension. The client is called with the results.
- void Start();
+ // Start processing the extension, either from a CRX file or already unzipped
+ // in a directory. The client is called with the results. The directory form
+ // requires the id and base64-encoded public key (for insertion into the
+ // 'key' field of the manifest.json file).
+ void StartWithCrx(const CRXFileInfo& crx_info);
+ void StartWithDirectory(const std::string& extension_id,
+ const std::string& public_key_base64,
+ const base::FilePath& directory);
private:
class ProcessHostClient;
@@ -163,6 +175,10 @@ class SandboxedUnpacker : public content::UtilityProcessHostClient {
// SandboxedUnpacker::ValidateSignature()
CRX_HASH_VERIFICATION_FAILED,
+ UNZIP_FAILED,
+ DIRECTORY_MOVE_FAILED,
+ COULD_NOT_START_UTILITY_PROCESS,
+
NUM_FAILURE_REASONS
};
@@ -181,16 +197,19 @@ class SandboxedUnpacker : public content::UtilityProcessHostClient {
// Validates the signature of the extension and extract the key to
// |public_key_|. Returns true if the signature validates, false otherwise.
- bool ValidateSignature();
+ bool ValidateSignature(const base::FilePath& crx_path,
+ const std::string& expected_hash);
- // Starts the utility process that unpacks our extension.
- void StartProcessOnIOThread(const base::FilePath& temp_crx_path);
+ void StartUnzipOnIOThread(const base::FilePath& crx_path);
+ void StartUnpackOnIOThread(const base::FilePath& directory_path);
// UtilityProcessHostClient
bool OnMessageReceived(const IPC::Message& message) override;
void OnProcessCrashed(int exit_code) override;
// IPC message handlers.
+ void OnUnzipToDirSucceeded(const base::FilePath& directory);
+ void OnUnzipToDirFailed(const std::string& error);
void OnUnpackExtensionSucceeded(const base::DictionaryValue& manifest);
void OnUnpackExtensionFailed(const base::string16& error_message);
@@ -211,14 +230,41 @@ class SandboxedUnpacker : public content::UtilityProcessHostClient {
// Cleans up temp directory artifacts.
void Cleanup();
- // The path to the CRX to unpack.
- base::FilePath crx_path_;
-
- // The package SHA256 hash sum that was reported from the Web Store.
- std::string package_hash_;
+ // This is a helper class to make it easier to keep track of the lifecycle of
+ // a UtilityProcessHost, including automatic begin and end of batch mode.
+ class UtilityHostWrapper : public base::RefCountedThreadSafe<
+ UtilityHostWrapper,
+ content::BrowserThread::DeleteOnIOThread> {
+ public:
+ UtilityHostWrapper();
+
+ // Start up the utility process if it is not already started, putting it
+ // into batch mode and giving it access to |exposed_dir|. This should only
+ // be called on the IO thread. Returns false if there was an error starting
+ // the utility process or putting it into batch mode.
+ bool StartIfNeeded(
+ const base::FilePath& exposed_dir,
+ const scoped_refptr<UtilityProcessHostClient>& client,
+ const scoped_refptr<base::SequencedTaskRunner>& client_task_runner);
+
+ // This should only be called on the IO thread.
+ content::UtilityProcessHost* host() const;
+
+ private:
+ friend struct content::BrowserThread::DeleteOnThread<
+ content::BrowserThread::IO>;
+ friend class base::DeleteHelper<UtilityHostWrapper>;
+ ~UtilityHostWrapper();
+
+ // Should only be used on the IO thread.
+ base::WeakPtr<content::UtilityProcessHost> utility_host_;
+
+ DISALLOW_COPY_AND_ASSIGN(UtilityHostWrapper);
+ };
- // Whether we need to check the .crx hash sum.
- bool check_crx_hash_;
+ // If we unpacked a crx file, we hold on to the path for use in various
+ // histograms.
+ base::FilePath crx_path_for_histograms_;
// Our client.
scoped_refptr<SandboxedUnpackerClient> client_;
@@ -245,8 +291,9 @@ class SandboxedUnpacker : public content::UtilityProcessHostClient {
// header.
std::string extension_id_;
- // Time at which unpacking started. Used to compute the time unpacking takes.
- base::TimeTicks unpack_start_time_;
+ // If we unpacked a .crx file, the time at which unpacking started. Used to
+ // compute the time unpacking takes.
+ base::TimeTicks crx_unpack_start_time_;
// Location to use for the unpacked extension.
Manifest::Location location_;
@@ -257,8 +304,13 @@ class SandboxedUnpacker : public content::UtilityProcessHostClient {
// Sequenced task runner where file I/O operations will be performed at.
scoped_refptr<base::SequencedTaskRunner> unpacker_io_task_runner_;
+
+ // Used for sending tasks to the utility process.
+ scoped_refptr<UtilityHostWrapper> utility_wrapper_;
+
+ DISALLOW_COPY_AND_ASSIGN(SandboxedUnpacker);
};
} // namespace extensions
-#endif // CHROME_BROWSER_EXTENSIONS_SANDBOXED_UNPACKER_H_
+#endif // EXTENSIONS_BROWSER_SANDBOXED_UNPACKER_H_
diff --git a/extensions/browser/sandboxed_unpacker_unittest.cc b/extensions/browser/sandboxed_unpacker_unittest.cc
index 02fceaf..a66b2fa 100644
--- a/extensions/browser/sandboxed_unpacker_unittest.cc
+++ b/extensions/browser/sandboxed_unpacker_unittest.cc
@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+#include "base/base64.h"
#include "base/bind.h"
#include "base/command_line.h"
#include "base/files/file_util.h"
@@ -11,6 +12,7 @@
#include "base/strings/string_util.h"
#include "base/thread_task_runner_handle.h"
#include "base/values.h"
+#include "components/crx_file/id_util.h"
#include "content/public/test/test_browser_thread_bundle.h"
#include "content/public/test/test_utils.h"
#include "extensions/browser/extensions_test.h"
@@ -21,6 +23,7 @@
#include "extensions/common/switches.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/skia/include/core/SkBitmap.h"
+#include "third_party/zlib/google/zip.h"
namespace extensions {
@@ -69,6 +72,10 @@ class SandboxedUnpackerTest : public ExtensionsTest {
new content::InProcessUtilityThreadHelper);
// It will delete itself.
client_ = new MockSandboxedUnpackerClient;
+
+ sandboxed_unpacker_ = new SandboxedUnpacker(
+ Manifest::INTERNAL, Extension::NO_FLAGS, extensions_dir_.path(),
+ base::ThreadTaskRunnerHandle::Get(), client_);
}
void TearDown() override {
@@ -79,21 +86,38 @@ class SandboxedUnpackerTest : public ExtensionsTest {
ExtensionsTest::TearDown();
}
+ base::FilePath GetCrxFullPath(const std::string& crx_name) {
+ base::FilePath full_path;
+ EXPECT_TRUE(PathService::Get(extensions::DIR_TEST_DATA, &full_path));
+ full_path = full_path.AppendASCII("unpacker").AppendASCII(crx_name);
+ EXPECT_TRUE(base::PathExists(full_path)) << full_path.value();
+ return full_path;
+ }
+
void SetupUnpacker(const std::string& crx_name,
const std::string& package_hash) {
- base::FilePath original_path;
- ASSERT_TRUE(PathService::Get(extensions::DIR_TEST_DATA, &original_path));
- original_path = original_path.AppendASCII("unpacker").AppendASCII(crx_name);
- ASSERT_TRUE(base::PathExists(original_path)) << original_path.value();
+ base::FilePath crx_path = GetCrxFullPath(crx_name);
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE,
+ base::Bind(
+ &SandboxedUnpacker::StartWithCrx, sandboxed_unpacker_,
+ extensions::CRXFileInfo(std::string(), crx_path, package_hash)));
+ client_->WaitForUnpack();
+ }
- sandboxed_unpacker_ = new SandboxedUnpacker(
- extensions::CRXFileInfo(std::string(), original_path, package_hash),
- Manifest::INTERNAL, Extension::NO_FLAGS, extensions_dir_.path(),
- base::ThreadTaskRunnerHandle::Get(), client_);
+ void SetupUnpackerWithDirectory(const std::string& crx_name) {
+ base::ScopedTempDir temp_dir;
+ ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+ base::FilePath crx_path = GetCrxFullPath(crx_name);
+ ASSERT_TRUE(zip::Unzip(crx_path, temp_dir.path()));
+ std::string fake_id = crx_file::id_util::GenerateId(crx_name);
+ std::string fake_public_key;
+ base::Base64Encode(std::string(2048, 'k'), &fake_public_key);
base::ThreadTaskRunnerHandle::Get()->PostTask(
- FROM_HERE,
- base::Bind(&SandboxedUnpacker::Start, sandboxed_unpacker_.get()));
+ FROM_HERE, base::Bind(&SandboxedUnpacker::StartWithDirectory,
+ sandboxed_unpacker_.get(), fake_id,
+ fake_public_key, temp_dir.Take()));
client_->WaitForUnpack();
}
@@ -119,6 +143,13 @@ TEST_F(SandboxedUnpackerTest, NoCatalogsSuccess) {
EXPECT_FALSE(base::PathExists(install_path));
}
+TEST_F(SandboxedUnpackerTest, FromDirNoCatalogsSuccess) {
+ SetupUnpackerWithDirectory("no_l10n.crx");
+ // Check that there is no _locales folder.
+ base::FilePath install_path = GetInstallPath().Append(kLocaleFolder);
+ EXPECT_FALSE(base::PathExists(install_path));
+}
+
TEST_F(SandboxedUnpackerTest, WithCatalogsSuccess) {
SetupUnpacker("good_l10n.crx", "");
// Check that there is _locales folder.
@@ -126,6 +157,13 @@ TEST_F(SandboxedUnpackerTest, WithCatalogsSuccess) {
EXPECT_TRUE(base::PathExists(install_path));
}
+TEST_F(SandboxedUnpackerTest, FromDirWithCatalogsSuccess) {
+ SetupUnpackerWithDirectory("good_l10n.crx");
+ // Check that there is _locales folder.
+ base::FilePath install_path = GetInstallPath().Append(kLocaleFolder);
+ EXPECT_TRUE(base::PathExists(install_path));
+}
+
TEST_F(SandboxedUnpackerTest, FailHashCheck) {
base::CommandLine::ForCurrentProcess()->AppendSwitch(
extensions::switches::kEnableCrxHashCheck);
diff --git a/extensions/common/extension_utility_messages.h b/extensions/common/extension_utility_messages.h
index 1d14047..cfaf273 100644
--- a/extensions/common/extension_utility_messages.h
+++ b/extensions/common/extension_utility_messages.h
@@ -47,10 +47,10 @@ IPC_MESSAGE_CONTROL2(ExtensionUtilityMsg_UnzipToDir,
base::FilePath /* zip_file */,
base::FilePath /* dir */)
-// Tells the utility process to unpack the given extension file in its
-// directory and verify that it is valid.
+// Tells the utility process to validate and sanitize the extension in a
+// directory.
IPC_MESSAGE_CONTROL4(ExtensionUtilityMsg_UnpackExtension,
- base::FilePath /* extension_filename */,
+ base::FilePath /* directory_path */,
std::string /* extension_id */,
int /* Manifest::Location */,
int /* InitFromValue flags */)
diff --git a/extensions/utility/utility_handler.cc b/extensions/utility/utility_handler.cc
index f3c8a8c..4f4ce47 100644
--- a/extensions/utility/utility_handler.cc
+++ b/extensions/utility/utility_handler.cc
@@ -91,38 +91,22 @@ void UtilityHandler::OnUnzipToDir(const base::FilePath& zip_path,
ReleaseProcessIfNeeded();
}
-void UtilityHandler::OnUnpackExtension(
- const base::FilePath& extension_path,
- const std::string& extension_id,
- int location,
- int creation_flags) {
+void UtilityHandler::OnUnpackExtension(const base::FilePath& directory_path,
+ const std::string& extension_id,
+ int location,
+ int creation_flags) {
CHECK_GT(location, Manifest::INVALID_LOCATION);
CHECK_LT(location, Manifest::NUM_LOCATIONS);
DCHECK(ExtensionsClient::Get());
content::UtilityThread::Get()->EnsureBlinkInitialized();
- base::FilePath working_dir = extension_path.DirName();
- base::FilePath unzipped_dir = working_dir.AppendASCII(kTempExtensionName);
- base::string16 error;
- if (!base::CreateDirectory(unzipped_dir)) {
- Send(new ExtensionUtilityHostMsg_UnpackExtension_Failed(
- l10n_util::GetStringFUTF16(
- IDS_EXTENSION_PACKAGE_DIRECTORY_ERROR,
- base::i18n::GetDisplayStringInLTRDirectionality(
- unzipped_dir.LossyDisplayName()))));
- } else if (!zip::Unzip(extension_path, unzipped_dir)) {
- Send(new ExtensionUtilityHostMsg_UnpackExtension_Failed(
- l10n_util::GetStringUTF16(IDS_EXTENSION_PACKAGE_UNZIP_ERROR)));
+ Unpacker unpacker(directory_path.DirName(), directory_path, extension_id,
+ static_cast<Manifest::Location>(location), creation_flags);
+ if (unpacker.Run()) {
+ Send(new ExtensionUtilityHostMsg_UnpackExtension_Succeeded(
+ *unpacker.parsed_manifest()));
} else {
- Unpacker unpacker(working_dir, unzipped_dir, extension_id,
- static_cast<Manifest::Location>(location),
- creation_flags);
- if (unpacker.Run()) {
- Send(new ExtensionUtilityHostMsg_UnpackExtension_Succeeded(
- *unpacker.parsed_manifest()));
- } else {
- Send(new ExtensionUtilityHostMsg_UnpackExtension_Failed(
- unpacker.error_message()));
- }
+ Send(new ExtensionUtilityHostMsg_UnpackExtension_Failed(
+ unpacker.error_message()));
}
ReleaseProcessIfNeeded();
}
diff --git a/extensions/utility/utility_handler.h b/extensions/utility/utility_handler.h
index d3f0b6c..b32707f 100644
--- a/extensions/utility/utility_handler.h
+++ b/extensions/utility/utility_handler.h
@@ -34,9 +34,10 @@ class UtilityHandler {
// IPC message handlers.
void OnParseUpdateManifest(const std::string& xml);
void OnUnzipToDir(const base::FilePath& zip_path, const base::FilePath& dir);
- void OnUnpackExtension(const base::FilePath& extension_path,
+ void OnUnpackExtension(const base::FilePath& directory_path,
const std::string& extension_id,
- int location, int creation_flags);
+ int location,
+ int creation_flags);
DISALLOW_COPY_AND_ASSIGN(UtilityHandler);
};