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