| OLD | NEW |
| 1 // Copyright (c) 2009 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2009 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "chrome/browser/extensions/sandboxed_extension_unpacker.h" | 5 #include "chrome/browser/extensions/sandboxed_extension_unpacker.h" |
| 6 | 6 |
| 7 #include <set> | 7 #include <set> |
| 8 | 8 |
| 9 #include "app/gfx/codec/png_codec.h" | 9 #include "app/gfx/codec/png_codec.h" |
| 10 #include "base/crypto/signature_verifier.h" | 10 #include "base/crypto/signature_verifier.h" |
| 11 #include "base/file_util.h" | 11 #include "base/file_util.h" |
| 12 #include "base/message_loop.h" | 12 #include "base/message_loop.h" |
| 13 #include "base/scoped_handle.h" | 13 #include "base/scoped_handle.h" |
| 14 #include "base/task.h" | 14 #include "base/task.h" |
| 15 #include "chrome/browser/chrome_thread.h" | 15 #include "chrome/browser/chrome_thread.h" |
| 16 #include "chrome/browser/extensions/extensions_service.h" | 16 #include "chrome/browser/extensions/extensions_service.h" |
| 17 #include "chrome/browser/renderer_host/resource_dispatcher_host.h" | 17 #include "chrome/browser/renderer_host/resource_dispatcher_host.h" |
| 18 #include "chrome/common/chrome_switches.h" | 18 #include "chrome/common/chrome_switches.h" |
| 19 #include "chrome/common/extensions/extension.h" | 19 #include "chrome/common/extensions/extension.h" |
| 20 #include "chrome/common/extensions/extension_constants.h" | 20 #include "chrome/common/extensions/extension_constants.h" |
| 21 #include "chrome/common/extensions/extension_unpacker.h" | 21 #include "chrome/common/extensions/extension_unpacker.h" |
| 22 #include "chrome/common/json_value_serializer.h" | 22 #include "chrome/common/json_value_serializer.h" |
| 23 #include "net/base/base64.h" | 23 #include "net/base/base64.h" |
| 24 | |
| 25 #include "third_party/skia/include/core/SkBitmap.h" | 24 #include "third_party/skia/include/core/SkBitmap.h" |
| 26 | 25 |
| 27 const char SandboxedExtensionUnpacker::kExtensionHeaderMagic[] = "Cr24"; | 26 const char SandboxedExtensionUnpacker::kExtensionHeaderMagic[] = "Cr24"; |
| 28 | 27 |
| 29 SandboxedExtensionUnpacker::SandboxedExtensionUnpacker( | 28 SandboxedExtensionUnpacker::SandboxedExtensionUnpacker( |
| 30 const FilePath& crx_path, ResourceDispatcherHost* rdh, | 29 const FilePath& crx_path, ResourceDispatcherHost* rdh, |
| 31 SandboxedExtensionUnpackerClient* client) | 30 SandboxedExtensionUnpackerClient* client) |
| 32 : crx_path_(crx_path), thread_identifier_(ChromeThread::ID_COUNT), | 31 : crx_path_(crx_path), thread_identifier_(ChromeThread::ID_COUNT), |
| 33 rdh_(rdh), client_(client), got_response_(false) { | 32 rdh_(rdh), client_(client), got_response_(false) { |
| 34 } | 33 } |
| (...skipping 41 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 76 ChromeThread::PostTask( | 75 ChromeThread::PostTask( |
| 77 ChromeThread::IO, FROM_HERE, | 76 ChromeThread::IO, FROM_HERE, |
| 78 NewRunnableMethod( | 77 NewRunnableMethod( |
| 79 this, | 78 this, |
| 80 &SandboxedExtensionUnpacker::StartProcessOnIOThread, | 79 &SandboxedExtensionUnpacker::StartProcessOnIOThread, |
| 81 temp_crx_path)); | 80 temp_crx_path)); |
| 82 } else { | 81 } else { |
| 83 // Otherwise, unpack the extension in this process. | 82 // Otherwise, unpack the extension in this process. |
| 84 ExtensionUnpacker unpacker(temp_crx_path); | 83 ExtensionUnpacker unpacker(temp_crx_path); |
| 85 if (unpacker.Run() && unpacker.DumpImagesToFile()) | 84 if (unpacker.Run() && unpacker.DumpImagesToFile()) |
| 86 OnUnpackExtensionSucceeded(*unpacker.parsed_manifest()); | 85 OnUnpackExtensionSucceeded(*unpacker.parsed_manifest(), |
| 86 *unpacker.parsed_catalogs()); |
| 87 else | 87 else |
| 88 OnUnpackExtensionFailed(unpacker.error_message()); | 88 OnUnpackExtensionFailed(unpacker.error_message()); |
| 89 } | 89 } |
| 90 } | 90 } |
| 91 | 91 |
| 92 void SandboxedExtensionUnpacker::StartProcessOnIOThread( | 92 void SandboxedExtensionUnpacker::StartProcessOnIOThread( |
| 93 const FilePath& temp_crx_path) { | 93 const FilePath& temp_crx_path) { |
| 94 UtilityProcessHost* host = new UtilityProcessHost( | 94 UtilityProcessHost* host = new UtilityProcessHost( |
| 95 rdh_, this, thread_identifier_); | 95 rdh_, this, thread_identifier_); |
| 96 host->StartExtensionUnpacker(temp_crx_path); | 96 host->StartExtensionUnpacker(temp_crx_path); |
| 97 } | 97 } |
| 98 | 98 |
| 99 void SandboxedExtensionUnpacker::OnUnpackExtensionSucceeded( | 99 void SandboxedExtensionUnpacker::OnUnpackExtensionSucceeded( |
| 100 const DictionaryValue& manifest) { | 100 const DictionaryValue& manifest, |
| 101 DCHECK(ChromeThread::CurrentlyOn(thread_identifier_)); | 101 const DictionaryValue& catalogs) { |
| 102 // Skip check for unittests. |
| 103 if (thread_identifier_ != ChromeThread::ID_COUNT) |
| 104 DCHECK(ChromeThread::CurrentlyOn(thread_identifier_)); |
| 102 got_response_ = true; | 105 got_response_ = true; |
| 103 | 106 |
| 104 ExtensionUnpacker::DecodedImages images; | 107 scoped_ptr<DictionaryValue> final_manifest(RewriteManifestFile(manifest)); |
| 105 if (!ExtensionUnpacker::ReadImagesFromFile(temp_dir_.path(), &images)) { | 108 if (!final_manifest.get()) |
| 106 ReportFailure("Couldn't read image data from disk."); | |
| 107 return; | 109 return; |
| 108 } | |
| 109 | |
| 110 // Add the public key extracted earlier to the parsed manifest and overwrite | |
| 111 // the original manifest. We do this to ensure the manifest doesn't contain an | |
| 112 // exploitable bug that could be used to compromise the browser. | |
| 113 scoped_ptr<DictionaryValue> final_manifest( | |
| 114 static_cast<DictionaryValue*>(manifest.DeepCopy())); | |
| 115 final_manifest->SetString(extension_manifest_keys::kPublicKey, public_key_); | |
| 116 | |
| 117 std::string manifest_json; | |
| 118 JSONStringValueSerializer serializer(&manifest_json); | |
| 119 serializer.set_pretty_print(true); | |
| 120 if (!serializer.Serialize(*final_manifest)) { | |
| 121 ReportFailure("Error serializing manifest.json."); | |
| 122 return; | |
| 123 } | |
| 124 | |
| 125 FilePath manifest_path = | |
| 126 extension_root_.AppendASCII(Extension::kManifestFilename); | |
| 127 if (!file_util::WriteFile(manifest_path, | |
| 128 manifest_json.data(), manifest_json.size())) { | |
| 129 ReportFailure("Error saving manifest.json."); | |
| 130 return; | |
| 131 } | |
| 132 | 110 |
| 133 // Create an extension object that refers to the temporary location the | 111 // Create an extension object that refers to the temporary location the |
| 134 // extension was unpacked to. We use this until the extension is finally | 112 // extension was unpacked to. We use this until the extension is finally |
| 135 // installed. For example, the install UI shows images from inside the | 113 // installed. For example, the install UI shows images from inside the |
| 136 // extension. | 114 // extension. |
| 137 extension_.reset(new Extension(extension_root_)); | 115 extension_.reset(new Extension(extension_root_)); |
| 138 | 116 |
| 139 std::string manifest_error; | 117 std::string manifest_error; |
| 140 if (!extension_->InitFromValue(*final_manifest, true, // require id | 118 if (!extension_->InitFromValue(*final_manifest, true, // require id |
| 141 &manifest_error)) { | 119 &manifest_error)) { |
| 142 ReportFailure(std::string("Manifest is invalid: ") + | 120 ReportFailure(std::string("Manifest is invalid: ") + |
| 143 manifest_error); | 121 manifest_error); |
| 144 return; | 122 return; |
| 145 } | 123 } |
| 146 | 124 |
| 147 // Delete any images that may be used by the browser. We're going to write | 125 if (!RewriteImageFiles()) |
| 148 // out our own versions of the parsed images, and we want to make sure the | |
| 149 // originals are gone for good. | |
| 150 std::set<FilePath> image_paths = extension_->GetBrowserImages(); | |
| 151 if (image_paths.size() != images.size()) { | |
| 152 ReportFailure("Decoded images don't match what's in the manifest."); | |
| 153 return; | 126 return; |
| 154 } | |
| 155 | 127 |
| 156 for (std::set<FilePath>::iterator it = image_paths.begin(); | 128 if (!RewriteCatalogFiles(catalogs)) |
| 157 it != image_paths.end(); ++it) { | 129 return; |
| 158 FilePath path = *it; | |
| 159 if (path.IsAbsolute() || path.ReferencesParent()) { | |
| 160 ReportFailure("Invalid path for browser image."); | |
| 161 return; | |
| 162 } | |
| 163 if (!file_util::Delete(extension_root_.Append(path), false)) { | |
| 164 ReportFailure("Error removing old image file."); | |
| 165 return; | |
| 166 } | |
| 167 } | |
| 168 | |
| 169 // Write our parsed images back to disk as well. | |
| 170 for (size_t i = 0; i < images.size(); ++i) { | |
| 171 const SkBitmap& image = images[i].a; | |
| 172 FilePath path_suffix = images[i].b; | |
| 173 if (path_suffix.IsAbsolute() || path_suffix.ReferencesParent()) { | |
| 174 ReportFailure("Invalid path for bitmap image."); | |
| 175 return; | |
| 176 } | |
| 177 FilePath path = extension_root_.Append(path_suffix); | |
| 178 | |
| 179 std::vector<unsigned char> image_data; | |
| 180 // TODO(mpcomplete): It's lame that we're encoding all images as PNG, even | |
| 181 // though they may originally be .jpg, etc. Figure something out. | |
| 182 // http://code.google.com/p/chromium/issues/detail?id=12459 | |
| 183 if (!gfx::PNGCodec::EncodeBGRASkBitmap(image, false, &image_data)) { | |
| 184 ReportFailure("Error re-encoding theme image."); | |
| 185 return; | |
| 186 } | |
| 187 | |
| 188 // Note: we're overwriting existing files that the utility process wrote, | |
| 189 // so we can be sure the directory exists. | |
| 190 const char* image_data_ptr = reinterpret_cast<const char*>(&image_data[0]); | |
| 191 if (!file_util::WriteFile(path, image_data_ptr, image_data.size())) { | |
| 192 ReportFailure("Error saving theme image."); | |
| 193 return; | |
| 194 } | |
| 195 } | |
| 196 | 130 |
| 197 ReportSuccess(); | 131 ReportSuccess(); |
| 198 } | 132 } |
| 199 | 133 |
| 200 void SandboxedExtensionUnpacker::OnUnpackExtensionFailed( | 134 void SandboxedExtensionUnpacker::OnUnpackExtensionFailed( |
| 201 const std::string& error) { | 135 const std::string& error) { |
| 202 DCHECK(ChromeThread::CurrentlyOn(thread_identifier_)); | 136 DCHECK(ChromeThread::CurrentlyOn(thread_identifier_)); |
| 203 got_response_ = true; | 137 got_response_ = true; |
| 204 ReportFailure(error); | 138 ReportFailure(error); |
| 205 } | 139 } |
| (...skipping 97 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 303 | 237 |
| 304 void SandboxedExtensionUnpacker::ReportFailure(const std::string& error) { | 238 void SandboxedExtensionUnpacker::ReportFailure(const std::string& error) { |
| 305 client_->OnUnpackFailure(error); | 239 client_->OnUnpackFailure(error); |
| 306 } | 240 } |
| 307 | 241 |
| 308 void SandboxedExtensionUnpacker::ReportSuccess() { | 242 void SandboxedExtensionUnpacker::ReportSuccess() { |
| 309 // Client takes ownership of temporary directory and extension. | 243 // Client takes ownership of temporary directory and extension. |
| 310 client_->OnUnpackSuccess(temp_dir_.Take(), extension_root_, | 244 client_->OnUnpackSuccess(temp_dir_.Take(), extension_root_, |
| 311 extension_.release()); | 245 extension_.release()); |
| 312 } | 246 } |
| 247 |
| 248 DictionaryValue* SandboxedExtensionUnpacker::RewriteManifestFile( |
| 249 const DictionaryValue& manifest) { |
| 250 // Add the public key extracted earlier to the parsed manifest and overwrite |
| 251 // the original manifest. We do this to ensure the manifest doesn't contain an |
| 252 // exploitable bug that could be used to compromise the browser. |
| 253 scoped_ptr<DictionaryValue> final_manifest( |
| 254 static_cast<DictionaryValue*>(manifest.DeepCopy())); |
| 255 final_manifest->SetString(extension_manifest_keys::kPublicKey, public_key_); |
| 256 |
| 257 std::string manifest_json; |
| 258 JSONStringValueSerializer serializer(&manifest_json); |
| 259 serializer.set_pretty_print(true); |
| 260 if (!serializer.Serialize(*final_manifest)) { |
| 261 ReportFailure("Error serializing manifest.json."); |
| 262 return NULL; |
| 263 } |
| 264 |
| 265 FilePath manifest_path = |
| 266 extension_root_.AppendASCII(Extension::kManifestFilename); |
| 267 if (!file_util::WriteFile(manifest_path, |
| 268 manifest_json.data(), manifest_json.size())) { |
| 269 ReportFailure("Error saving manifest.json."); |
| 270 return NULL; |
| 271 } |
| 272 |
| 273 return final_manifest.release(); |
| 274 } |
| 275 |
| 276 bool SandboxedExtensionUnpacker::RewriteImageFiles() { |
| 277 ExtensionUnpacker::DecodedImages images; |
| 278 if (!ExtensionUnpacker::ReadImagesFromFile(temp_dir_.path(), &images)) { |
| 279 ReportFailure("Couldn't read image data from disk."); |
| 280 return false; |
| 281 } |
| 282 |
| 283 // Delete any images that may be used by the browser. We're going to write |
| 284 // out our own versions of the parsed images, and we want to make sure the |
| 285 // originals are gone for good. |
| 286 std::set<FilePath> image_paths = extension_->GetBrowserImages(); |
| 287 if (image_paths.size() != images.size()) { |
| 288 ReportFailure("Decoded images don't match what's in the manifest."); |
| 289 return false; |
| 290 } |
| 291 |
| 292 for (std::set<FilePath>::iterator it = image_paths.begin(); |
| 293 it != image_paths.end(); ++it) { |
| 294 FilePath path = *it; |
| 295 if (path.IsAbsolute() || path.ReferencesParent()) { |
| 296 ReportFailure("Invalid path for browser image."); |
| 297 return false; |
| 298 } |
| 299 if (!file_util::Delete(extension_root_.Append(path), false)) { |
| 300 ReportFailure("Error removing old image file."); |
| 301 return false; |
| 302 } |
| 303 } |
| 304 |
| 305 // Write our parsed images back to disk as well. |
| 306 for (size_t i = 0; i < images.size(); ++i) { |
| 307 const SkBitmap& image = images[i].a; |
| 308 FilePath path_suffix = images[i].b; |
| 309 if (path_suffix.IsAbsolute() || path_suffix.ReferencesParent()) { |
| 310 ReportFailure("Invalid path for bitmap image."); |
| 311 return false; |
| 312 } |
| 313 FilePath path = extension_root_.Append(path_suffix); |
| 314 |
| 315 std::vector<unsigned char> image_data; |
| 316 // TODO(mpcomplete): It's lame that we're encoding all images as PNG, even |
| 317 // though they may originally be .jpg, etc. Figure something out. |
| 318 // http://code.google.com/p/chromium/issues/detail?id=12459 |
| 319 if (!gfx::PNGCodec::EncodeBGRASkBitmap(image, false, &image_data)) { |
| 320 ReportFailure("Error re-encoding theme image."); |
| 321 return false; |
| 322 } |
| 323 |
| 324 // Note: we're overwriting existing files that the utility process wrote, |
| 325 // so we can be sure the directory exists. |
| 326 const char* image_data_ptr = reinterpret_cast<const char*>(&image_data[0]); |
| 327 if (!file_util::WriteFile(path, image_data_ptr, image_data.size())) { |
| 328 ReportFailure("Error saving theme image."); |
| 329 return false; |
| 330 } |
| 331 } |
| 332 |
| 333 return true; |
| 334 } |
| 335 |
| 336 bool SandboxedExtensionUnpacker::RewriteCatalogFiles( |
| 337 const DictionaryValue& catalogs) { |
| 338 // Write our parsed catalogs back to disk. |
| 339 DictionaryValue::key_iterator key_it = catalogs.begin_keys(); |
| 340 for (; key_it != catalogs.end_keys(); ++key_it) { |
| 341 DictionaryValue* catalog; |
| 342 if (!catalogs.GetDictionary(*key_it, &catalog)) { |
| 343 ReportFailure("Invalid catalog data."); |
| 344 return false; |
| 345 } |
| 346 |
| 347 FilePath relative_path = FilePath::FromWStringHack(*key_it); |
| 348 relative_path = relative_path.AppendASCII(Extension::kMessagesFilename); |
| 349 if (relative_path.IsAbsolute() || relative_path.ReferencesParent()) { |
| 350 ReportFailure("Invalid path for catalog."); |
| 351 return false; |
| 352 } |
| 353 FilePath path = extension_root_.Append(relative_path); |
| 354 |
| 355 std::string catalog_json; |
| 356 JSONStringValueSerializer serializer(&catalog_json); |
| 357 serializer.set_pretty_print(true); |
| 358 if (!serializer.Serialize(*catalog)) { |
| 359 ReportFailure("Error serializing catalog."); |
| 360 return false; |
| 361 } |
| 362 |
| 363 // Note: we're overwriting existing files that the utility process read, |
| 364 // so we can be sure the directory exists. |
| 365 if (!file_util::WriteFile(path, |
| 366 catalog_json.c_str(), |
| 367 catalog_json.size())) { |
| 368 ReportFailure("Error saving catalog."); |
| 369 return false; |
| 370 } |
| 371 } |
| 372 |
| 373 return true; |
| 374 } |
| OLD | NEW |