chromium/chrome/installer/util/advanced_firewall_manager_win.cc

// Copyright 2014 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/40285824): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif

#include "chrome/installer/util/advanced_firewall_manager_win.h"

#include <objbase.h>

#include <stddef.h>

#include "base/logging.h"
#include "base/strings/string_number_conversions_win.h"
#include "base/strings/utf_string_conversions.h"
#include "base/uuid.h"
#include "base/win/scoped_bstr.h"
#include "base/win/scoped_variant.h"

namespace installer {

AdvancedFirewallManager::AdvancedFirewallManager() {}

AdvancedFirewallManager::~AdvancedFirewallManager() {}

bool AdvancedFirewallManager::Init(const std::wstring& app_name,
                                   const base::FilePath& app_path) {
  firewall_rules_ = nullptr;
  HRESULT hr = ::CoCreateInstance(CLSID_NetFwPolicy2, nullptr, CLSCTX_ALL,
                                  IID_PPV_ARGS(&firewall_policy_));
  if (FAILED(hr)) {
    DLOG(ERROR) << logging::SystemErrorCodeToString(hr);
    firewall_policy_ = nullptr;
    return false;
  }
  hr = firewall_policy_->get_Rules(&firewall_rules_);
  if (FAILED(hr)) {
    DLOG(ERROR) << logging::SystemErrorCodeToString(hr);
    firewall_rules_ = nullptr;
    return false;
  }
  app_name_ = app_name;
  app_path_ = app_path;
  return true;
}

bool AdvancedFirewallManager::IsFirewallEnabled() {
  long profile_types = 0;
  HRESULT hr = firewall_policy_->get_CurrentProfileTypes(&profile_types);
  if (FAILED(hr))
    return false;
  // The most-restrictive active profile takes precedence.
  const NET_FW_PROFILE_TYPE2 kProfileTypes[] = {
      NET_FW_PROFILE2_PUBLIC, NET_FW_PROFILE2_PRIVATE, NET_FW_PROFILE2_DOMAIN};
  for (size_t i = 0; i < std::size(kProfileTypes); ++i) {
    if ((profile_types & kProfileTypes[i]) != 0) {
      VARIANT_BOOL enabled = VARIANT_TRUE;
      hr = firewall_policy_->get_FirewallEnabled(kProfileTypes[i], &enabled);
      // Assume the firewall is enabled if we can't determine.
      if (FAILED(hr) || enabled != VARIANT_FALSE)
        return true;
    }
  }
  return false;
}

bool AdvancedFirewallManager::HasAnyRule() {
  std::vector<Microsoft::WRL::ComPtr<INetFwRule>> rules;
  GetAllRules(&rules);
  return !rules.empty();
}

bool AdvancedFirewallManager::AddUDPRule(const std::wstring& rule_name,
                                         const std::wstring& description,
                                         uint16_t port) {
  // Delete the rule. According MDSN |INetFwRules::Add| should replace rule with
  // same "rule identifier". It's not clear what is "rule identifier", but it
  // can successfully create many rule with same name.
  DeleteRuleByName(rule_name);

  // Create the rule and add it to the rule set (only succeeds if elevated).
  Microsoft::WRL::ComPtr<INetFwRule> udp_rule =
      CreateUDPRule(rule_name, description, port);
  if (!udp_rule.Get())
    return false;

  HRESULT hr = firewall_rules_->Add(udp_rule.Get());
  DLOG_IF(ERROR, FAILED(hr)) << logging::SystemErrorCodeToString(hr);
  return SUCCEEDED(hr);
}

void AdvancedFirewallManager::DeleteRuleByName(const std::wstring& rule_name) {
  std::vector<Microsoft::WRL::ComPtr<INetFwRule>> rules;
  GetAllRules(&rules);
  for (size_t i = 0; i < rules.size(); ++i) {
    base::win::ScopedBstr name;
    HRESULT hr = rules[i]->get_Name(name.Receive());
    if (SUCCEEDED(hr) && name.Get() && std::wstring(name.Get()) == rule_name) {
      DeleteRule(rules[i]);
    }
  }
}

void AdvancedFirewallManager::DeleteRule(
    Microsoft::WRL::ComPtr<INetFwRule> rule) {
  // Rename rule to unique name and delete by unique name. We can't just delete
  // rule by name. Multiple rules with the same name and different app are
  // possible.
  base::win::ScopedBstr unique_name(
      base::ASCIIToWide(base::Uuid::GenerateRandomV4().AsLowercaseString()));
  rule->put_Name(unique_name.Get());
  firewall_rules_->Remove(unique_name.Get());
}

void AdvancedFirewallManager::DeleteAllRules() {
  std::vector<Microsoft::WRL::ComPtr<INetFwRule>> rules;
  GetAllRules(&rules);
  for (size_t i = 0; i < rules.size(); ++i) {
    DeleteRule(rules[i]);
  }
}

Microsoft::WRL::ComPtr<INetFwRule> AdvancedFirewallManager::CreateUDPRule(
    const std::wstring& rule_name,
    const std::wstring& description,
    uint16_t port) {
  Microsoft::WRL::ComPtr<INetFwRule> udp_rule;

  HRESULT hr = ::CoCreateInstance(CLSID_NetFwRule, nullptr, CLSCTX_ALL,
                                  IID_PPV_ARGS(&udp_rule));
  if (FAILED(hr)) {
    DLOG(ERROR) << logging::SystemErrorCodeToString(hr);
    return Microsoft::WRL::ComPtr<INetFwRule>();
  }

  udp_rule->put_Name(base::win::ScopedBstr(rule_name).Get());
  udp_rule->put_Description(base::win::ScopedBstr(description).Get());
  udp_rule->put_ApplicationName(base::win::ScopedBstr(app_path_.value()).Get());
  udp_rule->put_Protocol(NET_FW_IP_PROTOCOL_UDP);
  udp_rule->put_Direction(NET_FW_RULE_DIR_IN);
  udp_rule->put_Enabled(VARIANT_TRUE);
  udp_rule->put_LocalPorts(
      base::win::ScopedBstr(base::NumberToWString(port)).Get());
  udp_rule->put_Grouping(base::win::ScopedBstr(app_name_).Get());
  udp_rule->put_Profiles(NET_FW_PROFILE2_ALL);
  udp_rule->put_Action(NET_FW_ACTION_ALLOW);

  return udp_rule;
}

void AdvancedFirewallManager::GetAllRules(
    std::vector<Microsoft::WRL::ComPtr<INetFwRule>>* rules) {
  Microsoft::WRL::ComPtr<IUnknown> rules_enum_unknown;
  HRESULT hr = firewall_rules_->get__NewEnum(&rules_enum_unknown);
  if (FAILED(hr)) {
    DLOG(ERROR) << logging::SystemErrorCodeToString(hr);
    return;
  }

  Microsoft::WRL::ComPtr<IEnumVARIANT> rules_enum;
  hr = rules_enum_unknown.As(&rules_enum);
  if (FAILED(hr)) {
    DLOG(ERROR) << logging::SystemErrorCodeToString(hr);
    return;
  }

  for (;;) {
    base::win::ScopedVariant rule_var;
    hr = rules_enum->Next(1, rule_var.Receive(), nullptr);
    DLOG_IF(ERROR, FAILED(hr)) << logging::SystemErrorCodeToString(hr);
    if (hr != S_OK)
      break;
    DCHECK_EQ(VT_DISPATCH, rule_var.type());
    if (VT_DISPATCH != rule_var.type()) {
      DLOG(ERROR) << "Unexpected type";
      continue;
    }
    Microsoft::WRL::ComPtr<INetFwRule> rule;
    hr = V_DISPATCH(rule_var.ptr())->QueryInterface(IID_PPV_ARGS(&rule));
    if (FAILED(hr)) {
      DLOG(ERROR) << logging::SystemErrorCodeToString(hr);
      continue;
    }

    base::win::ScopedBstr path;
    hr = rule->get_ApplicationName(path.Receive());
    if (FAILED(hr)) {
      DLOG(ERROR) << logging::SystemErrorCodeToString(hr);
      continue;
    }

    if (!path.Get() || !base::FilePath::CompareEqualIgnoreCase(
                           path.Get(), app_path_.value())) {
      continue;
    }

    rules->push_back(rule);
  }
}

}  // namespace installer