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