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