pnacl_component_installer.cc revision 010d83a9304c5a91596085d917d248abff47903a
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 <string>
8#include <vector>
9
10#include "base/atomicops.h"
11#include "base/base_paths.h"
12#include "base/bind.h"
13#include "base/callback.h"
14#include "base/command_line.h"
15#include "base/compiler_specific.h"
16#include "base/file_util.h"
17#include "base/files/file_enumerator.h"
18#include "base/files/file_path.h"
19#include "base/json/json_file_value_serializer.h"
20#include "base/logging.h"
21#include "base/path_service.h"
22#include "base/strings/string_util.h"
23#include "base/values.h"
24#include "base/version.h"
25#include "base/win/windows_version.h"
26#include "build/build_config.h"
27#include "chrome/browser/browser_process.h"
28#include "chrome/browser/component_updater/component_updater_service.h"
29#include "chrome/browser/omaha_query_params/omaha_query_params.h"
30#include "chrome/browser/profiles/profile.h"
31#include "chrome/browser/profiles/profile_manager.h"
32#include "chrome/common/chrome_paths.h"
33#include "components/nacl/common/nacl_switches.h"
34#include "content/public/browser/browser_thread.h"
35
36using chrome::OmahaQueryParams;
37using content::BrowserThread;
38
39namespace component_updater {
40
41namespace {
42
43// Name of the Pnacl component specified in the manifest.
44const char kPnaclManifestName[] = "PNaCl Translator";
45
46// Sanitize characters from Pnacl Arch value so that they can be used
47// in path names.  This should only be characters in the set: [a-z0-9_].
48// Keep in sync with chrome/browser/nacl_host/nacl_file_host.
49std::string SanitizeForPath(const std::string& input) {
50  std::string result;
51  base::ReplaceChars(input, "-", "_", &result);
52  return result;
53}
54
55// Set the component's hash to the multi-CRX PNaCl package.
56void SetPnaclHash(CrxComponent* component) {
57  static const uint8 sha256_hash[32] = {
58      // This corresponds to AppID: hnimpnehoodheedghdeeijklkeaacbdc
59      0x7d, 0x8c, 0xfd, 0x47, 0xee, 0x37, 0x44, 0x36,
60      0x73, 0x44, 0x89, 0xab, 0xa4, 0x00, 0x21, 0x32,
61      0x4a, 0x06, 0x06, 0xf1, 0x51, 0x3c, 0x51, 0xba,
62      0x31, 0x2f, 0xbc, 0xb3, 0x99, 0x07, 0xdc, 0x9c
63  };
64
65  component->pk_hash.assign(sha256_hash, &sha256_hash[arraysize(sha256_hash)]);
66}
67
68// If we don't have Pnacl installed, this is the version we claim.
69const char kNullVersion[] = "0.0.0.0";
70const char kMinPnaclVersion[] = "0.1.0.13154";
71
72// Initially say that we do not need OnDemand updates. This should be
73// updated by CheckVersionCompatiblity(), before doing any URLRequests
74// that depend on PNaCl.
75volatile base::subtle::Atomic32 needs_on_demand_update = 0;
76
77void CheckVersionCompatiblity(const base::Version& current_version) {
78  // Using NoBarrier, since needs_on_demand_update is standalone and does
79  // not have other associated data.
80  base::subtle::NoBarrier_Store(&needs_on_demand_update,
81                                current_version.IsOlderThan(kMinPnaclVersion));
82}
83
84// PNaCl is packaged as a multi-CRX.  This returns the platform-specific
85// subdirectory that is part of that multi-CRX.
86base::FilePath GetPlatformDir(const base::FilePath& base_path) {
87  std::string arch = SanitizeForPath(OmahaQueryParams::GetNaclArch());
88  return base_path.AppendASCII("_platform_specific").AppendASCII(arch);
89}
90
91// Tell the rest of the world where to find the platform-specific PNaCl files.
92void OverrideDirPnaclComponent(const base::FilePath& base_path) {
93  PathService::Override(chrome::DIR_PNACL_COMPONENT, GetPlatformDir(base_path));
94}
95
96bool GetLatestPnaclDirectory(PnaclComponentInstaller* pci,
97                             base::FilePath* latest_dir,
98                             Version* latest_version,
99                             std::vector<base::FilePath>* older_dirs) {
100  // Enumerate all versions starting from the base directory.
101  base::FilePath base_dir = pci->GetPnaclBaseDirectory();
102  bool found = false;
103  base::FileEnumerator file_enumerator(
104      base_dir, false, base::FileEnumerator::DIRECTORIES);
105  for (base::FilePath path = file_enumerator.Next(); !path.value().empty();
106       path = file_enumerator.Next()) {
107    Version version(path.BaseName().MaybeAsASCII());
108    if (!version.IsValid())
109      continue;
110    if (found) {
111      if (version.CompareTo(*latest_version) > 0) {
112        older_dirs->push_back(*latest_dir);
113        *latest_dir = path;
114        *latest_version = version;
115      } else {
116        older_dirs->push_back(path);
117      }
118    } else {
119      *latest_version = version;
120      *latest_dir = path;
121      found = true;
122    }
123  }
124  return found;
125}
126
127// Read a manifest file in.
128base::DictionaryValue* ReadJSONManifest(const base::FilePath& manifest_path) {
129  JSONFileValueSerializer serializer(manifest_path);
130  std::string error;
131  scoped_ptr<base::Value> root(serializer.Deserialize(NULL, &error));
132  if (!root.get())
133    return NULL;
134  if (!root->IsType(base::Value::TYPE_DICTIONARY))
135    return NULL;
136  return static_cast<base::DictionaryValue*>(root.release());
137}
138
139// Read the PNaCl specific manifest.
140base::DictionaryValue* ReadPnaclManifest(const base::FilePath& unpack_path) {
141  base::FilePath manifest_path =
142      GetPlatformDir(unpack_path).AppendASCII("pnacl_public_pnacl_json");
143  if (!base::PathExists(manifest_path))
144    return NULL;
145  return ReadJSONManifest(manifest_path);
146}
147
148// Read the component's manifest.json.
149base::DictionaryValue* ReadComponentManifest(
150    const base::FilePath& unpack_path) {
151  base::FilePath manifest_path =
152      unpack_path.Append(FILE_PATH_LITERAL("manifest.json"));
153  if (!base::PathExists(manifest_path))
154    return NULL;
155  return ReadJSONManifest(manifest_path);
156}
157
158// Check that the component's manifest is for PNaCl, and check the
159// PNaCl manifest indicates this is the correct arch-specific package.
160bool CheckPnaclComponentManifest(const base::DictionaryValue& manifest,
161                                 const base::DictionaryValue& pnacl_manifest,
162                                 Version* version_out) {
163  // Make sure we have the right |manifest| file.
164  std::string name;
165  if (!manifest.GetStringASCII("name", &name)) {
166    LOG(WARNING) << "'name' field is missing from manifest!";
167    return false;
168  }
169  // For the webstore, we've given different names to each of the
170  // architecture specific packages (and test/QA vs not test/QA)
171  // so only part of it is the same.
172  if (name.find(kPnaclManifestName) == std::string::npos) {
173    LOG(WARNING) << "'name' field in manifest is invalid (" << name
174                 << ") -- missing (" << kPnaclManifestName << ")";
175    return false;
176  }
177
178  std::string proposed_version;
179  if (!manifest.GetStringASCII("version", &proposed_version)) {
180    LOG(WARNING) << "'version' field is missing from manifest!";
181    return false;
182  }
183  Version version(proposed_version.c_str());
184  if (!version.IsValid()) {
185    LOG(WARNING) << "'version' field in manifest is invalid "
186                 << version.GetString();
187    return false;
188  }
189
190  // Now check the |pnacl_manifest|.
191  std::string arch;
192  if (!pnacl_manifest.GetStringASCII("pnacl-arch", &arch)) {
193    LOG(WARNING) << "'pnacl-arch' field is missing from pnacl-manifest!";
194    return false;
195  }
196  if (arch.compare(OmahaQueryParams::GetNaclArch()) != 0) {
197    LOG(WARNING) << "'pnacl-arch' field in manifest is invalid (" << arch
198                 << " vs " << OmahaQueryParams::GetNaclArch() << ")";
199    return false;
200  }
201
202  *version_out = version;
203  return true;
204}
205
206}  // namespace
207
208PnaclComponentInstaller::PnaclComponentInstaller()
209    : per_user_(false), updates_disabled_(false), 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 =
236        current_profile_path_.Append(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_ =
251      pm->user_data_dir().Append(pm->GetInitialProfileDir());
252}
253
254bool PnaclComponentInstaller::Install(const base::DictionaryValue& manifest,
255                                      const base::FilePath& unpack_path) {
256  scoped_ptr<base::DictionaryValue> pnacl_manifest(
257      ReadPnaclManifest(unpack_path));
258  if (pnacl_manifest == NULL) {
259    LOG(WARNING) << "Failed to read pnacl manifest.";
260    return false;
261  }
262
263  Version version;
264  if (!CheckPnaclComponentManifest(manifest, *pnacl_manifest, &version)) {
265    LOG(WARNING) << "CheckPnaclComponentManifest failed, not installing.";
266    return false;
267  }
268
269  // Don't install if the current version is actually newer.
270  if (current_version().CompareTo(version) > 0) {
271    return false;
272  }
273
274  // Passed the basic tests. Time to install it.
275  base::FilePath path =
276      GetPnaclBaseDirectory().AppendASCII(version.GetString());
277  if (base::PathExists(path)) {
278    if (!base::DeleteFile(path, true))
279      return false;
280  }
281  if (!base::Move(unpack_path, path)) {
282    LOG(WARNING) << "Move failed, not installing.";
283    return false;
284  }
285
286  // Installation is done. Now tell the rest of chrome.
287  // - The path service.
288  // - Callbacks that requested an update.
289  set_current_version(version);
290  CheckVersionCompatiblity(version);
291  OverrideDirPnaclComponent(path);
292  return true;
293}
294
295// Given |file|, which can be a path like "_platform_specific/arm/pnacl_foo",
296// returns the assumed install path. The path separator in |file| is '/'
297// for all platforms. Caller is responsible for checking that the
298// |installed_file| actually exists.
299bool PnaclComponentInstaller::GetInstalledFile(const std::string& file,
300                                               base::FilePath* installed_file) {
301  if (current_version().Equals(Version(kNullVersion)))
302    return false;
303
304  *installed_file = GetPnaclBaseDirectory()
305                        .AppendASCII(current_version().GetString())
306                        .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(ReadComponentManifest(path));
357    scoped_ptr<base::DictionaryValue> pnacl_manifest(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(BrowserThread::UI,
383                          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();
392       ++iter) {
393    base::DeleteFile(*iter, true);
394  }
395}
396
397// Remove old per-profile copies of PNaCl (was for ChromeOS).
398// TODO(jvoung): Delete this code once most ChromeOS users have reaped
399// their old per-profile copies of PNaCl.
400void ReapOldChromeOSPnaclFiles(PnaclComponentInstaller* pci) {
401  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
402  base::FilePath path = pci->GetPnaclBaseDirectory();
403  if (!base::PathExists(path))
404    return;
405
406  // Do a basic sanity check first.
407  if (pci->per_user() &&
408      path.BaseName().value().compare(FILE_PATH_LITERAL("pnacl")) == 0)
409    base::DeleteFile(path, true);
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,
424                          FROM_HERE,
425                          base::Bind(&ReapOldChromeOSPnaclFiles, pci));
426}
427
428}  // namespace
429
430void PnaclComponentInstaller::RegisterPnaclComponent(
431    ComponentUpdateService* cus,
432    const CommandLine& command_line) {
433  // Register PNaCl by default (can be disabled).
434  updates_disabled_ = command_line.HasSwitch(switches::kDisablePnaclInstall);
435  cus_ = cus;
436  // If per_user, create a profile observer to watch for logins.
437  // Only do so after cus_ is set to something non-null.
438  if (per_user_ && !profile_observer_) {
439    profile_observer_.reset(new PnaclProfileObserver(this));
440  }
441  if (per_user_) {
442    // Figure out profile information, before proceeding to look for files.
443    BrowserThread::PostTask(
444        BrowserThread::UI, FROM_HERE, base::Bind(&GetProfileInformation, this));
445  } else {
446    BrowserThread::PostTask(BrowserThread::FILE,
447                            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, base::Bind(&GetProfileInformation, this));
457}
458
459}  // namespace component_updater
460
461namespace pnacl {
462
463bool NeedsOnDemandUpdate() {
464  return base::subtle::NoBarrier_Load(
465             &component_updater::needs_on_demand_update) != 0;
466}
467
468}  // namespace pnacl
469