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/policy/policy_loader_win.h" 6 7 #include <rpc.h> // For struct GUID 8 #include <shlwapi.h> // For PathIsUNC() 9 #include <userenv.h> // For GPO functions 10 #include <windows.h> 11 12 #include <string> 13 #include <vector> 14 15 // shlwapi.dll is required for PathIsUNC(). 16 #pragma comment(lib, "shlwapi.lib") 17 // userenv.dll is required for various GPO functions. 18 #pragma comment(lib, "userenv.lib") 19 20 #include "base/basictypes.h" 21 #include "base/file_util.h" 22 #include "base/json/json_reader.h" 23 #include "base/lazy_instance.h" 24 #include "base/logging.h" 25 #include "base/scoped_native_library.h" 26 #include "base/stl_util.h" 27 #include "base/strings/string16.h" 28 #include "base/strings/string_util.h" 29 #include "chrome/browser/policy/policy_bundle.h" 30 #include "chrome/browser/policy/policy_load_status.h" 31 #include "chrome/browser/policy/policy_map.h" 32 #include "chrome/browser/policy/preg_parser_win.h" 33 #include "chrome/browser/policy/registry_dict_win.h" 34 #include "chrome/common/json_schema/json_schema_constants.h" 35 #include "policy/policy_constants.h" 36 37 namespace schema = json_schema_constants; 38 39 namespace policy { 40 41 namespace { 42 43 const char kKeyMandatory[] = "policy"; 44 const char kKeyRecommended[] = "recommended"; 45 const char kKeySchema[] = "schema"; 46 const char kKeyThirdParty[] = "3rdparty"; 47 48 // The GUID of the registry settings group policy extension. 49 GUID kRegistrySettingsCSEGUID = REGISTRY_EXTENSION_GUID; 50 51 // A helper class encapsulating run-time-linked function calls to Wow64 APIs. 52 class Wow64Functions { 53 public: 54 Wow64Functions() 55 : kernel32_lib_(base::FilePath(L"kernel32")), 56 is_wow_64_process_(NULL), 57 wow_64_disable_wow_64_fs_redirection_(NULL), 58 wow_64_revert_wow_64_fs_redirection_(NULL) { 59 if (kernel32_lib_.is_valid()) { 60 is_wow_64_process_ = reinterpret_cast<IsWow64Process>( 61 kernel32_lib_.GetFunctionPointer("IsWow64Process")); 62 wow_64_disable_wow_64_fs_redirection_ = 63 reinterpret_cast<Wow64DisableWow64FSRedirection>( 64 kernel32_lib_.GetFunctionPointer( 65 "Wow64DisableWow64FsRedirection")); 66 wow_64_revert_wow_64_fs_redirection_ = 67 reinterpret_cast<Wow64RevertWow64FSRedirection>( 68 kernel32_lib_.GetFunctionPointer( 69 "Wow64RevertWow64FsRedirection")); 70 } 71 } 72 73 bool is_valid() { 74 return is_wow_64_process_ && 75 wow_64_disable_wow_64_fs_redirection_ && 76 wow_64_revert_wow_64_fs_redirection_; 77 } 78 79 bool IsWow64() { 80 BOOL result = 0; 81 if (!is_wow_64_process_(GetCurrentProcess(), &result)) 82 PLOG(WARNING) << "IsWow64ProcFailed"; 83 return !!result; 84 } 85 86 bool DisableFsRedirection(PVOID* previous_state) { 87 return !!wow_64_disable_wow_64_fs_redirection_(previous_state); 88 } 89 90 bool RevertFsRedirection(PVOID previous_state) { 91 return !!wow_64_revert_wow_64_fs_redirection_(previous_state); 92 } 93 94 private: 95 typedef BOOL (WINAPI* IsWow64Process)(HANDLE, PBOOL); 96 typedef BOOL (WINAPI* Wow64DisableWow64FSRedirection)(PVOID*); 97 typedef BOOL (WINAPI* Wow64RevertWow64FSRedirection)(PVOID); 98 99 base::ScopedNativeLibrary kernel32_lib_; 100 101 IsWow64Process is_wow_64_process_; 102 Wow64DisableWow64FSRedirection wow_64_disable_wow_64_fs_redirection_; 103 Wow64RevertWow64FSRedirection wow_64_revert_wow_64_fs_redirection_; 104 105 DISALLOW_COPY_AND_ASSIGN(Wow64Functions); 106 }; 107 108 // Global Wow64Function instance used by ScopedDisableWow64Redirection below. 109 static base::LazyInstance<Wow64Functions> g_wow_64_functions = 110 LAZY_INSTANCE_INITIALIZER; 111 112 // Scoper that switches off Wow64 File System Redirection during its lifetime. 113 class ScopedDisableWow64Redirection { 114 public: 115 ScopedDisableWow64Redirection() 116 : active_(false), 117 previous_state_(NULL) { 118 Wow64Functions* wow64 = g_wow_64_functions.Pointer(); 119 if (wow64->is_valid() && wow64->IsWow64()) { 120 if (wow64->DisableFsRedirection(&previous_state_)) 121 active_ = true; 122 else 123 PLOG(WARNING) << "Wow64DisableWow64FSRedirection"; 124 } 125 } 126 127 ~ScopedDisableWow64Redirection() { 128 if (active_) 129 CHECK(g_wow_64_functions.Get().RevertFsRedirection(previous_state_)); 130 } 131 132 bool is_active() { return active_; } 133 134 private: 135 bool active_; 136 PVOID previous_state_; 137 138 DISALLOW_COPY_AND_ASSIGN(ScopedDisableWow64Redirection); 139 }; 140 141 // AppliedGPOListProvider implementation that calls actual Windows APIs. 142 class WinGPOListProvider : public AppliedGPOListProvider { 143 public: 144 virtual ~WinGPOListProvider() {} 145 146 // AppliedGPOListProvider: 147 virtual DWORD GetAppliedGPOList(DWORD flags, 148 LPCTSTR machine_name, 149 PSID sid_user, 150 GUID* extension_guid, 151 PGROUP_POLICY_OBJECT* gpo_list) OVERRIDE { 152 return ::GetAppliedGPOList(flags, machine_name, sid_user, extension_guid, 153 gpo_list); 154 } 155 156 virtual BOOL FreeGPOList(PGROUP_POLICY_OBJECT gpo_list) OVERRIDE { 157 return ::FreeGPOList(gpo_list); 158 } 159 }; 160 161 // The default windows GPO list provider used for PolicyLoaderWin. 162 static base::LazyInstance<WinGPOListProvider> g_win_gpo_list_provider = 163 LAZY_INSTANCE_INITIALIZER; 164 165 std::string GetSchemaTypeForValueType(base::Value::Type value_type) { 166 switch (value_type) { 167 case base::Value::TYPE_DICTIONARY: 168 return json_schema_constants::kObject; 169 case base::Value::TYPE_INTEGER: 170 return json_schema_constants::kInteger; 171 case base::Value::TYPE_LIST: 172 return json_schema_constants::kArray; 173 case base::Value::TYPE_BOOLEAN: 174 return json_schema_constants::kBoolean; 175 case base::Value::TYPE_STRING: 176 return json_schema_constants::kString; 177 default: 178 break; 179 } 180 181 NOTREACHED() << "Unsupported policy value type " << value_type; 182 return json_schema_constants::kNull; 183 } 184 185 // Parses |gpo_dict| according to |schema| and writes the resulting policy 186 // settings to |policy| for the given |scope| and |level|. 187 void ParsePolicy(const RegistryDict* gpo_dict, 188 PolicyLevel level, 189 PolicyScope scope, 190 const base::DictionaryValue* schema, 191 PolicyMap* policy) { 192 if (!gpo_dict) 193 return; 194 195 scoped_ptr<base::Value> policy_value(gpo_dict->ConvertToJSON(schema)); 196 const base::DictionaryValue* policy_dict = NULL; 197 if (!policy_value->GetAsDictionary(&policy_dict) || !policy_dict) { 198 LOG(WARNING) << "Root policy object is not a dictionary!"; 199 return; 200 } 201 202 policy->LoadFrom(policy_dict, level, scope); 203 } 204 205 } // namespace 206 207 const base::FilePath::CharType PolicyLoaderWin::kPRegFileName[] = 208 FILE_PATH_LITERAL("Registry.pol"); 209 210 PolicyLoaderWin::PolicyLoaderWin(const PolicyDefinitionList* policy_list, 211 const string16& chrome_policy_key, 212 AppliedGPOListProvider* gpo_provider) 213 : is_initialized_(false), 214 policy_list_(policy_list), 215 chrome_policy_key_(chrome_policy_key), 216 gpo_provider_(gpo_provider), 217 user_policy_changed_event_(false, false), 218 machine_policy_changed_event_(false, false), 219 user_policy_watcher_failed_(false), 220 machine_policy_watcher_failed_(false) { 221 if (!RegisterGPNotification(user_policy_changed_event_.handle(), false)) { 222 DPLOG(WARNING) << "Failed to register user group policy notification"; 223 user_policy_watcher_failed_ = true; 224 } 225 if (!RegisterGPNotification(machine_policy_changed_event_.handle(), true)) { 226 DPLOG(WARNING) << "Failed to register machine group policy notification."; 227 machine_policy_watcher_failed_ = true; 228 } 229 } 230 231 PolicyLoaderWin::~PolicyLoaderWin() { 232 user_policy_watcher_.StopWatching(); 233 machine_policy_watcher_.StopWatching(); 234 } 235 236 // static 237 scoped_ptr<PolicyLoaderWin> PolicyLoaderWin::Create( 238 const PolicyDefinitionList* policy_list) { 239 return make_scoped_ptr( 240 new PolicyLoaderWin(policy_list, kRegistryChromePolicyKey, 241 g_win_gpo_list_provider.Pointer())); 242 } 243 244 void PolicyLoaderWin::InitOnFile() { 245 is_initialized_ = true; 246 SetupWatches(); 247 } 248 249 scoped_ptr<PolicyBundle> PolicyLoaderWin::Load() { 250 // Reset the watches BEFORE reading the individual policies to avoid 251 // missing a change notification. 252 if (is_initialized_) 253 SetupWatches(); 254 255 if (chrome_policy_schema_.empty()) 256 BuildChromePolicySchema(); 257 258 // Policy scope and corresponding hive. 259 static const struct { 260 PolicyScope scope; 261 HKEY hive; 262 } kScopes[] = { 263 { POLICY_SCOPE_MACHINE, HKEY_LOCAL_MACHINE }, 264 { POLICY_SCOPE_USER, HKEY_CURRENT_USER }, 265 }; 266 267 // Load policy data for the different scopes/levels and merge them. 268 scoped_ptr<PolicyBundle> bundle(new PolicyBundle()); 269 PolicyMap* chrome_policy = 270 &bundle->Get(PolicyNamespace(POLICY_DOMAIN_CHROME, std::string())); 271 for (size_t i = 0; i < arraysize(kScopes); ++i) { 272 PolicyScope scope = kScopes[i].scope; 273 PolicyLoadStatusSample status; 274 RegistryDict gpo_dict; 275 276 // Note: GPO rules mandate a call to EnterCriticalPolicySection() here, and 277 // a matching LeaveCriticalPolicySection() call below after the 278 // ReadPolicyFromGPO() block. Unfortunately, the policy mutex may be 279 // unavailable for extended periods of time, and there are reports of this 280 // happening in the wild: http://crbug.com/265862. 281 // 282 // Blocking for minutes is neither acceptable for Chrome startup, nor on 283 // the FILE thread on which this code runs in steady state. Given that 284 // there have never been any reports of issues due to partially-applied / 285 // corrupt group policy, this code intentionally omits the 286 // EnterCriticalPolicySection() call. 287 // 288 // If there's ever reason to revisit this decision, one option could be to 289 // make the EnterCriticalPolicySection() call on a dedicated thread and 290 // timeout on it more aggressively. For now, there's no justification for 291 // the additional effort this would introduce. 292 293 if (!ReadPolicyFromGPO(scope, &gpo_dict, &status)) { 294 VLOG(1) << "Failed to read GPO files for " << scope 295 << " falling back to registry."; 296 gpo_dict.ReadRegistry(kScopes[i].hive, chrome_policy_key_); 297 } 298 299 // Remove special-cased entries from the GPO dictionary. 300 scoped_ptr<RegistryDict> recommended_dict( 301 gpo_dict.RemoveKey(kKeyRecommended)); 302 scoped_ptr<RegistryDict> third_party_dict( 303 gpo_dict.RemoveKey(kKeyThirdParty)); 304 305 // Load Chrome policy. 306 LoadChromePolicy(&gpo_dict, POLICY_LEVEL_MANDATORY, scope, chrome_policy); 307 LoadChromePolicy(recommended_dict.get(), POLICY_LEVEL_RECOMMENDED, scope, 308 chrome_policy); 309 310 // Load 3rd-party policy. 311 if (third_party_dict) 312 Load3rdPartyPolicy(third_party_dict.get(), scope, bundle.get()); 313 } 314 315 return bundle.Pass(); 316 } 317 318 void PolicyLoaderWin::BuildChromePolicySchema() { 319 scoped_ptr<base::DictionaryValue> properties(new base::DictionaryValue()); 320 for (const PolicyDefinitionList::Entry* e = policy_list_->begin; 321 e != policy_list_->end; ++e) { 322 const std::string schema_type = GetSchemaTypeForValueType(e->value_type); 323 scoped_ptr<base::DictionaryValue> entry_schema(new base::DictionaryValue()); 324 entry_schema->SetStringWithoutPathExpansion(json_schema_constants::kType, 325 schema_type); 326 327 if (e->value_type == base::Value::TYPE_LIST) { 328 scoped_ptr<base::DictionaryValue> items_schema( 329 new base::DictionaryValue()); 330 items_schema->SetStringWithoutPathExpansion( 331 json_schema_constants::kType, json_schema_constants::kString); 332 entry_schema->SetWithoutPathExpansion(json_schema_constants::kItems, 333 items_schema.release()); 334 } 335 properties->SetWithoutPathExpansion(e->name, entry_schema.release()); 336 } 337 chrome_policy_schema_.SetStringWithoutPathExpansion( 338 json_schema_constants::kType, json_schema_constants::kObject); 339 chrome_policy_schema_.SetWithoutPathExpansion( 340 json_schema_constants::kProperties, properties.release()); 341 } 342 343 bool PolicyLoaderWin::ReadPRegFile(const base::FilePath& preg_file, 344 RegistryDict* policy, 345 PolicyLoadStatusSample* status) { 346 // The following deals with the minor annoyance that Wow64 FS redirection 347 // might need to be turned off: This is the case if running as a 32-bit 348 // process on a 64-bit system, in which case Wow64 FS redirection redirects 349 // access to the %WINDIR%/System32/GroupPolicy directory to 350 // %WINDIR%/SysWOW64/GroupPolicy, but the file is actually in the 351 // system-native directory. 352 if (base::PathExists(preg_file)) { 353 return preg_parser::ReadFile(preg_file, chrome_policy_key_, policy, status); 354 } else { 355 // Try with redirection switched off. 356 ScopedDisableWow64Redirection redirection_disable; 357 if (redirection_disable.is_active() && base::PathExists(preg_file)) { 358 status->Add(POLICY_LOAD_STATUS_WOW64_REDIRECTION_DISABLED); 359 return preg_parser::ReadFile(preg_file, chrome_policy_key_, policy, 360 status); 361 } 362 } 363 364 // Report the error. 365 LOG(ERROR) << "PReg file doesn't exist: " << preg_file.value(); 366 status->Add(POLICY_LOAD_STATUS_MISSING); 367 return false; 368 } 369 370 bool PolicyLoaderWin::LoadGPOPolicy(PolicyScope scope, 371 PGROUP_POLICY_OBJECT policy_object_list, 372 RegistryDict* policy, 373 PolicyLoadStatusSample* status) { 374 RegistryDict parsed_policy; 375 RegistryDict forced_policy; 376 for (GROUP_POLICY_OBJECT* policy_object = policy_object_list; 377 policy_object; policy_object = policy_object->pNext) { 378 if (policy_object->dwOptions & GPO_FLAG_DISABLE) 379 continue; 380 381 if (PathIsUNC(policy_object->lpFileSysPath)) { 382 // UNC path: Assume this is an AD-managed machine, which updates the 383 // registry via GPO's standard registry CSE periodically. Fall back to 384 // reading from the registry in this case. 385 status->Add(POLICY_LOAD_STATUS_INACCCESSIBLE); 386 return false; 387 } 388 389 base::FilePath preg_file_path( 390 base::FilePath(policy_object->lpFileSysPath).Append(kPRegFileName)); 391 if (policy_object->dwOptions & GPO_FLAG_FORCE) { 392 RegistryDict new_forced_policy; 393 if (!ReadPRegFile(preg_file_path, &new_forced_policy, status)) 394 return false; 395 396 // Merge with existing forced policy, giving precedence to the existing 397 // forced policy. 398 new_forced_policy.Merge(forced_policy); 399 forced_policy.Swap(&new_forced_policy); 400 } else { 401 if (!ReadPRegFile(preg_file_path, &parsed_policy, status)) 402 return false; 403 } 404 } 405 406 // Merge, give precedence to forced policy. 407 parsed_policy.Merge(forced_policy); 408 policy->Swap(&parsed_policy); 409 410 return true; 411 } 412 413 bool PolicyLoaderWin::ReadPolicyFromGPO(PolicyScope scope, 414 RegistryDict* policy, 415 PolicyLoadStatusSample* status) { 416 PGROUP_POLICY_OBJECT policy_object_list = NULL; 417 DWORD flags = scope == POLICY_SCOPE_MACHINE ? GPO_LIST_FLAG_MACHINE : 0; 418 if (gpo_provider_->GetAppliedGPOList( 419 flags, NULL, NULL, &kRegistrySettingsCSEGUID, 420 &policy_object_list) != ERROR_SUCCESS) { 421 PLOG(ERROR) << "GetAppliedGPOList scope " << scope; 422 status->Add(POLICY_LOAD_STATUS_QUERY_FAILED); 423 return false; 424 } 425 426 bool result = true; 427 if (policy_object_list) { 428 result = LoadGPOPolicy(scope, policy_object_list, policy, status); 429 if (!gpo_provider_->FreeGPOList(policy_object_list)) 430 LOG(WARNING) << "FreeGPOList"; 431 } else { 432 status->Add(POLICY_LOAD_STATUS_NO_POLICY); 433 } 434 435 return result; 436 } 437 438 void PolicyLoaderWin::LoadChromePolicy(const RegistryDict* gpo_dict, 439 PolicyLevel level, 440 PolicyScope scope, 441 PolicyMap* chrome_policy_map) { 442 PolicyMap policy; 443 ParsePolicy(gpo_dict, level, scope, &chrome_policy_schema_, &policy); 444 chrome_policy_map->MergeFrom(policy); 445 } 446 447 void PolicyLoaderWin::Load3rdPartyPolicy(const RegistryDict* gpo_dict, 448 PolicyScope scope, 449 PolicyBundle* bundle) { 450 // Map of known 3rd party policy domain name to their enum values. 451 static const struct { 452 const char* name; 453 PolicyDomain domain; 454 } k3rdPartyDomains[] = { 455 { "extensions", POLICY_DOMAIN_EXTENSIONS }, 456 }; 457 458 // Policy level and corresponding path. 459 static const struct { 460 PolicyLevel level; 461 const char* path; 462 } kLevels[] = { 463 { POLICY_LEVEL_MANDATORY, kKeyMandatory }, 464 { POLICY_LEVEL_RECOMMENDED, kKeyRecommended }, 465 }; 466 467 for (size_t i = 0; i < arraysize(k3rdPartyDomains); i++) { 468 const char* name = k3rdPartyDomains[i].name; 469 const PolicyDomain domain = k3rdPartyDomains[i].domain; 470 const RegistryDict* domain_dict = gpo_dict->GetKey(name); 471 if (!domain_dict) 472 continue; 473 474 for (RegistryDict::KeyMap::const_iterator component( 475 domain_dict->keys().begin()); 476 component != domain_dict->keys().end(); 477 ++component) { 478 // Load the schema. 479 const base::DictionaryValue* schema_dict = NULL; 480 scoped_ptr<base::Value> schema; 481 std::string schema_json; 482 const base::Value* schema_value = component->second->GetValue(kKeySchema); 483 if (schema_value && schema_value->GetAsString(&schema_json)) { 484 schema.reset(base::JSONReader::Read(schema_json)); 485 if (!schema || !schema->GetAsDictionary(&schema_dict)) { 486 LOG(WARNING) << "Failed to parse 3rd-part policy schema for " 487 << domain << "/" << component->first; 488 } 489 } 490 491 // Parse policy. 492 for (size_t j = 0; j < arraysize(kLevels); j++) { 493 const RegistryDict* policy_dict = 494 component->second->GetKey(kLevels[j].path); 495 if (!policy_dict) 496 continue; 497 498 PolicyMap policy; 499 ParsePolicy(policy_dict, kLevels[j].level, scope, schema_dict, &policy); 500 PolicyNamespace policy_namespace(domain, component->first); 501 bundle->Get(policy_namespace).MergeFrom(policy); 502 } 503 } 504 } 505 } 506 507 void PolicyLoaderWin::SetupWatches() { 508 DCHECK(is_initialized_); 509 if (!user_policy_watcher_failed_ && 510 !user_policy_watcher_.GetWatchedObject() && 511 !user_policy_watcher_.StartWatching( 512 user_policy_changed_event_.handle(), this)) { 513 DLOG(WARNING) << "Failed to start watch for user policy change event"; 514 user_policy_watcher_failed_ = true; 515 } 516 if (!machine_policy_watcher_failed_ && 517 !machine_policy_watcher_.GetWatchedObject() && 518 !machine_policy_watcher_.StartWatching( 519 machine_policy_changed_event_.handle(), this)) { 520 DLOG(WARNING) << "Failed to start watch for machine policy change event"; 521 machine_policy_watcher_failed_ = true; 522 } 523 } 524 525 void PolicyLoaderWin::OnObjectSignaled(HANDLE object) { 526 DCHECK(object == user_policy_changed_event_.handle() || 527 object == machine_policy_changed_event_.handle()) 528 << "unexpected object signaled policy reload, obj = " 529 << std::showbase << std::hex << object; 530 Reload(false); 531 } 532 533 } // namespace policy 534