diff options
| author | Odd Stranne <odd@mullvad.net> | 2019-04-04 22:33:21 +0200 |
|---|---|---|
| committer | Odd Stranne <odd@mullvad.net> | 2019-04-04 22:33:21 +0200 |
| commit | 5a710f1291b438ad42eeb447d182fb9c47d88fd2 (patch) | |
| tree | d13f0a81cca5d4db1776b5a3d418f0631f4da0ca | |
| parent | 646ac0eff4253edf31934c36caa1dd1a2c778b27 (diff) | |
| parent | eca93f8828df00e6e34d7de3d50e6f324f42d408 (diff) | |
| download | mullvadvpn-5a710f1291b438ad42eeb447d182fb9c47d88fd2.tar.xz mullvadvpn-5a710f1291b438ad42eeb447d182fb9c47d88fd2.zip | |
Merge branch 'win-sticky-fw-rules'
26 files changed, 564 insertions, 104 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 7911c24e35..042a6b1950 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,10 @@ Line wrap the file at 100 chars. Th ### Fixed - Reset the tray icon padlock to the unsecured state, when losing connectivity with the daemon. +### Changed +#### Windows +- Make the firewall rules permanent until reboot, or until the daemon removes them. + ## [2019.3] - 2019-04-02 ### Fixed #### Windows diff --git a/talpid-core/src/firewall/android.rs b/talpid-core/src/firewall/android.rs index eb5d8574f9..d1959d7ae5 100644 --- a/talpid-core/src/firewall/android.rs +++ b/talpid-core/src/firewall/android.rs @@ -1,4 +1,4 @@ -use super::{FirewallPolicy, FirewallT}; +use super::{FirewallArguments, FirewallPolicy, FirewallT}; /// Stub error type for Firewall errors on Android. #[derive(Debug, err_derive::Error)] @@ -11,7 +11,7 @@ pub struct Firewall; impl FirewallT for Firewall { type Error = Error; - fn new() -> Result<Self, Self::Error> { + fn new(_args: FirewallArguments) -> Result<Self, Self::Error> { Ok(Firewall) } diff --git a/talpid-core/src/firewall/linux.rs b/talpid-core/src/firewall/linux.rs index 85aa82eafb..5c8d9e777e 100644 --- a/talpid-core/src/firewall/linux.rs +++ b/talpid-core/src/firewall/linux.rs @@ -1,4 +1,4 @@ -use super::{FirewallPolicy, FirewallT}; +use super::{FirewallArguments, FirewallPolicy, FirewallT}; use crate::tunnel; use ipnetwork::IpNetwork; use lazy_static::lazy_static; @@ -83,7 +83,7 @@ pub struct Firewall { impl FirewallT for Firewall { type Error = Error; - fn new() -> Result<Self> { + fn new(_args: FirewallArguments) -> Result<Self> { Ok(Firewall { table_name: TABLE_NAME.clone(), }) diff --git a/talpid-core/src/firewall/macos.rs b/talpid-core/src/firewall/macos.rs index 673f834d85..ba920d4e79 100644 --- a/talpid-core/src/firewall/macos.rs +++ b/talpid-core/src/firewall/macos.rs @@ -1,4 +1,4 @@ -use super::{FirewallPolicy, FirewallT}; +use super::{FirewallArguments, FirewallPolicy, FirewallT}; use pfctl::FilterRuleAction; use std::{ env, @@ -24,7 +24,7 @@ pub struct Firewall { impl FirewallT for Firewall { type Error = Error; - fn new() -> Result<Self> { + fn new(_args: FirewallArguments) -> Result<Self> { // Allows controlling whether firewall rules should log to pflog0. Useful for debugging the // rules. let firewall_debugging = env::var("TALPID_FIREWALL_DEBUG"); diff --git a/talpid-core/src/firewall/mod.rs b/talpid-core/src/firewall/mod.rs index 40aaf52040..c86ac65b68 100644 --- a/talpid-core/src/firewall/mod.rs +++ b/talpid-core/src/firewall/mod.rs @@ -131,11 +131,19 @@ pub struct Firewall { inner: imp::Firewall, } +/// Arguments required when first initializing the firewall. +pub struct FirewallArguments { + /// Determines whether the firewall should atomically enter the blocked state during init. + pub initialize_blocked: bool, + /// This argument is required for the blocked state to configure the firewall correctly. + pub allow_lan: Option<bool>, +} + impl Firewall { /// Returns a new `Firewall`, ready to apply policies. - pub fn new() -> Result<Self, Error> { + pub fn new(args: FirewallArguments) -> Result<Self, Error> { Ok(Firewall { - inner: imp::Firewall::new()?, + inner: imp::Firewall::new(args)?, }) } @@ -160,7 +168,7 @@ trait FirewallT: Sized { type Error: std::error::Error; /// Create new instance - fn new() -> Result<Self, Self::Error>; + fn new(args: FirewallArguments) -> Result<Self, Self::Error>; /// Enable the given FirewallPolicy fn apply_policy(&mut self, policy: FirewallPolicy) -> Result<(), Self::Error>; diff --git a/talpid-core/src/firewall/windows.rs b/talpid-core/src/firewall/windows.rs index 2ab290005f..e4af592f38 100644 --- a/talpid-core/src/firewall/windows.rs +++ b/talpid-core/src/firewall/windows.rs @@ -1,7 +1,7 @@ use std::{net::IpAddr, ptr}; use self::winfw::*; -use super::{FirewallPolicy, FirewallT}; +use super::{FirewallArguments, FirewallPolicy, FirewallT}; use crate::winnet; use log::{debug, error, trace}; use talpid_types::net::Endpoint; @@ -48,15 +48,29 @@ pub struct Firewall(()); impl FirewallT for Firewall { type Error = Error; - fn new() -> Result<Self, Self::Error> { - unsafe { - WinFw_Initialize( - WINFW_TIMEOUT_SECONDS, - Some(winnet::error_sink), - ptr::null_mut(), - ) - .into_result()? - }; + fn new(args: FirewallArguments) -> Result<Self, Self::Error> { + if args.initialize_blocked { + let cfg = &WinFwSettings::new(args.allow_lan.unwrap()); + unsafe { + WinFw_InitializeBlocked( + WINFW_TIMEOUT_SECONDS, + &cfg, + Some(winnet::error_sink), + ptr::null_mut(), + ) + .into_result()? + }; + } else { + unsafe { + WinFw_Initialize( + WINFW_TIMEOUT_SECONDS, + Some(winnet::error_sink), + ptr::null_mut(), + ) + .into_result()? + }; + } + trace!("Successfully initialized windows firewall module"); Ok(Firewall(())) } @@ -243,6 +257,14 @@ mod winfw { sink_context: *mut libc::c_void, ) -> InitializationResult; + #[link_name = "WinFw_InitializeBlocked"] + pub fn WinFw_InitializeBlocked( + timeout: libc::c_uint, + settings: &WinFwSettings, + sink: Option<winnet::ErrorSink>, + sink_context: *mut libc::c_void, + ) -> InitializationResult; + #[link_name = "WinFw_Deinitialize"] pub fn WinFw_Deinitialize() -> DeinitializationResult; diff --git a/talpid-core/src/tunnel_state_machine/mod.rs b/talpid-core/src/tunnel_state_machine/mod.rs index 370aad57bc..715c2d773c 100644 --- a/talpid-core/src/tunnel_state_machine/mod.rs +++ b/talpid-core/src/tunnel_state_machine/mod.rs @@ -14,7 +14,12 @@ use self::{ disconnected_state::DisconnectedState, disconnecting_state::{AfterDisconnect, DisconnectingState}, }; -use crate::{dns::DnsMonitor, firewall::Firewall, mpsc::IntoSender, offline}; +use crate::{ + dns::DnsMonitor, + firewall::{Firewall, FirewallArguments}, + mpsc::IntoSender, + offline, +}; use futures::{sync::mpsc, Async, Future, Poll, Stream}; use std::{ io, @@ -186,7 +191,18 @@ impl TunnelStateMachine { cache_dir: impl AsRef<Path>, commands: mpsc::UnboundedReceiver<TunnelCommand>, ) -> Result<Self, Error> { - let firewall = Firewall::new().map_err(Error::InitFirewallError)?; + let args = if block_when_disconnected { + FirewallArguments { + initialize_blocked: true, + allow_lan: Some(allow_lan), + } + } else { + FirewallArguments { + initialize_blocked: false, + allow_lan: None, + } + }; + let firewall = Firewall::new(args).map_err(Error::InitFirewallError)?; let dns_monitor = DnsMonitor::new(cache_dir).map_err(Error::InitDnsMonitorError)?; let mut shared_values = SharedTunnelStateValues { firewall, diff --git a/windows/libwfp b/windows/libwfp -Subproject d351ea55711894643128713c0d179dd8529e73c +Subproject 4065b5bdf56668ca09dd29bf83805f363126935 diff --git a/windows/windows-libraries b/windows/windows-libraries -Subproject d98f74cf41b4a40d7878e7d9686b3feb0d9b537 +Subproject d609c6dea5e97f615f18827d4086949b3e857f8 diff --git a/windows/winfw/src/winfw/fwcontext.cpp b/windows/winfw/src/winfw/fwcontext.cpp index f971f24b2e..b77ac82cb5 100644 --- a/windows/winfw/src/winfw/fwcontext.cpp +++ b/windows/winfw/src/winfw/fwcontext.cpp @@ -1,6 +1,7 @@ #include "stdafx.h" #include "fwcontext.h" #include "mullvadobjects.h" +#include "objectpurger.h" #include "rules/blockall.h" #include "rules/ifirewallrule.h" #include "rules/permitdhcp.h" @@ -13,6 +14,7 @@ #include "rules/restrictdns.h" #include "libwfp/transaction.h" #include "libwfp/filterengine.h" +#include "libwfp/ipaddress.h" #include <functional> #include <stdexcept> #include <utility> @@ -58,7 +60,7 @@ void AppendNetBlockedRules(FwContext::Ruleset &ruleset) FwContext::FwContext(uint32_t timeout) : m_baseline(0) { - auto engine = wfp::FilterEngine::DynamicSession(timeout); + auto engine = wfp::FilterEngine::StandardSession(timeout); // // Pass engine ownership to "session controller" @@ -73,6 +75,26 @@ FwContext::FwContext(uint32_t timeout) m_baseline = m_sessionController->checkpoint(); } +FwContext::FwContext(uint32_t timeout, const WinFwSettings &settings) + : m_baseline(0) +{ + auto engine = wfp::FilterEngine::StandardSession(timeout); + + // + // Pass engine ownership to "session controller" + // + m_sessionController = std::make_unique<SessionController>(std::move(engine)); + + uint32_t checkpoint = 0; + + if (false == applyBlockedBaseConfiguration(settings, checkpoint)) + { + throw std::runtime_error("Failed to apply base configuration in BFE."); + } + + m_baseline = checkpoint; +} + bool FwContext::applyPolicyConnecting(const WinFwSettings &settings, const WinFwRelay &relay) { Ruleset ruleset; @@ -89,7 +111,14 @@ bool FwContext::applyPolicyConnecting(const WinFwSettings &settings, const WinFw return applyRuleset(ruleset); } -bool FwContext::applyPolicyConnected(const WinFwSettings &settings, const WinFwRelay &relay, const wchar_t *tunnelInterfaceAlias, const wchar_t *v4Gateway, const wchar_t *v6Gateway) +bool FwContext::applyPolicyConnected +( + const WinFwSettings &settings, + const WinFwRelay &relay, + const wchar_t *tunnelInterfaceAlias, + const wchar_t *v4DnsHost, + const wchar_t *v6DnsHost +) { Ruleset ruleset; @@ -110,11 +139,10 @@ bool FwContext::applyPolicyConnected(const WinFwSettings &settings, const WinFwR tunnelInterfaceAlias )); - /// We currently expect DNS servers to only be ran on the tunnel gateway IPs ruleset.emplace_back(std::make_unique<rules::RestrictDns>( tunnelInterfaceAlias, - wfp::IpAddress(v4Gateway), - (v6Gateway != nullptr) ? std::make_unique<wfp::IpAddress>(v6Gateway) : nullptr + wfp::IpAddress(v4DnsHost), + (v6DnsHost != nullptr) ? std::make_unique<wfp::IpAddress>(v6DnsHost) : nullptr )); return applyRuleset(ruleset); @@ -122,51 +150,89 @@ bool FwContext::applyPolicyConnected(const WinFwSettings &settings, const WinFwR bool FwContext::applyPolicyBlocked(const WinFwSettings &settings) { + return applyRuleset(composePolicyBlocked(settings)); +} + +bool FwContext::reset() +{ + return m_sessionController->executeTransaction([this](SessionController &controller, wfp::FilterEngine &) + { + return controller.revert(m_baseline), true; + }); +} + +FwContext::Ruleset FwContext::composePolicyBlocked(const WinFwSettings &settings) +{ Ruleset ruleset; AppendNetBlockedRules(ruleset); AppendSettingsRules(ruleset, settings); - return applyRuleset(ruleset); + return ruleset; } -bool FwContext::reset() +bool FwContext::applyBaseConfiguration() { - return m_sessionController->executeTransaction([this]() + return m_sessionController->executeTransaction([this](SessionController &controller, wfp::FilterEngine &engine) { - m_sessionController->revert(m_baseline); - return true; + return applyCommonBaseConfiguration(controller, engine); }); } -bool FwContext::applyRuleset(const Ruleset &ruleset) +bool FwContext::applyBlockedBaseConfiguration(const WinFwSettings &settings, uint32_t &checkpoint) { - return m_sessionController->executeTransaction([&]() + return m_sessionController->executeTransaction([&](SessionController &controller, wfp::FilterEngine &engine) { - m_sessionController->revert(m_baseline); - - for (const auto &rule : ruleset) + if (false == applyCommonBaseConfiguration(controller, engine)) { - if (false == rule->apply(*m_sessionController)) - { - return false; - } + return false; } - return true; + // + // Record the current session state with only structural objects added. + // If we snapshot at a later time we'd accidentally include the blocking policy rules + // in the baseline checkpoint. + // + checkpoint = controller.peekCheckpoint(); + + return applyRulesetDirectly(composePolicyBlocked(settings), controller); }); } -bool FwContext::applyBaseConfiguration() +bool FwContext::applyCommonBaseConfiguration(SessionController &controller, wfp::FilterEngine &engine) { - return m_sessionController->executeTransaction([&]() - { - // - // Install structural objects - // + // + // Since we're using a standard WFP session we can make no assumptions + // about which objects are already installed since before. + // + ObjectPurger::GetRemoveAllFunctor()(engine); + + // + // Install structural objects + // + return controller.addProvider(*MullvadObjects::Provider()) + && controller.addSublayer(*MullvadObjects::SublayerWhitelist()) + && controller.addSublayer(*MullvadObjects::SublayerBlacklist()); +} - return m_sessionController->addProvider(*MullvadObjects::Provider()) - && m_sessionController->addSublayer(*MullvadObjects::SublayerWhitelist()) - && m_sessionController->addSublayer(*MullvadObjects::SublayerBlacklist()); +bool FwContext::applyRuleset(const Ruleset &ruleset) +{ + return m_sessionController->executeTransaction([&](SessionController &controller, wfp::FilterEngine &) + { + controller.revert(m_baseline); + return applyRulesetDirectly(ruleset, controller); }); } + +bool FwContext::applyRulesetDirectly(const Ruleset &ruleset, SessionController &controller) +{ + for (const auto &rule : ruleset) + { + if (false == rule->apply(controller)) + { + return false; + } + } + + return true; +} diff --git a/windows/winfw/src/winfw/fwcontext.h b/windows/winfw/src/winfw/fwcontext.h index 65c45ac539..89ef40e1d3 100644 --- a/windows/winfw/src/winfw/fwcontext.h +++ b/windows/winfw/src/winfw/fwcontext.h @@ -13,8 +13,18 @@ public: FwContext(uint32_t timeout); + // This ctor applies the "blocked" policy. + FwContext(uint32_t timeout, const WinFwSettings &settings); + bool applyPolicyConnecting(const WinFwSettings &settings, const WinFwRelay &relay); - bool applyPolicyConnected(const WinFwSettings &settings, const WinFwRelay &relay, const wchar_t *tunnelInterfaceAlias, const wchar_t *v4DnsHosts, const wchar_t *v6DnsHost); + bool applyPolicyConnected + ( + const WinFwSettings &settings, + const WinFwRelay &relay, + const wchar_t *tunnelInterfaceAlias, + const wchar_t *v4DnsHost, + const wchar_t *v6DnsHost + ); bool applyPolicyBlocked(const WinFwSettings &settings); bool reset(); @@ -26,8 +36,14 @@ private: FwContext(const FwContext &) = delete; FwContext &operator=(const FwContext &) = delete; + Ruleset composePolicyBlocked(const WinFwSettings &settings); + bool applyBaseConfiguration(); + bool applyBlockedBaseConfiguration(const WinFwSettings &settings, uint32_t &checkpoint); + bool applyCommonBaseConfiguration(SessionController &controller, wfp::FilterEngine &engine); + bool applyRuleset(const Ruleset &ruleset); + bool applyRulesetDirectly(const Ruleset &ruleset, SessionController &controller); std::unique_ptr<SessionController> m_sessionController; diff --git a/windows/winfw/src/winfw/guidhash.h b/windows/winfw/src/winfw/guidhash.h new file mode 100644 index 0000000000..6d730835a7 --- /dev/null +++ b/windows/winfw/src/winfw/guidhash.h @@ -0,0 +1,25 @@ +#pragma once + +#include <cstdint> +#include <utility> +#include <guiddef.h> + +// Specialize std::hash +namespace std +{ + +template<> +struct hash<GUID> +{ + size_t operator()(const GUID &guid) const noexcept + { + static_assert(sizeof(GUID) == (2 * sizeof(uint64_t))); + + // MOV on x86 supports non-aligned access. + auto data = reinterpret_cast<const uint64_t *>(&guid); + + return hash<uint64_t>()(data[0] ^ data[1]); + } +}; + +} diff --git a/windows/winfw/src/winfw/mullvadguids.cpp b/windows/winfw/src/winfw/mullvadguids.cpp index 8f717d329b..e68312957f 100644 --- a/windows/winfw/src/winfw/mullvadguids.cpp +++ b/windows/winfw/src/winfw/mullvadguids.cpp @@ -1,5 +1,82 @@ #include "stdafx.h" #include "mullvadguids.h" +#include <algorithm> +#include <iterator> + +//static +WfpObjectRegistry MullvadGuids::BuildRegistry() +{ + const auto detailedRegistry = DetailedRegistry(); + using ValueType = decltype(detailedRegistry)::const_reference; + + std::unordered_set<GUID> registry; + + std::transform(detailedRegistry.begin(), detailedRegistry.end(), std::inserter(registry, registry.end()), [](ValueType value) + { + return value.second; + }); + + return registry; +} + +//static +DetailedWfpObjectRegistry MullvadGuids::BuildDetailedRegistry() +{ + std::multimap<WfpObjectType, GUID> registry; + + registry.insert(std::make_pair(WfpObjectType::Provider, Provider())); + registry.insert(std::make_pair(WfpObjectType::Sublayer, SublayerWhitelist())); + registry.insert(std::make_pair(WfpObjectType::Sublayer, SublayerBlacklist())); + registry.insert(std::make_pair(WfpObjectType::Filter, FilterBlockAll_Outbound_Ipv4())); + registry.insert(std::make_pair(WfpObjectType::Filter, FilterBlockAll_Outbound_Ipv6())); + registry.insert(std::make_pair(WfpObjectType::Filter, FilterBlockAll_Inbound_Ipv4())); + registry.insert(std::make_pair(WfpObjectType::Filter, FilterBlockAll_Inbound_Ipv6())); + registry.insert(std::make_pair(WfpObjectType::Filter, FilterPermitLan_10_8())); + registry.insert(std::make_pair(WfpObjectType::Filter, FilterPermitLan_172_16_12())); + registry.insert(std::make_pair(WfpObjectType::Filter, FilterPermitLan_192_168_16())); + registry.insert(std::make_pair(WfpObjectType::Filter, FilterPermitLan_169_254_16())); + registry.insert(std::make_pair(WfpObjectType::Filter, FilterPermitLan_Multicast())); + registry.insert(std::make_pair(WfpObjectType::Filter, FilterPermitLan_Ipv6_fe80_10())); + registry.insert(std::make_pair(WfpObjectType::Filter, FilterPermitLan_Ipv6_Multicast())); + registry.insert(std::make_pair(WfpObjectType::Filter, FilterPermitLanService_10_8())); + registry.insert(std::make_pair(WfpObjectType::Filter, FilterPermitLanService_172_16_12())); + registry.insert(std::make_pair(WfpObjectType::Filter, FilterPermitLanService_192_168_16())); + registry.insert(std::make_pair(WfpObjectType::Filter, FilterPermitLanService_169_254_16())); + registry.insert(std::make_pair(WfpObjectType::Filter, FilterPermitLanService_Ipv6_fe80_10())); + registry.insert(std::make_pair(WfpObjectType::Filter, FilterPermitLoopback_Outbound_Ipv4())); + registry.insert(std::make_pair(WfpObjectType::Filter, FilterPermitLoopback_Outbound_Ipv6())); + registry.insert(std::make_pair(WfpObjectType::Filter, FilterPermitLoopback_Inbound_Ipv4())); + registry.insert(std::make_pair(WfpObjectType::Filter, FilterPermitLoopback_Inbound_Ipv6())); + registry.insert(std::make_pair(WfpObjectType::Filter, FilterPermitDhcpV4_Outbound_Request())); + registry.insert(std::make_pair(WfpObjectType::Filter, FilterPermitDhcpV6_Outbound_Request())); + registry.insert(std::make_pair(WfpObjectType::Filter, FilterPermitDhcpV4_Inbound_Response())); + registry.insert(std::make_pair(WfpObjectType::Filter, FilterPermitDhcpV6_Inbound_Response())); + registry.insert(std::make_pair(WfpObjectType::Filter, FilterPermitVpnRelay())); + registry.insert(std::make_pair(WfpObjectType::Filter, FilterPermitVpnTunnel_Outbound_Ipv4())); + registry.insert(std::make_pair(WfpObjectType::Filter, FilterPermitVpnTunnel_Outbound_Ipv6())); + registry.insert(std::make_pair(WfpObjectType::Filter, FilterRestrictDns_Outbound_Ipv4())); + registry.insert(std::make_pair(WfpObjectType::Filter, FilterRestrictDns_Outbound_Ipv6())); + registry.insert(std::make_pair(WfpObjectType::Filter, FilterRestrictDns_Outbound_Tunnel_Ipv4())); + registry.insert(std::make_pair(WfpObjectType::Filter, FilterRestrictDns_Outbound_Tunnel_Ipv6())); + registry.insert(std::make_pair(WfpObjectType::Filter, FilterPermitVpnTunnelService_Ipv4())); + registry.insert(std::make_pair(WfpObjectType::Filter, FilterPermitVpnTunnelService_Ipv6())); + + return registry; +} + +//static +const WfpObjectRegistry &MullvadGuids::Registry() +{ + static auto registry = BuildRegistry(); // TODO: Thread safety. + return registry; +} + +//static +const DetailedWfpObjectRegistry &MullvadGuids::DetailedRegistry() +{ + static auto registry = BuildDetailedRegistry(); // TODO: Thread safety. + return registry; +} //static const GUID &MullvadGuids::Provider() diff --git a/windows/winfw/src/winfw/mullvadguids.h b/windows/winfw/src/winfw/mullvadguids.h index 3b9e9bbecf..04cad0a6c8 100644 --- a/windows/winfw/src/winfw/mullvadguids.h +++ b/windows/winfw/src/winfw/mullvadguids.h @@ -1,10 +1,24 @@ #pragma once + +#include "wfpobjecttype.h" +#include "guidhash.h" #include <guiddef.h> +#include <unordered_set> +#include <map> + +using WfpObjectRegistry = std::unordered_set<GUID>; +using DetailedWfpObjectRegistry = std::multimap<WfpObjectType, GUID>; class MullvadGuids { + static WfpObjectRegistry BuildRegistry(); + static DetailedWfpObjectRegistry BuildDetailedRegistry(); + public: + static const WfpObjectRegistry &Registry(); + static const DetailedWfpObjectRegistry &DetailedRegistry(); + MullvadGuids() = delete; static const GUID &Provider(); diff --git a/windows/winfw/src/winfw/objectpurger.cpp b/windows/winfw/src/winfw/objectpurger.cpp new file mode 100644 index 0000000000..60c32cd53e --- /dev/null +++ b/windows/winfw/src/winfw/objectpurger.cpp @@ -0,0 +1,68 @@ +#include "stdafx.h" +#include "objectpurger.h" +#include "mullvadguids.h" +#include "wfpobjecttype.h" +#include "libwfp/filterengine.h" +#include "libwfp/objectdeleter.h" +#include "libwfp/transaction.h" +#include <algorithm> + +namespace +{ + +using ObjectDeleter = std::function<void(wfp::FilterEngine &, const GUID &)>; + +template<typename TRange> +void RemoveRange(wfp::FilterEngine &engine, ObjectDeleter deleter, TRange range) +{ + std::for_each(range.first, range.second, [&](const auto &record) + { + const GUID &objectId = record.second; + deleter(engine, objectId); + }); +} + +} // anonymous namespace + +//static +ObjectPurger::RemovalFunctor ObjectPurger::GetRemoveFiltersFunctor() +{ + return [](wfp::FilterEngine &engine) + { + const auto registry = MullvadGuids::DetailedRegistry(); + + // Resolve correct overload. + void (*deleter)(wfp::FilterEngine &, const GUID &) = wfp::ObjectDeleter::DeleteFilter; + + RemoveRange(engine, deleter, registry.equal_range(WfpObjectType::Filter)); + }; +} + +//static +ObjectPurger::RemovalFunctor ObjectPurger::GetRemoveAllFunctor() +{ + return [](wfp::FilterEngine &engine) + { + const auto registry = MullvadGuids::DetailedRegistry(); + + // Resolve correct overload. + void(*deleter)(wfp::FilterEngine &, const GUID &) = wfp::ObjectDeleter::DeleteFilter; + + RemoveRange(engine, deleter, registry.equal_range(WfpObjectType::Filter)); + RemoveRange(engine, wfp::ObjectDeleter::DeleteSublayer, registry.equal_range(WfpObjectType::Sublayer)); + RemoveRange(engine, wfp::ObjectDeleter::DeleteProvider, registry.equal_range(WfpObjectType::Provider)); + }; +} + +//static +bool ObjectPurger::Execute(RemovalFunctor f) +{ + auto engine = wfp::FilterEngine::StandardSession(); + + auto wrapper = [&]() + { + return f(*engine), true; + }; + + return wfp::Transaction::Execute(*engine, wrapper); +} diff --git a/windows/winfw/src/winfw/objectpurger.h b/windows/winfw/src/winfw/objectpurger.h new file mode 100644 index 0000000000..62f7ce2e11 --- /dev/null +++ b/windows/winfw/src/winfw/objectpurger.h @@ -0,0 +1,20 @@ +#pragma once + +#include "winfw.h" +#include "libwfp/filterengine.h" +#include <cstdint> +#include <functional> + +class ObjectPurger +{ +public: + + ObjectPurger() = delete; + + using RemovalFunctor = std::function<void(wfp::FilterEngine &engine)>; + + static RemovalFunctor GetRemoveFiltersFunctor(); + static RemovalFunctor GetRemoveAllFunctor(); + + static bool Execute(RemovalFunctor f); +}; diff --git a/windows/winfw/src/winfw/sessioncontroller.cpp b/windows/winfw/src/winfw/sessioncontroller.cpp index 2965233600..f3f948cd44 100644 --- a/windows/winfw/src/winfw/sessioncontroller.cpp +++ b/windows/winfw/src/winfw/sessioncontroller.cpp @@ -1,10 +1,13 @@ #include "stdafx.h" #include "sessioncontroller.h" +#include "wfpobjecttype.h" +#include "mullvadguids.h" #include "libwfp/objectinstaller.h" #include "libwfp/objectdeleter.h" #include "libwfp/transaction.h" #include "libcommon/memory.h" #include <utility> +#include <stdexcept> namespace { @@ -54,6 +57,17 @@ bool CheckpointKeyToIndex(const std::vector<SessionRecord> &container, uint32_t return false; } +void ValidateObject(const wfp::IIdentifiable &object) +{ + const auto registry = MullvadGuids::Registry(); + + if (registry.end() == registry.find(object.id())) + { + throw std::runtime_error("Attempting to install non-registered WFP object"); + } +} + + } // anonymous namespace SessionController::SessionController(std::unique_ptr<wfp::FilterEngine> &&engine) @@ -70,7 +84,7 @@ SessionController::~SessionController() try { - executeTransaction([this]() + executeTransaction([this](SessionController &, wfp::FilterEngine &) { reset(); return true; @@ -89,13 +103,15 @@ bool SessionController::addProvider(wfp::ProviderBuilder &providerBuilder) throw std::runtime_error("Cannot add provider outside transaction"); } + ValidateObject(providerBuilder); + GUID key; auto status = wfp::ObjectInstaller::AddProvider(*m_engine, providerBuilder, &key); if (status) { - m_transactionRecords.emplace_back(SessionRecord(key, SessionRecord::ObjectType::Provider)); + m_transactionRecords.emplace_back(SessionRecord(key, WfpObjectType::Provider)); } return status; @@ -108,13 +124,15 @@ bool SessionController::addSublayer(wfp::SublayerBuilder &sublayerBuilder) throw std::runtime_error("Cannot add sublayer outside transaction"); } + ValidateObject(sublayerBuilder); + GUID key; auto status = wfp::ObjectInstaller::AddSublayer(*m_engine, sublayerBuilder, &key); if (status) { - m_transactionRecords.emplace_back(SessionRecord(key, SessionRecord::ObjectType::Sublayer)); + m_transactionRecords.emplace_back(SessionRecord(key, WfpObjectType::Sublayer)); } return status; @@ -127,6 +145,8 @@ bool SessionController::addFilter(wfp::FilterBuilder &filterBuilder, const wfp:: throw std::runtime_error("Cannot add filter outside transaction"); } + ValidateObject(filterBuilder); + UINT64 id; auto status = wfp::ObjectInstaller::AddFilter(*m_engine, filterBuilder, conditionBuilder, &id); @@ -139,7 +159,7 @@ bool SessionController::addFilter(wfp::FilterBuilder &filterBuilder, const wfp:: return status; } -bool SessionController::executeTransaction(std::function<bool()> operation) +bool SessionController::executeTransaction(TransactionFunctor operation) { if (m_activeTransaction.exchange(true)) { @@ -155,7 +175,12 @@ bool SessionController::executeTransaction(std::function<bool()> operation) m_transactionRecords = m_records; - auto status = wfp::Transaction::Execute(*m_engine, operation); + auto transactionForwarder = [this, operation]() + { + return operation(*this, *m_engine); + }; + + auto status = wfp::Transaction::Execute(*m_engine, transactionForwarder); if (status) { @@ -165,7 +190,7 @@ bool SessionController::executeTransaction(std::function<bool()> operation) return status; } -bool SessionController::executeReadOnlyTransaction(std::function<bool()> operation) +bool SessionController::executeReadOnlyTransaction(TransactionFunctor operation) { if (m_activeTransaction.exchange(true)) { @@ -179,7 +204,12 @@ bool SessionController::executeReadOnlyTransaction(std::function<bool()> operati m_activeTransaction.store(false); }; - return wfp::Transaction::ExecuteReadOnly(*m_engine, operation); + auto transactionForwarder = [this, operation]() + { + return operation(*this, *m_engine); + }; + + return wfp::Transaction::ExecuteReadOnly(*m_engine, transactionForwarder); } uint32_t SessionController::checkpoint() @@ -197,6 +227,16 @@ uint32_t SessionController::checkpoint() return m_records.back().key(); } +uint32_t SessionController::peekCheckpoint() +{ + if (m_transactionRecords.empty()) + { + return 0; + } + + return m_transactionRecords.back().key(); +} + void SessionController::revert(uint32_t key) { if (false == m_activeTransaction) diff --git a/windows/winfw/src/winfw/sessioncontroller.h b/windows/winfw/src/winfw/sessioncontroller.h index 61163533c4..243a501083 100644 --- a/windows/winfw/src/winfw/sessioncontroller.h +++ b/windows/winfw/src/winfw/sessioncontroller.h @@ -3,6 +3,8 @@ #include "iobjectinstaller.h" #include "sessionrecord.h" #include "libwfp/filterengine.h" +#include "libwfp/iidentifiable.h" +#include <functional> #include <atomic> #include <memory> #include <vector> @@ -18,8 +20,10 @@ public: bool addSublayer(wfp::SublayerBuilder &sublayerBuilder) override; bool addFilter(wfp::FilterBuilder &filterBuilder, const wfp::IConditionBuilder &conditionBuilder) override; - bool executeTransaction(std::function<bool()> operation); - bool executeReadOnlyTransaction(std::function<bool()> operation); + using TransactionFunctor = std::function<bool(SessionController &, wfp::FilterEngine &)>; + + bool executeTransaction(TransactionFunctor operation); + bool executeReadOnlyTransaction(TransactionFunctor operation); // // Retrieve checkpoint key that can be used to restore the current session state @@ -28,6 +32,11 @@ public: uint32_t checkpoint(); // + // Hack. Read checkpoint while currently inside a transaction. + // + uint32_t peekCheckpoint(); + + // // Purge objects in the stack and return to an earlier state // Use only inside active transaction // diff --git a/windows/winfw/src/winfw/sessionrecord.cpp b/windows/winfw/src/winfw/sessionrecord.cpp index ad11f4c013..f57b06afc1 100644 --- a/windows/winfw/src/winfw/sessionrecord.cpp +++ b/windows/winfw/src/winfw/sessionrecord.cpp @@ -12,7 +12,7 @@ std::atomic<uint32_t> g_keybase = 0; } // anonymous namespace -SessionRecord::SessionRecord(const GUID &id, ObjectType type) +SessionRecord::SessionRecord(const GUID &id, WfpObjectType type) : m_type(type) , m_id(id) , m_key(g_keybase++) @@ -20,7 +20,7 @@ SessionRecord::SessionRecord(const GUID &id, ObjectType type) } SessionRecord::SessionRecord(UINT64 id) - : m_type(ObjectType::Filter) + : m_type(WfpObjectType::Filter) , m_filterId(id) , m_key(g_keybase++) { @@ -30,17 +30,17 @@ void SessionRecord::purge(wfp::FilterEngine &engine) { switch (m_type) { - case ObjectType::Provider: + case WfpObjectType::Provider: { wfp::ObjectDeleter::DeleteProvider(engine, m_id); break; } - case ObjectType::Sublayer: + case WfpObjectType::Sublayer: { wfp::ObjectDeleter::DeleteSublayer(engine, m_id); break; } - case ObjectType::Filter: + case WfpObjectType::Filter: { wfp::ObjectDeleter::DeleteFilter(engine, m_filterId); break; diff --git a/windows/winfw/src/winfw/sessionrecord.h b/windows/winfw/src/winfw/sessionrecord.h index 8ad3be8c86..761a4c863f 100644 --- a/windows/winfw/src/winfw/sessionrecord.h +++ b/windows/winfw/src/winfw/sessionrecord.h @@ -1,6 +1,7 @@ #pragma once #include "libwfp/filterengine.h" +#include "wfpobjecttype.h" #include <guiddef.h> #include <windows.h> @@ -8,14 +9,7 @@ class SessionRecord { public: - enum class ObjectType - { - Provider, - Sublayer, - Filter - }; - - SessionRecord(const GUID &id, ObjectType type); + SessionRecord(const GUID &id, WfpObjectType type); SessionRecord(UINT64 id); SessionRecord(const SessionRecord &) = default; @@ -28,7 +22,7 @@ public: private: - ObjectType m_type; + WfpObjectType m_type; GUID m_id; UINT64 m_filterId; diff --git a/windows/winfw/src/winfw/wfpobjecttype.h b/windows/winfw/src/winfw/wfpobjecttype.h new file mode 100644 index 0000000000..0e31da2969 --- /dev/null +++ b/windows/winfw/src/winfw/wfpobjecttype.h @@ -0,0 +1,8 @@ +#pragma once + +enum class WfpObjectType +{ + Provider, + Sublayer, + Filter +}; diff --git a/windows/winfw/src/winfw/winfw.cpp b/windows/winfw/src/winfw/winfw.cpp index f391d691b2..7b9ea2dc6b 100644 --- a/windows/winfw/src/winfw/winfw.cpp +++ b/windows/winfw/src/winfw/winfw.cpp @@ -1,7 +1,7 @@ #include "stdafx.h" #include "winfw.h" #include "fwcontext.h" -#include "libwfp/ipaddress.h" +#include "objectpurger.h" #include <windows.h> #include <stdexcept> @@ -10,8 +10,8 @@ namespace uint32_t g_timeout = 0; -WinFwErrorSink g_ErrorSink = nullptr; -void * g_ErrorContext = nullptr; +WinFwErrorSink g_errorSink = nullptr; +void * g_errorContext = nullptr; FwContext *g_fwContext = nullptr; @@ -38,8 +38,8 @@ WinFw_Initialize( // Convert seconds to milliseconds. g_timeout = timeout * 1000; - g_ErrorSink = errorSink; - g_ErrorContext = errorContext; + g_errorSink = errorSink; + g_errorContext = errorContext; try { @@ -47,9 +47,56 @@ WinFw_Initialize( } catch (std::exception &err) { - if (nullptr != g_ErrorSink) + if (nullptr != g_errorSink) { - g_ErrorSink(err.what(), g_ErrorContext); + g_errorSink(err.what(), g_errorContext); + } + + return false; + } + catch (...) + { + return false; + } + + return true; +} + +extern "C" +WINFW_LINKAGE +bool +WINFW_API +WinFw_InitializeBlocked( + uint32_t timeout, + const WinFwSettings &settings, + WinFwErrorSink errorSink, + void *errorContext +) +{ + if (nullptr != g_fwContext) + { + // + // This is an error. + // The existing instance may have a different timeout etc. + // + return false; + } + + // Convert seconds to milliseconds. + g_timeout = timeout * 1000; + + g_errorSink = errorSink; + g_errorContext = errorContext; + + try + { + g_fwContext = new FwContext(g_timeout, settings); + } + catch (std::exception &err) + { + if (nullptr != g_errorSink) + { + g_errorSink(err.what(), g_errorContext); } return false; @@ -97,9 +144,9 @@ WinFw_ApplyPolicyConnecting( } catch (std::exception &err) { - if (nullptr != g_ErrorSink) + if (nullptr != g_errorSink) { - g_ErrorSink(err.what(), g_ErrorContext); + g_errorSink(err.what(), g_errorContext); } return false; @@ -117,8 +164,8 @@ WinFw_ApplyPolicyConnected( const WinFwSettings &settings, const WinFwRelay &relay, const wchar_t *tunnelInterfaceAlias, - const wchar_t *v4Gateway, - const wchar_t *v6Gateway + const wchar_t *v4DnsHost, + const wchar_t *v6DnsHost ) { if (nullptr == g_fwContext) @@ -128,13 +175,13 @@ WinFw_ApplyPolicyConnected( try { - return g_fwContext->applyPolicyConnected(settings, relay, tunnelInterfaceAlias, v4Gateway, v6Gateway); + return g_fwContext->applyPolicyConnected(settings, relay, tunnelInterfaceAlias, v4DnsHost, v6DnsHost); } catch (std::exception &err) { - if (nullptr != g_ErrorSink) + if (nullptr != g_errorSink) { - g_ErrorSink(err.what(), g_ErrorContext); + g_errorSink(err.what(), g_errorContext); } return false; @@ -163,9 +210,9 @@ WinFw_ApplyPolicyBlocked( } catch (std::exception &err) { - if (nullptr != g_ErrorSink) + if (nullptr != g_errorSink) { - g_ErrorSink(err.what(), g_ErrorContext); + g_errorSink(err.what(), g_errorContext); } return false; @@ -181,24 +228,20 @@ bool WINFW_API WinFw_Reset() { - if (nullptr == g_fwContext) - { - // - // This is OK because the practical difference between having no instance - // and having a reset instance is negligible. - // - return true; - } - try { + if (nullptr == g_fwContext) + { + return ObjectPurger::Execute(ObjectPurger::GetRemoveAllFunctor()); + } + return g_fwContext->reset(); } catch (std::exception &err) { - if (nullptr != g_ErrorSink) + if (nullptr != g_errorSink) { - g_ErrorSink(err.what(), g_ErrorContext); + g_errorSink(err.what(), g_errorContext); } return false; diff --git a/windows/winfw/src/winfw/winfw.def b/windows/winfw/src/winfw/winfw.def index e8ef663dae..20d59bb4c8 100644 --- a/windows/winfw/src/winfw/winfw.def +++ b/windows/winfw/src/winfw/winfw.def @@ -2,6 +2,7 @@ LIBRARY winfw EXPORTS WinFw_Initialize +WinFw_InitializeBlocked WinFw_Deinitialize WinFw_ApplyPolicyConnecting WinFw_ApplyPolicyConnected diff --git a/windows/winfw/src/winfw/winfw.h b/windows/winfw/src/winfw/winfw.h index f0c1adb2dc..95e66a608f 100644 --- a/windows/winfw/src/winfw/winfw.h +++ b/windows/winfw/src/winfw/winfw.h @@ -73,6 +73,27 @@ WinFw_Initialize( ); // +// WinFw_InitializeBlocked +// +// Same as `WinFw_Initialize` with the addition that the blocked policy is +// immediately applied, within the same initialization transaction. +// +// This function is preferred rather than first initializing and then applying +// the blocked policy. Using two separate operations leaves a tiny window +// for traffic to leak out. +// +extern "C" +WINFW_LINKAGE +bool +WINFW_API +WinFw_InitializeBlocked( + uint32_t timeout, + const WinFwSettings &settings, + WinFwErrorSink errorSink, + void *errorContext +); + +// // Deinitialize: // // Call this function once before unloading WINFW or exiting the process. @@ -112,7 +133,7 @@ WinFw_ApplyPolicyConnecting( // // tunnelInterfaceAlias: // Friendly name of VPN tunnel interface -// primaryDns: +// v4DnsHost/v6DnsHost: // String encoded IP address of DNS to use inside tunnel // extern "C" @@ -123,8 +144,8 @@ WinFw_ApplyPolicyConnected( const WinFwSettings &settings, const WinFwRelay &relay, const wchar_t *tunnelInterfaceAlias, - const wchar_t *v4Gateway, - const wchar_t *v6Gateway + const wchar_t *v4DnsHost, + const wchar_t *v6DnsHost ); // diff --git a/windows/winfw/src/winfw/winfw.vcxproj b/windows/winfw/src/winfw/winfw.vcxproj index 7f6a919cd2..e2db2fd432 100644 --- a/windows/winfw/src/winfw/winfw.vcxproj +++ b/windows/winfw/src/winfw/winfw.vcxproj @@ -22,6 +22,7 @@ <ClCompile Include="dllmain.cpp" /> <ClCompile Include="mullvadguids.cpp" /> <ClCompile Include="mullvadobjects.cpp" /> + <ClCompile Include="objectpurger.cpp" /> <ClCompile Include="rules\blockall.cpp" /> <ClCompile Include="rules\permitdhcp.cpp" /> <ClCompile Include="rules\permitlan.cpp" /> @@ -43,9 +44,12 @@ <ClCompile Include="winfw.cpp" /> </ItemGroup> <ItemGroup> + <ClInclude Include="guidhash.h" /> <ClInclude Include="iobjectinstaller.h" /> <ClInclude Include="mullvadguids.h" /> <ClInclude Include="mullvadobjects.h" /> + <ClInclude Include="objectpurger.h" /> + <ClInclude Include="wfpobjecttype.h" /> <ClInclude Include="rules\blockall.h" /> <ClInclude Include="rules\ifirewallrule.h" /> <ClInclude Include="rules\permitdhcp.h" /> diff --git a/windows/winfw/src/winfw/winfw.vcxproj.filters b/windows/winfw/src/winfw/winfw.vcxproj.filters index a43d966614..8ccdaa4627 100644 --- a/windows/winfw/src/winfw/winfw.vcxproj.filters +++ b/windows/winfw/src/winfw/winfw.vcxproj.filters @@ -36,6 +36,7 @@ <ClCompile Include="rules\permitvpntunnelservice.cpp"> <Filter>rules</Filter> </ClCompile> + <ClCompile Include="objectpurger.cpp" /> </ItemGroup> <ItemGroup> <ClInclude Include="stdafx.h" /> @@ -77,6 +78,9 @@ <ClInclude Include="rules\permitvpntunnelservice.h"> <Filter>rules</Filter> </ClInclude> + <ClInclude Include="wfpobjecttype.h" /> + <ClInclude Include="guidhash.h" /> + <ClInclude Include="objectpurger.h" /> </ItemGroup> <ItemGroup> <Filter Include="rules"> |
