pnacl_component_installer.cc revision 868fa2fe829687343ffae624259930155e16dbd8
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/component_updater/pnacl/pnacl_component_installer.h"
6
7#include "base/base_paths.h"
8#include "base/bind.h"
9#include "base/command_line.h"
10#include "base/compiler_specific.h"
11#include "base/file_util.h"
12#include "base/files/file_enumerator.h"
13#include "base/files/file_path.h"
14#include "base/json/json_file_value_serializer.h"
15#include "base/logging.h"
16#include "base/path_service.h"
17#include "base/strings/string_util.h"
18#include "base/values.h"
19#include "base/version.h"
20#include "base/win/windows_version.h"
21#include "build/build_config.h"
22#include "chrome/browser/browser_process.h"
23#include "chrome/browser/component_updater/component_updater_service.h"
24#include "chrome/browser/profiles/profile.h"
25#include "chrome/browser/profiles/profile_manager.h"
26#include "chrome/common/chrome_paths.h"
27#include "chrome/common/chrome_switches.h"
28#include "chrome/common/omaha_query_params/omaha_query_params.h"
29#include "content/public/browser/browser_thread.h"
30
31using chrome::OmahaQueryParams;
32using content::BrowserThread;
33
34namespace {
35
36// If PNaCl isn't installed yet, but a user is running chrome with
37// --enable-pnacl, this is the amount of time to wait before starting
38// a background install.
39const int kInitialDelaySeconds = 10;
40
41// Name of the Pnacl component specified in the manifest.
42const char kPnaclManifestNamePrefix[] = "PNaCl";
43
44// Sanitize characters from Pnacl Arch value so that they can be used
45// in path names.  This should only be characters in the set: [a-z0-9_].
46// Keep in sync with chrome/browser/nacl_host/nacl_file_host.
47std::string SanitizeForPath(const std::string& input) {
48  std::string result;
49  ReplaceChars(input, "-", "_", &result);
50  return result;
51}
52
53// Set the component's hash to the arch-specific PNaCl package.
54void SetPnaclHash(CrxComponent* component) {
55#if defined(ARCH_CPU_X86_FAMILY)
56  // Define both x86_32 and x86_64, and choose below.
57  static const uint8 x86_sha256_hash[][32] = {
58    { // This corresponds to AppID (x86-32): aealhdcgieaiikaifafholmmeooeeioj
59      0x04, 0x0b, 0x73, 0x26, 0x84, 0x08, 0x8a, 0x08, 0x50, 0x57,
60      0xeb, 0xcc, 0x4e, 0xe4, 0x48, 0xe9, 0x44, 0x2c, 0xc8, 0xa6, 0xd6,
61      0x96, 0x11, 0xd4, 0x2a, 0xc5, 0x26, 0x64, 0x34, 0x76, 0x3d, 0x14},
62    { // This corresponds to AppID (x86-64): knlfebnofcjjnkpkapbgfphaagefndik
63      0xad, 0xb5, 0x41, 0xde, 0x52, 0x99, 0xda, 0xfa, 0x0f, 0x16,
64      0x5f, 0x70, 0x06, 0x45, 0xd3, 0x8a, 0x32, 0x20, 0x84, 0x57, 0x5c,
65      0x1f, 0xef, 0xb4, 0x42, 0x32, 0xce, 0x4a, 0x3c, 0x2d, 0x7e, 0x3a}
66  };
67
68#if defined(ARCH_CPU_X86_64)
69  component->pk_hash.assign(
70      x86_sha256_hash[1],
71      &x86_sha256_hash[1][sizeof(x86_sha256_hash[1])]);
72#elif defined(OS_WIN)
73  bool x86_64 = (base::win::OSInfo::GetInstance()->wow64_status() ==
74                 base::win::OSInfo::WOW64_ENABLED);
75  if (x86_64) {
76    component->pk_hash.assign(
77        x86_sha256_hash[1],
78        &x86_sha256_hash[1][sizeof(x86_sha256_hash[1])]);
79  } else {
80    component->pk_hash.assign(
81        x86_sha256_hash[0],
82        &x86_sha256_hash[0][sizeof(x86_sha256_hash[0])]);
83  }
84#else
85  component->pk_hash.assign(
86      x86_sha256_hash[0],
87      &x86_sha256_hash[0][sizeof(x86_sha256_hash[0])]);
88#endif
89#elif defined(ARCH_CPU_ARMEL)
90  // This corresponds to AppID: jgobdlakdbanalhiagkdgcnofkbebejj
91  static const uint8 arm_sha256_hash[] = {
92    0x96, 0xe1, 0x3b, 0x0a, 0x31, 0x0d, 0x0b, 0x78, 0x06, 0xa3,
93    0x62, 0xde, 0x5a, 0x14, 0x14, 0x99, 0xd4, 0xd9, 0x01, 0x85, 0xc6,
94    0x9a, 0xd2, 0x51, 0x90, 0xa4, 0xb4, 0x94, 0xbd, 0xb8, 0x8b, 0xe8};
95
96  component->pk_hash.assign(arm_sha256_hash,
97                            &arm_sha256_hash[sizeof(arm_sha256_hash)]);
98#elif defined(ARCH_CPU_MIPSEL)
99  // This is a dummy CRX hash for MIPS, so that it will at least compile.
100  static const uint8 mips32_sha256_hash[] = {
101    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
102    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
103    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
104
105  component->pk_hash.assign(mips32_sha256_hash,
106                            &mips32_sha256_hash[sizeof(mips32_sha256_hash)]);
107#else
108#error "Add support for your architecture to Pnacl Component Installer."
109#endif
110}
111
112
113// If we don't have Pnacl installed, this is the version we claim.
114const char kNullVersion[] = "0.0.0.0";
115
116bool GetLatestPnaclDirectory(PnaclComponentInstaller* pci,
117                             base::FilePath* latest_dir,
118                             Version* latest_version,
119                             std::vector<base::FilePath>* older_dirs) {
120  // Enumerate all versions starting from the base directory.
121  base::FilePath base_dir = pci->GetPnaclBaseDirectory();
122  bool found = false;
123  base::FileEnumerator
124      file_enumerator(base_dir, false, base::FileEnumerator::DIRECTORIES);
125  for (base::FilePath path = file_enumerator.Next(); !path.value().empty();
126       path = file_enumerator.Next()) {
127    Version version(path.BaseName().MaybeAsASCII());
128    if (!version.IsValid())
129      continue;
130    if (found) {
131      if (version.CompareTo(*latest_version) > 0) {
132        older_dirs->push_back(*latest_dir);
133        *latest_dir = path;
134        *latest_version = version;
135      } else {
136        older_dirs->push_back(path);
137      }
138    } else {
139      *latest_version = version;
140      *latest_dir = path;
141      found = true;
142    }
143  }
144  return found;
145}
146
147// Read a manifest file in.
148base::DictionaryValue* ReadJSONManifest(
149    const base::FilePath& manifest_path) {
150  JSONFileValueSerializer serializer(manifest_path);
151  std::string error;
152  scoped_ptr<base::Value> root(serializer.Deserialize(NULL, &error));
153  if (!root.get())
154    return NULL;
155  if (!root->IsType(base::Value::TYPE_DICTIONARY))
156    return NULL;
157  return static_cast<base::DictionaryValue*>(root.release());
158}
159
160// Read the PNaCl specific manifest.
161base::DictionaryValue* ReadPnaclManifest(const base::FilePath& unpack_path) {
162  base::FilePath manifest_path = unpack_path.Append(
163      FILE_PATH_LITERAL("pnacl_public_pnacl_json"));
164  if (!file_util::PathExists(manifest_path))
165    return NULL;
166  return ReadJSONManifest(manifest_path);
167}
168
169// Read the component's manifest.json.
170base::DictionaryValue* ReadComponentManifest(
171    const base::FilePath& unpack_path) {
172  base::FilePath manifest_path = unpack_path.Append(
173      FILE_PATH_LITERAL("manifest.json"));
174  if (!file_util::PathExists(manifest_path))
175    return NULL;
176  return ReadJSONManifest(manifest_path);
177}
178
179}  // namespace
180
181// Check that the component's manifest is for PNaCl, and check the
182// PNaCl manifest indicates this is the correct arch-specific package.
183bool CheckPnaclComponentManifest(const base::DictionaryValue& manifest,
184                                 const base::DictionaryValue& pnacl_manifest,
185                                 Version* version_out) {
186  // Make sure we have the right |manifest| file.
187  std::string name;
188  manifest.GetStringASCII("name", &name);
189  // For the webstore, we've given different names to each of the
190  // architecture specific packages, so only the prefix is the same.
191  if (StartsWithASCII(kPnaclManifestNamePrefix, name, false)) {
192    LOG(WARNING) << "'name' field in manifest is invalid ("
193                 << name << ") -- missing prefix ("
194                 << kPnaclManifestNamePrefix << ")";
195    return false;
196  }
197
198  std::string proposed_version;
199  manifest.GetStringASCII("version", &proposed_version);
200  Version version(proposed_version.c_str());
201  if (!version.IsValid()) {
202    LOG(WARNING) << "'version' field in manifest is invalid "
203                 << version.GetString();
204    return false;
205  }
206
207  // Now check the |pnacl_manifest|.
208  std::string arch;
209  pnacl_manifest.GetStringASCII("pnacl-arch", &arch);
210  if (arch.compare(OmahaQueryParams::getNaclArch()) != 0) {
211    LOG(WARNING) << "'pnacl-arch' field in manifest is invalid ("
212                 << arch << " vs " << OmahaQueryParams::getNaclArch() << ")";
213    return false;
214  }
215
216  *version_out = version;
217  return true;
218}
219
220PnaclComponentInstaller::PnaclComponentInstaller()
221    : per_user_(false),
222      cus_(NULL) {
223#if defined(OS_CHROMEOS)
224  per_user_ = true;
225#endif
226}
227
228PnaclComponentInstaller::~PnaclComponentInstaller() {
229}
230
231void PnaclComponentInstaller::OnUpdateError(int error) {
232  NOTREACHED() << "Pnacl update error: " << error;
233}
234
235// Pnacl components have the version encoded in the path itself:
236// <profile>\AppData\Local\Google\Chrome\User Data\Pnacl\0.1.2.3\.
237// and the base directory will be:
238// <profile>\AppData\Local\Google\Chrome\User Data\Pnacl\.
239base::FilePath PnaclComponentInstaller::GetPnaclBaseDirectory() {
240  // For ChromeOS, temporarily make this user-dependent (for integrity) until
241  // we find a better solution.
242  // This is not ideal because of the following:
243  //   (a) We end up with per-user copies instead of a single copy
244  //   (b) The profile can change as users log in to different accounts
245  //   so we need to watch for user-login-events (see pnacl_profile_observer.h).
246  if (per_user_) {
247    DCHECK(!current_profile_path_.empty());
248    base::FilePath path = current_profile_path_.Append(
249        FILE_PATH_LITERAL("pnacl"));
250    return path;
251  } else {
252    base::FilePath result;
253    CHECK(PathService::Get(chrome::DIR_PNACL_BASE, &result));
254    return result;
255  }
256}
257
258void PnaclComponentInstaller::OnProfileChange() {
259  // On chromeos, we want to find the --login-profile=<foo> dir.
260  // Even though the path does vary between users, the content
261  // changes when logging out and logging in.
262  ProfileManager* pm = g_browser_process->profile_manager();
263  current_profile_path_ = pm->user_data_dir().Append(
264      pm->GetInitialProfileDir());
265}
266
267bool PnaclComponentInstaller::Install(const base::DictionaryValue& manifest,
268                                      const base::FilePath& unpack_path) {
269  scoped_ptr<base::DictionaryValue> pnacl_manifest(
270      ReadPnaclManifest(unpack_path));
271  if (pnacl_manifest == NULL) {
272    LOG(WARNING) << "Failed to read pnacl manifest.";
273    return false;
274  }
275
276  Version version;
277  if (!CheckPnaclComponentManifest(manifest, *pnacl_manifest, &version)) {
278    LOG(WARNING) << "CheckPnaclComponentManifest failed, not installing.";
279    return false;
280  }
281
282  // Don't install if the current version is actually newer.
283  if (current_version().CompareTo(version) > 0)
284    return false;
285
286  // Passed the basic tests. Time to install it.
287  base::FilePath path = GetPnaclBaseDirectory().AppendASCII(
288      version.GetString());
289  if (file_util::PathExists(path)) {
290    LOG(WARNING) << "Target path already exists, not installing.";
291    return false;
292  }
293  if (!file_util::Move(unpack_path, path)) {
294    LOG(WARNING) << "Move failed, not installing.";
295    return false;
296  }
297
298  // Installation is done. Now tell the rest of chrome (just the path service
299  // for now). TODO(jvoung): we need notifications if someone surfed to a
300  // Pnacl webpage and Pnacl was just installed at this time. They should
301  // then be able to reload the page and retry (or something).
302  // See: http://code.google.com/p/chromium/issues/detail?id=107438
303  set_current_version(version);
304
305  PathService::Override(chrome::DIR_PNACL_COMPONENT, path);
306  return true;
307}
308
309namespace {
310
311void DoCheckForUpdate(ComponentUpdateService* cus,
312                   const CrxComponent& pnacl) {
313  if (cus->CheckForUpdateSoon(pnacl) != ComponentUpdateService::kOk) {
314    LOG(WARNING) << "Pnacl check for update failed.";
315  }
316}
317
318// Finally, do the registration with the right version number.
319void FinishPnaclUpdateRegistration(const Version& current_version,
320                                   PnaclComponentInstaller* pci) {
321  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
322  CrxComponent pnacl_component;
323  pnacl_component.version = current_version;
324  pnacl_component.name = "pnacl";
325  pnacl_component.installer = pci;
326  pci->set_current_version(current_version);
327  SetPnaclHash(&pnacl_component);
328
329  ComponentUpdateService::Status status =
330      pci->cus()->RegisterComponent(pnacl_component);
331  if (status != ComponentUpdateService::kOk
332      && status != ComponentUpdateService::kReplaced) {
333    NOTREACHED() << "Pnacl component registration failed.";
334  }
335
336  // If PNaCl is not yet installed but it is requested by --enable-pnacl,
337  // we want it to be available "soon", so kick off an update check
338  // earlier than usual.
339  Version null_version(kNullVersion);
340  if (pci->current_version().Equals(null_version)) {
341    BrowserThread::PostDelayedTask(
342        BrowserThread::UI, FROM_HERE,
343        base::Bind(DoCheckForUpdate, pci->cus(), pnacl_component),
344        base::TimeDelta::FromSeconds(kInitialDelaySeconds));
345  }
346}
347
348// Check if there is an existing version on disk first to know when
349// a hosted version is actually newer.
350void StartPnaclUpdateRegistration(PnaclComponentInstaller* pci) {
351  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
352  base::FilePath path = pci->GetPnaclBaseDirectory();
353  if (!file_util::PathExists(path)) {
354    if (!file_util::CreateDirectory(path)) {
355      NOTREACHED() << "Could not create base Pnacl directory.";
356      return;
357    }
358  }
359
360  Version version(kNullVersion);
361  std::vector<base::FilePath> older_dirs;
362  if (GetLatestPnaclDirectory(pci, &path, &version, &older_dirs)) {
363    scoped_ptr<base::DictionaryValue> manifest(
364        ReadComponentManifest(path));
365    scoped_ptr<base::DictionaryValue> pnacl_manifest(
366        ReadPnaclManifest(path));
367    Version manifest_version;
368    // Check that the component manifest and PNaCl manifest files
369    // are legit, and that the indicated version matches the one
370    // encoded within the path name.
371    if (!CheckPnaclComponentManifest(*manifest, *pnacl_manifest,
372                                     &manifest_version)
373        || !version.Equals(manifest_version)) {
374      version = Version(kNullVersion);
375    } else {
376      PathService::Override(chrome::DIR_PNACL_COMPONENT, path);
377    }
378  }
379
380  BrowserThread::PostTask(
381      BrowserThread::UI, FROM_HERE,
382      base::Bind(&FinishPnaclUpdateRegistration, version, pci));
383
384  // Remove older versions of PNaCl.
385  for (std::vector<base::FilePath>::iterator iter = older_dirs.begin();
386       iter != older_dirs.end(); ++iter) {
387    file_util::Delete(*iter, true);
388  }
389}
390
391void GetProfileInformation(PnaclComponentInstaller* pci) {
392  // Bail if not logged in yet.
393  if (!g_browser_process->profile_manager()->IsLoggedIn()) {
394    return;
395  }
396
397  pci->OnProfileChange();
398
399  BrowserThread::PostTask(
400     BrowserThread::FILE, FROM_HERE,
401     base::Bind(&StartPnaclUpdateRegistration, pci));
402}
403
404
405}  // namespace
406
407void PnaclComponentInstaller::RegisterPnaclComponent(
408                            ComponentUpdateService* cus,
409                            const CommandLine& command_line) {
410  // Only register when given the right flag.  This is important since
411  // we do an early component updater check above (in DoCheckForUpdate).
412  if (command_line.HasSwitch(switches::kEnablePnacl)) {
413    cus_ = cus;
414    // If per_user, create a profile observer to watch for logins.
415    // Only do so after cus_ is set to something non-null.
416    if (per_user_ && !profile_observer_) {
417      profile_observer_.reset(new PnaclProfileObserver(this));
418    }
419    if (per_user_) {
420      // Figure out profile information, before proceeding to look for files.
421      BrowserThread::PostTask(
422           BrowserThread::UI, FROM_HERE,
423           base::Bind(&GetProfileInformation, this));
424    } else {
425      BrowserThread::PostTask(
426           BrowserThread::FILE, FROM_HERE,
427           base::Bind(&StartPnaclUpdateRegistration, this));
428    }
429  }
430}
431
432void PnaclComponentInstaller::ReRegisterPnacl() {
433  // No need to check the commandline flags again here.
434  // We could only have gotten here after RegisterPnaclComponent
435  // found --enable-pnacl, since that is where we create the profile_observer_,
436  // which in turn calls ReRegisterPnacl.
437  DCHECK(per_user_);
438  // Figure out profile information, before proceeding to look for files.
439  BrowserThread::PostTask(
440      BrowserThread::UI, FROM_HERE,
441      base::Bind(&GetProfileInformation, this));
442}
443