Home | History | Annotate | Download | only in common
      1 // Copyright (c) 2014 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 "components/policy/core/common/policy_loader_win.h"
      6 
      7 #include <windows.h>
      8 #include <lm.h>       // For limits.
      9 #include <ntdsapi.h>  // For Ds[Un]Bind
     10 #include <rpc.h>      // For struct GUID
     11 #include <shlwapi.h>  // For PathIsUNC()
     12 #include <userenv.h>  // For GPO functions
     13 
     14 #include <string>
     15 #include <vector>
     16 
     17 // shlwapi.dll is required for PathIsUNC().
     18 #pragma comment(lib, "shlwapi.lib")
     19 // userenv.dll is required for various GPO functions.
     20 #pragma comment(lib, "userenv.lib")
     21 // ntdsapi.dll is required for Ds[Un]Bind calls.
     22 #pragma comment(lib, "ntdsapi.lib")
     23 
     24 #include "base/basictypes.h"
     25 #include "base/bind.h"
     26 #include "base/file_util.h"
     27 #include "base/json/json_reader.h"
     28 #include "base/json/json_writer.h"
     29 #include "base/lazy_instance.h"
     30 #include "base/logging.h"
     31 #include "base/memory/scoped_ptr.h"
     32 #include "base/metrics/histogram.h"
     33 #include "base/scoped_native_library.h"
     34 #include "base/sequenced_task_runner.h"
     35 #include "base/stl_util.h"
     36 #include "base/strings/string16.h"
     37 #include "base/strings/string_util.h"
     38 #include "base/values.h"
     39 #include "base/win/win_util.h"
     40 #include "base/win/windows_version.h"
     41 #include "components/json_schema/json_schema_constants.h"
     42 #include "components/policy/core/common/policy_bundle.h"
     43 #include "components/policy/core/common/policy_load_status.h"
     44 #include "components/policy/core/common/policy_map.h"
     45 #include "components/policy/core/common/policy_namespace.h"
     46 #include "components/policy/core/common/preg_parser_win.h"
     47 #include "components/policy/core/common/registry_dict_win.h"
     48 #include "components/policy/core/common/schema.h"
     49 #include "policy/policy_constants.h"
     50 
     51 namespace schema = json_schema_constants;
     52 
     53 namespace policy {
     54 
     55 namespace {
     56 
     57 const char kKeyMandatory[] = "policy";
     58 const char kKeyRecommended[] = "recommended";
     59 const char kKeySchema[] = "schema";
     60 const char kKeyThirdParty[] = "3rdparty";
     61 
     62 // The Legacy Browser Support was the first user of the policy-for-extensions
     63 // API, and relied on behavior that will be phased out. If this extension is
     64 // present then its policies will be loaded in a special way.
     65 // TODO(joaodasilva): remove this for M35. http://crbug.com/325349
     66 const char kLegacyBrowserSupportExtensionId[] =
     67     "heildphpnddilhkemkielfhnkaagiabh";
     68 
     69 // The web store url that is the only trusted source for extensions.
     70 const char kExpectedWebStoreUrl[] =
     71     ";https://clients2.google.com/service/update2/crx";
     72 // String to be prepended to each blocked entry.
     73 const char kBlockedExtensionPrefix[] = "[BLOCKED]";
     74 
     75 // The GUID of the registry settings group policy extension.
     76 GUID kRegistrySettingsCSEGUID = REGISTRY_EXTENSION_GUID;
     77 
     78 // The list of possible errors that can occur while collecting information about
     79 // the current enterprise environment.
     80 enum DomainCheckErrors {
     81   DOMAIN_CHECK_ERROR_GET_JOIN_INFO = 0,
     82   DOMAIN_CHECK_ERROR_DS_BIND,
     83   DOMAIN_CHECK_ERROR_LAST,
     84 };
     85 
     86 // If the LBS extension is found and contains a schema in the registry then this
     87 // function is used to patch it, and make it compliant. The fix is to
     88 // add an "items" attribute to lists that don't declare it.
     89 std::string PatchSchema(const std::string& schema) {
     90   base::JSONParserOptions options = base::JSON_PARSE_RFC;
     91   scoped_ptr<base::Value> json(base::JSONReader::Read(schema, options));
     92   base::DictionaryValue* dict = NULL;
     93   base::DictionaryValue* properties = NULL;
     94   if (!json ||
     95       !json->GetAsDictionary(&dict) ||
     96       !dict->GetDictionary(schema::kProperties, &properties)) {
     97     return schema;
     98   }
     99 
    100   for (base::DictionaryValue::Iterator it(*properties);
    101        !it.IsAtEnd(); it.Advance()) {
    102     base::DictionaryValue* policy_schema = NULL;
    103     std::string type;
    104     if (properties->GetDictionary(it.key(), &policy_schema) &&
    105         policy_schema->GetString(schema::kType, &type) &&
    106         type == schema::kArray &&
    107         !policy_schema->HasKey(schema::kItems)) {
    108       scoped_ptr<base::DictionaryValue> items(new base::DictionaryValue());
    109       items->SetString(schema::kType, schema::kString);
    110       policy_schema->Set(schema::kItems, items.release());
    111     }
    112   }
    113 
    114   std::string serialized;
    115   base::JSONWriter::Write(json.get(), &serialized);
    116   return serialized;
    117 }
    118 
    119 // Verifies that untrusted policies contain only safe values. Modifies the
    120 // |policy| in place.
    121 void FilterUntrustedPolicy(PolicyMap* policy) {
    122   if (base::win::IsEnrolledToDomain())
    123     return;
    124 
    125   const PolicyMap::Entry* map_entry =
    126       policy->Get(policy::key::kExtensionInstallForcelist);
    127   if (map_entry && map_entry->value) {
    128     int invalid_policies = 0;
    129     const base::ListValue* policy_list_value = NULL;
    130     if (!map_entry->value->GetAsList(&policy_list_value))
    131       return;
    132 
    133     scoped_ptr<base::ListValue> filtered_values(new base::ListValue);
    134     for (base::ListValue::const_iterator list_entry(policy_list_value->begin());
    135          list_entry != policy_list_value->end(); ++list_entry) {
    136       std::string entry;
    137       if (!(*list_entry)->GetAsString(&entry))
    138         continue;
    139       size_t pos = entry.find(';');
    140       if (pos == std::string::npos)
    141         continue;
    142       // Only allow custom update urls in enterprise environments.
    143       if (!LowerCaseEqualsASCII(entry.substr(pos), kExpectedWebStoreUrl)) {
    144         entry = kBlockedExtensionPrefix + entry;
    145         invalid_policies++;
    146       }
    147 
    148       filtered_values->AppendString(entry);
    149     }
    150     policy->Set(policy::key::kExtensionInstallForcelist,
    151                 map_entry->level, map_entry->scope,
    152                 filtered_values.release(),
    153                 map_entry->external_data_fetcher);
    154     UMA_HISTOGRAM_COUNTS("EnterpriseCheck.InvalidPoliciesDetected",
    155                          invalid_policies);
    156   }
    157 }
    158 
    159 // A helper class encapsulating run-time-linked function calls to Wow64 APIs.
    160 class Wow64Functions {
    161  public:
    162   Wow64Functions()
    163     : kernel32_lib_(base::FilePath(L"kernel32")),
    164       is_wow_64_process_(NULL),
    165       wow_64_disable_wow_64_fs_redirection_(NULL),
    166       wow_64_revert_wow_64_fs_redirection_(NULL) {
    167     if (kernel32_lib_.is_valid()) {
    168       is_wow_64_process_ = reinterpret_cast<IsWow64Process>(
    169           kernel32_lib_.GetFunctionPointer("IsWow64Process"));
    170       wow_64_disable_wow_64_fs_redirection_ =
    171           reinterpret_cast<Wow64DisableWow64FSRedirection>(
    172               kernel32_lib_.GetFunctionPointer(
    173                   "Wow64DisableWow64FsRedirection"));
    174       wow_64_revert_wow_64_fs_redirection_ =
    175           reinterpret_cast<Wow64RevertWow64FSRedirection>(
    176               kernel32_lib_.GetFunctionPointer(
    177                   "Wow64RevertWow64FsRedirection"));
    178     }
    179   }
    180 
    181   bool is_valid() {
    182     return is_wow_64_process_ &&
    183         wow_64_disable_wow_64_fs_redirection_ &&
    184         wow_64_revert_wow_64_fs_redirection_;
    185  }
    186 
    187   bool IsWow64() {
    188     BOOL result = 0;
    189     if (!is_wow_64_process_(GetCurrentProcess(), &result))
    190       PLOG(WARNING) << "IsWow64ProcFailed";
    191     return !!result;
    192   }
    193 
    194   bool DisableFsRedirection(PVOID* previous_state) {
    195     return !!wow_64_disable_wow_64_fs_redirection_(previous_state);
    196   }
    197 
    198   bool RevertFsRedirection(PVOID previous_state) {
    199     return !!wow_64_revert_wow_64_fs_redirection_(previous_state);
    200   }
    201 
    202  private:
    203   typedef BOOL (WINAPI* IsWow64Process)(HANDLE, PBOOL);
    204   typedef BOOL (WINAPI* Wow64DisableWow64FSRedirection)(PVOID*);
    205   typedef BOOL (WINAPI* Wow64RevertWow64FSRedirection)(PVOID);
    206 
    207   base::ScopedNativeLibrary kernel32_lib_;
    208 
    209   IsWow64Process is_wow_64_process_;
    210   Wow64DisableWow64FSRedirection wow_64_disable_wow_64_fs_redirection_;
    211   Wow64RevertWow64FSRedirection wow_64_revert_wow_64_fs_redirection_;
    212 
    213   DISALLOW_COPY_AND_ASSIGN(Wow64Functions);
    214 };
    215 
    216 // Global Wow64Function instance used by ScopedDisableWow64Redirection below.
    217 static base::LazyInstance<Wow64Functions> g_wow_64_functions =
    218     LAZY_INSTANCE_INITIALIZER;
    219 
    220 // Scoper that switches off Wow64 File System Redirection during its lifetime.
    221 class ScopedDisableWow64Redirection {
    222  public:
    223   ScopedDisableWow64Redirection()
    224     : active_(false),
    225       previous_state_(NULL) {
    226     Wow64Functions* wow64 = g_wow_64_functions.Pointer();
    227     if (wow64->is_valid() && wow64->IsWow64()) {
    228       if (wow64->DisableFsRedirection(&previous_state_))
    229         active_ = true;
    230       else
    231         PLOG(WARNING) << "Wow64DisableWow64FSRedirection";
    232     }
    233   }
    234 
    235   ~ScopedDisableWow64Redirection() {
    236     if (active_)
    237       CHECK(g_wow_64_functions.Get().RevertFsRedirection(previous_state_));
    238   }
    239 
    240   bool is_active() { return active_; }
    241 
    242  private:
    243   bool active_;
    244   PVOID previous_state_;
    245 
    246   DISALLOW_COPY_AND_ASSIGN(ScopedDisableWow64Redirection);
    247 };
    248 
    249 // AppliedGPOListProvider implementation that calls actual Windows APIs.
    250 class WinGPOListProvider : public AppliedGPOListProvider {
    251  public:
    252   virtual ~WinGPOListProvider() {}
    253 
    254   // AppliedGPOListProvider:
    255   virtual DWORD GetAppliedGPOList(DWORD flags,
    256                                   LPCTSTR machine_name,
    257                                   PSID sid_user,
    258                                   GUID* extension_guid,
    259                                   PGROUP_POLICY_OBJECT* gpo_list) OVERRIDE {
    260     return ::GetAppliedGPOList(flags, machine_name, sid_user, extension_guid,
    261                                gpo_list);
    262   }
    263 
    264   virtual BOOL FreeGPOList(PGROUP_POLICY_OBJECT gpo_list) OVERRIDE {
    265     return ::FreeGPOList(gpo_list);
    266   }
    267 };
    268 
    269 // The default windows GPO list provider used for PolicyLoaderWin.
    270 static base::LazyInstance<WinGPOListProvider> g_win_gpo_list_provider =
    271     LAZY_INSTANCE_INITIALIZER;
    272 
    273 // Parses |gpo_dict| according to |schema| and writes the resulting policy
    274 // settings to |policy| for the given |scope| and |level|.
    275 void ParsePolicy(const RegistryDict* gpo_dict,
    276                  PolicyLevel level,
    277                  PolicyScope scope,
    278                  const Schema& schema,
    279                  PolicyMap* policy) {
    280   if (!gpo_dict)
    281     return;
    282 
    283   scoped_ptr<base::Value> policy_value(gpo_dict->ConvertToJSON(schema));
    284   const base::DictionaryValue* policy_dict = NULL;
    285   if (!policy_value->GetAsDictionary(&policy_dict) || !policy_dict) {
    286     LOG(WARNING) << "Root policy object is not a dictionary!";
    287     return;
    288   }
    289 
    290   policy->LoadFrom(policy_dict, level, scope);
    291 }
    292 
    293 // Collects stats about the enterprise environment that can be used to decide
    294 // how to parse the existing policy information.
    295 void CollectEnterpriseUMAs() {
    296   // Collect statistics about the windows suite.
    297   UMA_HISTOGRAM_ENUMERATION("EnterpriseCheck.OSType",
    298                             base::win::OSInfo::GetInstance()->version_type(),
    299                             base::win::SUITE_LAST);
    300 
    301   // Get the computer's domain status.
    302   LPWSTR domain;
    303   NETSETUP_JOIN_STATUS join_status;
    304   if (NERR_Success != ::NetGetJoinInformation(NULL, &domain, &join_status)) {
    305     UMA_HISTOGRAM_ENUMERATION("EnterpriseCheck.DomainCheckFailed",
    306                               DOMAIN_CHECK_ERROR_GET_JOIN_INFO,
    307                               DOMAIN_CHECK_ERROR_LAST);
    308     return;
    309   }
    310   ::NetApiBufferFree(domain);
    311 
    312   bool in_domain = join_status == NetSetupDomainName;
    313   UMA_HISTOGRAM_BOOLEAN("EnterpriseCheck.InDomain", in_domain);
    314   if (in_domain) {
    315     // This check will tell us how often are domain computers actually
    316     // connected to the enterprise network while Chrome is running.
    317     HANDLE server_bind;
    318     if (ERROR_SUCCESS == ::DsBind(NULL, NULL, &server_bind)) {
    319       UMA_HISTOGRAM_COUNTS("EnterpriseCheck.DomainBindSucceeded", 1);
    320       ::DsUnBind(&server_bind);
    321     } else {
    322       UMA_HISTOGRAM_ENUMERATION("EnterpriseCheck.DomainCheckFailed",
    323                                 DOMAIN_CHECK_ERROR_DS_BIND,
    324                                 DOMAIN_CHECK_ERROR_LAST);
    325     }
    326   }
    327 }
    328 
    329 }  // namespace
    330 
    331 const base::FilePath::CharType PolicyLoaderWin::kPRegFileName[] =
    332     FILE_PATH_LITERAL("Registry.pol");
    333 
    334 PolicyLoaderWin::PolicyLoaderWin(
    335     scoped_refptr<base::SequencedTaskRunner> task_runner,
    336     const base::string16& chrome_policy_key,
    337     AppliedGPOListProvider* gpo_provider)
    338     : AsyncPolicyLoader(task_runner),
    339       is_initialized_(false),
    340       chrome_policy_key_(chrome_policy_key),
    341       gpo_provider_(gpo_provider),
    342       user_policy_changed_event_(false, false),
    343       machine_policy_changed_event_(false, false),
    344       user_policy_watcher_failed_(false),
    345       machine_policy_watcher_failed_(false) {
    346   if (!::RegisterGPNotification(user_policy_changed_event_.handle(), false)) {
    347     DPLOG(WARNING) << "Failed to register user group policy notification";
    348     user_policy_watcher_failed_ = true;
    349   }
    350   if (!::RegisterGPNotification(machine_policy_changed_event_.handle(), true)) {
    351     DPLOG(WARNING) << "Failed to register machine group policy notification.";
    352     machine_policy_watcher_failed_ = true;
    353   }
    354 }
    355 
    356 PolicyLoaderWin::~PolicyLoaderWin() {
    357   if (!user_policy_watcher_failed_) {
    358     ::UnregisterGPNotification(user_policy_changed_event_.handle());
    359     user_policy_watcher_.StopWatching();
    360   }
    361   if (!machine_policy_watcher_failed_) {
    362     ::UnregisterGPNotification(machine_policy_changed_event_.handle());
    363     machine_policy_watcher_.StopWatching();
    364   }
    365 }
    366 
    367 // static
    368 scoped_ptr<PolicyLoaderWin> PolicyLoaderWin::Create(
    369     scoped_refptr<base::SequencedTaskRunner> task_runner,
    370     const base::string16& chrome_policy_key) {
    371   return make_scoped_ptr(
    372       new PolicyLoaderWin(task_runner,
    373                           chrome_policy_key,
    374                           g_win_gpo_list_provider.Pointer()));
    375 }
    376 
    377 void PolicyLoaderWin::InitOnBackgroundThread() {
    378   is_initialized_ = true;
    379   SetupWatches();
    380   CollectEnterpriseUMAs();
    381 }
    382 
    383 scoped_ptr<PolicyBundle> PolicyLoaderWin::Load() {
    384   // Reset the watches BEFORE reading the individual policies to avoid
    385   // missing a change notification.
    386   if (is_initialized_)
    387     SetupWatches();
    388 
    389   // Policy scope and corresponding hive.
    390   static const struct {
    391     PolicyScope scope;
    392     HKEY hive;
    393   } kScopes[] = {
    394     { POLICY_SCOPE_MACHINE, HKEY_LOCAL_MACHINE },
    395     { POLICY_SCOPE_USER,    HKEY_CURRENT_USER  },
    396   };
    397 
    398   bool is_enterprise = base::win::IsEnrolledToDomain();
    399   VLOG(1) << "Reading policy from the registry is "
    400           << (is_enterprise ? "enabled." : "disabled.");
    401 
    402   // Load policy data for the different scopes/levels and merge them.
    403   scoped_ptr<PolicyBundle> bundle(new PolicyBundle());
    404   PolicyMap* chrome_policy =
    405       &bundle->Get(PolicyNamespace(POLICY_DOMAIN_CHROME, std::string()));
    406   for (size_t i = 0; i < arraysize(kScopes); ++i) {
    407     PolicyScope scope = kScopes[i].scope;
    408     PolicyLoadStatusSample status;
    409     RegistryDict gpo_dict;
    410 
    411     // Note: GPO rules mandate a call to EnterCriticalPolicySection() here, and
    412     // a matching LeaveCriticalPolicySection() call below after the
    413     // ReadPolicyFromGPO() block. Unfortunately, the policy mutex may be
    414     // unavailable for extended periods of time, and there are reports of this
    415     // happening in the wild: http://crbug.com/265862.
    416     //
    417     // Blocking for minutes is neither acceptable for Chrome startup, nor on
    418     // the FILE thread on which this code runs in steady state. Given that
    419     // there have never been any reports of issues due to partially-applied /
    420     // corrupt group policy, this code intentionally omits the
    421     // EnterCriticalPolicySection() call.
    422     //
    423     // If there's ever reason to revisit this decision, one option could be to
    424     // make the EnterCriticalPolicySection() call on a dedicated thread and
    425     // timeout on it more aggressively. For now, there's no justification for
    426     // the additional effort this would introduce.
    427 
    428     if (is_enterprise || !ReadPolicyFromGPO(scope, &gpo_dict, &status)) {
    429       VLOG_IF(1, !is_enterprise) << "Failed to read GPO files for " << scope
    430                                  << " falling back to registry.";
    431       gpo_dict.ReadRegistry(kScopes[i].hive, chrome_policy_key_);
    432     }
    433 
    434     // Remove special-cased entries from the GPO dictionary.
    435     scoped_ptr<RegistryDict> recommended_dict(
    436         gpo_dict.RemoveKey(kKeyRecommended));
    437     scoped_ptr<RegistryDict> third_party_dict(
    438         gpo_dict.RemoveKey(kKeyThirdParty));
    439 
    440     // Load Chrome policy.
    441     LoadChromePolicy(&gpo_dict, POLICY_LEVEL_MANDATORY, scope, chrome_policy);
    442     LoadChromePolicy(recommended_dict.get(), POLICY_LEVEL_RECOMMENDED, scope,
    443                      chrome_policy);
    444 
    445     // Load 3rd-party policy.
    446     if (third_party_dict)
    447       Load3rdPartyPolicy(third_party_dict.get(), scope, bundle.get());
    448   }
    449 
    450   return bundle.Pass();
    451 }
    452 
    453 bool PolicyLoaderWin::ReadPRegFile(const base::FilePath& preg_file,
    454                                    RegistryDict* policy,
    455                                    PolicyLoadStatusSample* status) {
    456   // The following deals with the minor annoyance that Wow64 FS redirection
    457   // might need to be turned off: This is the case if running as a 32-bit
    458   // process on a 64-bit system, in which case Wow64 FS redirection redirects
    459   // access to the %WINDIR%/System32/GroupPolicy directory to
    460   // %WINDIR%/SysWOW64/GroupPolicy, but the file is actually in the
    461   // system-native directory.
    462   if (base::PathExists(preg_file)) {
    463     return preg_parser::ReadFile(preg_file, chrome_policy_key_, policy, status);
    464   } else {
    465     // Try with redirection switched off.
    466     ScopedDisableWow64Redirection redirection_disable;
    467     if (redirection_disable.is_active() && base::PathExists(preg_file)) {
    468       status->Add(POLICY_LOAD_STATUS_WOW64_REDIRECTION_DISABLED);
    469       return preg_parser::ReadFile(preg_file, chrome_policy_key_, policy,
    470                                    status);
    471     }
    472   }
    473 
    474   // Report the error.
    475   LOG(ERROR) << "PReg file doesn't exist: " << preg_file.value();
    476   status->Add(POLICY_LOAD_STATUS_MISSING);
    477   return false;
    478 }
    479 
    480 bool PolicyLoaderWin::LoadGPOPolicy(PolicyScope scope,
    481                                     PGROUP_POLICY_OBJECT policy_object_list,
    482                                     RegistryDict* policy,
    483                                     PolicyLoadStatusSample* status) {
    484   RegistryDict parsed_policy;
    485   RegistryDict forced_policy;
    486   for (GROUP_POLICY_OBJECT* policy_object = policy_object_list;
    487        policy_object; policy_object = policy_object->pNext) {
    488     if (policy_object->dwOptions & GPO_FLAG_DISABLE)
    489       continue;
    490 
    491     if (PathIsUNC(policy_object->lpFileSysPath)) {
    492       // UNC path: Assume this is an AD-managed machine, which updates the
    493       // registry via GPO's standard registry CSE periodically. Fall back to
    494       // reading from the registry in this case.
    495       status->Add(POLICY_LOAD_STATUS_INACCCESSIBLE);
    496       return false;
    497     }
    498 
    499     base::FilePath preg_file_path(
    500         base::FilePath(policy_object->lpFileSysPath).Append(kPRegFileName));
    501     if (policy_object->dwOptions & GPO_FLAG_FORCE) {
    502       RegistryDict new_forced_policy;
    503       if (!ReadPRegFile(preg_file_path, &new_forced_policy, status))
    504         return false;
    505 
    506       // Merge with existing forced policy, giving precedence to the existing
    507       // forced policy.
    508       new_forced_policy.Merge(forced_policy);
    509       forced_policy.Swap(&new_forced_policy);
    510     } else {
    511       if (!ReadPRegFile(preg_file_path, &parsed_policy, status))
    512         return false;
    513     }
    514   }
    515 
    516   // Merge, give precedence to forced policy.
    517   parsed_policy.Merge(forced_policy);
    518   policy->Swap(&parsed_policy);
    519 
    520   return true;
    521 }
    522 
    523 bool PolicyLoaderWin::ReadPolicyFromGPO(PolicyScope scope,
    524                                         RegistryDict* policy,
    525                                         PolicyLoadStatusSample* status) {
    526   PGROUP_POLICY_OBJECT policy_object_list = NULL;
    527   DWORD flags = scope == POLICY_SCOPE_MACHINE ? GPO_LIST_FLAG_MACHINE : 0;
    528   if (gpo_provider_->GetAppliedGPOList(
    529           flags, NULL, NULL, &kRegistrySettingsCSEGUID,
    530           &policy_object_list) != ERROR_SUCCESS) {
    531     PLOG(ERROR) << "GetAppliedGPOList scope " << scope;
    532     status->Add(POLICY_LOAD_STATUS_QUERY_FAILED);
    533     return false;
    534   }
    535 
    536   bool result = true;
    537   if (policy_object_list) {
    538     result = LoadGPOPolicy(scope, policy_object_list, policy, status);
    539     if (!gpo_provider_->FreeGPOList(policy_object_list))
    540       LOG(WARNING) << "FreeGPOList";
    541   } else {
    542     status->Add(POLICY_LOAD_STATUS_NO_POLICY);
    543   }
    544 
    545   return result;
    546 }
    547 
    548 void PolicyLoaderWin::LoadChromePolicy(const RegistryDict* gpo_dict,
    549                                        PolicyLevel level,
    550                                        PolicyScope scope,
    551                                        PolicyMap* chrome_policy_map) {
    552   PolicyMap policy;
    553   const Schema* chrome_schema =
    554       schema_map()->GetSchema(PolicyNamespace(POLICY_DOMAIN_CHROME, ""));
    555   ParsePolicy(gpo_dict, level, scope, *chrome_schema, &policy);
    556   FilterUntrustedPolicy(&policy);
    557   chrome_policy_map->MergeFrom(policy);
    558 }
    559 
    560 void PolicyLoaderWin::Load3rdPartyPolicy(const RegistryDict* gpo_dict,
    561                                          PolicyScope scope,
    562                                          PolicyBundle* bundle) {
    563   // Map of known 3rd party policy domain name to their enum values.
    564   static const struct {
    565     const char* name;
    566     PolicyDomain domain;
    567   } k3rdPartyDomains[] = {
    568     { "extensions", POLICY_DOMAIN_EXTENSIONS },
    569   };
    570 
    571   // Policy level and corresponding path.
    572   static const struct {
    573     PolicyLevel level;
    574     const char* path;
    575   } kLevels[] = {
    576     { POLICY_LEVEL_MANDATORY,   kKeyMandatory   },
    577     { POLICY_LEVEL_RECOMMENDED, kKeyRecommended },
    578   };
    579 
    580   for (size_t i = 0; i < arraysize(k3rdPartyDomains); i++) {
    581     const char* name = k3rdPartyDomains[i].name;
    582     const PolicyDomain domain = k3rdPartyDomains[i].domain;
    583     const RegistryDict* domain_dict = gpo_dict->GetKey(name);
    584     if (!domain_dict)
    585       continue;
    586 
    587     for (RegistryDict::KeyMap::const_iterator component(
    588              domain_dict->keys().begin());
    589          component != domain_dict->keys().end();
    590          ++component) {
    591       const PolicyNamespace policy_namespace(domain, component->first);
    592 
    593       const Schema* schema_from_map = schema_map()->GetSchema(policy_namespace);
    594       if (!schema_from_map) {
    595         // This extension isn't installed or doesn't support policies.
    596         continue;
    597       }
    598       Schema schema = *schema_from_map;
    599 
    600       if (!schema.valid() &&
    601           policy_namespace.domain == POLICY_DOMAIN_EXTENSIONS &&
    602           policy_namespace.component_id == kLegacyBrowserSupportExtensionId) {
    603         // TODO(joaodasilva): remove this special treatment for LBS by M35.
    604         std::string schema_json;
    605         const base::Value* value = component->second->GetValue(kKeySchema);
    606         if (value && value->GetAsString(&schema_json)) {
    607           std::string error;
    608           schema = Schema::Parse(PatchSchema(schema_json), &error);
    609           if (!schema.valid())
    610             LOG(WARNING) << "Invalid schema in the registry for LBS: " << error;
    611         }
    612       }
    613 
    614       // Parse policy.
    615       for (size_t j = 0; j < arraysize(kLevels); j++) {
    616         const RegistryDict* policy_dict =
    617             component->second->GetKey(kLevels[j].path);
    618         if (!policy_dict)
    619           continue;
    620 
    621         PolicyMap policy;
    622         ParsePolicy(policy_dict, kLevels[j].level, scope, schema, &policy);
    623         bundle->Get(policy_namespace).MergeFrom(policy);
    624       }
    625     }
    626   }
    627 }
    628 
    629 void PolicyLoaderWin::SetupWatches() {
    630   DCHECK(is_initialized_);
    631   if (!user_policy_watcher_failed_ &&
    632       !user_policy_watcher_.GetWatchedObject() &&
    633       !user_policy_watcher_.StartWatching(
    634           user_policy_changed_event_.handle(), this)) {
    635     DLOG(WARNING) << "Failed to start watch for user policy change event";
    636     user_policy_watcher_failed_ = true;
    637   }
    638   if (!machine_policy_watcher_failed_ &&
    639       !machine_policy_watcher_.GetWatchedObject() &&
    640       !machine_policy_watcher_.StartWatching(
    641           machine_policy_changed_event_.handle(), this)) {
    642     DLOG(WARNING) << "Failed to start watch for machine policy change event";
    643     machine_policy_watcher_failed_ = true;
    644   }
    645 }
    646 
    647 void PolicyLoaderWin::OnObjectSignaled(HANDLE object) {
    648   DCHECK(object == user_policy_changed_event_.handle() ||
    649          object == machine_policy_changed_event_.handle())
    650       << "unexpected object signaled policy reload, obj = "
    651       << std::showbase << std::hex << object;
    652   Reload(false);
    653 }
    654 
    655 }  // namespace policy
    656