Home | History | Annotate | Download | only in policy
      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