| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | |
| 2 // Use of this source code is governed by a BSD-style license that can be | |
| 3 // found in the LICENSE file. | |
| 4 | |
| 5 #include "chrome/browser/extensions/sandboxed_unpacker.h" | |
| 6 | |
| 7 #include <set> | |
| 8 | |
| 9 #include "base/base64.h" | |
| 10 #include "base/bind.h" | |
| 11 #include "base/command_line.h" | |
| 12 #include "base/files/file_util.h" | |
| 13 #include "base/files/file_util_proxy.h" | |
| 14 #include "base/files/scoped_file.h" | |
| 15 #include "base/json/json_string_value_serializer.h" | |
| 16 #include "base/message_loop/message_loop.h" | |
| 17 #include "base/metrics/histogram.h" | |
| 18 #include "base/numerics/safe_conversions.h" | |
| 19 #include "base/path_service.h" | |
| 20 #include "base/sequenced_task_runner.h" | |
| 21 #include "base/strings/utf_string_conversions.h" | |
| 22 #include "base/threading/sequenced_worker_pool.h" | |
| 23 #include "chrome/browser/extensions/extension_service.h" | |
| 24 #include "chrome/common/chrome_paths.h" | |
| 25 #include "chrome/common/chrome_switches.h" | |
| 26 #include "chrome/common/chrome_utility_messages.h" | |
| 27 #include "chrome/common/extensions/chrome_utility_extensions_messages.h" | |
| 28 #include "chrome/grit/generated_resources.h" | |
| 29 #include "components/crx_file/constants.h" | |
| 30 #include "components/crx_file/crx_file.h" | |
| 31 #include "components/crx_file/id_util.h" | |
| 32 #include "content/public/browser/browser_thread.h" | |
| 33 #include "content/public/browser/utility_process_host.h" | |
| 34 #include "content/public/common/common_param_traits.h" | |
| 35 #include "crypto/signature_verifier.h" | |
| 36 #include "extensions/common/constants.h" | |
| 37 #include "extensions/common/extension.h" | |
| 38 #include "extensions/common/extension_l10n_util.h" | |
| 39 #include "extensions/common/extension_utility_messages.h" | |
| 40 #include "extensions/common/extensions_client.h" | |
| 41 #include "extensions/common/file_util.h" | |
| 42 #include "extensions/common/manifest_constants.h" | |
| 43 #include "extensions/common/manifest_handlers/icons_handler.h" | |
| 44 #include "third_party/skia/include/core/SkBitmap.h" | |
| 45 #include "ui/base/l10n/l10n_util.h" | |
| 46 #include "ui/gfx/codec/png_codec.h" | |
| 47 | |
| 48 using base::ASCIIToUTF16; | |
| 49 using content::BrowserThread; | |
| 50 using content::UtilityProcessHost; | |
| 51 using crx_file::CrxFile; | |
| 52 | |
| 53 // The following macro makes histograms that record the length of paths | |
| 54 // in this file much easier to read. | |
| 55 // Windows has a short max path length. If the path length to a | |
| 56 // file being unpacked from a CRX exceeds the max length, we might | |
| 57 // fail to install. To see if this is happening, see how long the | |
| 58 // path to the temp unpack directory is. See crbug.com/69693 . | |
| 59 #define PATH_LENGTH_HISTOGRAM(name, path) \ | |
| 60 UMA_HISTOGRAM_CUSTOM_COUNTS(name, path.value().length(), 0, 500, 100) | |
| 61 | |
| 62 // Record a rate (kB per second) at which extensions are unpacked. | |
| 63 // Range from 1kB/s to 100mB/s. | |
| 64 #define UNPACK_RATE_HISTOGRAM(name, rate) \ | |
| 65 UMA_HISTOGRAM_CUSTOM_COUNTS(name, rate, 1, 100000, 100); | |
| 66 | |
| 67 namespace extensions { | |
| 68 namespace { | |
| 69 | |
| 70 void RecordSuccessfulUnpackTimeHistograms( | |
| 71 const base::FilePath& crx_path, const base::TimeDelta unpack_time) { | |
| 72 | |
| 73 const int64 kBytesPerKb = 1024; | |
| 74 const int64 kBytesPerMb = 1024 * 1024; | |
| 75 | |
| 76 UMA_HISTOGRAM_TIMES("Extensions.SandboxUnpackSuccessTime", unpack_time); | |
| 77 | |
| 78 // To get a sense of how CRX size impacts unpack time, record unpack | |
| 79 // time for several increments of CRX size. | |
| 80 int64 crx_file_size; | |
| 81 if (!base::GetFileSize(crx_path, &crx_file_size)) { | |
| 82 UMA_HISTOGRAM_COUNTS("Extensions.SandboxUnpackSuccessCantGetCrxSize", 1); | |
| 83 return; | |
| 84 } | |
| 85 | |
| 86 // Cast is safe as long as the number of bytes in the CRX is less than | |
| 87 // 2^31 * 2^10. | |
| 88 int crx_file_size_kb = static_cast<int>(crx_file_size / kBytesPerKb); | |
| 89 UMA_HISTOGRAM_COUNTS( | |
| 90 "Extensions.SandboxUnpackSuccessCrxSize", crx_file_size_kb); | |
| 91 | |
| 92 // We have time in seconds and file size in bytes. We want the rate bytes are | |
| 93 // unpacked in kB/s. | |
| 94 double file_size_kb = | |
| 95 static_cast<double>(crx_file_size) / static_cast<double>(kBytesPerKb); | |
| 96 int unpack_rate_kb_per_s = | |
| 97 static_cast<int>(file_size_kb / unpack_time.InSecondsF()); | |
| 98 UNPACK_RATE_HISTOGRAM("Extensions.SandboxUnpackRate", unpack_rate_kb_per_s); | |
| 99 | |
| 100 if (crx_file_size < 50.0 * kBytesPerKb) { | |
| 101 UNPACK_RATE_HISTOGRAM( | |
| 102 "Extensions.SandboxUnpackRateUnder50kB", unpack_rate_kb_per_s); | |
| 103 | |
| 104 } else if (crx_file_size < 1 * kBytesPerMb) { | |
| 105 UNPACK_RATE_HISTOGRAM( | |
| 106 "Extensions.SandboxUnpackRate50kBTo1mB", unpack_rate_kb_per_s); | |
| 107 | |
| 108 } else if (crx_file_size < 2 * kBytesPerMb) { | |
| 109 UNPACK_RATE_HISTOGRAM( | |
| 110 "Extensions.SandboxUnpackRate1To2mB", unpack_rate_kb_per_s); | |
| 111 | |
| 112 } else if (crx_file_size < 5 * kBytesPerMb) { | |
| 113 UNPACK_RATE_HISTOGRAM( | |
| 114 "Extensions.SandboxUnpackRate2To5mB", unpack_rate_kb_per_s); | |
| 115 | |
| 116 } else if (crx_file_size < 10 * kBytesPerMb) { | |
| 117 UNPACK_RATE_HISTOGRAM( | |
| 118 "Extensions.SandboxUnpackRate5To10mB", unpack_rate_kb_per_s); | |
| 119 | |
| 120 } else { | |
| 121 UNPACK_RATE_HISTOGRAM( | |
| 122 "Extensions.SandboxUnpackRateOver10mB", unpack_rate_kb_per_s); | |
| 123 } | |
| 124 } | |
| 125 | |
| 126 // Work horse for FindWritableTempLocation. Creates a temp file in the folder | |
| 127 // and uses NormalizeFilePath to check if the path is junction free. | |
| 128 bool VerifyJunctionFreeLocation(base::FilePath* temp_dir) { | |
| 129 if (temp_dir->empty()) | |
| 130 return false; | |
| 131 | |
| 132 base::FilePath temp_file; | |
| 133 if (!base::CreateTemporaryFileInDir(*temp_dir, &temp_file)) { | |
| 134 LOG(ERROR) << temp_dir->value() << " is not writable"; | |
| 135 return false; | |
| 136 } | |
| 137 // NormalizeFilePath requires a non-empty file, so write some data. | |
| 138 // If you change the exit points of this function please make sure all | |
| 139 // exit points delete this temp file! | |
| 140 if (base::WriteFile(temp_file, ".", 1) != 1) | |
| 141 return false; | |
| 142 | |
| 143 base::FilePath normalized_temp_file; | |
| 144 bool normalized = base::NormalizeFilePath(temp_file, &normalized_temp_file); | |
| 145 if (!normalized) { | |
| 146 // If |temp_file| contains a link, the sandbox will block al file system | |
| 147 // operations, and the install will fail. | |
| 148 LOG(ERROR) << temp_dir->value() << " seem to be on remote drive."; | |
| 149 } else { | |
| 150 *temp_dir = normalized_temp_file.DirName(); | |
| 151 } | |
| 152 // Clean up the temp file. | |
| 153 base::DeleteFile(temp_file, false); | |
| 154 | |
| 155 return normalized; | |
| 156 } | |
| 157 | |
| 158 // This function tries to find a location for unpacking the extension archive | |
| 159 // that is writable and does not lie on a shared drive so that the sandboxed | |
| 160 // unpacking process can write there. If no such location exists we can not | |
| 161 // proceed and should fail. | |
| 162 // The result will be written to |temp_dir|. The function will write to this | |
| 163 // parameter even if it returns false. | |
| 164 bool FindWritableTempLocation(const base::FilePath& extensions_dir, | |
| 165 base::FilePath* temp_dir) { | |
| 166 // On ChromeOS, we will only attempt to unpack extension in cryptohome (profile) | |
| 167 // directory to provide additional security/privacy and speed up the rest of | |
| 168 // the extension install process. | |
| 169 #if !defined(OS_CHROMEOS) | |
| 170 PathService::Get(base::DIR_TEMP, temp_dir); | |
| 171 if (VerifyJunctionFreeLocation(temp_dir)) | |
| 172 return true; | |
| 173 #endif | |
| 174 | |
| 175 *temp_dir = file_util::GetInstallTempDir(extensions_dir); | |
| 176 if (VerifyJunctionFreeLocation(temp_dir)) | |
| 177 return true; | |
| 178 // Neither paths is link free chances are good installation will fail. | |
| 179 LOG(ERROR) << "Both the %TEMP% folder and the profile seem to be on " | |
| 180 << "remote drives or read-only. Installation can not complete!"; | |
| 181 return false; | |
| 182 } | |
| 183 | |
| 184 // Read the decoded images back from the file we saved them to. | |
| 185 // |extension_path| is the path to the extension we unpacked that wrote the | |
| 186 // data. Returns true on success. | |
| 187 bool ReadImagesFromFile(const base::FilePath& extension_path, | |
| 188 DecodedImages* images) { | |
| 189 base::FilePath path = | |
| 190 extension_path.AppendASCII(kDecodedImagesFilename); | |
| 191 std::string file_str; | |
| 192 if (!base::ReadFileToString(path, &file_str)) | |
| 193 return false; | |
| 194 | |
| 195 IPC::Message pickle(file_str.data(), file_str.size()); | |
| 196 PickleIterator iter(pickle); | |
| 197 return IPC::ReadParam(&pickle, &iter, images); | |
| 198 } | |
| 199 | |
| 200 // Read the decoded message catalogs back from the file we saved them to. | |
| 201 // |extension_path| is the path to the extension we unpacked that wrote the | |
| 202 // data. Returns true on success. | |
| 203 bool ReadMessageCatalogsFromFile(const base::FilePath& extension_path, | |
| 204 base::DictionaryValue* catalogs) { | |
| 205 base::FilePath path = extension_path.AppendASCII( | |
| 206 kDecodedMessageCatalogsFilename); | |
| 207 std::string file_str; | |
| 208 if (!base::ReadFileToString(path, &file_str)) | |
| 209 return false; | |
| 210 | |
| 211 IPC::Message pickle(file_str.data(), file_str.size()); | |
| 212 PickleIterator iter(pickle); | |
| 213 return IPC::ReadParam(&pickle, &iter, catalogs); | |
| 214 } | |
| 215 | |
| 216 } // namespace | |
| 217 | |
| 218 SandboxedUnpacker::SandboxedUnpacker( | |
| 219 const base::FilePath& crx_path, | |
| 220 Manifest::Location location, | |
| 221 int creation_flags, | |
| 222 const base::FilePath& extensions_dir, | |
| 223 const scoped_refptr<base::SequencedTaskRunner>& unpacker_io_task_runner, | |
| 224 SandboxedUnpackerClient* client) | |
| 225 : crx_path_(crx_path), | |
| 226 client_(client), | |
| 227 extensions_dir_(extensions_dir), | |
| 228 got_response_(false), | |
| 229 location_(location), | |
| 230 creation_flags_(creation_flags), | |
| 231 unpacker_io_task_runner_(unpacker_io_task_runner) { | |
| 232 } | |
| 233 | |
| 234 bool SandboxedUnpacker::CreateTempDirectory() { | |
| 235 CHECK(unpacker_io_task_runner_->RunsTasksOnCurrentThread()); | |
| 236 | |
| 237 base::FilePath temp_dir; | |
| 238 if (!FindWritableTempLocation(extensions_dir_, &temp_dir)) { | |
| 239 ReportFailure( | |
| 240 COULD_NOT_GET_TEMP_DIRECTORY, | |
| 241 l10n_util::GetStringFUTF16( | |
| 242 IDS_EXTENSION_PACKAGE_INSTALL_ERROR, | |
| 243 ASCIIToUTF16("COULD_NOT_GET_TEMP_DIRECTORY"))); | |
| 244 return false; | |
| 245 } | |
| 246 | |
| 247 if (!temp_dir_.CreateUniqueTempDirUnderPath(temp_dir)) { | |
| 248 ReportFailure( | |
| 249 COULD_NOT_CREATE_TEMP_DIRECTORY, | |
| 250 l10n_util::GetStringFUTF16( | |
| 251 IDS_EXTENSION_PACKAGE_INSTALL_ERROR, | |
| 252 ASCIIToUTF16("COULD_NOT_CREATE_TEMP_DIRECTORY"))); | |
| 253 return false; | |
| 254 } | |
| 255 | |
| 256 return true; | |
| 257 } | |
| 258 | |
| 259 void SandboxedUnpacker::Start() { | |
| 260 // We assume that we are started on the thread that the client wants us to do | |
| 261 // file IO on. | |
| 262 CHECK(unpacker_io_task_runner_->RunsTasksOnCurrentThread()); | |
| 263 | |
| 264 unpack_start_time_ = base::TimeTicks::Now(); | |
| 265 | |
| 266 PATH_LENGTH_HISTOGRAM("Extensions.SandboxUnpackInitialCrxPathLength", | |
| 267 crx_path_); | |
| 268 if (!CreateTempDirectory()) | |
| 269 return; // ReportFailure() already called. | |
| 270 | |
| 271 // Initialize the path that will eventually contain the unpacked extension. | |
| 272 extension_root_ = temp_dir_.path().AppendASCII(kTempExtensionName); | |
| 273 PATH_LENGTH_HISTOGRAM("Extensions.SandboxUnpackUnpackedCrxPathLength", | |
| 274 extension_root_); | |
| 275 | |
| 276 // Extract the public key and validate the package. | |
| 277 if (!ValidateSignature()) | |
| 278 return; // ValidateSignature() already reported the error. | |
| 279 | |
| 280 // Copy the crx file into our working directory. | |
| 281 base::FilePath temp_crx_path = temp_dir_.path().Append(crx_path_.BaseName()); | |
| 282 PATH_LENGTH_HISTOGRAM("Extensions.SandboxUnpackTempCrxPathLength", | |
| 283 temp_crx_path); | |
| 284 | |
| 285 if (!base::CopyFile(crx_path_, temp_crx_path)) { | |
| 286 // Failed to copy extension file to temporary directory. | |
| 287 ReportFailure( | |
| 288 FAILED_TO_COPY_EXTENSION_FILE_TO_TEMP_DIRECTORY, | |
| 289 l10n_util::GetStringFUTF16( | |
| 290 IDS_EXTENSION_PACKAGE_INSTALL_ERROR, | |
| 291 ASCIIToUTF16("FAILED_TO_COPY_EXTENSION_FILE_TO_TEMP_DIRECTORY"))); | |
| 292 return; | |
| 293 } | |
| 294 | |
| 295 // The utility process will have access to the directory passed to | |
| 296 // SandboxedUnpacker. That directory should not contain a symlink or NTFS | |
| 297 // reparse point. When the path is used, following the link/reparse point | |
| 298 // will cause file system access outside the sandbox path, and the sandbox | |
| 299 // will deny the operation. | |
| 300 base::FilePath link_free_crx_path; | |
| 301 if (!base::NormalizeFilePath(temp_crx_path, &link_free_crx_path)) { | |
| 302 LOG(ERROR) << "Could not get the normalized path of " | |
| 303 << temp_crx_path.value(); | |
| 304 ReportFailure( | |
| 305 COULD_NOT_GET_SANDBOX_FRIENDLY_PATH, | |
| 306 l10n_util::GetStringUTF16(IDS_EXTENSION_UNPACK_FAILED)); | |
| 307 return; | |
| 308 } | |
| 309 PATH_LENGTH_HISTOGRAM("Extensions.SandboxUnpackLinkFreeCrxPathLength", | |
| 310 link_free_crx_path); | |
| 311 | |
| 312 BrowserThread::PostTask( | |
| 313 BrowserThread::IO, FROM_HERE, | |
| 314 base::Bind( | |
| 315 &SandboxedUnpacker::StartProcessOnIOThread, | |
| 316 this, | |
| 317 link_free_crx_path)); | |
| 318 } | |
| 319 | |
| 320 SandboxedUnpacker::~SandboxedUnpacker() { | |
| 321 } | |
| 322 | |
| 323 bool SandboxedUnpacker::OnMessageReceived(const IPC::Message& message) { | |
| 324 bool handled = true; | |
| 325 IPC_BEGIN_MESSAGE_MAP(SandboxedUnpacker, message) | |
| 326 IPC_MESSAGE_HANDLER(ChromeUtilityHostMsg_UnpackExtension_Succeeded, | |
| 327 OnUnpackExtensionSucceeded) | |
| 328 IPC_MESSAGE_HANDLER(ChromeUtilityHostMsg_UnpackExtension_Failed, | |
| 329 OnUnpackExtensionFailed) | |
| 330 IPC_MESSAGE_UNHANDLED(handled = false) | |
| 331 IPC_END_MESSAGE_MAP() | |
| 332 return handled; | |
| 333 } | |
| 334 | |
| 335 void SandboxedUnpacker::OnProcessCrashed(int exit_code) { | |
| 336 // Don't report crashes if they happen after we got a response. | |
| 337 if (got_response_) | |
| 338 return; | |
| 339 | |
| 340 // Utility process crashed while trying to install. | |
| 341 ReportFailure( | |
| 342 UTILITY_PROCESS_CRASHED_WHILE_TRYING_TO_INSTALL, | |
| 343 l10n_util::GetStringFUTF16( | |
| 344 IDS_EXTENSION_PACKAGE_INSTALL_ERROR, | |
| 345 ASCIIToUTF16("UTILITY_PROCESS_CRASHED_WHILE_TRYING_TO_INSTALL")) + | |
| 346 ASCIIToUTF16(". ") + | |
| 347 l10n_util::GetStringUTF16(IDS_EXTENSION_INSTALL_PROCESS_CRASHED)); | |
| 348 } | |
| 349 | |
| 350 void SandboxedUnpacker::StartProcessOnIOThread( | |
| 351 const base::FilePath& temp_crx_path) { | |
| 352 UtilityProcessHost* host = | |
| 353 UtilityProcessHost::Create(this, unpacker_io_task_runner_.get()); | |
| 354 // Grant the subprocess access to the entire subdir the extension file is | |
| 355 // in, so that it can unpack to that dir. | |
| 356 host->SetExposedDir(temp_crx_path.DirName()); | |
| 357 host->Send( | |
| 358 new ChromeUtilityMsg_UnpackExtension( | |
| 359 temp_crx_path, extension_id_, location_, creation_flags_)); | |
| 360 } | |
| 361 | |
| 362 void SandboxedUnpacker::OnUnpackExtensionSucceeded( | |
| 363 const base::DictionaryValue& manifest) { | |
| 364 CHECK(unpacker_io_task_runner_->RunsTasksOnCurrentThread()); | |
| 365 got_response_ = true; | |
| 366 | |
| 367 scoped_ptr<base::DictionaryValue> final_manifest( | |
| 368 RewriteManifestFile(manifest)); | |
| 369 if (!final_manifest) | |
| 370 return; | |
| 371 | |
| 372 // Create an extension object that refers to the temporary location the | |
| 373 // extension was unpacked to. We use this until the extension is finally | |
| 374 // installed. For example, the install UI shows images from inside the | |
| 375 // extension. | |
| 376 | |
| 377 // Localize manifest now, so confirm UI gets correct extension name. | |
| 378 | |
| 379 // TODO(rdevlin.cronin): Continue removing std::string errors and replacing | |
| 380 // with base::string16 | |
| 381 std::string utf8_error; | |
| 382 if (!extension_l10n_util::LocalizeExtension(extension_root_, | |
| 383 final_manifest.get(), | |
| 384 &utf8_error)) { | |
| 385 ReportFailure( | |
| 386 COULD_NOT_LOCALIZE_EXTENSION, | |
| 387 l10n_util::GetStringFUTF16( | |
| 388 IDS_EXTENSION_PACKAGE_ERROR_MESSAGE, | |
| 389 base::UTF8ToUTF16(utf8_error))); | |
| 390 return; | |
| 391 } | |
| 392 | |
| 393 extension_ = Extension::Create( | |
| 394 extension_root_, | |
| 395 location_, | |
| 396 *final_manifest, | |
| 397 Extension::REQUIRE_KEY | creation_flags_, | |
| 398 &utf8_error); | |
| 399 | |
| 400 if (!extension_.get()) { | |
| 401 ReportFailure(INVALID_MANIFEST, | |
| 402 ASCIIToUTF16("Manifest is invalid: " + utf8_error)); | |
| 403 return; | |
| 404 } | |
| 405 | |
| 406 SkBitmap install_icon; | |
| 407 if (!RewriteImageFiles(&install_icon)) | |
| 408 return; | |
| 409 | |
| 410 if (!RewriteCatalogFiles()) | |
| 411 return; | |
| 412 | |
| 413 ReportSuccess(manifest, install_icon); | |
| 414 } | |
| 415 | |
| 416 void SandboxedUnpacker::OnUnpackExtensionFailed(const base::string16& error) { | |
| 417 CHECK(unpacker_io_task_runner_->RunsTasksOnCurrentThread()); | |
| 418 got_response_ = true; | |
| 419 ReportFailure( | |
| 420 UNPACKER_CLIENT_FAILED, | |
| 421 l10n_util::GetStringFUTF16( | |
| 422 IDS_EXTENSION_PACKAGE_ERROR_MESSAGE, | |
| 423 error)); | |
| 424 } | |
| 425 | |
| 426 bool SandboxedUnpacker::ValidateSignature() { | |
| 427 base::ScopedFILE file(base::OpenFile(crx_path_, "rb")); | |
| 428 | |
| 429 if (!file.get()) { | |
| 430 // Could not open crx file for reading. | |
| 431 #if defined (OS_WIN) | |
| 432 // On windows, get the error code. | |
| 433 uint32 error_code = ::GetLastError(); | |
| 434 // TODO(skerner): Use this histogram to understand why so many | |
| 435 // windows users hit this error. crbug.com/69693 | |
| 436 | |
| 437 // Windows errors are unit32s, but all of likely errors are in | |
| 438 // [1, 1000]. See winerror.h for the meaning of specific values. | |
| 439 // Clip errors outside the expected range to a single extra value. | |
| 440 // If there are errors in that extra bucket, we will know to expand | |
| 441 // the range. | |
| 442 const uint32 kMaxErrorToSend = 1001; | |
| 443 error_code = std::min(error_code, kMaxErrorToSend); | |
| 444 UMA_HISTOGRAM_ENUMERATION("Extensions.ErrorCodeFromCrxOpen", | |
| 445 error_code, kMaxErrorToSend); | |
| 446 #endif | |
| 447 | |
| 448 ReportFailure( | |
| 449 CRX_FILE_NOT_READABLE, | |
| 450 l10n_util::GetStringFUTF16( | |
| 451 IDS_EXTENSION_PACKAGE_ERROR_CODE, | |
| 452 ASCIIToUTF16("CRX_FILE_NOT_READABLE"))); | |
| 453 return false; | |
| 454 } | |
| 455 | |
| 456 // Read and verify the header. | |
| 457 // TODO(erikkay): Yuck. I'm not a big fan of this kind of code, but it | |
| 458 // appears that we don't have any endian/alignment aware serialization | |
| 459 // code in the code base. So for now, this assumes that we're running | |
| 460 // on a little endian machine with 4 byte alignment. | |
| 461 CrxFile::Header header; | |
| 462 size_t len = fread(&header, 1, sizeof(header), file.get()); | |
| 463 if (len < sizeof(header)) { | |
| 464 // Invalid crx header | |
| 465 ReportFailure( | |
| 466 CRX_HEADER_INVALID, | |
| 467 l10n_util::GetStringFUTF16( | |
| 468 IDS_EXTENSION_PACKAGE_ERROR_CODE, | |
| 469 ASCIIToUTF16("CRX_HEADER_INVALID"))); | |
| 470 return false; | |
| 471 } | |
| 472 | |
| 473 CrxFile::Error error; | |
| 474 scoped_ptr<CrxFile> crx(CrxFile::Parse(header, &error)); | |
| 475 if (!crx) { | |
| 476 switch (error) { | |
| 477 case CrxFile::kWrongMagic: | |
| 478 ReportFailure( | |
| 479 CRX_MAGIC_NUMBER_INVALID, | |
| 480 l10n_util::GetStringFUTF16( | |
| 481 IDS_EXTENSION_PACKAGE_ERROR_CODE, | |
| 482 ASCIIToUTF16("CRX_MAGIC_NUMBER_INVALID"))); | |
| 483 break; | |
| 484 case CrxFile::kInvalidVersion: | |
| 485 // Bad version numer | |
| 486 ReportFailure( | |
| 487 CRX_VERSION_NUMBER_INVALID, | |
| 488 l10n_util::GetStringFUTF16( | |
| 489 IDS_EXTENSION_PACKAGE_ERROR_CODE, | |
| 490 ASCIIToUTF16("CRX_VERSION_NUMBER_INVALID"))); | |
| 491 break; | |
| 492 case CrxFile::kInvalidKeyTooLarge: | |
| 493 case CrxFile::kInvalidSignatureTooLarge: | |
| 494 // Excessively large key or signature | |
| 495 ReportFailure( | |
| 496 CRX_EXCESSIVELY_LARGE_KEY_OR_SIGNATURE, | |
| 497 l10n_util::GetStringFUTF16( | |
| 498 IDS_EXTENSION_PACKAGE_ERROR_CODE, | |
| 499 ASCIIToUTF16("CRX_EXCESSIVELY_LARGE_KEY_OR_SIGNATURE"))); | |
| 500 break; | |
| 501 case CrxFile::kInvalidKeyTooSmall: | |
| 502 // Key length is zero | |
| 503 ReportFailure( | |
| 504 CRX_ZERO_KEY_LENGTH, | |
| 505 l10n_util::GetStringFUTF16( | |
| 506 IDS_EXTENSION_PACKAGE_ERROR_CODE, | |
| 507 ASCIIToUTF16("CRX_ZERO_KEY_LENGTH"))); | |
| 508 break; | |
| 509 case CrxFile::kInvalidSignatureTooSmall: | |
| 510 // Signature length is zero | |
| 511 ReportFailure( | |
| 512 CRX_ZERO_SIGNATURE_LENGTH, | |
| 513 l10n_util::GetStringFUTF16( | |
| 514 IDS_EXTENSION_PACKAGE_ERROR_CODE, | |
| 515 ASCIIToUTF16("CRX_ZERO_SIGNATURE_LENGTH"))); | |
| 516 break; | |
| 517 } | |
| 518 return false; | |
| 519 } | |
| 520 | |
| 521 std::vector<uint8> key; | |
| 522 key.resize(header.key_size); | |
| 523 len = fread(&key.front(), sizeof(uint8), header.key_size, file.get()); | |
| 524 if (len < header.key_size) { | |
| 525 // Invalid public key | |
| 526 ReportFailure( | |
| 527 CRX_PUBLIC_KEY_INVALID, | |
| 528 l10n_util::GetStringFUTF16( | |
| 529 IDS_EXTENSION_PACKAGE_ERROR_CODE, | |
| 530 ASCIIToUTF16("CRX_PUBLIC_KEY_INVALID"))); | |
| 531 return false; | |
| 532 } | |
| 533 | |
| 534 std::vector<uint8> signature; | |
| 535 signature.resize(header.signature_size); | |
| 536 len = fread(&signature.front(), sizeof(uint8), header.signature_size, | |
| 537 file.get()); | |
| 538 if (len < header.signature_size) { | |
| 539 // Invalid signature | |
| 540 ReportFailure( | |
| 541 CRX_SIGNATURE_INVALID, | |
| 542 l10n_util::GetStringFUTF16( | |
| 543 IDS_EXTENSION_PACKAGE_ERROR_CODE, | |
| 544 ASCIIToUTF16("CRX_SIGNATURE_INVALID"))); | |
| 545 return false; | |
| 546 } | |
| 547 | |
| 548 crypto::SignatureVerifier verifier; | |
| 549 if (!verifier.VerifyInit(crx_file::kSignatureAlgorithm, | |
| 550 sizeof(crx_file::kSignatureAlgorithm), | |
| 551 &signature.front(), | |
| 552 signature.size(), | |
| 553 &key.front(), | |
| 554 key.size())) { | |
| 555 // Signature verification initialization failed. This is most likely | |
| 556 // caused by a public key in the wrong format (should encode algorithm). | |
| 557 ReportFailure( | |
| 558 CRX_SIGNATURE_VERIFICATION_INITIALIZATION_FAILED, | |
| 559 l10n_util::GetStringFUTF16( | |
| 560 IDS_EXTENSION_PACKAGE_ERROR_CODE, | |
| 561 ASCIIToUTF16("CRX_SIGNATURE_VERIFICATION_INITIALIZATION_FAILED"))); | |
| 562 return false; | |
| 563 } | |
| 564 | |
| 565 unsigned char buf[1 << 12]; | |
| 566 while ((len = fread(buf, 1, sizeof(buf), file.get())) > 0) | |
| 567 verifier.VerifyUpdate(buf, len); | |
| 568 | |
| 569 if (!verifier.VerifyFinal()) { | |
| 570 // Signature verification failed | |
| 571 ReportFailure( | |
| 572 CRX_SIGNATURE_VERIFICATION_FAILED, | |
| 573 l10n_util::GetStringFUTF16( | |
| 574 IDS_EXTENSION_PACKAGE_ERROR_CODE, | |
| 575 ASCIIToUTF16("CRX_SIGNATURE_VERIFICATION_FAILED"))); | |
| 576 return false; | |
| 577 } | |
| 578 | |
| 579 std::string public_key = | |
| 580 std::string(reinterpret_cast<char*>(&key.front()), key.size()); | |
| 581 base::Base64Encode(public_key, &public_key_); | |
| 582 | |
| 583 extension_id_ = crx_file::id_util::GenerateId(public_key); | |
| 584 | |
| 585 return true; | |
| 586 } | |
| 587 | |
| 588 void SandboxedUnpacker::ReportFailure(FailureReason reason, | |
| 589 const base::string16& error) { | |
| 590 UMA_HISTOGRAM_ENUMERATION("Extensions.SandboxUnpackFailureReason", | |
| 591 reason, NUM_FAILURE_REASONS); | |
| 592 UMA_HISTOGRAM_TIMES("Extensions.SandboxUnpackFailureTime", | |
| 593 base::TimeTicks::Now() - unpack_start_time_); | |
| 594 Cleanup(); | |
| 595 client_->OnUnpackFailure(error); | |
| 596 } | |
| 597 | |
| 598 void SandboxedUnpacker::ReportSuccess( | |
| 599 const base::DictionaryValue& original_manifest, | |
| 600 const SkBitmap& install_icon) { | |
| 601 UMA_HISTOGRAM_COUNTS("Extensions.SandboxUnpackSuccess", 1); | |
| 602 | |
| 603 RecordSuccessfulUnpackTimeHistograms( | |
| 604 crx_path_, base::TimeTicks::Now() - unpack_start_time_); | |
| 605 | |
| 606 // Client takes ownership of temporary directory and extension. | |
| 607 client_->OnUnpackSuccess( | |
| 608 temp_dir_.Take(), extension_root_, &original_manifest, extension_.get(), | |
| 609 install_icon); | |
| 610 extension_ = NULL; | |
| 611 } | |
| 612 | |
| 613 base::DictionaryValue* SandboxedUnpacker::RewriteManifestFile( | |
| 614 const base::DictionaryValue& manifest) { | |
| 615 // Add the public key extracted earlier to the parsed manifest and overwrite | |
| 616 // the original manifest. We do this to ensure the manifest doesn't contain an | |
| 617 // exploitable bug that could be used to compromise the browser. | |
| 618 scoped_ptr<base::DictionaryValue> final_manifest(manifest.DeepCopy()); | |
| 619 final_manifest->SetString(manifest_keys::kPublicKey, public_key_); | |
| 620 | |
| 621 std::string manifest_json; | |
| 622 JSONStringValueSerializer serializer(&manifest_json); | |
| 623 serializer.set_pretty_print(true); | |
| 624 if (!serializer.Serialize(*final_manifest)) { | |
| 625 // Error serializing manifest.json. | |
| 626 ReportFailure( | |
| 627 ERROR_SERIALIZING_MANIFEST_JSON, | |
| 628 l10n_util::GetStringFUTF16( | |
| 629 IDS_EXTENSION_PACKAGE_INSTALL_ERROR, | |
| 630 ASCIIToUTF16("ERROR_SERIALIZING_MANIFEST_JSON"))); | |
| 631 return NULL; | |
| 632 } | |
| 633 | |
| 634 base::FilePath manifest_path = | |
| 635 extension_root_.Append(kManifestFilename); | |
| 636 int size = base::checked_cast<int>(manifest_json.size()); | |
| 637 if (base::WriteFile(manifest_path, manifest_json.data(), size) != size) { | |
| 638 // Error saving manifest.json. | |
| 639 ReportFailure( | |
| 640 ERROR_SAVING_MANIFEST_JSON, | |
| 641 l10n_util::GetStringFUTF16( | |
| 642 IDS_EXTENSION_PACKAGE_INSTALL_ERROR, | |
| 643 ASCIIToUTF16("ERROR_SAVING_MANIFEST_JSON"))); | |
| 644 return NULL; | |
| 645 } | |
| 646 | |
| 647 return final_manifest.release(); | |
| 648 } | |
| 649 | |
| 650 bool SandboxedUnpacker::RewriteImageFiles(SkBitmap* install_icon) { | |
| 651 DecodedImages images; | |
| 652 if (!ReadImagesFromFile(temp_dir_.path(), &images)) { | |
| 653 // Couldn't read image data from disk. | |
| 654 ReportFailure( | |
| 655 COULD_NOT_READ_IMAGE_DATA_FROM_DISK, | |
| 656 l10n_util::GetStringFUTF16( | |
| 657 IDS_EXTENSION_PACKAGE_INSTALL_ERROR, | |
| 658 ASCIIToUTF16("COULD_NOT_READ_IMAGE_DATA_FROM_DISK"))); | |
| 659 return false; | |
| 660 } | |
| 661 | |
| 662 // Delete any images that may be used by the browser. We're going to write | |
| 663 // out our own versions of the parsed images, and we want to make sure the | |
| 664 // originals are gone for good. | |
| 665 std::set<base::FilePath> image_paths = | |
| 666 ExtensionsClient::Get()->GetBrowserImagePaths(extension_.get()); | |
| 667 if (image_paths.size() != images.size()) { | |
| 668 // Decoded images don't match what's in the manifest. | |
| 669 ReportFailure( | |
| 670 DECODED_IMAGES_DO_NOT_MATCH_THE_MANIFEST, | |
| 671 l10n_util::GetStringFUTF16( | |
| 672 IDS_EXTENSION_PACKAGE_INSTALL_ERROR, | |
| 673 ASCIIToUTF16("DECODED_IMAGES_DO_NOT_MATCH_THE_MANIFEST"))); | |
| 674 return false; | |
| 675 } | |
| 676 | |
| 677 for (std::set<base::FilePath>::iterator it = image_paths.begin(); | |
| 678 it != image_paths.end(); ++it) { | |
| 679 base::FilePath path = *it; | |
| 680 if (path.IsAbsolute() || path.ReferencesParent()) { | |
| 681 // Invalid path for browser image. | |
| 682 ReportFailure( | |
| 683 INVALID_PATH_FOR_BROWSER_IMAGE, | |
| 684 l10n_util::GetStringFUTF16( | |
| 685 IDS_EXTENSION_PACKAGE_INSTALL_ERROR, | |
| 686 ASCIIToUTF16("INVALID_PATH_FOR_BROWSER_IMAGE"))); | |
| 687 return false; | |
| 688 } | |
| 689 if (!base::DeleteFile(extension_root_.Append(path), false)) { | |
| 690 // Error removing old image file. | |
| 691 ReportFailure( | |
| 692 ERROR_REMOVING_OLD_IMAGE_FILE, | |
| 693 l10n_util::GetStringFUTF16( | |
| 694 IDS_EXTENSION_PACKAGE_INSTALL_ERROR, | |
| 695 ASCIIToUTF16("ERROR_REMOVING_OLD_IMAGE_FILE"))); | |
| 696 return false; | |
| 697 } | |
| 698 } | |
| 699 | |
| 700 const std::string& install_icon_path = | |
| 701 IconsInfo::GetIcons(extension_.get()).Get( | |
| 702 extension_misc::EXTENSION_ICON_LARGE, ExtensionIconSet::MATCH_BIGGER); | |
| 703 | |
| 704 // Write our parsed images back to disk as well. | |
| 705 for (size_t i = 0; i < images.size(); ++i) { | |
| 706 if (BrowserThread::GetBlockingPool()->IsShutdownInProgress()) { | |
| 707 // Abort package installation if shutdown was initiated, crbug.com/235525 | |
| 708 ReportFailure( | |
| 709 ABORTED_DUE_TO_SHUTDOWN, | |
| 710 l10n_util::GetStringFUTF16( | |
| 711 IDS_EXTENSION_PACKAGE_INSTALL_ERROR, | |
| 712 ASCIIToUTF16("ABORTED_DUE_TO_SHUTDOWN"))); | |
| 713 return false; | |
| 714 } | |
| 715 | |
| 716 const SkBitmap& image = get<0>(images[i]); | |
| 717 base::FilePath path_suffix = get<1>(images[i]); | |
| 718 if (path_suffix.MaybeAsASCII() == install_icon_path) | |
| 719 *install_icon = image; | |
| 720 | |
| 721 if (path_suffix.IsAbsolute() || path_suffix.ReferencesParent()) { | |
| 722 // Invalid path for bitmap image. | |
| 723 ReportFailure( | |
| 724 INVALID_PATH_FOR_BITMAP_IMAGE, | |
| 725 l10n_util::GetStringFUTF16( | |
| 726 IDS_EXTENSION_PACKAGE_INSTALL_ERROR, | |
| 727 ASCIIToUTF16("INVALID_PATH_FOR_BITMAP_IMAGE"))); | |
| 728 return false; | |
| 729 } | |
| 730 base::FilePath path = extension_root_.Append(path_suffix); | |
| 731 | |
| 732 std::vector<unsigned char> image_data; | |
| 733 // TODO(mpcomplete): It's lame that we're encoding all images as PNG, even | |
| 734 // though they may originally be .jpg, etc. Figure something out. | |
| 735 // http://code.google.com/p/chromium/issues/detail?id=12459 | |
| 736 if (!gfx::PNGCodec::EncodeBGRASkBitmap(image, false, &image_data)) { | |
| 737 // Error re-encoding theme image. | |
| 738 ReportFailure( | |
| 739 ERROR_RE_ENCODING_THEME_IMAGE, | |
| 740 l10n_util::GetStringFUTF16( | |
| 741 IDS_EXTENSION_PACKAGE_INSTALL_ERROR, | |
| 742 ASCIIToUTF16("ERROR_RE_ENCODING_THEME_IMAGE"))); | |
| 743 return false; | |
| 744 } | |
| 745 | |
| 746 // Note: we're overwriting existing files that the utility process wrote, | |
| 747 // so we can be sure the directory exists. | |
| 748 const char* image_data_ptr = reinterpret_cast<const char*>(&image_data[0]); | |
| 749 int size = base::checked_cast<int>(image_data.size()); | |
| 750 if (base::WriteFile(path, image_data_ptr, size) != size) { | |
| 751 // Error saving theme image. | |
| 752 ReportFailure( | |
| 753 ERROR_SAVING_THEME_IMAGE, | |
| 754 l10n_util::GetStringFUTF16( | |
| 755 IDS_EXTENSION_PACKAGE_INSTALL_ERROR, | |
| 756 ASCIIToUTF16("ERROR_SAVING_THEME_IMAGE"))); | |
| 757 return false; | |
| 758 } | |
| 759 } | |
| 760 | |
| 761 return true; | |
| 762 } | |
| 763 | |
| 764 bool SandboxedUnpacker::RewriteCatalogFiles() { | |
| 765 base::DictionaryValue catalogs; | |
| 766 if (!ReadMessageCatalogsFromFile(temp_dir_.path(), &catalogs)) { | |
| 767 // Could not read catalog data from disk. | |
| 768 ReportFailure( | |
| 769 COULD_NOT_READ_CATALOG_DATA_FROM_DISK, | |
| 770 l10n_util::GetStringFUTF16( | |
| 771 IDS_EXTENSION_PACKAGE_INSTALL_ERROR, | |
| 772 ASCIIToUTF16("COULD_NOT_READ_CATALOG_DATA_FROM_DISK"))); | |
| 773 return false; | |
| 774 } | |
| 775 | |
| 776 // Write our parsed catalogs back to disk. | |
| 777 for (base::DictionaryValue::Iterator it(catalogs); | |
| 778 !it.IsAtEnd(); it.Advance()) { | |
| 779 const base::DictionaryValue* catalog = NULL; | |
| 780 if (!it.value().GetAsDictionary(&catalog)) { | |
| 781 // Invalid catalog data. | |
| 782 ReportFailure( | |
| 783 INVALID_CATALOG_DATA, | |
| 784 l10n_util::GetStringFUTF16( | |
| 785 IDS_EXTENSION_PACKAGE_INSTALL_ERROR, | |
| 786 ASCIIToUTF16("INVALID_CATALOG_DATA"))); | |
| 787 return false; | |
| 788 } | |
| 789 | |
| 790 base::FilePath relative_path = base::FilePath::FromUTF8Unsafe(it.key()); | |
| 791 relative_path = relative_path.Append(kMessagesFilename); | |
| 792 if (relative_path.IsAbsolute() || relative_path.ReferencesParent()) { | |
| 793 // Invalid path for catalog. | |
| 794 ReportFailure( | |
| 795 INVALID_PATH_FOR_CATALOG, | |
| 796 l10n_util::GetStringFUTF16( | |
| 797 IDS_EXTENSION_PACKAGE_INSTALL_ERROR, | |
| 798 ASCIIToUTF16("INVALID_PATH_FOR_CATALOG"))); | |
| 799 return false; | |
| 800 } | |
| 801 base::FilePath path = extension_root_.Append(relative_path); | |
| 802 | |
| 803 std::string catalog_json; | |
| 804 JSONStringValueSerializer serializer(&catalog_json); | |
| 805 serializer.set_pretty_print(true); | |
| 806 if (!serializer.Serialize(*catalog)) { | |
| 807 // Error serializing catalog. | |
| 808 ReportFailure( | |
| 809 ERROR_SERIALIZING_CATALOG, | |
| 810 l10n_util::GetStringFUTF16( | |
| 811 IDS_EXTENSION_PACKAGE_INSTALL_ERROR, | |
| 812 ASCIIToUTF16("ERROR_SERIALIZING_CATALOG"))); | |
| 813 return false; | |
| 814 } | |
| 815 | |
| 816 // Note: we're overwriting existing files that the utility process read, | |
| 817 // so we can be sure the directory exists. | |
| 818 int size = base::checked_cast<int>(catalog_json.size()); | |
| 819 if (base::WriteFile(path, catalog_json.c_str(), size) != size) { | |
| 820 // Error saving catalog. | |
| 821 ReportFailure( | |
| 822 ERROR_SAVING_CATALOG, | |
| 823 l10n_util::GetStringFUTF16( | |
| 824 IDS_EXTENSION_PACKAGE_INSTALL_ERROR, | |
| 825 ASCIIToUTF16("ERROR_SAVING_CATALOG"))); | |
| 826 return false; | |
| 827 } | |
| 828 } | |
| 829 | |
| 830 return true; | |
| 831 } | |
| 832 | |
| 833 void SandboxedUnpacker::Cleanup() { | |
| 834 DCHECK(unpacker_io_task_runner_->RunsTasksOnCurrentThread()); | |
| 835 if (!temp_dir_.Delete()) { | |
| 836 LOG(WARNING) << "Can not delete temp directory at " | |
| 837 << temp_dir_.path().value(); | |
| 838 } | |
| 839 } | |
| 840 | |
| 841 } // namespace extensions | |
| OLD | NEW |