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.
5 #include "chrome/browser/policy/policy_loader_win.h"
7 #include <rpc.h> // For struct GUID
8 #include <shlwapi.h> // For PathIsUNC()
9 #include <userenv.h> // For GPO functions
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")
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/sequenced_task_runner.h"
27 #include "base/stl_util.h"
28 #include "base/strings/string16.h"
29 #include "base/strings/string_util.h"
30 #include "chrome/browser/policy/policy_bundle.h"
31 #include "chrome/browser/policy/policy_load_status.h"
32 #include "chrome/browser/policy/policy_map.h"
33 #include "chrome/browser/policy/preg_parser_win.h"
34 #include "chrome/browser/policy/registry_dict_win.h"
35 #include "components/json_schema/json_schema_constants.h"
36 #include "policy/policy_constants.h"
38 namespace schema = json_schema_constants;
44 const char kKeyMandatory[] = "policy";
45 const char kKeyRecommended[] = "recommended";
46 const char kKeySchema[] = "schema";
47 const char kKeyThirdParty[] = "3rdparty";
49 // The GUID of the registry settings group policy extension.
50 GUID kRegistrySettingsCSEGUID = REGISTRY_EXTENSION_GUID;
52 // A helper class encapsulating run-time-linked function calls to Wow64 APIs.
53 class Wow64Functions {
56 : kernel32_lib_(base::FilePath(L"kernel32")),
57 is_wow_64_process_(NULL),
58 wow_64_disable_wow_64_fs_redirection_(NULL),
59 wow_64_revert_wow_64_fs_redirection_(NULL) {
60 if (kernel32_lib_.is_valid()) {
61 is_wow_64_process_ = reinterpret_cast<IsWow64Process>(
62 kernel32_lib_.GetFunctionPointer("IsWow64Process"));
63 wow_64_disable_wow_64_fs_redirection_ =
64 reinterpret_cast<Wow64DisableWow64FSRedirection>(
65 kernel32_lib_.GetFunctionPointer(
66 "Wow64DisableWow64FsRedirection"));
67 wow_64_revert_wow_64_fs_redirection_ =
68 reinterpret_cast<Wow64RevertWow64FSRedirection>(
69 kernel32_lib_.GetFunctionPointer(
70 "Wow64RevertWow64FsRedirection"));
75 return is_wow_64_process_ &&
76 wow_64_disable_wow_64_fs_redirection_ &&
77 wow_64_revert_wow_64_fs_redirection_;
82 if (!is_wow_64_process_(GetCurrentProcess(), &result))
83 PLOG(WARNING) << "IsWow64ProcFailed";
87 bool DisableFsRedirection(PVOID* previous_state) {
88 return !!wow_64_disable_wow_64_fs_redirection_(previous_state);
91 bool RevertFsRedirection(PVOID previous_state) {
92 return !!wow_64_revert_wow_64_fs_redirection_(previous_state);
96 typedef BOOL (WINAPI* IsWow64Process)(HANDLE, PBOOL);
97 typedef BOOL (WINAPI* Wow64DisableWow64FSRedirection)(PVOID*);
98 typedef BOOL (WINAPI* Wow64RevertWow64FSRedirection)(PVOID);
100 base::ScopedNativeLibrary kernel32_lib_;
102 IsWow64Process is_wow_64_process_;
103 Wow64DisableWow64FSRedirection wow_64_disable_wow_64_fs_redirection_;
104 Wow64RevertWow64FSRedirection wow_64_revert_wow_64_fs_redirection_;
106 DISALLOW_COPY_AND_ASSIGN(Wow64Functions);
109 // Global Wow64Function instance used by ScopedDisableWow64Redirection below.
110 static base::LazyInstance<Wow64Functions> g_wow_64_functions =
111 LAZY_INSTANCE_INITIALIZER;
113 // Scoper that switches off Wow64 File System Redirection during its lifetime.
114 class ScopedDisableWow64Redirection {
116 ScopedDisableWow64Redirection()
118 previous_state_(NULL) {
119 Wow64Functions* wow64 = g_wow_64_functions.Pointer();
120 if (wow64->is_valid() && wow64->IsWow64()) {
121 if (wow64->DisableFsRedirection(&previous_state_))
124 PLOG(WARNING) << "Wow64DisableWow64FSRedirection";
128 ~ScopedDisableWow64Redirection() {
130 CHECK(g_wow_64_functions.Get().RevertFsRedirection(previous_state_));
133 bool is_active() { return active_; }
137 PVOID previous_state_;
139 DISALLOW_COPY_AND_ASSIGN(ScopedDisableWow64Redirection);
142 // AppliedGPOListProvider implementation that calls actual Windows APIs.
143 class WinGPOListProvider : public AppliedGPOListProvider {
145 virtual ~WinGPOListProvider() {}
147 // AppliedGPOListProvider:
148 virtual DWORD GetAppliedGPOList(DWORD flags,
149 LPCTSTR machine_name,
151 GUID* extension_guid,
152 PGROUP_POLICY_OBJECT* gpo_list) OVERRIDE {
153 return ::GetAppliedGPOList(flags, machine_name, sid_user, extension_guid,
157 virtual BOOL FreeGPOList(PGROUP_POLICY_OBJECT gpo_list) OVERRIDE {
158 return ::FreeGPOList(gpo_list);
162 // The default windows GPO list provider used for PolicyLoaderWin.
163 static base::LazyInstance<WinGPOListProvider> g_win_gpo_list_provider =
164 LAZY_INSTANCE_INITIALIZER;
166 std::string GetSchemaTypeForValueType(base::Value::Type value_type) {
167 switch (value_type) {
168 case base::Value::TYPE_DICTIONARY:
169 return json_schema_constants::kObject;
170 case base::Value::TYPE_INTEGER:
171 return json_schema_constants::kInteger;
172 case base::Value::TYPE_LIST:
173 return json_schema_constants::kArray;
174 case base::Value::TYPE_BOOLEAN:
175 return json_schema_constants::kBoolean;
176 case base::Value::TYPE_STRING:
177 return json_schema_constants::kString;
182 NOTREACHED() << "Unsupported policy value type " << value_type;
183 return json_schema_constants::kNull;
186 // Parses |gpo_dict| according to |schema| and writes the resulting policy
187 // settings to |policy| for the given |scope| and |level|.
188 void ParsePolicy(const RegistryDict* gpo_dict,
191 const base::DictionaryValue* schema,
196 scoped_ptr<base::Value> policy_value(gpo_dict->ConvertToJSON(schema));
197 const base::DictionaryValue* policy_dict = NULL;
198 if (!policy_value->GetAsDictionary(&policy_dict) || !policy_dict) {
199 LOG(WARNING) << "Root policy object is not a dictionary!";
203 policy->LoadFrom(policy_dict, level, scope);
208 const base::FilePath::CharType PolicyLoaderWin::kPRegFileName[] =
209 FILE_PATH_LITERAL("Registry.pol");
211 PolicyLoaderWin::PolicyLoaderWin(
212 scoped_refptr<base::SequencedTaskRunner> task_runner,
213 const PolicyDefinitionList* policy_list,
214 const string16& chrome_policy_key,
215 AppliedGPOListProvider* gpo_provider)
216 : AsyncPolicyLoader(task_runner),
217 is_initialized_(false),
218 policy_list_(policy_list),
219 chrome_policy_key_(chrome_policy_key),
220 gpo_provider_(gpo_provider),
221 user_policy_changed_event_(false, false),
222 machine_policy_changed_event_(false, false),
223 user_policy_watcher_failed_(false),
224 machine_policy_watcher_failed_(false) {
225 if (!RegisterGPNotification(user_policy_changed_event_.handle(), false)) {
226 DPLOG(WARNING) << "Failed to register user group policy notification";
227 user_policy_watcher_failed_ = true;
229 if (!RegisterGPNotification(machine_policy_changed_event_.handle(), true)) {
230 DPLOG(WARNING) << "Failed to register machine group policy notification.";
231 machine_policy_watcher_failed_ = true;
235 PolicyLoaderWin::~PolicyLoaderWin() {
236 user_policy_watcher_.StopWatching();
237 machine_policy_watcher_.StopWatching();
241 scoped_ptr<PolicyLoaderWin> PolicyLoaderWin::Create(
242 scoped_refptr<base::SequencedTaskRunner> task_runner,
243 const PolicyDefinitionList* policy_list) {
244 return make_scoped_ptr(
245 new PolicyLoaderWin(task_runner, policy_list, kRegistryChromePolicyKey,
246 g_win_gpo_list_provider.Pointer()));
249 void PolicyLoaderWin::InitOnBackgroundThread() {
250 is_initialized_ = true;
254 scoped_ptr<PolicyBundle> PolicyLoaderWin::Load() {
255 // Reset the watches BEFORE reading the individual policies to avoid
256 // missing a change notification.
260 if (chrome_policy_schema_.empty())
261 BuildChromePolicySchema();
263 // Policy scope and corresponding hive.
264 static const struct {
268 { POLICY_SCOPE_MACHINE, HKEY_LOCAL_MACHINE },
269 { POLICY_SCOPE_USER, HKEY_CURRENT_USER },
272 // Load policy data for the different scopes/levels and merge them.
273 scoped_ptr<PolicyBundle> bundle(new PolicyBundle());
274 PolicyMap* chrome_policy =
275 &bundle->Get(PolicyNamespace(POLICY_DOMAIN_CHROME, std::string()));
276 for (size_t i = 0; i < arraysize(kScopes); ++i) {
277 PolicyScope scope = kScopes[i].scope;
278 PolicyLoadStatusSample status;
279 RegistryDict gpo_dict;
281 // Note: GPO rules mandate a call to EnterCriticalPolicySection() here, and
282 // a matching LeaveCriticalPolicySection() call below after the
283 // ReadPolicyFromGPO() block. Unfortunately, the policy mutex may be
284 // unavailable for extended periods of time, and there are reports of this
285 // happening in the wild: http://crbug.com/265862.
287 // Blocking for minutes is neither acceptable for Chrome startup, nor on
288 // the FILE thread on which this code runs in steady state. Given that
289 // there have never been any reports of issues due to partially-applied /
290 // corrupt group policy, this code intentionally omits the
291 // EnterCriticalPolicySection() call.
293 // If there's ever reason to revisit this decision, one option could be to
294 // make the EnterCriticalPolicySection() call on a dedicated thread and
295 // timeout on it more aggressively. For now, there's no justification for
296 // the additional effort this would introduce.
298 if (!ReadPolicyFromGPO(scope, &gpo_dict, &status)) {
299 VLOG(1) << "Failed to read GPO files for " << scope
300 << " falling back to registry.";
301 gpo_dict.ReadRegistry(kScopes[i].hive, chrome_policy_key_);
304 // Remove special-cased entries from the GPO dictionary.
305 scoped_ptr<RegistryDict> recommended_dict(
306 gpo_dict.RemoveKey(kKeyRecommended));
307 scoped_ptr<RegistryDict> third_party_dict(
308 gpo_dict.RemoveKey(kKeyThirdParty));
310 // Load Chrome policy.
311 LoadChromePolicy(&gpo_dict, POLICY_LEVEL_MANDATORY, scope, chrome_policy);
312 LoadChromePolicy(recommended_dict.get(), POLICY_LEVEL_RECOMMENDED, scope,
315 // Load 3rd-party policy.
316 if (third_party_dict)
317 Load3rdPartyPolicy(third_party_dict.get(), scope, bundle.get());
320 return bundle.Pass();
323 void PolicyLoaderWin::BuildChromePolicySchema() {
324 scoped_ptr<base::DictionaryValue> properties(new base::DictionaryValue());
325 for (const PolicyDefinitionList::Entry* e = policy_list_->begin;
326 e != policy_list_->end; ++e) {
327 const std::string schema_type = GetSchemaTypeForValueType(e->value_type);
328 scoped_ptr<base::DictionaryValue> entry_schema(new base::DictionaryValue());
329 entry_schema->SetStringWithoutPathExpansion(json_schema_constants::kType,
332 if (e->value_type == base::Value::TYPE_LIST) {
333 scoped_ptr<base::DictionaryValue> items_schema(
334 new base::DictionaryValue());
335 items_schema->SetStringWithoutPathExpansion(
336 json_schema_constants::kType, json_schema_constants::kString);
337 entry_schema->SetWithoutPathExpansion(json_schema_constants::kItems,
338 items_schema.release());
340 properties->SetWithoutPathExpansion(e->name, entry_schema.release());
342 chrome_policy_schema_.SetStringWithoutPathExpansion(
343 json_schema_constants::kType, json_schema_constants::kObject);
344 chrome_policy_schema_.SetWithoutPathExpansion(
345 json_schema_constants::kProperties, properties.release());
348 bool PolicyLoaderWin::ReadPRegFile(const base::FilePath& preg_file,
349 RegistryDict* policy,
350 PolicyLoadStatusSample* status) {
351 // The following deals with the minor annoyance that Wow64 FS redirection
352 // might need to be turned off: This is the case if running as a 32-bit
353 // process on a 64-bit system, in which case Wow64 FS redirection redirects
354 // access to the %WINDIR%/System32/GroupPolicy directory to
355 // %WINDIR%/SysWOW64/GroupPolicy, but the file is actually in the
356 // system-native directory.
357 if (base::PathExists(preg_file)) {
358 return preg_parser::ReadFile(preg_file, chrome_policy_key_, policy, status);
360 // Try with redirection switched off.
361 ScopedDisableWow64Redirection redirection_disable;
362 if (redirection_disable.is_active() && base::PathExists(preg_file)) {
363 status->Add(POLICY_LOAD_STATUS_WOW64_REDIRECTION_DISABLED);
364 return preg_parser::ReadFile(preg_file, chrome_policy_key_, policy,
370 LOG(ERROR) << "PReg file doesn't exist: " << preg_file.value();
371 status->Add(POLICY_LOAD_STATUS_MISSING);
375 bool PolicyLoaderWin::LoadGPOPolicy(PolicyScope scope,
376 PGROUP_POLICY_OBJECT policy_object_list,
377 RegistryDict* policy,
378 PolicyLoadStatusSample* status) {
379 RegistryDict parsed_policy;
380 RegistryDict forced_policy;
381 for (GROUP_POLICY_OBJECT* policy_object = policy_object_list;
382 policy_object; policy_object = policy_object->pNext) {
383 if (policy_object->dwOptions & GPO_FLAG_DISABLE)
386 if (PathIsUNC(policy_object->lpFileSysPath)) {
387 // UNC path: Assume this is an AD-managed machine, which updates the
388 // registry via GPO's standard registry CSE periodically. Fall back to
389 // reading from the registry in this case.
390 status->Add(POLICY_LOAD_STATUS_INACCCESSIBLE);
394 base::FilePath preg_file_path(
395 base::FilePath(policy_object->lpFileSysPath).Append(kPRegFileName));
396 if (policy_object->dwOptions & GPO_FLAG_FORCE) {
397 RegistryDict new_forced_policy;
398 if (!ReadPRegFile(preg_file_path, &new_forced_policy, status))
401 // Merge with existing forced policy, giving precedence to the existing
403 new_forced_policy.Merge(forced_policy);
404 forced_policy.Swap(&new_forced_policy);
406 if (!ReadPRegFile(preg_file_path, &parsed_policy, status))
411 // Merge, give precedence to forced policy.
412 parsed_policy.Merge(forced_policy);
413 policy->Swap(&parsed_policy);
418 bool PolicyLoaderWin::ReadPolicyFromGPO(PolicyScope scope,
419 RegistryDict* policy,
420 PolicyLoadStatusSample* status) {
421 PGROUP_POLICY_OBJECT policy_object_list = NULL;
422 DWORD flags = scope == POLICY_SCOPE_MACHINE ? GPO_LIST_FLAG_MACHINE : 0;
423 if (gpo_provider_->GetAppliedGPOList(
424 flags, NULL, NULL, &kRegistrySettingsCSEGUID,
425 &policy_object_list) != ERROR_SUCCESS) {
426 PLOG(ERROR) << "GetAppliedGPOList scope " << scope;
427 status->Add(POLICY_LOAD_STATUS_QUERY_FAILED);
432 if (policy_object_list) {
433 result = LoadGPOPolicy(scope, policy_object_list, policy, status);
434 if (!gpo_provider_->FreeGPOList(policy_object_list))
435 LOG(WARNING) << "FreeGPOList";
437 status->Add(POLICY_LOAD_STATUS_NO_POLICY);
443 void PolicyLoaderWin::LoadChromePolicy(const RegistryDict* gpo_dict,
446 PolicyMap* chrome_policy_map) {
448 ParsePolicy(gpo_dict, level, scope, &chrome_policy_schema_, &policy);
449 chrome_policy_map->MergeFrom(policy);
452 void PolicyLoaderWin::Load3rdPartyPolicy(const RegistryDict* gpo_dict,
454 PolicyBundle* bundle) {
455 // Map of known 3rd party policy domain name to their enum values.
456 static const struct {
459 } k3rdPartyDomains[] = {
460 { "extensions", POLICY_DOMAIN_EXTENSIONS },
463 // Policy level and corresponding path.
464 static const struct {
468 { POLICY_LEVEL_MANDATORY, kKeyMandatory },
469 { POLICY_LEVEL_RECOMMENDED, kKeyRecommended },
472 for (size_t i = 0; i < arraysize(k3rdPartyDomains); i++) {
473 const char* name = k3rdPartyDomains[i].name;
474 const PolicyDomain domain = k3rdPartyDomains[i].domain;
475 const RegistryDict* domain_dict = gpo_dict->GetKey(name);
479 for (RegistryDict::KeyMap::const_iterator component(
480 domain_dict->keys().begin());
481 component != domain_dict->keys().end();
484 const base::DictionaryValue* schema_dict = NULL;
485 scoped_ptr<base::Value> schema;
486 std::string schema_json;
487 const base::Value* schema_value = component->second->GetValue(kKeySchema);
488 if (schema_value && schema_value->GetAsString(&schema_json)) {
489 schema.reset(base::JSONReader::Read(schema_json));
490 if (!schema || !schema->GetAsDictionary(&schema_dict)) {
491 LOG(WARNING) << "Failed to parse 3rd-part policy schema for "
492 << domain << "/" << component->first;
497 for (size_t j = 0; j < arraysize(kLevels); j++) {
498 const RegistryDict* policy_dict =
499 component->second->GetKey(kLevels[j].path);
504 ParsePolicy(policy_dict, kLevels[j].level, scope, schema_dict, &policy);
505 PolicyNamespace policy_namespace(domain, component->first);
506 bundle->Get(policy_namespace).MergeFrom(policy);
512 void PolicyLoaderWin::SetupWatches() {
513 DCHECK(is_initialized_);
514 if (!user_policy_watcher_failed_ &&
515 !user_policy_watcher_.GetWatchedObject() &&
516 !user_policy_watcher_.StartWatching(
517 user_policy_changed_event_.handle(), this)) {
518 DLOG(WARNING) << "Failed to start watch for user policy change event";
519 user_policy_watcher_failed_ = true;
521 if (!machine_policy_watcher_failed_ &&
522 !machine_policy_watcher_.GetWatchedObject() &&
523 !machine_policy_watcher_.StartWatching(
524 machine_policy_changed_event_.handle(), this)) {
525 DLOG(WARNING) << "Failed to start watch for machine policy change event";
526 machine_policy_watcher_failed_ = true;
530 void PolicyLoaderWin::OnObjectSignaled(HANDLE object) {
531 DCHECK(object == user_policy_changed_event_.handle() ||
532 object == machine_policy_changed_event_.handle())
533 << "unexpected object signaled policy reload, obj = "
534 << std::showbase << std::hex << object;
538 } // namespace policy