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, ¤t_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 ¤t_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