pnacl_component_installer.cc revision a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7
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/atomicops.h"
8#include "base/base_paths.h"
9#include "base/bind.h"
10#include "base/callback.h"
11#include "base/command_line.h"
12#include "base/compiler_specific.h"
13#include "base/file_util.h"
14#include "base/files/file_enumerator.h"
15#include "base/files/file_path.h"
16#include "base/json/json_file_value_serializer.h"
17#include "base/logging.h"
18#include "base/path_service.h"
19#include "base/strings/string_util.h"
20#include "base/values.h"
21#include "base/version.h"
22#include "base/win/windows_version.h"
23#include "build/build_config.h"
24#include "chrome/browser/browser_process.h"
25#include "chrome/browser/component_updater/component_updater_service.h"
26#include "chrome/browser/profiles/profile.h"
27#include "chrome/browser/profiles/profile_manager.h"
28#include "chrome/common/chrome_paths.h"
29#include "chrome/common/omaha_query_params/omaha_query_params.h"
30#include "components/nacl/common/nacl_switches.h"
31#include "content/public/browser/browser_thread.h"
32
33using chrome::OmahaQueryParams;
34using content::BrowserThread;
35
36namespace {
37
38// Name of the Pnacl component specified in the manifest.
39const char kPnaclManifestName[] = "PNaCl Translator";
40
41// Sanitize characters from Pnacl Arch value so that they can be used
42// in path names.  This should only be characters in the set: [a-z0-9_].
43// Keep in sync with chrome/browser/nacl_host/nacl_file_host.
44std::string SanitizeForPath(const std::string& input) {
45  std::string result;
46  base::ReplaceChars(input, "-", "_", &result);
47  return result;
48}
49
50// Set the component's hash to the multi-CRX PNaCl package.
51void SetPnaclHash(CrxComponent* component) {
52 static const uint8 sha256_hash[32] =
53     { // This corresponds to AppID: hnimpnehoodheedghdeeijklkeaacbdc
54       0x7d, 0x8c, 0xfd, 0x47, 0xee, 0x37, 0x44, 0x36, 0x73, 0x44,
55       0x89, 0xab, 0xa4, 0x00, 0x21, 0x32, 0x4a, 0x06, 0x06, 0xf1, 0x51,
56       0x3c, 0x51, 0xba, 0x31, 0x2f, 0xbc, 0xb3, 0x99, 0x07, 0xdc, 0x9c};
57
58  component->pk_hash.assign(sha256_hash,
59                            &sha256_hash[arraysize(sha256_hash)]);
60}
61
62// If we don't have Pnacl installed, this is the version we claim.
63const char kNullVersion[] = "0.0.0.0";
64const char kMinPnaclVersion[] = "0.1.0.12181";
65
66// Initially say that we do not need OnDemand updates. This should be
67// updated by CheckVersionCompatiblity(), before doing any URLRequests
68// that depend on PNaCl.
69volatile base::subtle::Atomic32 needs_on_demand_update = 0;
70
71void CheckVersionCompatiblity(const base::Version& current_version) {
72  // Using NoBarrier, since needs_on_demand_update is standalone and does
73  // not have other associated data.
74  base::subtle::NoBarrier_Store(&needs_on_demand_update,
75                                current_version.IsOlderThan(kMinPnaclVersion));
76}
77
78// PNaCl is packaged as a multi-CRX.  This returns the platform-specific
79// subdirectory that is part of that multi-CRX.
80base::FilePath GetPlatformDir(const base::FilePath& base_path) {
81  std::string arch = SanitizeForPath(OmahaQueryParams::getNaclArch());
82  return base_path.AppendASCII("_platform_specific").AppendASCII(arch);
83}
84
85// Tell the rest of the world where to find the platform-specific PNaCl files.
86void OverrideDirPnaclComponent(const base::FilePath& base_path) {
87  PathService::Override(chrome::DIR_PNACL_COMPONENT,
88                        GetPlatformDir(base_path));
89}
90
91bool GetLatestPnaclDirectory(PnaclComponentInstaller* pci,
92                             base::FilePath* latest_dir,
93                             Version* latest_version,
94                             std::vector<base::FilePath>* older_dirs) {
95  // Enumerate all versions starting from the base directory.
96  base::FilePath base_dir = pci->GetPnaclBaseDirectory();
97  bool found = false;
98  base::FileEnumerator
99      file_enumerator(base_dir, false, base::FileEnumerator::DIRECTORIES);
100  for (base::FilePath path = file_enumerator.Next(); !path.value().empty();
101       path = file_enumerator.Next()) {
102    Version version(path.BaseName().MaybeAsASCII());
103    if (!version.IsValid())
104      continue;
105    if (found) {
106      if (version.CompareTo(*latest_version) > 0) {
107        older_dirs->push_back(*latest_dir);
108        *latest_dir = path;
109        *latest_version = version;
110      } else {
111        older_dirs->push_back(path);
112      }
113    } else {
114      *latest_version = version;
115      *latest_dir = path;
116      found = true;
117    }
118  }
119  return found;
120}
121
122// Read a manifest file in.
123base::DictionaryValue* ReadJSONManifest(
124    const base::FilePath& manifest_path) {
125  JSONFileValueSerializer serializer(manifest_path);
126  std::string error;
127  scoped_ptr<base::Value> root(serializer.Deserialize(NULL, &error));
128  if (!root.get())
129    return NULL;
130  if (!root->IsType(base::Value::TYPE_DICTIONARY))
131    return NULL;
132  return static_cast<base::DictionaryValue*>(root.release());
133}
134
135// Read the PNaCl specific manifest.
136base::DictionaryValue* ReadPnaclManifest(const base::FilePath& unpack_path) {
137  base::FilePath manifest_path = GetPlatformDir(unpack_path).AppendASCII(
138      "pnacl_public_pnacl_json");
139  if (!base::PathExists(manifest_path))
140    return NULL;
141  return ReadJSONManifest(manifest_path);
142}
143
144// Read the component's manifest.json.
145base::DictionaryValue* ReadComponentManifest(
146    const base::FilePath& unpack_path) {
147  base::FilePath manifest_path = unpack_path.Append(
148      FILE_PATH_LITERAL("manifest.json"));
149  if (!base::PathExists(manifest_path))
150    return NULL;
151  return ReadJSONManifest(manifest_path);
152}
153
154// Check that the component's manifest is for PNaCl, and check the
155// PNaCl manifest indicates this is the correct arch-specific package.
156bool CheckPnaclComponentManifest(const base::DictionaryValue& manifest,
157                                 const base::DictionaryValue& pnacl_manifest,
158                                 Version* version_out) {
159  // Make sure we have the right |manifest| file.
160  std::string name;
161  if (!manifest.GetStringASCII("name", &name)) {
162    LOG(WARNING) << "'name' field is missing from manifest!";
163    return false;
164  }
165  // For the webstore, we've given different names to each of the
166  // architecture specific packages (and test/QA vs not test/QA)
167  // so only part of it is the same.
168  if (name.find(kPnaclManifestName) == std::string::npos) {
169    LOG(WARNING) << "'name' field in manifest is invalid ("
170                 << name << ") -- missing ("
171                 << kPnaclManifestName << ")";
172    return false;
173  }
174
175  std::string proposed_version;
176  if (!manifest.GetStringASCII("version", &proposed_version)) {
177    LOG(WARNING) << "'version' field is missing from manifest!";
178    return false;
179  }
180  Version version(proposed_version.c_str());
181  if (!version.IsValid()) {
182    LOG(WARNING) << "'version' field in manifest is invalid "
183                 << version.GetString();
184    return false;
185  }
186
187  // Now check the |pnacl_manifest|.
188  std::string arch;
189  if (!pnacl_manifest.GetStringASCII("pnacl-arch", &arch)) {
190    LOG(WARNING) << "'pnacl-arch' field is missing from pnacl-manifest!";
191    return false;
192  }
193  if (arch.compare(OmahaQueryParams::getNaclArch()) != 0) {
194    LOG(WARNING) << "'pnacl-arch' field in manifest is invalid ("
195                 << arch << " vs " << OmahaQueryParams::getNaclArch() << ")";
196    return false;
197  }
198
199  *version_out = version;
200  return true;
201}
202
203}  // namespace
204
205PnaclComponentInstaller::PnaclComponentInstaller()
206    : per_user_(false),
207      updates_disabled_(false),
208      cus_(NULL) {
209#if defined(OS_CHROMEOS)
210  per_user_ = true;
211#endif
212}
213
214PnaclComponentInstaller::~PnaclComponentInstaller() {
215}
216
217void PnaclComponentInstaller::OnUpdateError(int error) {
218  NOTREACHED() << "Pnacl update error: " << error;
219}
220
221// Pnacl components have the version encoded in the path itself:
222// <profile>\AppData\Local\Google\Chrome\User Data\pnacl\0.1.2.3\.
223// and the base directory will be:
224// <profile>\AppData\Local\Google\Chrome\User Data\pnacl\.
225base::FilePath PnaclComponentInstaller::GetPnaclBaseDirectory() {
226  // For ChromeOS, temporarily make this user-dependent (for integrity) until
227  // we find a better solution.
228  // This is not ideal because of the following:
229  //   (a) We end up with per-user copies instead of a single copy
230  //   (b) The profile can change as users log in to different accounts
231  //   so we need to watch for user-login-events (see pnacl_profile_observer.h).
232  if (per_user_) {
233    DCHECK(!current_profile_path_.empty());
234    base::FilePath path = current_profile_path_.Append(
235        FILE_PATH_LITERAL("pnacl"));
236    return path;
237  } else {
238    base::FilePath result;
239    CHECK(PathService::Get(chrome::DIR_PNACL_BASE, &result));
240    return result;
241  }
242}
243
244void PnaclComponentInstaller::OnProfileChange() {
245  // On chromeos, we want to find the --login-profile=<foo> dir.
246  // Even though the path does vary between users, the content
247  // changes when logging out and logging in.
248  ProfileManager* pm = g_browser_process->profile_manager();
249  current_profile_path_ = pm->user_data_dir().Append(
250      pm->GetInitialProfileDir());
251}
252
253bool PnaclComponentInstaller::Install(const base::DictionaryValue& manifest,
254                                      const base::FilePath& unpack_path) {
255  scoped_ptr<base::DictionaryValue> pnacl_manifest(
256      ReadPnaclManifest(unpack_path));
257  if (pnacl_manifest == NULL) {
258    LOG(WARNING) << "Failed to read pnacl manifest.";
259    return false;
260  }
261
262  Version version;
263  if (!CheckPnaclComponentManifest(manifest, *pnacl_manifest, &version)) {
264    LOG(WARNING) << "CheckPnaclComponentManifest failed, not installing.";
265    return false;
266  }
267
268  // Don't install if the current version is actually newer.
269  if (current_version().CompareTo(version) > 0) {
270    return false;
271  }
272
273  // Passed the basic tests. Time to install it.
274  base::FilePath path = GetPnaclBaseDirectory().AppendASCII(
275      version.GetString());
276  if (base::PathExists(path)) {
277    if (!base::DeleteFile(path, true))
278      return false;
279  }
280  if (!base::Move(unpack_path, path)) {
281    LOG(WARNING) << "Move failed, not installing.";
282    return false;
283  }
284
285  // Installation is done. Now tell the rest of chrome.
286  // - The path service.
287  // - Callbacks that requested an update.
288  set_current_version(version);
289  CheckVersionCompatiblity(version);
290  OverrideDirPnaclComponent(path);
291  return true;
292}
293
294// Given |file|, which can be a path like "_platform_specific/arm/pnacl_foo",
295// returns the assumed install path. The path separator in |file| is '/'
296// for all platforms. Caller is responsible for checking that the
297// |installed_file| actually exists.
298bool PnaclComponentInstaller::GetInstalledFile(
299    const std::string& file, base::FilePath* installed_file) {
300  if (current_version().Equals(Version(kNullVersion)))
301    return false;
302
303  *installed_file = GetPnaclBaseDirectory().AppendASCII(
304      current_version().GetString()).AppendASCII(file);
305  return true;
306}
307
308CrxComponent PnaclComponentInstaller::GetCrxComponent() {
309  CrxComponent pnacl_component;
310  pnacl_component.version = current_version();
311  pnacl_component.name = "pnacl";
312  pnacl_component.installer = this;
313  pnacl_component.fingerprint = current_fingerprint();
314  SetPnaclHash(&pnacl_component);
315
316  return pnacl_component;
317}
318
319namespace {
320
321void FinishPnaclUpdateRegistration(const Version& current_version,
322                                   const std::string& current_fingerprint,
323                                   PnaclComponentInstaller* pci) {
324  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
325  pci->set_current_version(current_version);
326  CheckVersionCompatiblity(current_version);
327  pci->set_current_fingerprint(current_fingerprint);
328  CrxComponent pnacl_component = pci->GetCrxComponent();
329
330  ComponentUpdateService::Status status =
331      pci->cus()->RegisterComponent(pnacl_component);
332  if (status != ComponentUpdateService::kOk
333      && status != ComponentUpdateService::kReplaced) {
334    NOTREACHED() << "Pnacl component registration failed.";
335  }
336}
337
338// Check if there is an existing version on disk first to know when
339// a hosted version is actually newer.
340void StartPnaclUpdateRegistration(PnaclComponentInstaller* pci) {
341  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
342  base::FilePath path = pci->GetPnaclBaseDirectory();
343  if (!base::PathExists(path)) {
344    if (!base::CreateDirectory(path)) {
345      NOTREACHED() << "Could not create base Pnacl directory.";
346      return;
347    }
348  }
349
350  Version current_version(kNullVersion);
351  std::string current_fingerprint;
352  std::vector<base::FilePath> older_dirs;
353  if (GetLatestPnaclDirectory(pci, &path, &current_version, &older_dirs)) {
354    scoped_ptr<base::DictionaryValue> manifest(
355        ReadComponentManifest(path));
356    scoped_ptr<base::DictionaryValue> pnacl_manifest(
357        ReadPnaclManifest(path));
358    Version manifest_version;
359    // Check that the component manifest and PNaCl manifest files
360    // are legit, and that the indicated version matches the one
361    // encoded within the path name.
362    if (manifest == NULL || pnacl_manifest == NULL
363        || !CheckPnaclComponentManifest(*manifest,
364                                        *pnacl_manifest,
365                                        &manifest_version)
366        || !current_version.Equals(manifest_version)) {
367      current_version = Version(kNullVersion);
368    } else {
369      OverrideDirPnaclComponent(path);
370      base::ReadFileToString(path.AppendASCII("manifest.fingerprint"),
371                             &current_fingerprint);
372    }
373  }
374
375  // If updates are disabled, only discover the current version
376  // and OverrideDirPnaclComponent. That way, developers can use
377  // a pinned version. Do not actually finish registration with
378  // the component update service.
379  if (pci->updates_disabled())
380    return;
381
382  BrowserThread::PostTask(
383      BrowserThread::UI, FROM_HERE,
384      base::Bind(&FinishPnaclUpdateRegistration,
385                 current_version,
386                 current_fingerprint,
387                 pci));
388
389  // Remove older versions of PNaCl.
390  for (std::vector<base::FilePath>::iterator iter = older_dirs.begin();
391       iter != older_dirs.end(); ++iter) {
392    base::DeleteFile(*iter, true);
393  }
394}
395
396// Remove old per-profile copies of PNaCl (was for ChromeOS).
397// TODO(jvoung): Delete this code once most ChromeOS users have reaped
398// their old per-profile copies of PNaCl.
399void ReapOldChromeOSPnaclFiles(PnaclComponentInstaller* pci) {
400  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
401  base::FilePath path = pci->GetPnaclBaseDirectory();
402  if (!base::PathExists(path))
403    return;
404
405  // Do a basic sanity check first.
406  if (pci->per_user()
407      && path.BaseName().value().compare(FILE_PATH_LITERAL("pnacl")) == 0)
408    base::DeleteFile(path, true);
409}
410
411
412void GetProfileInformation(PnaclComponentInstaller* pci) {
413  // Bail if not logged in yet.
414  if (!g_browser_process->profile_manager()->IsLoggedIn()) {
415    return;
416  }
417
418  pci->OnProfileChange();
419
420  // Do not actually register PNaCl for component updates, for CHROMEOS.
421  // Just get the profile information and delete the per-profile files
422  // if they exist.
423 BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE,
424                         base::Bind(&ReapOldChromeOSPnaclFiles, pci));
425}
426
427}  // namespace
428
429void PnaclComponentInstaller::RegisterPnaclComponent(
430                            ComponentUpdateService* cus,
431                            const CommandLine& command_line) {
432  // Register PNaCl by default (can be disabled).
433  updates_disabled_ = command_line.HasSwitch(switches::kDisablePnaclInstall);
434  cus_ = cus;
435  // If per_user, create a profile observer to watch for logins.
436  // Only do so after cus_ is set to something non-null.
437  if (per_user_ && !profile_observer_) {
438    profile_observer_.reset(new PnaclProfileObserver(this));
439  }
440  if (per_user_) {
441    // Figure out profile information, before proceeding to look for files.
442    BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
443                            base::Bind(&GetProfileInformation, this));
444  } else {
445    BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE,
446                            base::Bind(&StartPnaclUpdateRegistration, this));
447  }
448}
449
450void PnaclComponentInstaller::ReRegisterPnacl() {
451  DCHECK(per_user_);
452  // Figure out profile information, before proceeding to look for files.
453  BrowserThread::PostTask(
454      BrowserThread::UI, FROM_HERE,
455      base::Bind(&GetProfileInformation, this));
456}
457
458
459namespace pnacl {
460
461bool NeedsOnDemandUpdate() {
462  return base::subtle::NoBarrier_Load(&needs_on_demand_update) != 0;
463}
464
465}  // namespace pnacl
466