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