diff options
Diffstat (limited to 'chrome/browser/extensions')
-rw-r--r-- | chrome/browser/extensions/extension_creator.cc | 12 | ||||
-rw-r--r-- | chrome/browser/extensions/extensions_service.cc | 398 | ||||
-rw-r--r-- | chrome/browser/extensions/extensions_service.h | 59 | ||||
-rw-r--r-- | chrome/browser/extensions/sandboxed_extension_unpacker.cc | 280 | ||||
-rw-r--r-- | chrome/browser/extensions/sandboxed_extension_unpacker.h | 125 |
5 files changed, 473 insertions, 401 deletions
diff --git a/chrome/browser/extensions/extension_creator.cc b/chrome/browser/extensions/extension_creator.cc index 9abd030..7b7c389 100644 --- a/chrome/browser/extensions/extension_creator.cc +++ b/chrome/browser/extensions/extension_creator.cc @@ -12,7 +12,7 @@ #include "base/file_util.h" #include "base/scoped_handle.h" #include "base/string_util.h" -#include "chrome/browser/extensions/extensions_service.h" +#include "chrome/browser/extensions/sandboxed_extension_unpacker.h" #include "chrome/common/extensions/extension.h" #include "chrome/common/zip.h" #include "net/base/base64.h" @@ -166,14 +166,14 @@ bool ExtensionCreator::WriteCRX(const FilePath& zip_path, return false; } - ExtensionsService::ExtensionHeader header; - memcpy(&header.magic, ExtensionsService::kExtensionHeaderMagic, - ExtensionsService::kExtensionHeaderMagicSize); - header.version = ExtensionsService::kCurrentVersion; + SandboxedExtensionUnpacker::ExtensionHeader header; + memcpy(&header.magic, SandboxedExtensionUnpacker::kExtensionHeaderMagic, + SandboxedExtensionUnpacker::kExtensionHeaderMagicSize); + header.version = SandboxedExtensionUnpacker::kCurrentVersion; header.key_size = public_key.size(); header.signature_size = signature.size(); - fwrite(&header, sizeof(ExtensionsService::ExtensionHeader), 1, + fwrite(&header, sizeof(SandboxedExtensionUnpacker::ExtensionHeader), 1, crx_handle.get()); fwrite(&public_key.front(), sizeof(uint8), public_key.size(), crx_handle.get()); diff --git a/chrome/browser/extensions/extensions_service.cc b/chrome/browser/extensions/extensions_service.cc index 2acc256..1b993e6 100644 --- a/chrome/browser/extensions/extensions_service.cc +++ b/chrome/browser/extensions/extensions_service.cc @@ -6,23 +6,16 @@ #include "app/l10n_util.h" #include "base/command_line.h" -#include "base/crypto/signature_verifier.h" #include "base/file_util.h" -#include "base/gfx/png_encoder.h" -#include "base/scoped_handle.h" #include "base/scoped_temp_dir.h" #include "base/stl_util-inl.h" #include "base/string_util.h" -#include "base/third_party/nss/blapi.h" -#include "base/third_party/nss/sha256.h" -#include "base/thread.h" #include "base/values.h" #include "net/base/file_stream.h" #include "chrome/browser/browser.h" #include "chrome/browser/browser_list.h" #include "chrome/browser/browser_process.h" #include "chrome/browser/chrome_thread.h" -#include "chrome/browser/extensions/extension_creator.h" #include "chrome/browser/extensions/extension_browser_event_router.h" #include "chrome/browser/extensions/extension_process_manager.h" #include "chrome/browser/extensions/extension_updater.h" @@ -31,22 +24,17 @@ #include "chrome/browser/extensions/theme_preview_infobar_delegate.h" #include "chrome/browser/profile.h" #include "chrome/browser/tab_contents/tab_contents.h" -#include "chrome/browser/utility_process_host.h" #include "chrome/common/chrome_switches.h" #include "chrome/common/extensions/extension.h" #include "chrome/common/extensions/extension_constants.h" #include "chrome/common/extensions/extension_error_reporter.h" -#include "chrome/common/extensions/extension_unpacker.h" #include "chrome/common/json_value_serializer.h" #include "chrome/common/notification_service.h" #include "chrome/common/pref_names.h" #include "chrome/common/pref_service.h" -#include "chrome/common/zip.h" #include "chrome/common/url_constants.h" #include "grit/chromium_strings.h" #include "grit/generated_resources.h" -#include "net/base/base64.h" -#include "third_party/skia/include/core/SkBitmap.h" #if defined(OS_WIN) #include "app/win_util.h" @@ -59,8 +47,6 @@ // ExtensionsService. -const char ExtensionsService::kExtensionHeaderMagic[] = "Cr24"; - const char* ExtensionsService::kInstallDirectoryName = "Extensions"; const char* ExtensionsService::kCurrentVersionFileName = "Current Version"; @@ -69,27 +55,6 @@ const char* ExtensionsService::kGalleryDownloadURLPrefix = const char* ExtensionsService::kGalleryURLPrefix = "https://tools.google.com/chrome/"; -const char* ExtensionsServiceBackend::kTempExtensionName = "TEMP_INSTALL"; - -namespace { - -// A temporary subdirectory where we unpack extensions. -const char* kUnpackExtensionDir = "TEMP_UNPACK"; - -// Unpacking errors -const char* kBadMagicNumberError = "Bad magic number"; -const char* kBadHeaderSizeError = "Excessively large key or signature"; -const char* kBadVersionNumberError = "Bad version number"; -const char* kInvalidExtensionHeaderError = "Invalid extension header"; -const char* kInvalidPublicKeyError = "Invalid public key"; -const char* kInvalidSignatureError = "Invalid signature"; -const char* kSignatureVerificationFailed = "Signature verification failed"; -const char* kSignatureVerificationInitFailed = - "Signature verification initialization failed. This is most likely " - "caused by a public key in the wrong format (should encode algorithm)."; - -} - // static bool ExtensionsService::IsDownloadFromGallery(const GURL& download_url, const GURL& referrer_url) { @@ -101,131 +66,46 @@ bool ExtensionsService::IsDownloadFromGallery(const GURL& download_url, } } -// This class coordinates an extension unpack task which is run in a separate -// process. Results are sent back to this class, which we route to the -// ExtensionServiceBackend. +// This class hosts a SandboxedExtensionUnpacker task and routes the results +// back to ExtensionsService. The unpack process is started immediately on +// construction of this object. class ExtensionsServiceBackend::UnpackerClient - : public UtilityProcessHost::Client { + : public SandboxedExtensionUnpackerClient { public: UnpackerClient(ExtensionsServiceBackend* backend, const FilePath& extension_path, - const std::string& public_key, const std::string& expected_id, bool silent, bool from_gallery) : backend_(backend), extension_path_(extension_path), - public_key_(public_key), expected_id_(expected_id), got_response_(false), - silent_(silent), from_gallery_(from_gallery) { - } - - // Starts the unpack task. We call back to the backend when the task is done, - // or a problem occurs. - void Start() { - AddRef(); // balanced in OnUnpackExtensionReply() - - // TODO(mpcomplete): handle multiple installs - FilePath temp_dir = backend_->install_directory_.AppendASCII( - kUnpackExtensionDir); - if (!file_util::CreateDirectory(temp_dir)) { - backend_->ReportExtensionInstallError(extension_path_, - "Failed to create temporary directory."); - return; - } - - temp_extension_path_ = temp_dir.Append(extension_path_.BaseName()); - if (!file_util::CopyFile(extension_path_, temp_extension_path_)) { - backend_->ReportExtensionInstallError(extension_path_, - "Failed to copy extension file to temporary directory."); - return; - } - - if (backend_->resource_dispatcher_host_) { - ChromeThread::GetMessageLoop(ChromeThread::IO)->PostTask(FROM_HERE, - NewRunnableMethod(this, &UnpackerClient::StartProcessOnIOThread, - backend_->resource_dispatcher_host_, - MessageLoop::current())); - } else { - // Cheesy... but if we don't have a ResourceDispatcherHost, assume we're - // in a unit test and run the unpacker directly in-process. - ExtensionUnpacker unpacker(temp_extension_path_); - if (unpacker.Run()) { - OnUnpackExtensionSucceededImpl(*unpacker.parsed_manifest(), - unpacker.decoded_images()); - } else { - OnUnpackExtensionFailed(unpacker.error_message()); - } - } + expected_id_(expected_id), silent_(silent), from_gallery_(from_gallery) { + unpacker_ = new SandboxedExtensionUnpacker(extension_path, + backend->resource_dispatcher_host_, this); + unpacker_->Start(); } private: - // UtilityProcessHost::Client - virtual void OnProcessCrashed() { - // Don't report crashes if they happen after we got a response. - if (got_response_) - return; - - OnUnpackExtensionFailed("Chrome crashed while trying to install."); + // SandboxedExtensionUnpackerClient + virtual void OnUnpackSuccess(const FilePath& temp_dir, + const FilePath& extension_dir, + Extension* extension) { + backend_->OnExtensionUnpacked(extension_path_, extension_dir, extension, + expected_id_, silent_, from_gallery_); + file_util::Delete(temp_dir, true); + delete this; } - virtual void OnUnpackExtensionSucceeded(const DictionaryValue& manifest) { - ExtensionUnpacker::DecodedImages images; - if (!ExtensionUnpacker::ReadImagesFromFile(temp_extension_path_, - &images)) { - OnUnpackExtensionFailed("Couldn't read image data from disk."); - } else { - OnUnpackExtensionSucceededImpl(manifest, images); - } - } - - void OnUnpackExtensionSucceededImpl( - const DictionaryValue& manifest, - const ExtensionUnpacker::DecodedImages& images) { - // Add our public key into the parsed manifest. We want it to be saved so - // that we can later refer to it (eg for generating ids, validating - // signatures, etc). - // The const_cast is hacky, but seems like the right thing here, rather than - // making a full copy just to make this change. - const_cast<DictionaryValue*>(&manifest)->SetString( - extension_manifest_keys::kPublicKey, public_key_); - - // The extension was unpacked to the temp dir inside our unpacking dir. - FilePath extension_dir = temp_extension_path_.DirName().AppendASCII( - ExtensionsServiceBackend::kTempExtensionName); - backend_->OnExtensionUnpacked(extension_path_, extension_dir, - expected_id_, manifest, images, silent_, - from_gallery_); - Cleanup(); - } - - virtual void OnUnpackExtensionFailed(const std::string& error_message) { + virtual void OnUnpackFailure(const std::string& error_message) { backend_->ReportExtensionInstallError(extension_path_, error_message); - Cleanup(); - } - - // Cleans up our temp directory. - void Cleanup() { - if (got_response_) - return; - - got_response_ = true; - file_util::Delete(temp_extension_path_.DirName(), true); - Release(); // balanced in Run() + delete this; } - // Starts the utility process that unpacks our extension. - void StartProcessOnIOThread(ResourceDispatcherHost* rdh, - MessageLoop* file_loop) { - UtilityProcessHost* host = new UtilityProcessHost(rdh, this, file_loop); - host->StartExtensionUnpacker(temp_extension_path_); - } + scoped_refptr<SandboxedExtensionUnpacker> unpacker_; scoped_refptr<ExtensionsServiceBackend> backend_; // The path to the crx file that we're installing. FilePath extension_path_; - // The public key of the extension we're installing. - std::string public_key_; - // The path to the copy of the crx file in the temporary directory where we're // unpacking it. FilePath temp_extension_path_; @@ -233,10 +113,6 @@ class ExtensionsServiceBackend::UnpackerClient // The ID we expect this extension to have, if any. std::string expected_id_; - // True if we got a response from the utility process and have cleaned up - // already. - bool got_response_; - // True if the install should be done with no confirmation dialog. bool silent_; @@ -673,11 +549,6 @@ void ExtensionsServiceBackend::GarbageCollectExtensions( std::string extension_id = WideToASCII( extension_path.BaseName().ToWStringHack()); - // The utility process might be in the middle of unpacking an extension, so - // ignore the temp unpacking directory. - if (extension_id == kUnpackExtensionDir) - continue; - // If there is no Current Version file, just delete the directory and move // on. This can legitimately happen when an uninstall does not complete, for // example, when a plugin is in use at uninstall time. @@ -1038,130 +909,28 @@ void ExtensionsServiceBackend::UpdateExtension(const std::string& id, void ExtensionsServiceBackend::InstallOrUpdateExtension( const FilePath& extension_path, bool from_gallery, const std::string& expected_id, bool silent) { - std::string actual_public_key; - if (!ValidateSignature(extension_path, &actual_public_key)) - return; // Failures reported within ValidateSignature(). - - UnpackerClient* client = new UnpackerClient( - this, extension_path, actual_public_key, expected_id, silent, - from_gallery); - client->Start(); -} - -bool ExtensionsServiceBackend::ValidateSignature(const FilePath& extension_path, - std::string* key_out) { - ScopedStdioHandle file(file_util::OpenFile(extension_path, "rb")); - if (!file.get()) { - ReportExtensionInstallError(extension_path, "Could not open file."); - return NULL; - } - - // Read and verify the header. - ExtensionsService::ExtensionHeader header; - size_t len; - - // TODO(erikkay): Yuck. I'm not a big fan of this kind of code, but it - // appears that we don't have any endian/alignment aware serialization - // code in the code base. So for now, this assumes that we're running - // on a little endian machine with 4 byte alignment. - len = fread(&header, 1, sizeof(ExtensionsService::ExtensionHeader), - file.get()); - if (len < sizeof(ExtensionsService::ExtensionHeader)) { - ReportExtensionInstallError(extension_path, kInvalidExtensionHeaderError); - return false; - } - if (strncmp(ExtensionsService::kExtensionHeaderMagic, header.magic, - sizeof(header.magic))) { - ReportExtensionInstallError(extension_path, kBadMagicNumberError); - return false; - } - if (header.version != ExtensionsService::kCurrentVersion) { - ReportExtensionInstallError(extension_path, kBadVersionNumberError); - return false; - } - if (header.key_size > ExtensionsService::kMaxPublicKeySize || - header.signature_size > ExtensionsService::kMaxSignatureSize) { - ReportExtensionInstallError(extension_path, kBadHeaderSizeError); - return false; - } - - std::vector<uint8> key; - key.resize(header.key_size); - len = fread(&key.front(), sizeof(uint8), header.key_size, file.get()); - if (len < header.key_size) { - ReportExtensionInstallError(extension_path, kInvalidPublicKeyError); - return false; - } - - std::vector<uint8> signature; - signature.resize(header.signature_size); - len = fread(&signature.front(), sizeof(uint8), header.signature_size, - file.get()); - if (len < header.signature_size) { - ReportExtensionInstallError(extension_path, kInvalidSignatureError); - return false; - } - - // Note: this structure is an ASN.1 which encodes the algorithm used - // with its parameters. This is defined in PKCS #1 v2.1 (RFC 3447). - // It is encoding: { OID sha1WithRSAEncryption PARAMETERS NULL } - // TODO(aa): This needs to be factored away someplace common. - const uint8 signature_algorithm[15] = { - 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, - 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00 - }; - - base::SignatureVerifier verifier; - if (!verifier.VerifyInit(signature_algorithm, - sizeof(signature_algorithm), - &signature.front(), - signature.size(), - &key.front(), - key.size())) { - ReportExtensionInstallError(extension_path, - kSignatureVerificationInitFailed); - return false; - } - - unsigned char buf[1 << 12]; - while ((len = fread(buf, 1, sizeof(buf), file.get())) > 0) - verifier.VerifyUpdate(buf, len); - - if (!verifier.VerifyFinal()) { - ReportExtensionInstallError(extension_path, kSignatureVerificationFailed); - return false; - } - - net::Base64Encode(std::string(reinterpret_cast<char*>(&key.front()), - key.size()), key_out); - return true; + // NOTE: We don't need to keep a reference to this, it deletes itself when it + // is done. + new UnpackerClient(this, extension_path, expected_id, silent, from_gallery); } void ExtensionsServiceBackend::OnExtensionUnpacked( - const FilePath& extension_path, - const FilePath& temp_extension_dir, - const std::string& expected_id, - const DictionaryValue& manifest, - const std::vector< Tuple2<SkBitmap, FilePath> >& images, - bool silent, bool from_gallery) { - Extension extension; - std::string error; - if (!extension.InitFromValue(manifest, - true, // require ID - &error)) { - ReportExtensionInstallError(extension_path, "Invalid extension manifest."); - return; - } + const FilePath& crx_path, const FilePath& unpacked_path, + Extension* extension, const std::string expected_id, bool silent, + bool from_gallery) { + // Take ownership of the extension object. + scoped_ptr<Extension> extension_deleter(extension); Extension::Location location = Extension::INTERNAL; - LookupExternalExtension(extension.id(), NULL, &location); + LookupExternalExtension(extension->id(), NULL, &location); + extension->set_location(location); bool allow_install = false; if (extensions_enabled_) allow_install = true; // Always allow themes. - if (extension.IsTheme()) + if (extension->IsTheme()) allow_install = true; // Always allow externally installed extensions (partners use this). @@ -1169,8 +938,7 @@ void ExtensionsServiceBackend::OnExtensionUnpacked( allow_install = true; if (!allow_install) { - ReportExtensionInstallError(extension_path, - "Extensions are not enabled."); + ReportExtensionInstallError(crx_path, "Extensions are not enabled."); return; } @@ -1182,7 +950,7 @@ void ExtensionsServiceBackend::OnExtensionUnpacked( // - during tests (!frontend->show_extension_prompts()) // - autoupdate (silent). bool show_dialog = true; - if (extension.IsTheme()) + if (extension->IsTheme()) show_dialog = false; if (Extension::IsExternalLocation(location)) @@ -1215,7 +983,7 @@ void ExtensionsServiceBackend::OnExtensionUnpacked( NULL, CFSTR("Cancel"), NULL, &response); if (response == kCFUserNotificationAlternateResponse) { - ReportExtensionInstallError(extension_path, + ReportExtensionInstallError(crx_path, "User did not allow extension to be installed."); return; } @@ -1223,132 +991,66 @@ void ExtensionsServiceBackend::OnExtensionUnpacked( } // If an expected id was provided, make sure it matches. - if (!expected_id.empty() && expected_id != extension.id()) { + if (!expected_id.empty() && expected_id != extension->id()) { std::string error_msg = "ID in new extension manifest ("; - error_msg += extension.id(); + error_msg += extension->id(); error_msg += ") does not match expected ID ("; error_msg += expected_id; error_msg += ")"; - ReportExtensionInstallError(extension_path, error_msg); + ReportExtensionInstallError(crx_path, error_msg); return; } // <profile>/Extensions/<id> - FilePath dest_dir = install_directory_.AppendASCII(extension.id()); - std::string version = extension.VersionString(); + FilePath dest_dir = install_directory_.AppendASCII(extension->id()); + std::string version = extension->VersionString(); std::string current_version; Extension::InstallType install_type = - CompareToInstalledVersion(extension.id(), version, ¤t_version); + CompareToInstalledVersion(extension->id(), version, ¤t_version); // Do not allow downgrade. if (install_type == Extension::DOWNGRADE) { - ReportExtensionInstallError(extension_path, + ReportExtensionInstallError(crx_path, "Error: Attempt to downgrade extension from more recent version."); return; } if (install_type == Extension::REINSTALL) { - if (NeedsReinstall(extension.id(), current_version)) { + if (NeedsReinstall(extension->id(), current_version)) { // Treat corrupted existing installation as new install case. install_type = Extension::NEW_INSTALL; } else { // The client may use this as a signal (to switch themes, for instance). - ReportExtensionOverinstallAttempted(extension.id(), extension_path); - return; - } - } - - // Write our parsed manifest back to disk, to ensure it doesn't contain an - // exploitable bug that can be used to compromise the browser. - std::string manifest_json; - JSONStringValueSerializer serializer(&manifest_json); - serializer.set_pretty_print(true); - if (!serializer.Serialize(manifest)) { - ReportExtensionInstallError(extension_path, - "Error serializing manifest.json."); - return; - } - - FilePath manifest_path = - temp_extension_dir.AppendASCII(Extension::kManifestFilename); - if (!file_util::WriteFile(manifest_path, - manifest_json.data(), manifest_json.size())) { - ReportExtensionInstallError(extension_path, "Error saving manifest.json."); - return; - } - - // Delete any images that may be used by the browser. We're going to write - // out our own versions of the parsed images, and we want to make sure the - // originals are gone for good. - std::set<FilePath> image_paths = extension.GetBrowserImages(); - if (image_paths.size() != images.size()) { - ReportExtensionInstallError(extension_path, - "Decoded images don't match what's in the manifest."); - return; - } - - for (std::set<FilePath>::iterator it = image_paths.begin(); - it != image_paths.end(); ++it) { - if (!file_util::Delete(temp_extension_dir.Append(*it), false)) { - ReportExtensionInstallError(extension_path, - "Error removing old image file."); - return; - } - } - - // Write our parsed images back to disk as well. - for (size_t i = 0; i < images.size(); ++i) { - const SkBitmap& image = images[i].a; - FilePath path = temp_extension_dir.Append(images[i].b); - - std::vector<unsigned char> image_data; - // TODO(mpcomplete): It's lame that we're encoding all images as PNG, even - // though they may originally be .jpg, etc. Figure something out. - // http://code.google.com/p/chromium/issues/detail?id=12459 - if (!PNGEncoder::EncodeBGRASkBitmap(image, false, &image_data)) { - ReportExtensionInstallError(extension_path, - "Error re-encoding theme image."); - return; - } - - // Note: we're overwriting existing files that the utility process wrote, - // so we can be sure the directory exists. - const char* image_data_ptr = reinterpret_cast<const char*>(&image_data[0]); - if (!file_util::WriteFile(path, image_data_ptr, image_data.size())) { - ReportExtensionInstallError(extension_path, "Error saving theme image."); + ReportExtensionOverinstallAttempted(extension->id(), crx_path); return; } } // <profile>/Extensions/<dir_name>/<version> FilePath version_dir = dest_dir.AppendASCII(version); + extension->set_path(version_dir); // If anything fails after this, we want to delete the extension dir. ScopedTempDir scoped_version_dir; scoped_version_dir.Set(version_dir); - if (!InstallDirSafely(temp_extension_dir, version_dir)) + if (!InstallDirSafely(unpacked_path, version_dir)) return; if (!SetCurrentVersion(dest_dir, version)) return; - Extension* loaded = LoadExtension(version_dir, - location, - true); // require id - CHECK(loaded); - frontend_loop_->PostTask(FROM_HERE, NewRunnableMethod( - frontend_, &ExtensionsService::OnExtensionInstalled, extension_path, - loaded, install_type)); + frontend_, &ExtensionsService::OnExtensionInstalled, crx_path, + extension, install_type)); // Only one extension, but ReportExtensionsLoaded can handle multiple, // so we need to construct a list. - scoped_ptr<ExtensionList> extensions(new ExtensionList); - extensions->push_back(loaded); + ExtensionList* extensions = new ExtensionList; - // Hand off ownership of the loaded extensions to the frontend. - ReportExtensionsLoaded(extensions.release()); + // Hand off ownership of the extension to the frontend. + extensions->push_back(extension_deleter.release()); + ReportExtensionsLoaded(extensions); scoped_version_dir.Take(); } diff --git a/chrome/browser/extensions/extensions_service.h b/chrome/browser/extensions/extensions_service.h index b664b22..5fc036b 100644 --- a/chrome/browser/extensions/extensions_service.h +++ b/chrome/browser/extensions/extensions_service.h @@ -20,6 +20,7 @@ #include "chrome/browser/extensions/extension_prefs.h" #include "chrome/browser/extensions/extension_process_manager.h" #include "chrome/browser/extensions/external_extension_provider.h" +#include "chrome/browser/extensions/sandboxed_extension_unpacker.h" #include "chrome/common/extensions/extension.h" class Browser; @@ -32,7 +33,6 @@ class MessageLoop; class PrefService; class Profile; class ResourceDispatcherHost; -class SkBitmap; class SiteInstance; typedef std::vector<Extension*> ExtensionList; @@ -60,37 +60,6 @@ class ExtensionsService public base::RefCountedThreadSafe<ExtensionsService> { public: - // TODO(port): Move Crx package definitions to ExtentionCreator. They are - // currently here because ExtensionCreator is excluded on linux & mac. - - // The size of the magic character sequence at the beginning of each crx - // file, in bytes. This should be a multiple of 4. - static const size_t kExtensionHeaderMagicSize = 4; - - // This header is the first data at the beginning of an extension. Its - // contents are purposely 32-bit aligned so that it can just be slurped into - // a struct without manual parsing. - struct ExtensionHeader { - char magic[kExtensionHeaderMagicSize]; - uint32 version; - size_t key_size; // The size of the public key, in bytes. - size_t signature_size; // The size of the signature, in bytes. - // An ASN.1-encoded PublicKeyInfo structure follows. - // The signature follows. - }; - - // The maximum size the crx parser will tolerate for a public key. - static const size_t kMaxPublicKeySize = 1 << 16; - - // The maximum size the crx parser will tolerate for a signature. - static const size_t kMaxSignatureSize = 1 << 16; - - // The magic character sequence at the beginning of each crx file. - static const char kExtensionHeaderMagic[]; - - // The current version of the crx format. - static const uint32 kCurrentVersion = 2; - // The name of the directory inside the profile where extensions are // installed to. static const char* kInstallDirectoryName; @@ -395,22 +364,18 @@ class ExtensionsServiceBackend bool from_gallery, const std::string& expected_id, bool silent); - // Validates the signature of the extension in |extension_path|. Returns true - // and the public key (in |key|) if the signature validates, false otherwise. - bool ValidateSignature(const FilePath& extension_path, std::string* key_out); - - // Finish installing an extension after it has been unpacked to - // |temp_extension_dir| by our utility process. If |expected_id| is not - // empty, it's verified against the extension's manifest before installation. - // |manifest| and |images| are parsed information from the extension that - // we want to write to disk in the browser process. If |silent| is true, there - // will be no install confirmation dialog. + // Finish installing the extension in |crx_path| after it has been unpacked to + // |unpacked_path|. If |expected_id| is not empty, it's verified against the + // extension's manifest before installation. If |silent| is true, there will + // be no install confirmation dialog. |from_gallery| indicates whether the + // crx was installed from our gallery, which results in different UI. + // + // Note: We take ownership of |extension|. void OnExtensionUnpacked( - const FilePath& extension_path, - const FilePath& temp_extension_dir, - const std::string& expected_id, - const DictionaryValue& manifest, - const std::vector< Tuple2<SkBitmap, FilePath> >& images, + const FilePath& crx_path, + const FilePath& unpacked_path, + Extension* extension, + const std::string expected_id, bool silent, bool from_gallery); diff --git a/chrome/browser/extensions/sandboxed_extension_unpacker.cc b/chrome/browser/extensions/sandboxed_extension_unpacker.cc new file mode 100644 index 0000000..28b3b2b --- /dev/null +++ b/chrome/browser/extensions/sandboxed_extension_unpacker.cc @@ -0,0 +1,280 @@ +// Copyright (c) 2009 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/extensions/sandboxed_extension_unpacker.h" + +#include <set> + +#include "base/crypto/signature_verifier.h" +#include "base/file_util.h" +#include "base/gfx/png_encoder.h" +#include "base/message_loop.h" +#include "base/scoped_handle.h" +#include "base/task.h" +#include "chrome/browser/chrome_thread.h" +#include "chrome/browser/extensions/extensions_service.h" +#include "chrome/browser/renderer_host/resource_dispatcher_host.h" +#include "chrome/common/extensions/extension.h" +#include "chrome/common/extensions/extension_constants.h" +#include "chrome/common/extensions/extension_unpacker.h" +#include "chrome/common/json_value_serializer.h" +#include "net/base/base64.h" + +#include "third_party/skia/include/core/SkBitmap.h" + +const char SandboxedExtensionUnpacker::kExtensionHeaderMagic[] = "Cr24"; + +SandboxedExtensionUnpacker::SandboxedExtensionUnpacker( + const FilePath& crx_path, ResourceDispatcherHost* rdh, + SandboxedExtensionUnpackerClient* client) + : crx_path_(crx_path), client_loop_(MessageLoop::current()), rdh_(rdh), + client_(client), got_response_(false) { + + AddRef(); +} + +void SandboxedExtensionUnpacker::Start() { + // Create a temporary directory to work in. + if (!temp_dir_.CreateUniqueTempDir()) { + ReportFailure("Could not create temporary directory."); + return; + } + + // Initialize the path that will eventually contain the unpacked extension. + extension_root_ = temp_dir_.path().AppendASCII("TEMP_INSTALL"); + + // Extract the public key and validate the package. + if (!ValidateSignature()) + return; // ValidateSignature() already reported the error. + + // Copy the crx file into our working directory. + FilePath temp_crx_path = temp_dir_.path().Append(crx_path_.BaseName()); + if (!file_util::CopyFile(crx_path_, temp_crx_path)) { + ReportFailure("Failed to copy extension file to temporary directory."); + return; + } + + // If we are supposed to use a subprocess, copy the crx to the temp directory + // and kick off the subprocess. + if (rdh_) { + ChromeThread::GetMessageLoop(ChromeThread::IO)->PostTask(FROM_HERE, + NewRunnableMethod(this, + &SandboxedExtensionUnpacker::StartProcessOnIOThread, + temp_crx_path)); + } else { + // Otherwise, unpack the extension in this process. + ExtensionUnpacker unpacker(temp_crx_path); + if (unpacker.Run() && unpacker.DumpImagesToFile()) + OnUnpackExtensionSucceeded(*unpacker.parsed_manifest()); + else + OnUnpackExtensionFailed(unpacker.error_message()); + } +} + +void SandboxedExtensionUnpacker::StartProcessOnIOThread( + const FilePath& temp_crx_path) { + UtilityProcessHost* host = new UtilityProcessHost(rdh_, this, + MessageLoop::current()); + host->StartExtensionUnpacker(temp_crx_path); +} + +void SandboxedExtensionUnpacker::OnUnpackExtensionSucceeded( + const DictionaryValue& manifest) { + got_response_ = true; + + ExtensionUnpacker::DecodedImages images; + if (!ExtensionUnpacker::ReadImagesFromFile(temp_dir_.path(), &images)) { + ReportFailure("Couldn't read image data from disk."); + return; + } + + // 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. + scoped_ptr<DictionaryValue> final_manifest( + static_cast<DictionaryValue*>(manifest.DeepCopy())); + final_manifest->SetString(extension_manifest_keys::kPublicKey, public_key_); + + std::string manifest_json; + JSONStringValueSerializer serializer(&manifest_json); + serializer.set_pretty_print(true); + if (!serializer.Serialize(*final_manifest)) { + ReportFailure("Error serializing manifest.json."); + return; + } + + FilePath manifest_path = + extension_root_.AppendASCII(Extension::kManifestFilename); + if (!file_util::WriteFile(manifest_path, + manifest_json.data(), manifest_json.size())) { + ReportFailure("Error saving manifest.json."); + return; + } + + // Delete any images that may be used by the browser. We're going to write + // out our own versions of the parsed images, and we want to make sure the + // originals are gone for good. + extension_.reset(new Extension); + std::string manifest_error; + if (!extension_->InitFromValue(*final_manifest, true, // require id + &manifest_error)) { + ReportFailure(std::string("Manifest is invalid: ") + + manifest_error); + return; + } + + std::set<FilePath> image_paths = extension_->GetBrowserImages(); + if (image_paths.size() != images.size()) { + ReportFailure("Decoded images don't match what's in the manifest."); + return; + } + + for (std::set<FilePath>::iterator it = image_paths.begin(); + it != image_paths.end(); ++it) { + if (!file_util::Delete(extension_root_.Append(*it), false)) { + ReportFailure("Error removing old image file."); + return; + } + } + + // Write our parsed images back to disk as well. + for (size_t i = 0; i < images.size(); ++i) { + const SkBitmap& image = images[i].a; + FilePath path = extension_root_.Append(images[i].b); + + std::vector<unsigned char> image_data; + // TODO(mpcomplete): It's lame that we're encoding all images as PNG, even + // though they may originally be .jpg, etc. Figure something out. + // http://code.google.com/p/chromium/issues/detail?id=12459 + if (!PNGEncoder::EncodeBGRASkBitmap(image, false, &image_data)) { + ReportFailure("Error re-encoding theme image."); + return; + } + + // Note: we're overwriting existing files that the utility process wrote, + // so we can be sure the directory exists. + const char* image_data_ptr = reinterpret_cast<const char*>(&image_data[0]); + if (!file_util::WriteFile(path, image_data_ptr, image_data.size())) { + ReportFailure("Error saving theme image."); + return; + } + } + + ReportSuccess(); +} + +void SandboxedExtensionUnpacker::OnUnpackExtensionFailed( + const std::string& error) { + got_response_ = true; + ReportFailure(error); +} + +void SandboxedExtensionUnpacker::OnProcessCrashed() { + // Don't report crashes if they happen after we got a response. + if (got_response_) + return; + + ReportFailure("Utility process crashed while trying to install."); +} + +bool SandboxedExtensionUnpacker::ValidateSignature() { + ScopedStdioHandle file(file_util::OpenFile(crx_path_, "rb")); + if (!file.get()) { + ReportFailure("Could not open crx file for reading"); + return false; + } + + // Read and verify the header. + ExtensionHeader header; + size_t len; + + // TODO(erikkay): Yuck. I'm not a big fan of this kind of code, but it + // appears that we don't have any endian/alignment aware serialization + // code in the code base. So for now, this assumes that we're running + // on a little endian machine with 4 byte alignment. + len = fread(&header, 1, sizeof(ExtensionHeader), + file.get()); + if (len < sizeof(ExtensionHeader)) { + ReportFailure("Invalid crx header"); + return false; + } + if (strncmp(kExtensionHeaderMagic, header.magic, + sizeof(header.magic))) { + ReportFailure("Bad magic number"); + return false; + } + if (header.version != kCurrentVersion) { + ReportFailure("Bad version number"); + return false; + } + if (header.key_size > kMaxPublicKeySize || + header.signature_size > kMaxSignatureSize) { + ReportFailure("Excessively large key or signature"); + return false; + } + + std::vector<uint8> key; + key.resize(header.key_size); + len = fread(&key.front(), sizeof(uint8), header.key_size, file.get()); + if (len < header.key_size) { + ReportFailure("Invalid public key"); + return false; + } + + std::vector<uint8> signature; + signature.resize(header.signature_size); + len = fread(&signature.front(), sizeof(uint8), header.signature_size, + file.get()); + if (len < header.signature_size) { + ReportFailure("Invalid signature"); + return false; + } + + // Note: this structure is an ASN.1 which encodes the algorithm used + // with its parameters. This is defined in PKCS #1 v2.1 (RFC 3447). + // It is encoding: { OID sha1WithRSAEncryption PARAMETERS NULL } + // TODO(aa): This needs to be factored away someplace common. + const uint8 signature_algorithm[15] = { + 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, + 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00 + }; + + base::SignatureVerifier verifier; + if (!verifier.VerifyInit(signature_algorithm, + sizeof(signature_algorithm), + &signature.front(), + signature.size(), + &key.front(), + key.size())) { + ReportFailure("Signature verification initialization failed. " + "This is most likely caused by a public key in " + "the wrong format (should encode algorithm)."); + return false; + } + + unsigned char buf[1 << 12]; + while ((len = fread(buf, 1, sizeof(buf), file.get())) > 0) + verifier.VerifyUpdate(buf, len); + + if (!verifier.VerifyFinal()) { + ReportFailure("Signature verification failed"); + return false; + } + + net::Base64Encode(std::string(reinterpret_cast<char*>(&key.front()), + key.size()), &public_key_); + return true; +} + +void SandboxedExtensionUnpacker::ReportFailure(const std::string& error) { + client_->OnUnpackFailure(error); + Release(); +} + +void SandboxedExtensionUnpacker::ReportSuccess() { + // Client takes ownership of temporary directory and extension. + client_->OnUnpackSuccess(temp_dir_.Take(), extension_root_, + extension_.release()); + Release(); +} diff --git a/chrome/browser/extensions/sandboxed_extension_unpacker.h b/chrome/browser/extensions/sandboxed_extension_unpacker.h new file mode 100644 index 0000000..4023ea9 --- /dev/null +++ b/chrome/browser/extensions/sandboxed_extension_unpacker.h @@ -0,0 +1,125 @@ +// Copyright (c) 2009 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_BROWSER_EXTENSIONS_SANDBOXED_EXTENSION_UNPACKER_H_ +#define CHROME_BROWSER_EXTENSIONS_SANDBOXED_EXTENSION_UNPACKER_H_ + +#include <string> + +#include "base/file_path.h" +#include "base/ref_counted.h" +#include "base/scoped_temp_dir.h" +#include "base/values.h" +#include "chrome/browser/utility_process_host.h" + +class Extension; +class MessageLoop; +class ResourceDispatcherHost; + +class SandboxedExtensionUnpackerClient { + public: + // temp_dir - A temporary directoy containing the results of the extension + // unpacking. The client is responsible for deleting this directory. + // + // extension_root - The path to the extension root inside of temp_dir. + // + // extension - The extension that was unpacked. The client is responsible + // for deleting this memory. + virtual void OnUnpackSuccess(const FilePath& temp_dir, + const FilePath& extension_root, + Extension* extension) = 0; + virtual void OnUnpackFailure(const std::string& error) = 0; +}; + +// SandboxedExtensionUnpacker 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. +// +// Unpacking an extension using this class makes minor changes to its source, +// such as transcoding all images to PNG and rewriting the manifest JSON. As +// such, it should not be used when the output is not intended to be given back +// to the author. +// +// NOTE: This class should only be used on the file thread. + + +class SandboxedExtensionUnpacker : public UtilityProcessHost::Client { + public: + // The size of the magic character sequence at the beginning of each crx + // file, in bytes. This should be a multiple of 4. + static const size_t kExtensionHeaderMagicSize = 4; + + // This header is the first data at the beginning of an extension. Its + // contents are purposely 32-bit aligned so that it can just be slurped into + // a struct without manual parsing. + struct ExtensionHeader { + char magic[kExtensionHeaderMagicSize]; + uint32 version; + size_t key_size; // The size of the public key, in bytes. + size_t signature_size; // The size of the signature, in bytes. + // An ASN.1-encoded PublicKeyInfo structure follows. + // The signature follows. + }; + + // The maximum size the crx parser will tolerate for a public key. + static const size_t kMaxPublicKeySize = 1 << 16; + + // The maximum size the crx parser will tolerate for a signature. + static const size_t kMaxSignatureSize = 1 << 16; + + // The magic character sequence at the beginning of each crx file. + static const char kExtensionHeaderMagic[]; + + // The current version of the crx format. + static const uint32 kCurrentVersion = 2; + + // Unpacks the extension in |crx_path| into a temporary directory and calls + // |client| with the result. If |rdh| is provided, unpacking is done in a + // sandboxed subprocess. Otherwise, it is done in-process. + SandboxedExtensionUnpacker(const FilePath& crx_path, + ResourceDispatcherHost* rdh, + SandboxedExtensionUnpackerClient* cilent); + + // Start unpacking the extension. The client is called with the results. + void Start(); + + private: + class ProcessHostClient; + friend class ProcessHostClient; + + // Validates the signature of the extension and extract the key to + // |public_key_|. Returns true if the signature validates, false otherwise. + // + // NOTE: Having this method here is a bit ugly. This code should really live + // in ExtensionUnpacker as it is not specific to sandboxed unpacking. It was + // put here because we cannot run windows crypto code in the sandbox. But we + // could still have this method statically on ExtensionUnpacker so that code + // just for unpacking is there and code just for sandboxing of unpacking is + // here. + bool ValidateSignature(); + + // Starts the utility process that unpacks our extension. + void StartProcessOnIOThread(const FilePath& temp_crx_path); + + // SandboxedExtensionUnpacker + void OnUnpackExtensionSucceeded(const DictionaryValue& manifest); + void OnUnpackExtensionFailed(const std::string& error_message); + void OnProcessCrashed(); + + void ReportFailure(const std::string& message); + void ReportSuccess(); + + FilePath crx_path_; + MessageLoop* client_loop_; + ResourceDispatcherHost* rdh_; + SandboxedExtensionUnpackerClient* client_; + ScopedTempDir temp_dir_; + FilePath extension_root_; + scoped_ptr<Extension> extension_; + bool got_response_; + std::string public_key_; +}; + +#endif // CHROME_BROWSER_EXTENSIONS_SANDBOXED_EXTENSION_UNPACKER_H_ |