| OLD | NEW |
| (Empty) | |
| 1 // Copyright 2016 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/metrics/antivirus_metrics_provider_win.h" |
| 6 |
| 7 #include <iwscapi.h> |
| 8 #include <stddef.h> |
| 9 #include <wbemidl.h> |
| 10 #include <windows.h> |
| 11 #include <wscapi.h> |
| 12 |
| 13 #include <string> |
| 14 |
| 15 #include "base/bind.h" |
| 16 #include "base/callback.h" |
| 17 #include "base/feature_list.h" |
| 18 #include "base/file_version_info_win.h" |
| 19 #include "base/files/file_path.h" |
| 20 #include "base/files/file_util.h" |
| 21 #include "base/metrics/field_trial.h" |
| 22 #include "base/metrics/histogram.h" |
| 23 #include "base/path_service.h" |
| 24 #include "base/strings/string_util.h" |
| 25 #include "base/strings/sys_string_conversions.h" |
| 26 #include "base/task_runner_util.h" |
| 27 #include "base/threading/thread_restrictions.h" |
| 28 #include "base/version.h" |
| 29 #include "base/win/scoped_bstr.h" |
| 30 #include "base/win/scoped_com_initializer.h" |
| 31 #include "base/win/scoped_comptr.h" |
| 32 #include "base/win/scoped_variant.h" |
| 33 #include "base/win/windows_version.h" |
| 34 #include "chrome/common/channel_info.h" |
| 35 #include "components/metrics/proto/system_profile.pb.h" |
| 36 #include "components/variations/metrics_util.h" |
| 37 #include "components/version_info/version_info.h" |
| 38 |
| 39 namespace { |
| 40 |
| 41 // This is an undocumented structure returned from querying the "productState" |
| 42 // uint32 from the AntiVirusProduct in WMI. |
| 43 // http://neophob.com/2010/03/wmi-query-windows-securitycenter2/ gives a good |
| 44 // summary and testing was also done with a variety of AV products to determine |
| 45 // these values as accurately as possible. |
| 46 #pragma pack(push) |
| 47 #pragma pack(1) |
| 48 struct PRODUCT_STATE { |
| 49 uint8_t unknown_1 : 4; |
| 50 uint8_t definition_state : 4; // 1 = Out of date, 0 = Up to date. |
| 51 uint8_t unknown_2 : 4; |
| 52 uint8_t security_state : 4; // 0 = Inactive, 1 = Active, 2 = Snoozed. |
| 53 uint8_t security_provider; // matches WSC_SECURITY_PROVIDER in wscapi.h. |
| 54 uint8_t unknown_3; |
| 55 }; |
| 56 #pragma pack(pop) |
| 57 |
| 58 static_assert(sizeof(PRODUCT_STATE) == 4, "Wrong packing!"); |
| 59 |
| 60 bool ShouldReportFullNames() { |
| 61 // The expectation is that this will be disabled for the majority of users, |
| 62 // but this allows a small group to be enabled on other channels if there are |
| 63 // a large percentage of hashes collected on these channels that are not |
| 64 // resolved to names previously collected on Canary channel. |
| 65 bool enabled = base::FeatureList::IsEnabled( |
| 66 AntiVirusMetricsProvider::kReportNamesFeature); |
| 67 |
| 68 if (chrome::GetChannel() == version_info::Channel::CANARY) |
| 69 return true; |
| 70 |
| 71 return enabled; |
| 72 } |
| 73 |
| 74 // Helper function for expanding all environment variables in |path|. |
| 75 std::wstring ExpandEnvironmentVariables(const std::wstring& path) { |
| 76 static const DWORD kMaxBuffer = 32 * 1024; // Max according to MSDN. |
| 77 std::wstring path_expanded; |
| 78 DWORD path_len = MAX_PATH; |
| 79 do { |
| 80 DWORD result = ExpandEnvironmentStrings( |
| 81 path.c_str(), base::WriteInto(&path_expanded, path_len), path_len); |
| 82 if (!result) { |
| 83 // Failed to expand variables. Return the original string. |
| 84 DPLOG(ERROR) << path; |
| 85 break; |
| 86 } |
| 87 if (result <= path_len) |
| 88 return path_expanded.substr(0, result - 1); |
| 89 path_len = result; |
| 90 } while (path_len < kMaxBuffer); |
| 91 |
| 92 return path; |
| 93 } |
| 94 |
| 95 // Helper function to take a |path| to a file, that might contain environment |
| 96 // strings, and read the file version information in |product_version|. Returns |
| 97 // true if it was possible to extract the file information correctly. |
| 98 bool GetProductVersion(std::wstring* path, std::string* product_version) { |
| 99 base::FilePath full_path(ExpandEnvironmentVariables(*path)); |
| 100 |
| 101 #if !defined(_WIN64) |
| 102 if (!base::PathExists(full_path)) { |
| 103 // On 32-bit builds, path might contain C:\Program Files (x86) instead of |
| 104 // C:\Program Files. |
| 105 base::ReplaceFirstSubstringAfterOffset(path, 0, L"%ProgramFiles%", |
| 106 L"%ProgramW6432%"); |
| 107 full_path = base::FilePath(ExpandEnvironmentVariables(*path)); |
| 108 } |
| 109 #endif // !defined(_WIN64) |
| 110 std::unique_ptr<FileVersionInfo> version_info( |
| 111 FileVersionInfo::CreateFileVersionInfo(full_path)); |
| 112 |
| 113 // It is not an error if the product version cannot be read, so continue in |
| 114 // this case. |
| 115 if (version_info.get()) { |
| 116 FileVersionInfoWin* version_info_win = |
| 117 static_cast<FileVersionInfoWin*>(version_info.get()); |
| 118 std::string version_str = |
| 119 base::SysWideToUTF8(version_info_win->product_version()); |
| 120 |
| 121 *product_version = std::move(version_str); |
| 122 return true; |
| 123 } |
| 124 |
| 125 return false; |
| 126 } |
| 127 |
| 128 } // namespace |
| 129 |
| 130 constexpr base::Feature AntiVirusMetricsProvider::kReportNamesFeature; |
| 131 |
| 132 AntiVirusMetricsProvider::AntiVirusMetricsProvider( |
| 133 scoped_refptr<base::TaskRunner> task_runner) |
| 134 : task_runner_(task_runner), weak_ptr_factory_(this) {} |
| 135 |
| 136 AntiVirusMetricsProvider::~AntiVirusMetricsProvider() {} |
| 137 |
| 138 void AntiVirusMetricsProvider::ProvideSystemProfileMetrics( |
| 139 metrics::SystemProfileProto* system_profile_proto) { |
| 140 for (const auto& av_product : av_products_) { |
| 141 metrics::SystemProfileProto_AntiVirusProduct* product = |
| 142 system_profile_proto->add_antivirus_product(); |
| 143 *product = av_product; |
| 144 } |
| 145 } |
| 146 |
| 147 void AntiVirusMetricsProvider::GetAntiVirusMetrics( |
| 148 const base::Closure& done_callback) { |
| 149 base::PostTaskAndReplyWithResult( |
| 150 task_runner_.get(), FROM_HERE, |
| 151 base::Bind(&AntiVirusMetricsProvider::GetAntiVirusProductsOnFileThread), |
| 152 base::Bind(&AntiVirusMetricsProvider::GotAntiVirusProducts, |
| 153 weak_ptr_factory_.GetWeakPtr(), done_callback)); |
| 154 } |
| 155 |
| 156 // static |
| 157 std::vector<AntiVirusMetricsProvider::AvProduct> |
| 158 AntiVirusMetricsProvider::GetAntiVirusProductsOnFileThread() { |
| 159 std::vector<AvProduct> av_products; |
| 160 |
| 161 ResultCode result = RESULT_GENERIC_FAILURE; |
| 162 |
| 163 base::win::OSInfo* os_info = base::win::OSInfo::GetInstance(); |
| 164 |
| 165 // Windows Security Center APIs are not available on Server products. |
| 166 // See https://msdn.microsoft.com/en-us/library/bb432506.aspx. |
| 167 if (os_info->version_type() == base::win::SUITE_SERVER) { |
| 168 result = RESULT_WSC_NOT_AVAILABLE; |
| 169 } else { |
| 170 // The WSC interface is preferred here as it's fully documented, but only |
| 171 // available on Windows 8 and above, so instead use the undocumented WMI |
| 172 // interface on Windows 7 and below. |
| 173 if (os_info->version() >= base::win::VERSION_WIN8) |
| 174 result = FillAntiVirusProductsFromWSC(&av_products); |
| 175 else |
| 176 result = FillAntiVirusProductsFromWMI(&av_products); |
| 177 } |
| 178 |
| 179 UMA_HISTOGRAM_ENUMERATION("UMA.AntiVirusMetricsProvider.Result", |
| 180 result, |
| 181 RESULT_COUNT); |
| 182 |
| 183 return av_products; |
| 184 } |
| 185 |
| 186 void AntiVirusMetricsProvider::GotAntiVirusProducts( |
| 187 const base::Closure& done_callback, |
| 188 const std::vector<AvProduct>& av_products) { |
| 189 DCHECK(thread_checker_.CalledOnValidThread()); |
| 190 av_products_ = av_products; |
| 191 done_callback.Run(); |
| 192 } |
| 193 |
| 194 // static |
| 195 AntiVirusMetricsProvider::ResultCode |
| 196 AntiVirusMetricsProvider::FillAntiVirusProductsFromWSC( |
| 197 std::vector<AvProduct>* products) { |
| 198 std::vector<AvProduct> result_list; |
| 199 base::ThreadRestrictions::AssertIOAllowed(); |
| 200 base::win::ScopedCOMInitializer com_initializer; |
| 201 |
| 202 if (!com_initializer.succeeded()) |
| 203 return RESULT_FAILED_TO_INITIALIZE_COM; |
| 204 |
| 205 base::win::ScopedComPtr<IWSCProductList> product_list; |
| 206 HRESULT result = |
| 207 CoCreateInstance(__uuidof(WSCProductList), nullptr, CLSCTX_INPROC_SERVER, |
| 208 __uuidof(IWSCProductList), product_list.ReceiveVoid()); |
| 209 if (FAILED(result)) |
| 210 return RESULT_FAILED_TO_CREATE_INSTANCE; |
| 211 |
| 212 result = product_list->Initialize(WSC_SECURITY_PROVIDER_ANTIVIRUS); |
| 213 if (FAILED(result)) |
| 214 return RESULT_FAILED_TO_INITIALIZE_PRODUCT_LIST; |
| 215 |
| 216 LONG product_count; |
| 217 result = product_list->get_Count(&product_count); |
| 218 if (FAILED(result)) |
| 219 return RESULT_FAILED_TO_GET_PRODUCT_COUNT; |
| 220 |
| 221 for (LONG i = 0; i < product_count; i++) { |
| 222 IWscProduct* product = nullptr; |
| 223 result = product_list->get_Item(i, &product); |
| 224 if (FAILED(result)) |
| 225 return RESULT_FAILED_TO_GET_ITEM; |
| 226 |
| 227 static_assert(metrics::SystemProfileProto::AntiVirusState:: |
| 228 SystemProfileProto_AntiVirusState_STATE_ON == |
| 229 static_cast<metrics::SystemProfileProto::AntiVirusState>( |
| 230 WSC_SECURITY_PRODUCT_STATE_ON), |
| 231 "proto and API values must be the same"); |
| 232 static_assert(metrics::SystemProfileProto::AntiVirusState:: |
| 233 SystemProfileProto_AntiVirusState_STATE_OFF == |
| 234 static_cast<metrics::SystemProfileProto::AntiVirusState>( |
| 235 WSC_SECURITY_PRODUCT_STATE_OFF), |
| 236 "proto and API values must be the same"); |
| 237 static_assert(metrics::SystemProfileProto::AntiVirusState:: |
| 238 SystemProfileProto_AntiVirusState_STATE_SNOOZED == |
| 239 static_cast<metrics::SystemProfileProto::AntiVirusState>( |
| 240 WSC_SECURITY_PRODUCT_STATE_SNOOZED), |
| 241 "proto and API values must be the same"); |
| 242 static_assert(metrics::SystemProfileProto::AntiVirusState:: |
| 243 SystemProfileProto_AntiVirusState_STATE_EXPIRED == |
| 244 static_cast<metrics::SystemProfileProto::AntiVirusState>( |
| 245 WSC_SECURITY_PRODUCT_STATE_EXPIRED), |
| 246 "proto and API values must be the same"); |
| 247 |
| 248 AvProduct av_product; |
| 249 WSC_SECURITY_PRODUCT_STATE product_state; |
| 250 result = product->get_ProductState(&product_state); |
| 251 if (FAILED(result)) |
| 252 return RESULT_FAILED_TO_GET_PRODUCT_STATE; |
| 253 |
| 254 if (!metrics::SystemProfileProto_AntiVirusState_IsValid(product_state)) |
| 255 return RESULT_PRODUCT_STATE_INVALID; |
| 256 |
| 257 av_product.set_product_state( |
| 258 static_cast<metrics::SystemProfileProto::AntiVirusState>( |
| 259 product_state)); |
| 260 |
| 261 base::win::ScopedBstr product_name; |
| 262 result = product->get_ProductName(product_name.Receive()); |
| 263 if (FAILED(result)) |
| 264 return RESULT_FAILED_TO_GET_PRODUCT_NAME; |
| 265 std::string name = |
| 266 base::SysWideToUTF8(std::wstring(product_name, product_name.Length())); |
| 267 product_name.Release(); |
| 268 if (ShouldReportFullNames()) |
| 269 av_product.set_product_name(name); |
| 270 av_product.set_product_name_hash(metrics::HashName(name)); |
| 271 |
| 272 base::win::ScopedBstr remediation_path; |
| 273 result = product->get_RemediationPath(remediation_path.Receive()); |
| 274 if (FAILED(result)) |
| 275 return RESULT_FAILED_TO_GET_REMEDIATION_PATH; |
| 276 std::wstring path_str(remediation_path, remediation_path.Length()); |
| 277 remediation_path.Release(); |
| 278 |
| 279 std::string product_version; |
| 280 // Not a failure if the product version cannot be read from the file on |
| 281 // disk. |
| 282 if (GetProductVersion(&path_str, &product_version)) { |
| 283 if (ShouldReportFullNames()) |
| 284 av_product.set_product_version(product_version); |
| 285 av_product.set_product_version_hash(metrics::HashName(product_version)); |
| 286 } |
| 287 |
| 288 result_list.push_back(av_product); |
| 289 } |
| 290 |
| 291 *products = std::move(result_list); |
| 292 |
| 293 return RESULT_SUCCESS; |
| 294 } |
| 295 |
| 296 AntiVirusMetricsProvider::ResultCode |
| 297 AntiVirusMetricsProvider::FillAntiVirusProductsFromWMI( |
| 298 std::vector<AvProduct>* products) { |
| 299 std::vector<AvProduct> result_list; |
| 300 base::ThreadRestrictions::AssertIOAllowed(); |
| 301 base::win::ScopedCOMInitializer com_initializer; |
| 302 |
| 303 if (!com_initializer.succeeded()) |
| 304 return RESULT_FAILED_TO_INITIALIZE_COM; |
| 305 |
| 306 base::win::ScopedComPtr<IWbemLocator> wmi_locator; |
| 307 HRESULT hr = wmi_locator.CreateInstance(CLSID_WbemLocator, nullptr, |
| 308 CLSCTX_INPROC_SERVER); |
| 309 if (FAILED(hr)) |
| 310 return RESULT_FAILED_TO_CREATE_INSTANCE; |
| 311 |
| 312 base::win::ScopedComPtr<IWbemServices> wmi_services; |
| 313 hr = wmi_locator->ConnectServer( |
| 314 base::win::ScopedBstr(L"ROOT\\SecurityCenter2"), nullptr, nullptr, |
| 315 nullptr, 0, nullptr, nullptr, wmi_services.Receive()); |
| 316 if (FAILED(hr)) |
| 317 return RESULT_FAILED_TO_CONNECT_TO_WMI; |
| 318 |
| 319 hr = ::CoSetProxyBlanket(wmi_services.get(), RPC_C_AUTHN_WINNT, |
| 320 RPC_C_AUTHZ_NONE, nullptr, RPC_C_AUTHN_LEVEL_CALL, |
| 321 RPC_C_IMP_LEVEL_IMPERSONATE, nullptr, EOAC_NONE); |
| 322 if (FAILED(hr)) |
| 323 return RESULT_FAILED_TO_SET_SECURITY_BLANKET; |
| 324 |
| 325 // This interface is available on Windows Vista and above, and is officially |
| 326 // undocumented. |
| 327 base::win::ScopedBstr query_language(L"WQL"); |
| 328 base::win::ScopedBstr query(L"SELECT * FROM AntiVirusProduct"); |
| 329 base::win::ScopedComPtr<IEnumWbemClassObject> enumerator; |
| 330 |
| 331 hr = wmi_services->ExecQuery( |
| 332 query_language, query, |
| 333 WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY, nullptr, |
| 334 enumerator.Receive()); |
| 335 if (FAILED(hr)) |
| 336 return RESULT_FAILED_TO_EXEC_WMI_QUERY; |
| 337 |
| 338 // Iterate over the results of the WMI query. Each result will be an |
| 339 // AntiVirusProduct instance. |
| 340 while (true) { |
| 341 base::win::ScopedComPtr<IWbemClassObject> class_object; |
| 342 ULONG items_returned = 0; |
| 343 hr = enumerator->Next(WBEM_INFINITE, 1, class_object.Receive(), |
| 344 &items_returned); |
| 345 if (FAILED(hr)) |
| 346 return RESULT_FAILED_TO_ITERATE_RESULTS; |
| 347 |
| 348 if (hr == WBEM_S_FALSE || items_returned == 0) |
| 349 break; |
| 350 |
| 351 AvProduct av_product; |
| 352 av_product.set_product_state( |
| 353 metrics::SystemProfileProto::AntiVirusState:: |
| 354 SystemProfileProto_AntiVirusState_STATE_ON); |
| 355 |
| 356 // See definition of PRODUCT_STATE structure above for how this is being |
| 357 // used. |
| 358 base::win::ScopedVariant product_state; |
| 359 hr = class_object->Get(L"productState", 0, product_state.Receive(), 0, 0); |
| 360 |
| 361 if (FAILED(hr) || product_state.type() != VT_I4) |
| 362 return RESULT_FAILED_TO_GET_PRODUCT_STATE; |
| 363 |
| 364 LONG state_val = V_I4(product_state.ptr()); |
| 365 // Map the values from product_state to the proto values. |
| 366 switch (reinterpret_cast<PRODUCT_STATE*>(&state_val)->security_state) { |
| 367 case 0: |
| 368 av_product.set_product_state( |
| 369 metrics::SystemProfileProto::AntiVirusState:: |
| 370 SystemProfileProto_AntiVirusState_STATE_OFF); |
| 371 break; |
| 372 case 1: |
| 373 av_product.set_product_state( |
| 374 metrics::SystemProfileProto::AntiVirusState:: |
| 375 SystemProfileProto_AntiVirusState_STATE_ON); |
| 376 break; |
| 377 case 2: |
| 378 av_product.set_product_state( |
| 379 metrics::SystemProfileProto::AntiVirusState:: |
| 380 SystemProfileProto_AntiVirusState_STATE_SNOOZED); |
| 381 break; |
| 382 default: |
| 383 // unknown state. |
| 384 return RESULT_PRODUCT_STATE_INVALID; |
| 385 break; |
| 386 } |
| 387 |
| 388 base::win::ScopedVariant display_name; |
| 389 hr = class_object->Get(L"displayName", 0, display_name.Receive(), 0, 0); |
| 390 |
| 391 if (FAILED(hr) || display_name.type() != VT_BSTR) |
| 392 return RESULT_FAILED_TO_GET_PRODUCT_NAME; |
| 393 |
| 394 // Owned by ScopedVariant. |
| 395 BSTR temp_bstr = V_BSTR(display_name.ptr()); |
| 396 std::string name(base::SysWideToUTF8( |
| 397 std::wstring(temp_bstr, ::SysStringLen(temp_bstr)))); |
| 398 |
| 399 if (ShouldReportFullNames()) |
| 400 av_product.set_product_name(name); |
| 401 av_product.set_product_name_hash(metrics::HashName(name)); |
| 402 |
| 403 base::win::ScopedVariant exe_path; |
| 404 hr = class_object->Get(L"pathToSignedProductExe", 0, exe_path.Receive(), 0, |
| 405 0); |
| 406 |
| 407 if (FAILED(hr) || exe_path.type() != VT_BSTR) |
| 408 return RESULT_FAILED_TO_GET_REMEDIATION_PATH; |
| 409 |
| 410 temp_bstr = V_BSTR(exe_path.ptr()); |
| 411 std::wstring path_str(temp_bstr, ::SysStringLen(temp_bstr)); |
| 412 |
| 413 std::string product_version; |
| 414 // Not a failure if the product version cannot be read from the file on |
| 415 // disk. |
| 416 if (GetProductVersion(&path_str, &product_version)) { |
| 417 if (ShouldReportFullNames()) |
| 418 av_product.set_product_version(product_version); |
| 419 av_product.set_product_version_hash(metrics::HashName(product_version)); |
| 420 } |
| 421 |
| 422 result_list.push_back(av_product); |
| 423 } |
| 424 |
| 425 *products = std::move(result_list); |
| 426 |
| 427 return RESULT_SUCCESS; |
| 428 } |
| OLD | NEW |