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/common/chrome_paths.h" 31 #include "components/nacl/common/nacl_switches.h" 32 #include "content/public/browser/browser_thread.h" 33 34 using chrome::OmahaQueryParams; 35 using content::BrowserThread; 36 37 namespace component_updater { 38 39 namespace { 40 41 // Name of the Pnacl component specified in the manifest. 42 const char kPnaclManifestName[] = "PNaCl Translator"; 43 44 // Sanitize characters from Pnacl Arch value so that they can be used 45 // in path names. This should only be characters in the set: [a-z0-9_]. 46 // Keep in sync with chrome/browser/nacl_host/nacl_file_host. 47 std::string SanitizeForPath(const std::string& input) { 48 std::string result; 49 base::ReplaceChars(input, "-", "_", &result); 50 return result; 51 } 52 53 // Set the component's hash to the multi-CRX PNaCl package. 54 void SetPnaclHash(CrxComponent* component) { 55 static const uint8 sha256_hash[32] = { 56 // This corresponds to AppID: hnimpnehoodheedghdeeijklkeaacbdc 57 0x7d, 0x8c, 0xfd, 0x47, 0xee, 0x37, 0x44, 0x36, 58 0x73, 0x44, 0x89, 0xab, 0xa4, 0x00, 0x21, 0x32, 59 0x4a, 0x06, 0x06, 0xf1, 0x51, 0x3c, 0x51, 0xba, 60 0x31, 0x2f, 0xbc, 0xb3, 0x99, 0x07, 0xdc, 0x9c 61 }; 62 63 component->pk_hash.assign(sha256_hash, &sha256_hash[arraysize(sha256_hash)]); 64 } 65 66 // If we don't have Pnacl installed, this is the version we claim. 67 const char kNullVersion[] = "0.0.0.0"; 68 const char kMinPnaclVersion[] = "0.1.0.13367"; 69 70 // Initially say that we do not need OnDemand updates. This should be 71 // updated by CheckVersionCompatiblity(), before doing any URLRequests 72 // that depend on PNaCl. 73 volatile base::subtle::Atomic32 needs_on_demand_update = 0; 74 75 void CheckVersionCompatiblity(const base::Version& current_version) { 76 // Using NoBarrier, since needs_on_demand_update is standalone and does 77 // not have other associated data. 78 base::subtle::NoBarrier_Store(&needs_on_demand_update, 79 current_version.IsOlderThan(kMinPnaclVersion)); 80 } 81 82 // PNaCl is packaged as a multi-CRX. This returns the platform-specific 83 // subdirectory that is part of that multi-CRX. 84 base::FilePath GetPlatformDir(const base::FilePath& base_path) { 85 std::string arch = SanitizeForPath(OmahaQueryParams::GetNaclArch()); 86 return base_path.AppendASCII("_platform_specific").AppendASCII(arch); 87 } 88 89 // Tell the rest of the world where to find the platform-specific PNaCl files. 90 void OverrideDirPnaclComponent(const base::FilePath& base_path) { 91 PathService::Override(chrome::DIR_PNACL_COMPONENT, GetPlatformDir(base_path)); 92 } 93 94 bool GetLatestPnaclDirectory(PnaclComponentInstaller* pci, 95 base::FilePath* latest_dir, 96 Version* latest_version, 97 std::vector<base::FilePath>* older_dirs) { 98 // Enumerate all versions starting from the base directory. 99 base::FilePath base_dir = pci->GetPnaclBaseDirectory(); 100 bool found = false; 101 base::FileEnumerator file_enumerator( 102 base_dir, false, base::FileEnumerator::DIRECTORIES); 103 for (base::FilePath path = file_enumerator.Next(); !path.value().empty(); 104 path = file_enumerator.Next()) { 105 Version version(path.BaseName().MaybeAsASCII()); 106 if (!version.IsValid()) 107 continue; 108 if (found) { 109 if (version.CompareTo(*latest_version) > 0) { 110 older_dirs->push_back(*latest_dir); 111 *latest_dir = path; 112 *latest_version = version; 113 } else { 114 older_dirs->push_back(path); 115 } 116 } else { 117 *latest_version = version; 118 *latest_dir = path; 119 found = true; 120 } 121 } 122 return found; 123 } 124 125 // Read a manifest file in. 126 base::DictionaryValue* ReadJSONManifest(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. 138 base::DictionaryValue* ReadPnaclManifest(const base::FilePath& unpack_path) { 139 base::FilePath manifest_path = 140 GetPlatformDir(unpack_path).AppendASCII("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. 147 base::DictionaryValue* ReadComponentManifest( 148 const base::FilePath& unpack_path) { 149 base::FilePath manifest_path = 150 unpack_path.Append(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. 158 bool 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 (" << name 172 << ") -- missing (" << kPnaclManifestName << ")"; 173 return false; 174 } 175 176 std::string proposed_version; 177 if (!manifest.GetStringASCII("version", &proposed_version)) { 178 LOG(WARNING) << "'version' field is missing from manifest!"; 179 return false; 180 } 181 Version version(proposed_version.c_str()); 182 if (!version.IsValid()) { 183 LOG(WARNING) << "'version' field in manifest is invalid " 184 << version.GetString(); 185 return false; 186 } 187 188 // Now check the |pnacl_manifest|. 189 std::string arch; 190 if (!pnacl_manifest.GetStringASCII("pnacl-arch", &arch)) { 191 LOG(WARNING) << "'pnacl-arch' field is missing from pnacl-manifest!"; 192 return false; 193 } 194 if (arch.compare(OmahaQueryParams::GetNaclArch()) != 0) { 195 LOG(WARNING) << "'pnacl-arch' field in manifest is invalid (" << arch 196 << " vs " << OmahaQueryParams::GetNaclArch() << ")"; 197 return false; 198 } 199 200 *version_out = version; 201 return true; 202 } 203 204 } // namespace 205 206 PnaclComponentInstaller::PnaclComponentInstaller() 207 : updates_disabled_(false), cus_(NULL) { 208 } 209 210 PnaclComponentInstaller::~PnaclComponentInstaller() { 211 } 212 213 void PnaclComponentInstaller::OnUpdateError(int error) { 214 NOTREACHED() << "Pnacl update error: " << error; 215 } 216 217 // Pnacl components have the version encoded in the path itself: 218 // <profile>\AppData\Local\Google\Chrome\User Data\pnacl\0.1.2.3\. 219 // and the base directory will be: 220 // <profile>\AppData\Local\Google\Chrome\User Data\pnacl\. 221 base::FilePath PnaclComponentInstaller::GetPnaclBaseDirectory() { 222 base::FilePath result; 223 CHECK(PathService::Get(chrome::DIR_PNACL_BASE, &result)); 224 return result; 225 } 226 227 bool PnaclComponentInstaller::Install(const base::DictionaryValue& manifest, 228 const base::FilePath& unpack_path) { 229 scoped_ptr<base::DictionaryValue> pnacl_manifest( 230 ReadPnaclManifest(unpack_path)); 231 if (pnacl_manifest == NULL) { 232 LOG(WARNING) << "Failed to read pnacl manifest."; 233 return false; 234 } 235 236 Version version; 237 if (!CheckPnaclComponentManifest(manifest, *pnacl_manifest, &version)) { 238 LOG(WARNING) << "CheckPnaclComponentManifest failed, not installing."; 239 return false; 240 } 241 242 // Don't install if the current version is actually newer. 243 if (current_version().CompareTo(version) > 0) { 244 return false; 245 } 246 247 // Passed the basic tests. Time to install it. 248 base::FilePath path = 249 GetPnaclBaseDirectory().AppendASCII(version.GetString()); 250 if (base::PathExists(path)) { 251 if (!base::DeleteFile(path, true)) 252 return false; 253 } 254 if (!base::Move(unpack_path, path)) { 255 LOG(WARNING) << "Move failed, not installing."; 256 return false; 257 } 258 259 // Installation is done. Now tell the rest of chrome. 260 // - The path service. 261 // - Callbacks that requested an update. 262 set_current_version(version); 263 CheckVersionCompatiblity(version); 264 OverrideDirPnaclComponent(path); 265 return true; 266 } 267 268 // Given |file|, which can be a path like "_platform_specific/arm/pnacl_foo", 269 // returns the assumed install path. The path separator in |file| is '/' 270 // for all platforms. Caller is responsible for checking that the 271 // |installed_file| actually exists. 272 bool PnaclComponentInstaller::GetInstalledFile(const std::string& file, 273 base::FilePath* installed_file) { 274 if (current_version().Equals(Version(kNullVersion))) 275 return false; 276 277 *installed_file = GetPnaclBaseDirectory() 278 .AppendASCII(current_version().GetString()) 279 .AppendASCII(file); 280 return true; 281 } 282 283 CrxComponent PnaclComponentInstaller::GetCrxComponent() { 284 CrxComponent pnacl_component; 285 pnacl_component.version = current_version(); 286 pnacl_component.name = "pnacl"; 287 pnacl_component.installer = this; 288 pnacl_component.fingerprint = current_fingerprint(); 289 SetPnaclHash(&pnacl_component); 290 291 return pnacl_component; 292 } 293 294 namespace { 295 296 void FinishPnaclUpdateRegistration(const Version& current_version, 297 const std::string& current_fingerprint, 298 PnaclComponentInstaller* pci) { 299 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 300 pci->set_current_version(current_version); 301 CheckVersionCompatiblity(current_version); 302 pci->set_current_fingerprint(current_fingerprint); 303 CrxComponent pnacl_component = pci->GetCrxComponent(); 304 305 ComponentUpdateService::Status status = 306 pci->cus()->RegisterComponent(pnacl_component); 307 if (status != ComponentUpdateService::kOk && 308 status != ComponentUpdateService::kReplaced) { 309 NOTREACHED() << "Pnacl component registration failed."; 310 } 311 } 312 313 // Check if there is an existing version on disk first to know when 314 // a hosted version is actually newer. 315 void StartPnaclUpdateRegistration(PnaclComponentInstaller* pci) { 316 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 317 base::FilePath path = pci->GetPnaclBaseDirectory(); 318 if (!base::PathExists(path)) { 319 if (!base::CreateDirectory(path)) { 320 NOTREACHED() << "Could not create base Pnacl directory."; 321 return; 322 } 323 } 324 325 Version current_version(kNullVersion); 326 std::string current_fingerprint; 327 std::vector<base::FilePath> older_dirs; 328 if (GetLatestPnaclDirectory(pci, &path, ¤t_version, &older_dirs)) { 329 scoped_ptr<base::DictionaryValue> manifest(ReadComponentManifest(path)); 330 scoped_ptr<base::DictionaryValue> pnacl_manifest(ReadPnaclManifest(path)); 331 Version manifest_version; 332 // Check that the component manifest and PNaCl manifest files 333 // are legit, and that the indicated version matches the one 334 // encoded within the path name. 335 if (manifest == NULL || pnacl_manifest == NULL || 336 !CheckPnaclComponentManifest(*manifest, 337 *pnacl_manifest, 338 &manifest_version) || 339 !current_version.Equals(manifest_version)) { 340 current_version = Version(kNullVersion); 341 } else { 342 OverrideDirPnaclComponent(path); 343 base::ReadFileToString(path.AppendASCII("manifest.fingerprint"), 344 ¤t_fingerprint); 345 } 346 } 347 348 // If updates are disabled, only discover the current version 349 // and OverrideDirPnaclComponent. That way, developers can use 350 // a pinned version. Do not actually finish registration with 351 // the component update service. 352 if (pci->updates_disabled()) 353 return; 354 355 BrowserThread::PostTask(BrowserThread::UI, 356 FROM_HERE, 357 base::Bind(&FinishPnaclUpdateRegistration, 358 current_version, 359 current_fingerprint, 360 pci)); 361 362 // Remove older versions of PNaCl. 363 for (std::vector<base::FilePath>::iterator iter = older_dirs.begin(); 364 iter != older_dirs.end(); 365 ++iter) { 366 base::DeleteFile(*iter, true); 367 } 368 } 369 370 } // namespace 371 372 void PnaclComponentInstaller::RegisterPnaclComponent( 373 ComponentUpdateService* cus, 374 const CommandLine& command_line) { 375 // Register PNaCl by default (can be disabled). 376 updates_disabled_ = command_line.HasSwitch(switches::kDisablePnaclInstall); 377 cus_ = cus; 378 BrowserThread::PostTask(BrowserThread::FILE, 379 FROM_HERE, 380 base::Bind(&StartPnaclUpdateRegistration, this)); 381 } 382 383 } // namespace component_updater 384 385 namespace pnacl { 386 387 bool NeedsOnDemandUpdate() { 388 return base::subtle::NoBarrier_Load( 389 &component_updater::needs_on_demand_update) != 0; 390 } 391 392 } // namespace pnacl 393