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 |