summaryrefslogtreecommitdiffstats
path: root/chromium/components/policy/core/common/policy_loader_win.cc
diff options
context:
space:
mode:
Diffstat (limited to 'chromium/components/policy/core/common/policy_loader_win.cc')
-rw-r--r--chromium/components/policy/core/common/policy_loader_win.cc419
1 files changed, 419 insertions, 0 deletions
diff --git a/chromium/components/policy/core/common/policy_loader_win.cc b/chromium/components/policy/core/common/policy_loader_win.cc
new file mode 100644
index 00000000000..d11e2aa15b2
--- /dev/null
+++ b/chromium/components/policy/core/common/policy_loader_win.cc
@@ -0,0 +1,419 @@
+// Copyright (c) 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/policy_loader_win.h"
+
+// Must be included before lm.h
+#include <windows.h>
+
+#include <lm.h> // For NetGetJoinInformation
+// <security.h> needs this.
+#define SECURITY_WIN32 1
+#include <security.h> // For GetUserNameEx()
+#include <stddef.h>
+#include <userenv.h>
+
+#include <memory>
+#include <string>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/callback_helpers.h"
+#include "base/cxx17_backports.h"
+#include "base/enterprise_util.h"
+#include "base/files/file_util.h"
+#include "base/json/json_reader.h"
+#include "base/json/json_writer.h"
+#include "base/lazy_instance.h"
+#include "base/logging.h"
+#include "base/metrics/histogram.h"
+#include "base/metrics/histogram_functions.h"
+#include "base/metrics/histogram_macros.h"
+#include "base/path_service.h"
+#include "base/scoped_native_library.h"
+#include "base/strings/string_util.h"
+#include "base/syslog_logging.h"
+#include "base/task/sequenced_task_runner.h"
+#include "base/threading/scoped_thread_priority.h"
+#include "base/values.h"
+#include "base/win/shlwapi.h" // For PathIsUNC()
+#include "base/win/win_util.h"
+#include "base/win/windows_version.h"
+#include "components/policy/core/common/policy_bundle.h"
+#include "components/policy/core/common/policy_load_status.h"
+#include "components/policy/core/common/policy_loader_common.h"
+#include "components/policy/core/common/policy_map.h"
+#include "components/policy/core/common/policy_namespace.h"
+#include "components/policy/core/common/policy_types.h"
+#include "components/policy/core/common/registry_dict.h"
+#include "components/policy/core/common/schema.h"
+#include "components/policy/policy_constants.h"
+
+namespace policy {
+
+namespace {
+
+const char kKeyMandatory[] = "policy";
+const char kKeyRecommended[] = "recommended";
+const char kKeyThirdParty[] = "3rdparty";
+
+// The list of possible errors that can occur while collecting information about
+// the current enterprise environment.
+// This enum is used to define the buckets for an enumerated UMA histogram.
+// Hence,
+// (a) existing enumerated constants should never be deleted or reordered, and
+// (b) new constants should only be appended at the end of the enumeration.
+enum DomainCheckErrors {
+ // The check error below is no longer possible.
+ DEPRECATED_DOMAIN_CHECK_ERROR_GET_JOIN_INFO = 0,
+ DOMAIN_CHECK_ERROR_DS_BIND = 1,
+ DOMAIN_CHECK_ERROR_SIZE, // Not a DomainCheckError. Must be last.
+};
+
+// Parses |gpo_dict| according to |schema| and writes the resulting policy
+// settings to |policy| for the given |scope| and |level|.
+void ParsePolicy(const RegistryDict* gpo_dict,
+ PolicyLevel level,
+ PolicyScope scope,
+ const Schema& schema,
+ PolicyMap* policy) {
+ if (!gpo_dict)
+ return;
+
+ std::unique_ptr<base::Value> policy_value(gpo_dict->ConvertToJSON(schema));
+ const base::DictionaryValue* policy_dict = nullptr;
+ if (!policy_value->GetAsDictionary(&policy_dict) || !policy_dict) {
+ SYSLOG(WARNING) << "Root policy object is not a dictionary!";
+ return;
+ }
+
+ policy->LoadFrom(policy_dict, level, scope, POLICY_SOURCE_PLATFORM);
+}
+
+// Returns a name, using the |get_name| callback, which may refuse the call if
+// the name is longer than _MAX_PATH. So this helper function takes care of the
+// retry with the required size.
+bool GetName(const base::RepeatingCallback<BOOL(LPWSTR, LPDWORD)>& get_name,
+ std::wstring* name) {
+ DCHECK(name);
+ DWORD size = _MAX_PATH;
+ if (!get_name.Run(base::WriteInto(name, size), &size)) {
+ if (::GetLastError() != ERROR_MORE_DATA)
+ return false;
+ // Try again with the required size. This time it must work, the size should
+ // not have changed in between the two calls.
+ if (!get_name.Run(base::WriteInto(name, size), &size))
+ return false;
+ }
+ return true;
+}
+
+// To convert the weird BOOLEAN return value type of ::GetUserNameEx().
+BOOL GetUserNameExBool(EXTENDED_NAME_FORMAT format, LPWSTR name, PULONG size) {
+ // ::GetUserNameEx is documented to return a nonzero value on success.
+ return ::GetUserNameEx(format, name, size) != 0;
+}
+
+// Make sure to use the real NetGetJoinInformation, otherwise fallback to the
+// linked one.
+bool IsDomainJoined() {
+ base::ScopedClosureRunner free_library;
+ decltype(&::NetGetJoinInformation) net_get_join_information_function =
+ &::NetGetJoinInformation;
+ decltype(&::NetApiBufferFree) net_api_buffer_free_function =
+ &::NetApiBufferFree;
+ bool got_function_addresses = false;
+ // Use an absolute path to load the DLL to avoid DLL preloading attacks.
+ base::FilePath path;
+ if (base::PathService::Get(base::DIR_SYSTEM, &path)) {
+ // Mitigate the issues caused by loading DLLs on a background thread
+ // (http://crbug/973868).
+ SCOPED_MAY_LOAD_LIBRARY_AT_BACKGROUND_PRIORITY_REPEATEDLY();
+
+ HINSTANCE net_api_library = ::LoadLibraryEx(
+ path.Append(FILE_PATH_LITERAL("netapi32.dll")).value().c_str(), nullptr,
+ LOAD_WITH_ALTERED_SEARCH_PATH);
+ if (net_api_library) {
+ free_library.ReplaceClosure(
+ base::BindOnce(base::IgnoreResult(&::FreeLibrary), net_api_library));
+ net_get_join_information_function =
+ reinterpret_cast<decltype(&::NetGetJoinInformation)>(
+ ::GetProcAddress(net_api_library, "NetGetJoinInformation"));
+ net_api_buffer_free_function =
+ reinterpret_cast<decltype(&::NetApiBufferFree)>(
+ ::GetProcAddress(net_api_library, "NetApiBufferFree"));
+
+ if (net_get_join_information_function && net_api_buffer_free_function) {
+ got_function_addresses = true;
+ } else {
+ net_get_join_information_function = &::NetGetJoinInformation;
+ net_api_buffer_free_function = &::NetApiBufferFree;
+ }
+ }
+ }
+ base::UmaHistogramBoolean("EnterpriseCheck.NetGetJoinInformationAddress",
+ got_function_addresses);
+
+ LPWSTR buffer = nullptr;
+ NETSETUP_JOIN_STATUS buffer_type = NetSetupUnknownStatus;
+ bool is_joined = net_get_join_information_function(
+ nullptr, &buffer, &buffer_type) == NERR_Success &&
+ buffer_type == NetSetupDomainName;
+ if (buffer)
+ net_api_buffer_free_function(buffer);
+
+ return is_joined;
+}
+
+// Collects stats about the enterprise environment that can be used to decide
+// how to parse the existing policy information.
+void CollectEnterpriseUMAs() {
+ // Collect statistics about the windows suite.
+ UMA_HISTOGRAM_ENUMERATION("EnterpriseCheck.OSType",
+ base::win::OSInfo::GetInstance()->version_type(),
+ base::win::SUITE_LAST);
+
+ base::UmaHistogramBoolean("EnterpriseCheck.IsDomainJoined", IsDomainJoined());
+ base::UmaHistogramBoolean("EnterpriseCheck.InDomain",
+ base::win::IsEnrolledToDomain());
+ base::UmaHistogramBoolean("EnterpriseCheck.IsManaged2",
+ base::win::IsDeviceRegisteredWithManagement());
+ base::UmaHistogramBoolean("EnterpriseCheck.IsEnterpriseUser",
+ base::IsMachineExternallyManaged());
+ base::UmaHistogramBoolean("EnterpriseCheck.IsJoinedToAzureAD",
+ base::win::IsJoinedToAzureAD());
+
+ std::wstring machine_name;
+ if (GetName(
+ base::BindRepeating(&::GetComputerNameEx, ::ComputerNameDnsHostname),
+ &machine_name)) {
+ std::wstring user_name;
+ if (GetName(base::BindRepeating(&GetUserNameExBool, ::NameSamCompatible),
+ &user_name)) {
+ // A local user has the machine name in its sam compatible name, e.g.,
+ // 'MACHINE_NAME\username', otherwise it is perfixed with the domain name
+ // as opposed to the machine, e.g., 'COMPANY\username'.
+ base::UmaHistogramBoolean(
+ "EnterpriseCheck.IsLocalUser",
+ base::StartsWith(user_name, machine_name,
+ base::CompareCase::INSENSITIVE_ASCII) &&
+ user_name[machine_name.size()] == L'\\');
+ }
+
+ std::wstring full_machine_name;
+ if (GetName(base::BindRepeating(&::GetComputerNameEx,
+ ::ComputerNameDnsFullyQualified),
+ &full_machine_name)) {
+ // ComputerNameDnsFullyQualified is the same as the
+ // ComputerNameDnsHostname when not domain joined, otherwise it has a
+ // suffix.
+ base::UmaHistogramBoolean(
+ "EnterpriseCheck.IsLocalMachine",
+ base::EqualsCaseInsensitiveASCII(machine_name, full_machine_name));
+ }
+ }
+}
+
+} // namespace
+
+PolicyLoaderWin::PolicyLoaderWin(
+ scoped_refptr<base::SequencedTaskRunner> task_runner,
+ ManagementService* management_service,
+ const std::wstring& chrome_policy_key)
+ : AsyncPolicyLoader(task_runner,
+ management_service,
+ /*periodic_updates=*/true),
+ is_initialized_(false),
+ chrome_policy_key_(chrome_policy_key),
+ user_policy_changed_event_(
+ base::WaitableEvent::ResetPolicy::AUTOMATIC,
+ base::WaitableEvent::InitialState::NOT_SIGNALED),
+ machine_policy_changed_event_(
+ base::WaitableEvent::ResetPolicy::AUTOMATIC,
+ base::WaitableEvent::InitialState::NOT_SIGNALED),
+ user_policy_watcher_failed_(false),
+ machine_policy_watcher_failed_(false) {
+ if (!::RegisterGPNotification(user_policy_changed_event_.handle(), false)) {
+ DPLOG(WARNING) << "Failed to register user group policy notification";
+ user_policy_watcher_failed_ = true;
+ }
+ if (!::RegisterGPNotification(machine_policy_changed_event_.handle(), true)) {
+ DPLOG(WARNING) << "Failed to register machine group policy notification.";
+ machine_policy_watcher_failed_ = true;
+ }
+}
+
+PolicyLoaderWin::~PolicyLoaderWin() {
+ // Mitigate the issues caused by loading DLLs or lazily resolving symbols on a
+ // background thread (http://crbug/973868) which can hold the process wide
+ // LoaderLock and cause contention on Foreground threads. This issue is solved
+ // on Windows version after Win7. This code can be removed when Win7 is no
+ // longer supported.
+ SCOPED_MAY_LOAD_LIBRARY_AT_BACKGROUND_PRIORITY();
+
+ if (!user_policy_watcher_failed_) {
+ ::UnregisterGPNotification(user_policy_changed_event_.handle());
+ user_policy_watcher_.StopWatching();
+ }
+ if (!machine_policy_watcher_failed_) {
+ ::UnregisterGPNotification(machine_policy_changed_event_.handle());
+ machine_policy_watcher_.StopWatching();
+ }
+}
+
+// static
+std::unique_ptr<PolicyLoaderWin> PolicyLoaderWin::Create(
+ scoped_refptr<base::SequencedTaskRunner> task_runner,
+ ManagementService* management_service,
+ const std::wstring& chrome_policy_key) {
+ return std::make_unique<PolicyLoaderWin>(task_runner, management_service,
+ chrome_policy_key);
+}
+
+void PolicyLoaderWin::InitOnBackgroundThread() {
+ is_initialized_ = true;
+ SetupWatches();
+ CollectEnterpriseUMAs();
+}
+
+std::unique_ptr<PolicyBundle> PolicyLoaderWin::Load() {
+ // Reset the watches BEFORE reading the individual policies to avoid
+ // missing a change notification.
+ if (is_initialized_)
+ SetupWatches();
+
+ // Policy scope and corresponding hive.
+ static const struct {
+ PolicyScope scope;
+ HKEY hive;
+ } kScopes[] = {
+ {POLICY_SCOPE_MACHINE, HKEY_LOCAL_MACHINE},
+ {POLICY_SCOPE_USER, HKEY_CURRENT_USER},
+ };
+
+ // Load policy data for the different scopes/levels and merge them.
+ std::unique_ptr<PolicyBundle> bundle(new PolicyBundle());
+ PolicyMap* chrome_policy =
+ &bundle->Get(PolicyNamespace(POLICY_DOMAIN_CHROME, std::string()));
+ for (size_t i = 0; i < std::size(kScopes); ++i) {
+ PolicyScope scope = kScopes[i].scope;
+ PolicyLoadStatusUmaReporter status;
+ RegistryDict gpo_dict;
+
+ gpo_dict.ReadRegistry(kScopes[i].hive, chrome_policy_key_);
+
+ // Remove special-cased entries from the GPO dictionary.
+ std::unique_ptr<RegistryDict> recommended_dict(
+ gpo_dict.RemoveKey(kKeyRecommended));
+ std::unique_ptr<RegistryDict> third_party_dict(
+ gpo_dict.RemoveKey(kKeyThirdParty));
+
+ // Load Chrome policy.
+ LoadChromePolicy(&gpo_dict, POLICY_LEVEL_MANDATORY, scope, chrome_policy);
+ LoadChromePolicy(recommended_dict.get(), POLICY_LEVEL_RECOMMENDED, scope,
+ chrome_policy);
+
+ // Load 3rd-party policy.
+ if (third_party_dict)
+ Load3rdPartyPolicy(third_party_dict.get(), scope, bundle.get());
+ }
+
+ return bundle;
+}
+
+void PolicyLoaderWin::LoadChromePolicy(const RegistryDict* gpo_dict,
+ PolicyLevel level,
+ PolicyScope scope,
+ PolicyMap* chrome_policy_map) {
+ PolicyMap policy;
+ const Schema* chrome_schema =
+ schema_map()->GetSchema(PolicyNamespace(POLICY_DOMAIN_CHROME, ""));
+ ParsePolicy(gpo_dict, level, scope, *chrome_schema, &policy);
+ if (ShouldFilterSensitivePolicies())
+ FilterSensitivePolicies(&policy);
+ chrome_policy_map->MergeFrom(policy);
+}
+
+void PolicyLoaderWin::Load3rdPartyPolicy(const RegistryDict* gpo_dict,
+ PolicyScope scope,
+ PolicyBundle* bundle) {
+ // Map of known 3rd party policy domain name to their enum values.
+ static const struct {
+ const char* name;
+ PolicyDomain domain;
+ } k3rdPartyDomains[] = {
+ {"extensions", POLICY_DOMAIN_EXTENSIONS},
+ };
+
+ // Policy level and corresponding path.
+ static const struct {
+ PolicyLevel level;
+ const char* path;
+ } kLevels[] = {
+ {POLICY_LEVEL_MANDATORY, kKeyMandatory},
+ {POLICY_LEVEL_RECOMMENDED, kKeyRecommended},
+ };
+
+ for (size_t i = 0; i < std::size(k3rdPartyDomains); i++) {
+ const char* name = k3rdPartyDomains[i].name;
+ const PolicyDomain domain = k3rdPartyDomains[i].domain;
+ const RegistryDict* domain_dict = gpo_dict->GetKey(name);
+ if (!domain_dict)
+ continue;
+
+ for (RegistryDict::KeyMap::const_iterator component(
+ domain_dict->keys().begin());
+ component != domain_dict->keys().end(); ++component) {
+ const PolicyNamespace policy_namespace(domain, component->first);
+
+ const Schema* schema_from_map = schema_map()->GetSchema(policy_namespace);
+ if (!schema_from_map) {
+ // This extension isn't installed or doesn't support policies.
+ continue;
+ }
+ Schema schema = *schema_from_map;
+
+ // Parse policy.
+ for (size_t j = 0; j < std::size(kLevels); j++) {
+ const RegistryDict* policy_dict =
+ component->second->GetKey(kLevels[j].path);
+ if (!policy_dict)
+ continue;
+
+ PolicyMap policy;
+ ParsePolicy(policy_dict, kLevels[j].level, scope, schema, &policy);
+ bundle->Get(policy_namespace).MergeFrom(policy);
+ }
+ }
+ }
+}
+
+void PolicyLoaderWin::SetupWatches() {
+ DCHECK(is_initialized_);
+ if (!user_policy_watcher_failed_ &&
+ !user_policy_watcher_.GetWatchedObject() &&
+ !user_policy_watcher_.StartWatchingOnce(
+ user_policy_changed_event_.handle(), this)) {
+ DLOG(WARNING) << "Failed to start watch for user policy change event";
+ user_policy_watcher_failed_ = true;
+ }
+ if (!machine_policy_watcher_failed_ &&
+ !machine_policy_watcher_.GetWatchedObject() &&
+ !machine_policy_watcher_.StartWatchingOnce(
+ machine_policy_changed_event_.handle(), this)) {
+ DLOG(WARNING) << "Failed to start watch for machine policy change event";
+ machine_policy_watcher_failed_ = true;
+ }
+}
+
+void PolicyLoaderWin::OnObjectSignaled(HANDLE object) {
+ DCHECK(object == user_policy_changed_event_.handle() ||
+ object == machine_policy_changed_event_.handle())
+ << "unexpected object signaled policy reload, obj = " << std::showbase
+ << std::hex << object;
+ Reload(false);
+}
+
+} // namespace policy