sandboxed_extension_unpacker.cc revision 513209b27ff55e2841eac0e4120199c23acce758
1// Copyright (c) 2010 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_extension_unpacker.h"
6
7#include <set>
8
9#include "base/base64.h"
10#include "base/crypto/signature_verifier.h"
11#include "base/file_util.h"
12#include "base/file_util_proxy.h"
13#include "base/message_loop.h"
14#include "base/scoped_handle.h"
15#include "base/task.h"
16#include "base/utf_string_conversions.h"  // TODO(viettrungluu): delete me.
17#include "chrome/browser/browser_thread.h"
18#include "chrome/browser/extensions/extensions_service.h"
19#include "chrome/browser/renderer_host/resource_dispatcher_host.h"
20#include "chrome/common/chrome_switches.h"
21#include "chrome/common/extensions/extension.h"
22#include "chrome/common/extensions/extension_constants.h"
23#include "chrome/common/extensions/extension_file_util.h"
24#include "chrome/common/extensions/extension_l10n_util.h"
25#include "chrome/common/extensions/extension_unpacker.h"
26#include "chrome/common/json_value_serializer.h"
27#include "gfx/codec/png_codec.h"
28#include "third_party/skia/include/core/SkBitmap.h"
29
30const char SandboxedExtensionUnpacker::kExtensionHeaderMagic[] = "Cr24";
31
32SandboxedExtensionUnpacker::SandboxedExtensionUnpacker(
33    const FilePath& crx_path,
34    const FilePath& temp_path,
35    ResourceDispatcherHost* rdh,
36    SandboxedExtensionUnpackerClient* client)
37    : crx_path_(crx_path), temp_path_(temp_path),
38      thread_identifier_(BrowserThread::ID_COUNT),
39      rdh_(rdh), client_(client), got_response_(false) {
40}
41
42void SandboxedExtensionUnpacker::Start() {
43  // We assume that we are started on the thread that the client wants us to do
44  // file IO on.
45  CHECK(BrowserThread::GetCurrentThreadIdentifier(&thread_identifier_));
46
47  // Create a temporary directory to work in.
48  if (!temp_dir_.CreateUniqueTempDirUnderPath(temp_path_)) {
49    ReportFailure("Could not create temporary directory.");
50    return;
51  }
52
53  // Initialize the path that will eventually contain the unpacked extension.
54  extension_root_ = temp_dir_.path().AppendASCII(
55      extension_filenames::kTempExtensionName);
56
57  // Extract the public key and validate the package.
58  if (!ValidateSignature())
59    return;  // ValidateSignature() already reported the error.
60
61  // Copy the crx file into our working directory.
62  FilePath temp_crx_path = temp_dir_.path().Append(crx_path_.BaseName());
63  if (!file_util::CopyFile(crx_path_, temp_crx_path)) {
64    ReportFailure("Failed to copy extension file to temporary directory.");
65    return;
66  }
67
68  // If we are supposed to use a subprocess, kick off the subprocess.
69  //
70  // TODO(asargent) we shouldn't need to do this branch here - instead
71  // UtilityProcessHost should handle it for us. (http://crbug.com/19192)
72  bool use_utility_process = rdh_ &&
73      !CommandLine::ForCurrentProcess()->HasSwitch(switches::kSingleProcess);
74  if (use_utility_process) {
75    // The utility process will have access to the directory passed to
76    // SandboxedExtensionUnpacker.  That directory should not contain a
77    // symlink or NTFS reparse point.  When the path is used, following
78    // the link/reparse point will cause file system access outside the
79    // sandbox path, and the sandbox will deny the operation.
80    FilePath link_free_crx_path;
81    if (!file_util::NormalizeFilePath(temp_crx_path, &link_free_crx_path)) {
82      LOG(ERROR) << "Could not get the normalized path of "
83                 << temp_crx_path.value();
84#if defined (OS_WIN)
85      // On windows, it is possible to mount a disk without the root of that
86      // disk having a drive letter.  The sandbox does not support this.
87      // See crbug/49530 .
88      ReportFailure(
89          "Can not unpack extension.  To safely unpack an extension, "
90          "there must be a path to your profile directory that starts "
91          "with a drive letter and does not contain a junction, mount "
92          "point, or symlink.  No such path exists for your profile.");
93#else
94      ReportFailure(
95          "Can not unpack extension.  To safely unpack an extension, "
96          "there must be a path to your profile directory that does "
97          "not contain a symlink.  No such path exists for your profile.");
98#endif
99      return;
100    }
101
102    BrowserThread::PostTask(
103        BrowserThread::IO, FROM_HERE,
104        NewRunnableMethod(
105            this,
106            &SandboxedExtensionUnpacker::StartProcessOnIOThread,
107            link_free_crx_path));
108  } else {
109    // Otherwise, unpack the extension in this process.
110    ExtensionUnpacker unpacker(temp_crx_path);
111    if (unpacker.Run() && unpacker.DumpImagesToFile() &&
112        unpacker.DumpMessageCatalogsToFile()) {
113      OnUnpackExtensionSucceeded(*unpacker.parsed_manifest());
114    } else {
115      OnUnpackExtensionFailed(unpacker.error_message());
116    }
117  }
118}
119
120SandboxedExtensionUnpacker::~SandboxedExtensionUnpacker() {
121  base::FileUtilProxy::Delete(
122      BrowserThread::GetMessageLoopProxyForThread(thread_identifier_),
123      temp_dir_.Take(),
124      true,
125      NULL);
126}
127
128void SandboxedExtensionUnpacker::StartProcessOnIOThread(
129    const FilePath& temp_crx_path) {
130  UtilityProcessHost* host = new UtilityProcessHost(
131      rdh_, this, thread_identifier_);
132  host->StartExtensionUnpacker(temp_crx_path);
133}
134
135void SandboxedExtensionUnpacker::OnUnpackExtensionSucceeded(
136    const DictionaryValue& manifest) {
137  // Skip check for unittests.
138  if (thread_identifier_ != BrowserThread::ID_COUNT)
139    DCHECK(BrowserThread::CurrentlyOn(thread_identifier_));
140  got_response_ = true;
141
142  scoped_ptr<DictionaryValue> final_manifest(RewriteManifestFile(manifest));
143  if (!final_manifest.get())
144    return;
145
146  // Create an extension object that refers to the temporary location the
147  // extension was unpacked to. We use this until the extension is finally
148  // installed. For example, the install UI shows images from inside the
149  // extension.
150
151  // Localize manifest now, so confirm UI gets correct extension name.
152  std::string error;
153  if (!extension_l10n_util::LocalizeExtension(extension_root_,
154                                              final_manifest.get(),
155                                              &error)) {
156    ReportFailure(error);
157    return;
158  }
159
160  extension_ = Extension::Create(
161      extension_root_, Extension::INTERNAL, *final_manifest, true, &error);
162
163  if (!extension_.get()) {
164    ReportFailure(std::string("Manifest is invalid: ") + error);
165    return;
166  }
167
168  if (!RewriteImageFiles())
169    return;
170
171  if (!RewriteCatalogFiles())
172    return;
173
174  ReportSuccess();
175}
176
177void SandboxedExtensionUnpacker::OnUnpackExtensionFailed(
178    const std::string& error) {
179  DCHECK(BrowserThread::CurrentlyOn(thread_identifier_));
180  got_response_ = true;
181  ReportFailure(error);
182}
183
184void SandboxedExtensionUnpacker::OnProcessCrashed() {
185  // Don't report crashes if they happen after we got a response.
186  if (got_response_)
187    return;
188
189  ReportFailure("Utility process crashed while trying to install.");
190}
191
192bool SandboxedExtensionUnpacker::ValidateSignature() {
193  ScopedStdioHandle file(file_util::OpenFile(crx_path_, "rb"));
194  if (!file.get()) {
195    ReportFailure("Could not open crx file for reading");
196    return false;
197  }
198
199  // Read and verify the header.
200  ExtensionHeader header;
201  size_t len;
202
203  // TODO(erikkay): Yuck.  I'm not a big fan of this kind of code, but it
204  // appears that we don't have any endian/alignment aware serialization
205  // code in the code base.  So for now, this assumes that we're running
206  // on a little endian machine with 4 byte alignment.
207  len = fread(&header, 1, sizeof(ExtensionHeader),
208      file.get());
209  if (len < sizeof(ExtensionHeader)) {
210    ReportFailure("Invalid crx header");
211    return false;
212  }
213  if (strncmp(kExtensionHeaderMagic, header.magic,
214      sizeof(header.magic))) {
215    ReportFailure("Bad magic number");
216    return false;
217  }
218  if (header.version != kCurrentVersion) {
219    ReportFailure("Bad version number");
220    return false;
221  }
222  if (header.key_size > kMaxPublicKeySize ||
223      header.signature_size > kMaxSignatureSize) {
224    ReportFailure("Excessively large key or signature");
225    return false;
226  }
227  if (header.key_size == 0) {
228    ReportFailure("Key length is zero");
229    return false;
230  }
231
232  std::vector<uint8> key;
233  key.resize(header.key_size);
234  len = fread(&key.front(), sizeof(uint8), header.key_size, file.get());
235  if (len < header.key_size) {
236    ReportFailure("Invalid public key");
237    return false;
238  }
239
240  std::vector<uint8> signature;
241  signature.resize(header.signature_size);
242  len = fread(&signature.front(), sizeof(uint8), header.signature_size,
243      file.get());
244  if (len < header.signature_size) {
245    ReportFailure("Invalid signature");
246    return false;
247  }
248
249  base::SignatureVerifier verifier;
250  if (!verifier.VerifyInit(extension_misc::kSignatureAlgorithm,
251                           sizeof(extension_misc::kSignatureAlgorithm),
252                           &signature.front(),
253                           signature.size(),
254                           &key.front(),
255                           key.size())) {
256    ReportFailure("Signature verification initialization failed. "
257                  "This is most likely caused by a public key in "
258                  "the wrong format (should encode algorithm).");
259    return false;
260  }
261
262  unsigned char buf[1 << 12];
263  while ((len = fread(buf, 1, sizeof(buf), file.get())) > 0)
264    verifier.VerifyUpdate(buf, len);
265
266  if (!verifier.VerifyFinal()) {
267    ReportFailure("Signature verification failed");
268    return false;
269  }
270
271  base::Base64Encode(std::string(reinterpret_cast<char*>(&key.front()),
272      key.size()), &public_key_);
273  return true;
274}
275
276void SandboxedExtensionUnpacker::ReportFailure(const std::string& error) {
277  client_->OnUnpackFailure(error);
278}
279
280void SandboxedExtensionUnpacker::ReportSuccess() {
281  // Client takes ownership of temporary directory and extension.
282  client_->OnUnpackSuccess(temp_dir_.Take(), extension_root_, extension_);
283  extension_ = NULL;
284}
285
286DictionaryValue* SandboxedExtensionUnpacker::RewriteManifestFile(
287    const DictionaryValue& manifest) {
288  // Add the public key extracted earlier to the parsed manifest and overwrite
289  // the original manifest. We do this to ensure the manifest doesn't contain an
290  // exploitable bug that could be used to compromise the browser.
291  scoped_ptr<DictionaryValue> final_manifest(
292      static_cast<DictionaryValue*>(manifest.DeepCopy()));
293  final_manifest->SetString(extension_manifest_keys::kPublicKey, public_key_);
294
295  std::string manifest_json;
296  JSONStringValueSerializer serializer(&manifest_json);
297  serializer.set_pretty_print(true);
298  if (!serializer.Serialize(*final_manifest)) {
299    ReportFailure("Error serializing manifest.json.");
300    return NULL;
301  }
302
303  FilePath manifest_path =
304      extension_root_.Append(Extension::kManifestFilename);
305  if (!file_util::WriteFile(manifest_path,
306                            manifest_json.data(), manifest_json.size())) {
307    ReportFailure("Error saving manifest.json.");
308    return NULL;
309  }
310
311  return final_manifest.release();
312}
313
314bool SandboxedExtensionUnpacker::RewriteImageFiles() {
315  ExtensionUnpacker::DecodedImages images;
316  if (!ExtensionUnpacker::ReadImagesFromFile(temp_dir_.path(), &images)) {
317    ReportFailure("Couldn't read image data from disk.");
318    return false;
319  }
320
321  // Delete any images that may be used by the browser.  We're going to write
322  // out our own versions of the parsed images, and we want to make sure the
323  // originals are gone for good.
324  std::set<FilePath> image_paths = extension_->GetBrowserImages();
325  if (image_paths.size() != images.size()) {
326    ReportFailure("Decoded images don't match what's in the manifest.");
327    return false;
328  }
329
330  for (std::set<FilePath>::iterator it = image_paths.begin();
331       it != image_paths.end(); ++it) {
332    FilePath path = *it;
333    if (path.IsAbsolute() || path.ReferencesParent()) {
334      ReportFailure("Invalid path for browser image.");
335      return false;
336    }
337    if (!file_util::Delete(extension_root_.Append(path), false)) {
338      ReportFailure("Error removing old image file.");
339      return false;
340    }
341  }
342
343  // Write our parsed images back to disk as well.
344  for (size_t i = 0; i < images.size(); ++i) {
345    const SkBitmap& image = images[i].a;
346    FilePath path_suffix = images[i].b;
347    if (path_suffix.IsAbsolute() || path_suffix.ReferencesParent()) {
348      ReportFailure("Invalid path for bitmap image.");
349      return false;
350    }
351    FilePath path = extension_root_.Append(path_suffix);
352
353    std::vector<unsigned char> image_data;
354    // TODO(mpcomplete): It's lame that we're encoding all images as PNG, even
355    // though they may originally be .jpg, etc.  Figure something out.
356    // http://code.google.com/p/chromium/issues/detail?id=12459
357    if (!gfx::PNGCodec::EncodeBGRASkBitmap(image, false, &image_data)) {
358      ReportFailure("Error re-encoding theme image.");
359      return false;
360    }
361
362    // Note: we're overwriting existing files that the utility process wrote,
363    // so we can be sure the directory exists.
364    const char* image_data_ptr = reinterpret_cast<const char*>(&image_data[0]);
365    if (!file_util::WriteFile(path, image_data_ptr, image_data.size())) {
366      ReportFailure("Error saving theme image.");
367      return false;
368    }
369  }
370
371  return true;
372}
373
374bool SandboxedExtensionUnpacker::RewriteCatalogFiles() {
375  DictionaryValue catalogs;
376  if (!ExtensionUnpacker::ReadMessageCatalogsFromFile(temp_dir_.path(),
377                                                      &catalogs)) {
378    ReportFailure("Could not read catalog data from disk.");
379    return false;
380  }
381
382  // Write our parsed catalogs back to disk.
383  for (DictionaryValue::key_iterator key_it = catalogs.begin_keys();
384       key_it != catalogs.end_keys(); ++key_it) {
385    DictionaryValue* catalog;
386    if (!catalogs.GetDictionaryWithoutPathExpansion(*key_it, &catalog)) {
387      ReportFailure("Invalid catalog data.");
388      return false;
389    }
390
391    // TODO(viettrungluu): Fix the |FilePath::FromWStringHack(UTF8ToWide())|
392    // hack and remove the corresponding #include.
393    FilePath relative_path = FilePath::FromWStringHack(UTF8ToWide(*key_it));
394    relative_path = relative_path.Append(Extension::kMessagesFilename);
395    if (relative_path.IsAbsolute() || relative_path.ReferencesParent()) {
396      ReportFailure("Invalid path for catalog.");
397      return false;
398    }
399    FilePath path = extension_root_.Append(relative_path);
400
401    std::string catalog_json;
402    JSONStringValueSerializer serializer(&catalog_json);
403    serializer.set_pretty_print(true);
404    if (!serializer.Serialize(*catalog)) {
405      ReportFailure("Error serializing catalog.");
406      return false;
407    }
408
409    // Note: we're overwriting existing files that the utility process read,
410    // so we can be sure the directory exists.
411    if (!file_util::WriteFile(path,
412                              catalog_json.c_str(),
413                              catalog_json.size())) {
414      ReportFailure("Error saving catalog.");
415      return false;
416    }
417  }
418
419  return true;
420}
421