Merge branch 'connect-redirect-localhost'

This commit is contained in:
Odd Stranne
2021-05-25 17:26:39 +02:00
19 changed files with 2180 additions and 520 deletions

View File

@@ -20,7 +20,10 @@ Line wrap the file at 100 chars. Th
* **Security**: in case of vulnerabilities.
## [Unreleased]
### Changed
Use improved model to determine when to split traffic. This has the following effects:
- TCP client sockets connecting to localhost can now be used successfully.
- Routing now works as expected, e.g. when being connected to multiple LANs.
## [1.0.2.0] - 2021-05-04
### Changed

View File

@@ -119,13 +119,13 @@ From the point of view of the driver, all DNS requests are made by a particular
This can be mitigated for individual apps if they can be configured to use DoT/DoH.
## Localhost communications
## Localhost UDP communications
Excluded apps aren't allowed to bind to `inaddr_any`. If a client socket isn't explicitly bound prior to calling `connect()`, the socket will momentarily be seen in the system as binding/bound towards `inaddr_any`, before the correct binding is realized. The driver sees the initial bind and redirects it to the primary network interface.
Excluded apps aren't allowed to bind to `inaddr_any`. In certain cases, if a client socket isn't explicitly bound, the socket will momentarily be seen in the system as binding/bound towards `inaddr_any`, before the correct binding is realized. The driver sees the initial bind and redirects it to the primary network interface.
This means that, for excluded apps, if a socket isn't explicitly bound to `127.0.0.1` before connecting, it won't be able to talk to localhost.
This means that, for excluded apps, if a UDP socket isn't explicitly bound to `127.0.0.1` before sending, it won't be able to talk to localhost.
Because explicitly bound client sockets are rare, it can be expected that most excluded apps are affected. No generally applicable mitigations are available.
Because explicitly bound UDP client sockets are rare, it can be expected that most excluded apps are affected. No generally applicable mitigations are available.
## Multicast reception

View File

@@ -1,283 +0,0 @@
#include "asyncbind.h"
#include "../util.h"
#include "../trace.h"
#include "asyncbind.tmh"
namespace firewall
{
namespace
{
const ULONGLONG RECORD_MAX_LIFETIME_MS = 10000;
bool
FailBindRequest
(
HANDLE ProcessId,
FWPS_CLASSIFY_OUT0 *ClassifyOut,
UINT64 ClassifyHandle,
UINT64 FilterId
)
{
DbgPrint("Failing bind request from process %p\n", ProcessId);
//
// There doesn't seem to be any support in WFP for blocking a bind request.
// Specifying `FWP_ACTION_BLOCK` will just resume request processing.
// So the best we can do is rewrite the bind to do as little harm as possible.
//
FWPS_BIND_REQUEST0 *bindRequest = NULL;
auto status = FwpsAcquireWritableLayerDataPointer0
(
ClassifyHandle,
FilterId,
0,
(PVOID*)&bindRequest,
ClassifyOut
);
if (!NT_SUCCESS(status))
{
DbgPrint("FwpsAcquireWritableLayerDataPointer0() failed 0x%X\n", status);
return false;
}
ClassifyOut->actionType = FWP_ACTION_PERMIT;
ClassifyOut->rights &= ~FWPS_RIGHT_ACTION_WRITE;
//
// This sets the port to 0, as well.
//
INETADDR_SETLOOPBACK((PSOCKADDR)&(bindRequest->localAddressAndPort));
FwpsApplyModifiedLayerData0(ClassifyHandle, (PVOID*)&bindRequest, 0);
return true;
}
void
ReauthPendedBindRequest
(
PENDED_BIND *Record
)
{
DbgPrint("Requesting re-auth for bind request from process %p\n", Record->ProcessId);
FwpsCompleteClassify0(Record->ClassifyHandle, 0, NULL);
FwpsReleaseClassifyHandle0(Record->ClassifyHandle);
ExFreePoolWithTag(Record, ST_POOL_TAG);
}
void
FailPendedBindRequest
(
PENDED_BIND *Record
)
{
const auto status = FailBindRequest(Record->ProcessId, &Record->ClassifyOut,
Record->ClassifyHandle, Record->FilterId);
if (!status)
{
//
// At this point there are basically two options:
//
// #1 Leak the bind request to prevent it from successfully binding to the tunnel interface.
// #2 Request a re-auth of the bind request.
//
// We choose to implement #2 in order to retry the processing.
//
ReauthPendedBindRequest(Record);
return;
}
FwpsCompleteClassify0(Record->ClassifyHandle, 0, &Record->ClassifyOut);
FwpsReleaseClassifyHandle0(Record->ClassifyHandle);
ExFreePoolWithTag(Record, ST_POOL_TAG);
}
} // anonymous namespace
NTSTATUS
PendBindRequest
(
CONTEXT *Context,
HANDLE ProcessId,
void *ClassifyContext,
UINT64 FilterId,
FWPS_CLASSIFY_OUT0 *ClassifyOut
)
{
DbgPrint("Pending bind request from process %p\n", ProcessId);
auto record = (PENDED_BIND*)
ExAllocatePoolWithTag(NonPagedPool, sizeof(PENDED_BIND), ST_POOL_TAG);
if (record == NULL)
{
return STATUS_INSUFFICIENT_RESOURCES;
}
UINT64 classifyHandle;
auto status = FwpsAcquireClassifyHandle0(ClassifyContext, 0, &classifyHandle);
if (!NT_SUCCESS(status))
{
ExFreePoolWithTag(record, ST_POOL_TAG);
return status;
}
status = FwpsPendClassify0(classifyHandle, FilterId, 0, ClassifyOut);
if (!NT_SUCCESS(status))
{
FwpsReleaseClassifyHandle0(classifyHandle);
ExFreePoolWithTag(record, ST_POOL_TAG);
return status;
}
record->ProcessId = ProcessId;
record->Timestamp = KeQueryInterruptTime();
record->ClassifyHandle = classifyHandle;
record->ClassifyOut = *ClassifyOut;
record->FilterId = FilterId;
WdfWaitLockAcquire(Context->PendedBinds.Lock, NULL);
InsertTailList(&Context->PendedBinds.Records, &record->ListEntry);
WdfWaitLockRelease(Context->PendedBinds.Lock);
return STATUS_SUCCESS;
}
void
FailBindRequest
(
HANDLE ProcessId,
void *ClassifyContext,
UINT64 FilterId,
FWPS_CLASSIFY_OUT0 *ClassifyOut
)
{
UINT64 classifyHandle = 0;
auto status = FwpsAcquireClassifyHandle0
(
ClassifyContext,
0,
&classifyHandle
);
if (!NT_SUCCESS(status))
{
DbgPrint("FwpsAcquireClassifyHandle0() failed 0x%X\n", status);
return;
}
FailBindRequest(ProcessId, ClassifyOut, classifyHandle, FilterId);
FwpsReleaseClassifyHandle0(classifyHandle);
}
void
HandleProcessEvent
(
HANDLE ProcessId,
bool Arriving,
void *Context
)
{
auto context = (CONTEXT*)Context;
auto timeNow = KeQueryInterruptTime();
static const ULONGLONG MS_TO_100NS_FACTOR = 10000;
auto maxAge = RECORD_MAX_LIFETIME_MS * MS_TO_100NS_FACTOR;
//
// Iterate over all pended bind requests.
//
// Fail all requests that are too old.
// Re-auth all requests that belong to the arriving process.
//
WdfWaitLockAcquire(context->PendedBinds.Lock, NULL);
for (auto rawRecord = context->PendedBinds.Records.Flink;
rawRecord != &context->PendedBinds.Records;
/* no post-condition */)
{
auto record = (PENDED_BIND*)rawRecord;
rawRecord = rawRecord->Flink;
auto timeDelta = timeNow - record->Timestamp;
if (timeDelta > maxAge)
{
RemoveEntryList(&record->ListEntry);
FailPendedBindRequest(record);
continue;
}
if (record->ProcessId != ProcessId)
{
continue;
}
RemoveEntryList(&record->ListEntry);
if (Arriving)
{
ReauthPendedBindRequest(record);
}
else
{
FailPendedBindRequest(record);
}
}
WdfWaitLockRelease(context->PendedBinds.Lock);
}
void
FailPendedBinds
(
CONTEXT *Context
)
{
auto context = (CONTEXT*)Context;
for (auto rawRecord = context->PendedBinds.Records.Flink;
rawRecord != &context->PendedBinds.Records;
/* no post-condition */)
{
auto record = (PENDED_BIND*)rawRecord;
rawRecord = rawRecord->Flink;
RemoveEntryList(&record->ListEntry);
FailPendedBindRequest(record);
}
}
} // namespace firewall

View File

@@ -1,43 +0,0 @@
#pragma once
#include "wfp.h"
#include <wdf.h>
#include "context.h"
namespace firewall
{
NTSTATUS
PendBindRequest
(
CONTEXT *Context,
HANDLE ProcessId,
void *ClassifyContext,
UINT64 FilterId,
FWPS_CLASSIFY_OUT0 *ClassifyOut
);
void
FailBindRequest
(
HANDLE ProcessId,
void *ClassifyContext,
UINT64 FilterId,
FWPS_CLASSIFY_OUT0 *ClassifyOut
);
void
HandleProcessEvent
(
HANDLE ProcessId,
bool Arriving,
void *Context
);
void
FailPendedBinds
(
CONTEXT *Context
);
} // namespace firewall

View File

@@ -2,8 +2,10 @@
#include "firewall.h"
#include "context.h"
#include "identifiers.h"
#include "asyncbind.h"
#include "pending.h"
#include "callouts.h"
#include "logging.h"
#include "classify.h"
#include "../util.h"
#include "../trace.h"
@@ -133,8 +135,13 @@ UnregisterCallout
//
// RewriteBind()
//
// This is where the splitting happens.
// Move socket binds from tunnel interface to the internet connected interface.
// Implements redirection for non-TCP socket binds.
//
// If a bind is attempted (or implied) with target inaddr_any or the tunnel interface,
// we rewrite the bind to move it to the internet interface.
//
// This has the unfortunate effect that client sockets which are not explicitly bound
// to localhost are prevented from connecting to localhost.
//
void
RewriteBind
@@ -183,21 +190,7 @@ RewriteBind
goto Cleanup_handle;
}
//
// According to documentation, FwpsAcquireWritableLayerDataPointer0() will update the
// `actionType` and `rights` fields with poorly chosen values:
//
// ```
// classifyOut->actionType = FWP_ACTION_BLOCK
// classifyOut->rights &= ~FWPS_RIGHT_ACTION_WRITE
// ```
//
// However, in practice it seems to not make any changes to those fields.
// But if it did we'd want to ensure the fields have sane values.
//
ClassifyOut->actionType = FWP_ACTION_CONTINUE;
ClassifyOut->rights |= FWPS_RIGHT_ACTION_WRITE;
ClassificationReset(ClassifyOut);
//
// There's a list with redirection history.
@@ -225,62 +218,44 @@ RewriteBind
const bool ipv4 = FixedValues->layerId == FWPS_LAYER_ALE_BIND_REDIRECT_V4;
WdfWaitLockAcquire(Context->IpAddresses.Lock, NULL);
WdfSpinLockAcquire(Context->IpAddresses.Lock);
if (ipv4)
{
auto bindTarget = (SOCKADDR_IN*)&(bindRequest->localAddressAndPort);
DbgPrint("Bind request eligible for splitting: %d.%d.%d.%d:%d\n",
bindTarget->sin_addr.S_un.S_un_b.s_b1,
bindTarget->sin_addr.S_un.S_un_b.s_b2,
bindTarget->sin_addr.S_un.S_un_b.s_b3,
bindTarget->sin_addr.S_un.S_un_b.s_b4,
ntohs(bindTarget->sin_port)
);
if (IN4_IS_ADDR_UNSPECIFIED(&(bindTarget->sin_addr))
|| IN4_ADDR_EQUAL(&(bindTarget->sin_addr), &(Context->IpAddresses.Addresses.TunnelIpv4)))
{
DbgPrint("SPLITTING\n");
const auto newTarget = &Context->IpAddresses.Addresses.InternetIpv4;
bindTarget->sin_addr = Context->IpAddresses.Addresses.InternetIpv4;
LogBindRedirect(HANDLE(MetaValues->processId), bindTarget, newTarget);
ClassifyOut->actionType = FWP_ACTION_PERMIT;
ClassifyOut->rights &= ~FWPS_RIGHT_ACTION_WRITE;
bindTarget->sin_addr = *newTarget;
ClassificationApplyHardPermit(ClassifyOut);
}
}
else
{
auto bindTarget = (SOCKADDR_IN6*)&(bindRequest->localAddressAndPort);
DbgPrint("Bind request eligible for splitting: [%X:%X:%X:%X:%X:%X:%X:%X]:%d\n",
ntohs(bindTarget->sin6_addr.u.Word[0]),
ntohs(bindTarget->sin6_addr.u.Word[1]),
ntohs(bindTarget->sin6_addr.u.Word[2]),
ntohs(bindTarget->sin6_addr.u.Word[3]),
ntohs(bindTarget->sin6_addr.u.Word[4]),
ntohs(bindTarget->sin6_addr.u.Word[5]),
ntohs(bindTarget->sin6_addr.u.Word[6]),
ntohs(bindTarget->sin6_addr.u.Word[7]),
ntohs(bindTarget->sin6_port)
);
static const IN6_ADDR IN6_ADDR_ANY = { 0 };
if (IN6_ADDR_EQUAL(&(bindTarget->sin6_addr), &IN6_ADDR_ANY)
|| IN6_ADDR_EQUAL(&(bindTarget->sin6_addr), &(Context->IpAddresses.Addresses.TunnelIpv6)))
{
DbgPrint("SPLITTING\n");
const auto newTarget = &Context->IpAddresses.Addresses.InternetIpv6;
bindTarget->sin6_addr = Context->IpAddresses.Addresses.InternetIpv6;
LogBindRedirect(HANDLE(MetaValues->processId), bindTarget, newTarget);
ClassifyOut->actionType = FWP_ACTION_PERMIT;
ClassifyOut->rights &= ~FWPS_RIGHT_ACTION_WRITE;
bindTarget->sin6_addr = *newTarget;
ClassificationApplyHardPermit(ClassifyOut);
}
}
WdfWaitLockRelease(Context->IpAddresses.Lock);
WdfSpinLockRelease(Context->IpAddresses.Lock);
Cleanup_data:
@@ -291,33 +266,37 @@ Cleanup_data:
// This is the correct logic according to documentation.
//
FwpsApplyModifiedLayerData0(classifyHandle, (PVOID*)&bindRequest, 0);
FwpsApplyModifiedLayerData0(classifyHandle, bindRequest, 0);
Cleanup_handle:
FwpsReleaseClassifyHandle0(classifyHandle);
}
//
// PendClassification()
//
// This function is used when, for an incoming request, we don't know what the correct action is.
// I.e. when the process making the request hasn't been categorized yet.
//
void
ClassifyUnknownBind
PendClassification
(
CONTEXT *Context,
pending::CONTEXT *Context,
HANDLE ProcessId,
UINT64 FilterId,
UINT16 LayerId,
const void *ClassifyContext,
FWPS_CLASSIFY_OUT0 *ClassifyOut
)
{
//
// Pend the bind and wait for process to become known and classified.
//
auto status = PendBindRequest
auto status = pending::PendRequest
(
Context,
ProcessId,
const_cast<void*>(ClassifyContext),
FilterId,
LayerId,
ClassifyOut
);
@@ -326,13 +305,12 @@ ClassifyUnknownBind
return;
}
DbgPrint("Could not pend bind request from process %p, blocking instead\n", ProcessId);
FailBindRequest
pending::FailRequest
(
ProcessId,
const_cast<void*>(ClassifyContext),
FilterId,
LayerId,
ClassifyOut
);
}
@@ -351,8 +329,7 @@ ClassifyUnknownBind
//
// ===
//
// Entry point for splitting traffic.
// Check whether the binding process is marked for having its traffic split.
// Entry point for splitting non-TCP socket binds.
//
// FWPS_LAYER_ALE_BIND_REDIRECT_V4
// FWPS_LAYER_ALE_BIND_REDIRECT_V6
@@ -374,8 +351,17 @@ CalloutClassifyBind
NT_ASSERT
(
FixedValues->layerId == FWPS_LAYER_ALE_BIND_REDIRECT_V4
|| FixedValues->layerId == FWPS_LAYER_ALE_BIND_REDIRECT_V6
(
FixedValues->layerId == FWPS_LAYER_ALE_BIND_REDIRECT_V4
&& FixedValues->incomingValue[FWPS_FIELD_ALE_BIND_REDIRECT_V4_IP_PROTOCOL] \
.value.uint8 != IPPROTO_TCP
)
||
(
FixedValues->layerId == FWPS_LAYER_ALE_BIND_REDIRECT_V6
&& FixedValues->incomingValue[FWPS_FIELD_ALE_BIND_REDIRECT_V6_IP_PROTOCOL] \
.value.uint8 != IPPROTO_TCP
)
);
NT_ASSERT
@@ -428,11 +414,359 @@ CalloutClassifyBind
}
case PROCESS_SPLIT_VERDICT::UNKNOWN:
{
ClassifyUnknownBind
PendClassification
(
context,
context->PendedClassifications,
HANDLE(MetaValues->processId),
Filter->filterId,
FixedValues->layerId,
ClassifyContext,
ClassifyOut
);
break;
}
};
}
bool
LocalAddress(const IN_ADDR *addr)
{
return IN4_IS_ADDR_LOOPBACK(addr) // 127/8
|| IN4_IS_ADDR_LINKLOCAL(addr) // 169.254/16
|| IN4_IS_ADDR_RFC1918(addr) // 10/8, 172.16/12, 192.168/16
|| IN4_IS_ADDR_MC_LINKLOCAL(addr) // 224.0.0/24
|| IN4_IS_ADDR_MC_ADMINLOCAL(addr) // 239.255/16
|| IN4_IS_ADDR_MC_SITELOCAL(addr) // 239/8
|| IN4_IS_ADDR_BROADCAST(addr) // 255.255.255.255
;
}
bool
IN6_IS_ADDR_ULA(const IN6_ADDR *a)
{
return (a->s6_bytes[0] == 0xfd);
}
bool
IN6_IS_ADDR_MC_NON_GLOBAL(const IN6_ADDR *a)
{
return IN6_IS_ADDR_MULTICAST(a)
&& !IN6_IS_ADDR_MC_GLOBAL(a);
}
bool
LocalAddress(const IN6_ADDR *addr)
{
return IN6_IS_ADDR_LOOPBACK(addr) // ::1/128
|| IN6_IS_ADDR_LINKLOCAL(addr) // fe80::/10
|| IN6_IS_ADDR_SITELOCAL(addr) // fec0::/10
|| IN6_IS_ADDR_ULA(addr) // fd00::/8
|| IN6_IS_ADDR_MC_NON_GLOBAL(addr) // ff00::/8 && !(ffxe::/16)
;
}
//
// RewriteConnection()
//
// See comment on CalloutClassifyConnect().
//
void
RewriteConnection
(
CONTEXT *Context,
const FWPS_INCOMING_VALUES0 *FixedValues,
const FWPS_INCOMING_METADATA_VALUES0 *MetaValues,
UINT64 FilterId,
const void *ClassifyContext,
FWPS_CLASSIFY_OUT0 *ClassifyOut
)
{
UNREFERENCED_PARAMETER(MetaValues);
WdfSpinLockAcquire(Context->IpAddresses.Lock);
const auto ipAddresses = Context->IpAddresses.Addresses;
WdfSpinLockRelease(Context->IpAddresses.Lock);
//
// Identify the specific cases we're interested in or abort.
//
const bool ipv4 = FixedValues->layerId == FWPS_LAYER_ALE_CONNECT_REDIRECT_V4;
if (ipv4)
{
const auto rawLocalAddress = RtlUlongByteSwap(FixedValues->incomingValue[
FWPS_FIELD_ALE_CONNECT_REDIRECT_V4_IP_LOCAL_ADDRESS].value.uint32);
const auto rawRemoteAddress = RtlUlongByteSwap(FixedValues->incomingValue[
FWPS_FIELD_ALE_CONNECT_REDIRECT_V4_IP_REMOTE_ADDRESS].value.uint32);
auto localAddress = reinterpret_cast<const IN_ADDR*>(&rawLocalAddress);
auto remoteAddress = reinterpret_cast<const IN_ADDR*>(&rawRemoteAddress);
const auto shouldRedirect = IN4_ADDR_EQUAL(localAddress, &ipAddresses.TunnelIpv4)
|| !LocalAddress(remoteAddress);
const auto localPort = FixedValues->incomingValue[
FWPS_FIELD_ALE_CONNECT_REDIRECT_V4_IP_LOCAL_PORT].value.uint16;
const auto remotePort = FixedValues->incomingValue[
FWPS_FIELD_ALE_CONNECT_REDIRECT_V4_IP_REMOTE_PORT].value.uint16;
if (!shouldRedirect)
{
LogConnectRedirectPass
(
HANDLE(MetaValues->processId),
localAddress,
localPort,
remoteAddress,
remotePort
);
return;
}
LogConnectRedirect
(
HANDLE(MetaValues->processId),
localAddress,
localPort,
&ipAddresses.InternetIpv4,
remoteAddress,
remotePort
);
}
else
{
auto localAddress = reinterpret_cast<const IN6_ADDR*>(FixedValues->incomingValue[
FWPS_FIELD_ALE_CONNECT_REDIRECT_V6_IP_LOCAL_ADDRESS].value.byteArray16);
auto remoteAddress = reinterpret_cast<const IN6_ADDR*>(FixedValues->incomingValue[
FWPS_FIELD_ALE_CONNECT_REDIRECT_V6_IP_REMOTE_ADDRESS].value.byteArray16);
const auto shouldRedirect = IN6_ADDR_EQUAL(localAddress, &ipAddresses.TunnelIpv6)
|| !LocalAddress(remoteAddress);
const auto localPort = FixedValues->incomingValue[
FWPS_FIELD_ALE_CONNECT_REDIRECT_V6_IP_LOCAL_PORT].value.uint16;
const auto remotePort = FixedValues->incomingValue[
FWPS_FIELD_ALE_CONNECT_REDIRECT_V6_IP_REMOTE_PORT].value.uint16;
if (!shouldRedirect)
{
LogConnectRedirectPass
(
HANDLE(MetaValues->processId),
localAddress,
localPort,
remoteAddress,
remotePort
);
return;
}
LogConnectRedirect
(
HANDLE(MetaValues->processId),
localAddress,
localPort,
&ipAddresses.InternetIpv6,
remoteAddress,
remotePort
);
}
//
// Patch local address to force connection off of tunnel interface.
//
UINT64 classifyHandle = 0;
auto status = FwpsAcquireClassifyHandle0
(
const_cast<void*>(ClassifyContext),
0,
&classifyHandle
);
if (!NT_SUCCESS(status))
{
DbgPrint("FwpsAcquireClassifyHandle0() failed 0x%X\n", status);
return;
}
FWPS_CONNECT_REQUEST0 *connectRequest = NULL;
status = FwpsAcquireWritableLayerDataPointer0
(
classifyHandle,
FilterId,
0,
(PVOID*)&connectRequest,
ClassifyOut
);
if (!NT_SUCCESS(status))
{
DbgPrint("FwpsAcquireWritableLayerDataPointer0() failed 0x%X\n", status);
goto Cleanup_handle;
}
ClassificationReset(ClassifyOut);
//
// There's a list with redirection history.
//
// This only ever comes into play if several callouts are fighting to redirect the connection.
//
// To prevent recursion, we need to check if we're on the list, and abort if so.
//
for (auto history = connectRequest->previousVersion;
history != NULL;
history = history->previousVersion)
{
if (history->modifierFilterId == FilterId)
{
DbgPrint("Aborting connection processing because already redirected by us\n");
goto Cleanup_data;
}
}
//
// Rewrite connection.
//
if (ipv4)
{
auto localDetails = (SOCKADDR_IN*)&connectRequest->localAddressAndPort;
localDetails->sin_addr = ipAddresses.InternetIpv4;
}
else
{
auto localDetails = (SOCKADDR_IN6*)&connectRequest->localAddressAndPort;
localDetails->sin6_addr = ipAddresses.InternetIpv6;
}
ClassificationApplyHardPermit(ClassifyOut);
Cleanup_data:
FwpsApplyModifiedLayerData0(classifyHandle, connectRequest, 0);
Cleanup_handle:
FwpsReleaseClassifyHandle0(classifyHandle);
}
//
// CalloutClassifyConnect()
//
// Adjust properties on new TCP connections.
//
// If an app is marked for splitting, and if a new connection is explicitly made on the
// tunnel interface, or can be assumed to be routed through the tunnel interface,
// then move the connection to the Internet connected interface (LAN interface usually).
//
// FWPS_LAYER_ALE_CONNECT_REDIRECT_V4
// FWPS_LAYER_ALE_CONNECT_REDIRECT_V6
//
void
CalloutClassifyConnect
(
const FWPS_INCOMING_VALUES0 *FixedValues,
const FWPS_INCOMING_METADATA_VALUES0 *MetaValues,
void *LayerData,
const void *ClassifyContext,
const FWPS_FILTER1 *Filter,
UINT64 FlowContext,
FWPS_CLASSIFY_OUT0 *ClassifyOut
)
{
UNREFERENCED_PARAMETER(LayerData);
UNREFERENCED_PARAMETER(FlowContext);
NT_ASSERT
(
(
FixedValues->layerId == FWPS_LAYER_ALE_CONNECT_REDIRECT_V4
&& FixedValues->incomingValue[FWPS_FIELD_ALE_CONNECT_REDIRECT_V4_IP_PROTOCOL] \
.value.uint8 == IPPROTO_TCP
)
||
(
FixedValues->layerId == FWPS_LAYER_ALE_CONNECT_REDIRECT_V6
&& FixedValues->incomingValue[FWPS_FIELD_ALE_CONNECT_REDIRECT_V6_IP_PROTOCOL] \
.value.uint8 == IPPROTO_TCP
)
);
NT_ASSERT
(
Filter->providerContext != NULL
&& Filter->providerContext->type == FWPM_GENERAL_CONTEXT
&& Filter->providerContext->dataBuffer->size == sizeof(CONTEXT*)
);
auto context = *(CONTEXT**)Filter->providerContext->dataBuffer->data;
if (0 == (ClassifyOut->rights & FWPS_RIGHT_ACTION_WRITE))
{
DbgPrint("Aborting connect-redirect processing because hard permit/block already applied\n");
return;
}
if (ClassifyOut->actionType == FWP_ACTION_NONE)
{
ClassifyOut->actionType = FWP_ACTION_CONTINUE;
}
if (!FWPS_IS_METADATA_FIELD_PRESENT(MetaValues, FWPS_METADATA_FIELD_PROCESS_ID))
{
DbgPrint("Failed to classify connection because PID was not provided\n");
return;
}
const CALLBACKS &callbacks = context->Callbacks;
const auto verdict = callbacks.QueryProcess(HANDLE(MetaValues->processId), callbacks.Context);
switch (verdict)
{
case PROCESS_SPLIT_VERDICT::DO_SPLIT:
{
RewriteConnection
(
context,
FixedValues,
MetaValues,
Filter->filterId,
ClassifyContext,
ClassifyOut
);
break;
}
case PROCESS_SPLIT_VERDICT::UNKNOWN:
{
PendClassification
(
context->PendedClassifications,
HANDLE(MetaValues->processId),
Filter->filterId,
FixedValues->layerId,
ClassifyContext,
ClassifyOut
);
@@ -482,14 +816,85 @@ bool IsAleReauthorize
return ((flags & FWP_CONDITION_FLAG_IS_REAUTHORIZE) != 0);
}
struct AuthLayerValueIndices
{
SIZE_T LocalAddress;
SIZE_T LocalPort;
SIZE_T RemoteAddress;
SIZE_T RemotePort;
};
bool
GetAuthLayerValueIndices
(
UINT16 LayerId,
AuthLayerValueIndices *Indices
)
{
switch (LayerId)
{
case FWPS_LAYER_ALE_AUTH_CONNECT_V4:
{
*Indices =
{
FWPS_FIELD_ALE_AUTH_CONNECT_V4_IP_LOCAL_ADDRESS,
FWPS_FIELD_ALE_AUTH_CONNECT_V4_IP_LOCAL_PORT,
FWPS_FIELD_ALE_AUTH_CONNECT_V4_IP_REMOTE_ADDRESS,
FWPS_FIELD_ALE_AUTH_CONNECT_V4_IP_REMOTE_PORT
};
return true;
}
case FWPS_LAYER_ALE_AUTH_RECV_ACCEPT_V4:
{
*Indices =
{
FWPS_FIELD_ALE_AUTH_RECV_ACCEPT_V4_IP_LOCAL_ADDRESS,
FWPS_FIELD_ALE_AUTH_RECV_ACCEPT_V4_IP_LOCAL_PORT,
FWPS_FIELD_ALE_AUTH_RECV_ACCEPT_V4_IP_REMOTE_ADDRESS,
FWPS_FIELD_ALE_AUTH_RECV_ACCEPT_V4_IP_REMOTE_PORT
};
return true;
}
case FWPS_LAYER_ALE_AUTH_CONNECT_V6:
{
*Indices =
{
FWPS_FIELD_ALE_AUTH_CONNECT_V6_IP_LOCAL_ADDRESS,
FWPS_FIELD_ALE_AUTH_CONNECT_V6_IP_LOCAL_PORT,
FWPS_FIELD_ALE_AUTH_CONNECT_V6_IP_REMOTE_ADDRESS,
FWPS_FIELD_ALE_AUTH_CONNECT_V6_IP_REMOTE_PORT
};
return true;
}
case FWPS_LAYER_ALE_AUTH_RECV_ACCEPT_V6:
{
*Indices =
{
FWPS_FIELD_ALE_AUTH_RECV_ACCEPT_V6_IP_LOCAL_ADDRESS,
FWPS_FIELD_ALE_AUTH_RECV_ACCEPT_V6_IP_LOCAL_PORT,
FWPS_FIELD_ALE_AUTH_RECV_ACCEPT_V6_IP_REMOTE_ADDRESS,
FWPS_FIELD_ALE_AUTH_RECV_ACCEPT_V6_IP_REMOTE_PORT
};
return true;
}
}
return false;
}
//
// CalloutPermitSplitApps()
//
// For processes being split, the bind will have already been moved off the
// tunnel interface.
//
// For processes being split, binds and connections will have already been aptly redirected.
// So now it's only a matter of approving the connection.
//
// The reason we have to explicitly approve these connections is because otherwise
// the default filters with lower weights would block all non-tunnel connections.
//
// FWPS_LAYER_ALE_AUTH_CONNECT_V4
// FWPS_LAYER_ALE_AUTH_CONNECT_V6
// FWPS_LAYER_ALE_AUTH_RECV_ACCEPT_V4
@@ -507,12 +912,8 @@ CalloutPermitSplitApps
FWPS_CLASSIFY_OUT0 *ClassifyOut
)
{
#if !DBG
UNREFERENCED_PARAMETER(FixedValues);
#endif
UNREFERENCED_PARAMETER(LayerData);
UNREFERENCED_PARAMETER(ClassifyContext);
UNREFERENCED_PARAMETER(Filter);
UNREFERENCED_PARAMETER(FlowContext);
NT_ASSERT
@@ -534,7 +935,7 @@ CalloutPermitSplitApps
if (0 == (ClassifyOut->rights & FWPS_RIGHT_ACTION_WRITE))
{
DbgPrint("Aborting connection processing because hard permit/block already applied\n");
DbgPrint("Aborting auth processing because hard permit/block already applied\n");
return;
}
@@ -546,7 +947,7 @@ CalloutPermitSplitApps
if (!FWPS_IS_METADATA_FIELD_PRESENT(MetaValues, FWPS_METADATA_FIELD_PROCESS_ID))
{
DbgPrint("Failed to classify connection because PID was not provided\n");
DbgPrint("Failed to complete auth processing because PID was not provided\n");
return;
}
@@ -555,23 +956,82 @@ CalloutPermitSplitApps
const auto verdict = callbacks.QueryProcess(HANDLE(MetaValues->processId), callbacks.Context);
if (verdict == PROCESS_SPLIT_VERDICT::DO_SPLIT)
{
DbgPrint("APPROVING CONNECTION\n");
//
// If the process is not marked for splitting we should just abort
// and not attempt to classify the connection.
//
ClassifyOut->actionType = FWP_ACTION_PERMIT;
ClassifyOut->rights &= ~FWPS_RIGHT_ACTION_WRITE;
if (verdict != PROCESS_SPLIT_VERDICT::DO_SPLIT)
{
return;
}
//
// Include extensive logging.
//
AuthLayerValueIndices indices = {0};
const auto status = GetAuthLayerValueIndices(FixedValues->layerId, &indices);
NT_ASSERT(status);
if (!status)
{
return;
}
const auto localPort = FixedValues->incomingValue[indices.LocalPort].value.uint16;
const auto remotePort = FixedValues->incomingValue[indices.RemotePort].value.uint16;
const bool ipv4 = FixedValues->layerId == FWPS_LAYER_ALE_AUTH_CONNECT_V4
|| FixedValues->layerId == FWPS_LAYER_ALE_AUTH_RECV_ACCEPT_V4;
if (ipv4)
{
const auto rawLocalAddress = RtlUlongByteSwap(FixedValues->incomingValue[
indices.LocalAddress].value.uint32);
const auto rawRemoteAddress = RtlUlongByteSwap(FixedValues->incomingValue[
indices.RemoteAddress].value.uint32);
auto localAddress = reinterpret_cast<const IN_ADDR*>(&rawLocalAddress);
auto remoteAddress = reinterpret_cast<const IN_ADDR*>(&rawRemoteAddress);
LogPermitConnection
(
HANDLE(MetaValues->processId),
localAddress,
localPort,
remoteAddress,
remotePort,
(FixedValues->layerId == FWPS_LAYER_ALE_AUTH_CONNECT_V4)
);
}
else
{
#if DBG
if (IsAleReauthorize(FixedValues))
{
DbgPrint("[CalloutPermitSplitApps] Reauthorized connection (PID: %p) is not explicitly "\
"approved by callout\n", HANDLE(MetaValues->processId));
}
#endif
auto localAddress = reinterpret_cast<const IN6_ADDR*>(FixedValues->incomingValue[
indices.LocalAddress].value.byteArray16);
auto remoteAddress = reinterpret_cast<const IN6_ADDR*>(FixedValues->incomingValue[
indices.RemoteAddress].value.byteArray16);
LogPermitConnection
(
HANDLE(MetaValues->processId),
localAddress,
localPort,
remoteAddress,
remotePort,
(FixedValues->layerId == FWPS_LAYER_ALE_AUTH_CONNECT_V6)
);
}
//
// Apply classification.
//
ClassificationApplyHardPermit(ClassifyOut);
}
//
@@ -583,6 +1043,11 @@ CalloutPermitSplitApps
// These connections need to be blocked to ensure the process exists on
// only one side of the tunnel.
//
// Additionally, block any processes which have not been evaluated.
//
// This normally isn't required because earlier callouts re-auth until the process
// is categorized, but it makes sense as a safety measure.
//
// FWPS_LAYER_ALE_AUTH_CONNECT_V4
// FWPS_LAYER_ALE_AUTH_CONNECT_V6
// FWPS_LAYER_ALE_AUTH_RECV_ACCEPT_V4
@@ -600,12 +1065,8 @@ CalloutBlockSplitApps
FWPS_CLASSIFY_OUT0 *ClassifyOut
)
{
#if !DBG
UNREFERENCED_PARAMETER(FixedValues);
#endif
UNREFERENCED_PARAMETER(LayerData);
UNREFERENCED_PARAMETER(ClassifyContext);
UNREFERENCED_PARAMETER(Filter);
UNREFERENCED_PARAMETER(FlowContext);
NT_ASSERT
@@ -627,7 +1088,7 @@ CalloutBlockSplitApps
if (0 == (ClassifyOut->rights & FWPS_RIGHT_ACTION_WRITE))
{
DbgPrint("Aborting connection processing because hard permit/block already applied\n");
DbgPrint("Aborting auth processing because hard permit/block already applied\n");
return;
}
@@ -639,7 +1100,7 @@ CalloutBlockSplitApps
if (!FWPS_IS_METADATA_FIELD_PRESENT(MetaValues, FWPS_METADATA_FIELD_PROCESS_ID))
{
DbgPrint("Failed to classify connection because PID was not provided\n");
DbgPrint("Failed to complete auth processing because PID was not provided\n");
return;
}
@@ -648,23 +1109,85 @@ CalloutBlockSplitApps
const auto verdict = callbacks.QueryProcess(HANDLE(MetaValues->processId), callbacks.Context);
if (verdict == PROCESS_SPLIT_VERDICT::DO_SPLIT)
{
DbgPrint("BLOCKING CONNECTION\n");
//
// Block any processes which have not yet been evaluated.
// This is a safety measure to prevent race conditions.
//
ClassifyOut->actionType = FWP_ACTION_BLOCK;
ClassifyOut->rights &= ~FWPS_RIGHT_ACTION_WRITE;
const auto shouldBlock = (verdict == PROCESS_SPLIT_VERDICT::DO_SPLIT)
|| (verdict == PROCESS_SPLIT_VERDICT::UNKNOWN);
if (!shouldBlock)
{
return;
}
//
// Include extensive logging.
//
AuthLayerValueIndices indices = {0};
const auto status = GetAuthLayerValueIndices(FixedValues->layerId, &indices);
NT_ASSERT(status);
if (!status)
{
return;
}
const auto localPort = FixedValues->incomingValue[indices.LocalPort].value.uint16;
const auto remotePort = FixedValues->incomingValue[indices.RemotePort].value.uint16;
const bool ipv4 = FixedValues->layerId == FWPS_LAYER_ALE_AUTH_CONNECT_V4
|| FixedValues->layerId == FWPS_LAYER_ALE_AUTH_RECV_ACCEPT_V4;
if (ipv4)
{
const auto rawLocalAddress = RtlUlongByteSwap(FixedValues->incomingValue[
indices.LocalAddress].value.uint32);
const auto rawRemoteAddress = RtlUlongByteSwap(FixedValues->incomingValue[
indices.RemoteAddress].value.uint32);
auto localAddress = reinterpret_cast<const IN_ADDR*>(&rawLocalAddress);
auto remoteAddress = reinterpret_cast<const IN_ADDR*>(&rawRemoteAddress);
LogBlockConnection
(
HANDLE(MetaValues->processId),
localAddress,
localPort,
remoteAddress,
remotePort,
(FixedValues->layerId == FWPS_LAYER_ALE_AUTH_CONNECT_V4)
);
}
else
{
#if DBG
if (IsAleReauthorize(FixedValues))
{
DbgPrint("[CalloutBlockSplitApps] Reauthorized connection (PID: %p) is not explicitly "\
"blocked by callout\n", HANDLE(MetaValues->processId));
}
#endif
auto localAddress = reinterpret_cast<const IN6_ADDR*>(FixedValues->incomingValue[
indices.LocalAddress].value.byteArray16);
auto remoteAddress = reinterpret_cast<const IN6_ADDR*>(FixedValues->incomingValue[
indices.RemoteAddress].value.byteArray16);
LogBlockConnection
(
HANDLE(MetaValues->processId),
localAddress,
localPort,
remoteAddress,
remotePort,
(FixedValues->layerId == FWPS_LAYER_ALE_AUTH_CONNECT_V6)
);
}
//
// Apply classification.
//
ClassificationApplyHardBlock(ClassifyOut);
}
} // anonymous namespace
@@ -732,6 +1255,69 @@ UnregisterCalloutClassifyBind
return STATUS_SUCCESS;
}
//
// RegisterCalloutClassifyConnectTx()
//
// Register callout with WFP. In all applicable layers.
//
// "Tx" (in transaction) suffix means there is no clean-up in failure paths.
//
NTSTATUS
RegisterCalloutClassifyConnectTx
(
PDEVICE_OBJECT DeviceObject,
HANDLE WfpSession
)
{
auto status = RegisterCalloutTx
(
DeviceObject,
WfpSession,
CalloutClassifyConnect,
&ST_FW_CALLOUT_CLASSIFY_CONNECT_IPV4_KEY,
&FWPM_LAYER_ALE_CONNECT_REDIRECT_V4,
L"Mullvad Split Tunnel Connect Redirect Callout (IPv4)",
L"Adjusts properties on new network connections"
);
if (!NT_SUCCESS(status))
{
return status;
}
status = RegisterCalloutTx
(
DeviceObject,
WfpSession,
CalloutClassifyConnect,
&ST_FW_CALLOUT_CLASSIFY_CONNECT_IPV6_KEY,
&FWPM_LAYER_ALE_CONNECT_REDIRECT_V6,
L"Mullvad Split Tunnel Connect Redirect Callout (IPv6)",
L"Adjusts properties on new network connections"
);
if (!NT_SUCCESS(status))
{
UnregisterCalloutClassifyConnect();
}
return status;
}
NTSTATUS
UnregisterCalloutClassifyConnect
(
)
{
auto s1 = UnregisterCallout(&ST_FW_CALLOUT_CLASSIFY_CONNECT_IPV4_KEY);
auto s2 = UnregisterCallout(&ST_FW_CALLOUT_CLASSIFY_CONNECT_IPV6_KEY);
RETURN_IF_UNSUCCESSFUL(s1);
RETURN_IF_UNSUCCESSFUL(s2);
return STATUS_SUCCESS;
}
//
// RegisterCalloutPermitSplitAppsTx()
//

View File

@@ -17,6 +17,18 @@ UnregisterCalloutClassifyBind
(
);
NTSTATUS
RegisterCalloutClassifyConnectTx
(
PDEVICE_OBJECT DeviceObject,
HANDLE WfpSession
);
NTSTATUS
UnregisterCalloutClassifyConnect
(
);
NTSTATUS
RegisterCalloutPermitSplitAppsTx
(

49
src/firewall/classify.cpp Normal file
View File

@@ -0,0 +1,49 @@
#include "classify.h"
namespace firewall
{
void
ClassificationReset
(
FWPS_CLASSIFY_OUT0 *ClassifyOut
)
{
//
// According to documentation, FwpsAcquireWritableLayerDataPointer0() will update the
// `actionType` and `rights` fields with poorly chosen values:
//
// ```
// classifyOut->actionType = FWP_ACTION_BLOCK
// classifyOut->rights &= ~FWPS_RIGHT_ACTION_WRITE
// ```
//
// However, in practice it seems to not make any changes to those fields.
// But if it did we'd want to ensure the fields have sane values.
//
ClassifyOut->actionType = FWP_ACTION_CONTINUE;
ClassifyOut->rights |= FWPS_RIGHT_ACTION_WRITE;
}
void
ClassificationApplyHardPermit
(
FWPS_CLASSIFY_OUT0 *ClassifyOut
)
{
ClassifyOut->actionType = FWP_ACTION_PERMIT;
ClassifyOut->rights &= ~FWPS_RIGHT_ACTION_WRITE;
}
void
ClassificationApplyHardBlock
(
FWPS_CLASSIFY_OUT0 *ClassifyOut
)
{
ClassifyOut->actionType = FWP_ACTION_BLOCK;
ClassifyOut->rights &= ~FWPS_RIGHT_ACTION_WRITE;
}
} // namespace firewall

27
src/firewall/classify.h Normal file
View File

@@ -0,0 +1,27 @@
#pragma once
#include "wfp.h"
namespace firewall
{
void
ClassificationReset
(
FWPS_CLASSIFY_OUT0 *ClassifyOut
);
void
ClassificationApplyHardPermit
(
FWPS_CLASSIFY_OUT0 *ClassifyOut
)
;
void
ClassificationApplyHardBlock
(
FWPS_CLASSIFY_OUT0 *ClassifyOut
);
} // namespace firewall

View File

@@ -4,6 +4,7 @@
#include <wdf.h>
#include "firewall.h"
#include "mode.h"
#include "pending.h"
#include "../ipaddr.h"
#include "../procbroker/procbroker.h"
#include "../eventing/eventing.h"
@@ -13,38 +14,11 @@ namespace firewall
struct IP_ADDRESSES_MGMT
{
WDFWAITLOCK Lock;
WDFSPINLOCK Lock;
ST_IP_ADDRESSES Addresses;
SPLITTING_MODE SplittingMode;
};
struct PENDED_BIND
{
LIST_ENTRY ListEntry;
// Process that is trying to bind.
HANDLE ProcessId;
// Timestamp when record was created.
ULONGLONG Timestamp;
// Handle used to trigger re-auth or resume request processing.
UINT64 ClassifyHandle;
// Classification data for when we don't want a re-auth
// but instead wish to break and deny the bind.
FWPS_CLASSIFY_OUT0 ClassifyOut;
// The filter that triggered the classification.
UINT64 FilterId;
};
struct PENDED_BIND_MGMT
{
WDFWAITLOCK Lock;
LIST_ENTRY Records;
};
struct TRANSACTION_MGMT
{
// Lock that is held for the duration of a transaction.
@@ -61,6 +35,8 @@ struct ACTIVE_FILTERS
{
bool BindRedirectIpv4;
bool BindRedirectIpv6;
bool ConnectRedirectIpv4;
bool ConnectRedirectIpv6;
bool PermitNonTunnelIpv4;
bool PermitNonTunnelIpv6;
bool BlockTunnelIpv4;
@@ -79,9 +55,7 @@ struct CONTEXT
IP_ADDRESSES_MGMT IpAddresses;
PENDED_BIND_MGMT PendedBinds;
procbroker::CONTEXT *ProcessEventBroker;
pending::CONTEXT *PendedClassifications;
eventing::CONTEXT *Eventing;

View File

@@ -14,7 +14,9 @@ RegisterFilterBindRedirectIpv4Tx
{
//
// Create filter that references callout.
// Not specifying any conditions makes it apply to all traffic.
//
// Use `protocol != TCP` as the sole condition.
// This is because TCP traffic is better dealt with in connect-redirect.
//
FWPM_FILTER0 filter = { 0 };
@@ -35,6 +37,16 @@ RegisterFilterBindRedirectIpv4Tx
filter.action.calloutKey = ST_FW_CALLOUT_CLASSIFY_BIND_IPV4_KEY;
filter.providerContextKey = ST_FW_PROVIDER_CONTEXT_KEY;
FWPM_FILTER_CONDITION0 cond;
cond.fieldKey = FWPM_CONDITION_IP_PROTOCOL;
cond.matchType = FWP_MATCH_NOT_EQUAL;
cond.conditionValue.type = FWP_UINT8;
cond.conditionValue.uint8 = IPPROTO_TCP;
filter.filterCondition = &cond;
filter.numFilterConditions = 1;
return FwpmFilterAdd0(WfpSession, &filter, NULL, NULL);
}
@@ -55,7 +67,9 @@ RegisterFilterBindRedirectIpv6Tx
{
//
// Create filter that references callout.
// Not specifying any conditions makes it apply to all traffic.
//
// Use `protocol != TCP` as the sole condition.
// This is because TCP traffic is better dealt with in connect-redirect.
//
FWPM_FILTER0 filter = { 0 };
@@ -76,6 +90,16 @@ RegisterFilterBindRedirectIpv6Tx
filter.action.calloutKey = ST_FW_CALLOUT_CLASSIFY_BIND_IPV6_KEY;
filter.providerContextKey = ST_FW_PROVIDER_CONTEXT_KEY;
FWPM_FILTER_CONDITION0 cond;
cond.fieldKey = FWPM_CONDITION_IP_PROTOCOL;
cond.matchType = FWP_MATCH_NOT_EQUAL;
cond.conditionValue.type = FWP_UINT8;
cond.conditionValue.uint8 = IPPROTO_TCP;
filter.filterCondition = &cond;
filter.numFilterConditions = 1;
return FwpmFilterAdd0(WfpSession, &filter, NULL, NULL);
}
@@ -88,6 +112,116 @@ RemoveFilterBindRedirectIpv6Tx
return FwpmFilterDeleteByKey0(WfpSession, &ST_FW_FILTER_CLASSIFY_BIND_IPV6_KEY);
}
NTSTATUS
RegisterFilterConnectRedirectIpv4Tx
(
HANDLE WfpSession
)
{
//
// Create filter that references callout.
//
// Use `protocol == TCP` as the sole condition.
//
// This is because the source address for non-TCP traffic can't be updated in connect-redirect.
// So that traffic is instead dealt with in bind-redirect.
//
FWPM_FILTER0 filter = { 0 };
const auto filterName = L"Mullvad Split Tunnel Connect Redirect Filter (IPv4)";
const auto filterDescription = L"Adjusts properties on new network connections";
filter.filterKey = ST_FW_FILTER_CLASSIFY_CONNECT_IPV4_KEY;
filter.displayData.name = const_cast<wchar_t*>(filterName);
filter.displayData.description = const_cast<wchar_t*>(filterDescription);
filter.flags = FWPM_FILTER_FLAG_CLEAR_ACTION_RIGHT | FWPM_FILTER_FLAG_HAS_PROVIDER_CONTEXT;
filter.providerKey = const_cast<GUID*>(&ST_FW_PROVIDER_KEY);
filter.layerKey = FWPM_LAYER_ALE_CONNECT_REDIRECT_V4;
filter.subLayerKey = ST_FW_WINFW_BASELINE_SUBLAYER_KEY;
filter.weight.type = FWP_UINT64;
filter.weight.uint64 = const_cast<UINT64*>(&ST_MAX_FILTER_WEIGHT);
filter.action.type = FWP_ACTION_CALLOUT_UNKNOWN;
filter.action.calloutKey = ST_FW_CALLOUT_CLASSIFY_CONNECT_IPV4_KEY;
filter.providerContextKey = ST_FW_PROVIDER_CONTEXT_KEY;
FWPM_FILTER_CONDITION0 cond;
cond.fieldKey = FWPM_CONDITION_IP_PROTOCOL;
cond.matchType = FWP_MATCH_EQUAL;
cond.conditionValue.type = FWP_UINT8;
cond.conditionValue.uint8 = IPPROTO_TCP;
filter.filterCondition = &cond;
filter.numFilterConditions = 1;
return FwpmFilterAdd0(WfpSession, &filter, NULL, NULL);
}
NTSTATUS
RemoveFilterConnectRedirectIpv4Tx
(
HANDLE WfpSession
)
{
return FwpmFilterDeleteByKey0(WfpSession, &ST_FW_FILTER_CLASSIFY_CONNECT_IPV4_KEY);
}
NTSTATUS
RegisterFilterConnectRedirectIpv6Tx
(
HANDLE WfpSession
)
{
//
// Create filter that references callout.
//
// Use `protocol == TCP` as the sole condition.
//
// This is because the source address for non-TCP traffic can't be updated in connect-redirect.
// So that traffic is instead dealt with in bind-redirect.
//
FWPM_FILTER0 filter = { 0 };
const auto filterName = L"Mullvad Split Tunnel Connect Redirect Filter (IPv6)";
const auto filterDescription = L"Adjusts properties on new network connections";
filter.filterKey = ST_FW_FILTER_CLASSIFY_CONNECT_IPV6_KEY;
filter.displayData.name = const_cast<wchar_t*>(filterName);
filter.displayData.description = const_cast<wchar_t*>(filterDescription);
filter.flags = FWPM_FILTER_FLAG_CLEAR_ACTION_RIGHT | FWPM_FILTER_FLAG_HAS_PROVIDER_CONTEXT;
filter.providerKey = const_cast<GUID*>(&ST_FW_PROVIDER_KEY);
filter.layerKey = FWPM_LAYER_ALE_CONNECT_REDIRECT_V6;
filter.subLayerKey = ST_FW_WINFW_BASELINE_SUBLAYER_KEY;
filter.weight.type = FWP_UINT64;
filter.weight.uint64 = const_cast<UINT64*>(&ST_MAX_FILTER_WEIGHT);
filter.action.type = FWP_ACTION_CALLOUT_UNKNOWN;
filter.action.calloutKey = ST_FW_CALLOUT_CLASSIFY_CONNECT_IPV6_KEY;
filter.providerContextKey = ST_FW_PROVIDER_CONTEXT_KEY;
FWPM_FILTER_CONDITION0 cond;
cond.fieldKey = FWPM_CONDITION_IP_PROTOCOL;
cond.matchType = FWP_MATCH_EQUAL;
cond.conditionValue.type = FWP_UINT8;
cond.conditionValue.uint8 = IPPROTO_TCP;
filter.filterCondition = &cond;
filter.numFilterConditions = 1;
return FwpmFilterAdd0(WfpSession, &filter, NULL, NULL);
}
NTSTATUS
RemoveFilterConnectRedirectIpv6Tx
(
HANDLE WfpSession
)
{
return FwpmFilterDeleteByKey0(WfpSession, &ST_FW_FILTER_CLASSIFY_CONNECT_IPV6_KEY);
}
NTSTATUS
RegisterFilterPermitNonTunnelIpv4Tx
(

View File

@@ -45,6 +45,45 @@ RemoveFilterBindRedirectIpv6Tx
HANDLE WfpSession
);
//
// RegisterFilterConnectRedirectIpv4Tx()
//
// Register filter, with linked callout, that will pass all connection requests through
// the connection callout for validation/redirection.
//
// The callout will look for and amend broken localhost client connections.
//
// "Tx" (in transaction) suffix means there's no clean-up in failure paths.
//
NTSTATUS
RegisterFilterConnectRedirectIpv4Tx
(
HANDLE WfpSession
);
NTSTATUS
RemoveFilterConnectRedirectIpv4Tx
(
HANDLE WfpSession
);
//
// RegisterFilterConnectRedirectIpv6Tx()
//
// Refer comment on corresponding function for IPv4.
//
NTSTATUS
RegisterFilterConnectRedirectIpv6Tx
(
HANDLE WfpSession
);
NTSTATUS
RemoveFilterConnectRedirectIpv6Tx
(
HANDLE WfpSession
);
//
// RegisterFilterPermitNonTunnelIpv4Tx()
//

View File

@@ -5,7 +5,8 @@
#include "filters.h"
#include "callouts.h"
#include "constants.h"
#include "asyncbind.h"
#include "pending.h"
#include "logging.h"
#include "../util.h"
#include "../eventing/builder.h"
#include "firewall.h"
@@ -129,7 +130,8 @@ UnregisterCallouts
{
auto s1 = UnregisterCalloutBlockSplitApps();
auto s2 = UnregisterCalloutPermitSplitApps();
auto s3 = UnregisterCalloutClassifyBind();
auto s3 = UnregisterCalloutClassifyConnect();
auto s4 = UnregisterCalloutClassifyBind();
if (!NT_SUCCESS(s1))
{
@@ -147,11 +149,18 @@ UnregisterCallouts
if (!NT_SUCCESS(s3))
{
DbgPrint("Could not unregister bind-redirect callout\n");
DbgPrint("Could not unregister connect-redirect callout\n");
return s3;
}
if (!NT_SUCCESS(s4))
{
DbgPrint("Could not unregister bind-redirect callout\n");
return s4;
}
return STATUS_SUCCESS;
}
@@ -179,6 +188,15 @@ RegisterCallouts
return status;
}
status = RegisterCalloutClassifyConnectTx(DeviceObject, WfpSession);
if (!NT_SUCCESS(status))
{
DbgPrint("Could not register connect-redirect callout\n");
goto Unregister_callouts;
}
status = RegisterCalloutPermitSplitAppsTx(DeviceObject, WfpSession);
if (!NT_SUCCESS(status))
@@ -320,6 +338,8 @@ ResetStructure
{
ActiveFilters->BindRedirectIpv4 = false;
ActiveFilters->BindRedirectIpv6 = false;
ActiveFilters->ConnectRedirectIpv4 = false;
ActiveFilters->ConnectRedirectIpv6 = false;
ActiveFilters->BlockTunnelIpv4 = false;
ActiveFilters->BlockTunnelIpv6 = false;
ActiveFilters->PermitNonTunnelIpv4 = false;
@@ -357,6 +377,12 @@ RegisterFiltersForModeTx
&ActiveFilters->BindRedirectIpv4
);
RFFM_SUCCEED_OR_RETURN
(
RegisterFilterConnectRedirectIpv4Tx(WfpSession),
&ActiveFilters->ConnectRedirectIpv4
);
RFFM_SUCCEED_OR_RETURN
(
RegisterFilterPermitNonTunnelIpv4Tx(WfpSession, &IpAddresses->TunnelIpv4),
@@ -375,6 +401,12 @@ RegisterFiltersForModeTx
&ActiveFilters->BindRedirectIpv6
);
RFFM_SUCCEED_OR_RETURN
(
RegisterFilterConnectRedirectIpv6Tx(WfpSession),
&ActiveFilters->ConnectRedirectIpv6
);
RFFM_SUCCEED_OR_RETURN
(
RegisterFilterPermitNonTunnelIpv6Tx(WfpSession, &IpAddresses->TunnelIpv6),
@@ -397,6 +429,12 @@ RegisterFiltersForModeTx
&ActiveFilters->BindRedirectIpv4
);
RFFM_SUCCEED_OR_RETURN
(
RegisterFilterConnectRedirectIpv4Tx(WfpSession),
&ActiveFilters->ConnectRedirectIpv4
);
RFFM_SUCCEED_OR_RETURN
(
RegisterFilterPermitNonTunnelIpv4Tx(WfpSession, &IpAddresses->TunnelIpv4),
@@ -419,6 +457,12 @@ RegisterFiltersForModeTx
&ActiveFilters->BindRedirectIpv4
);
RFFM_SUCCEED_OR_RETURN
(
RegisterFilterConnectRedirectIpv4Tx(WfpSession),
&ActiveFilters->ConnectRedirectIpv4
);
RFFM_SUCCEED_OR_RETURN
(
RegisterFilterPermitNonTunnelIpv4Tx(WfpSession, &IpAddresses->TunnelIpv4),
@@ -451,6 +495,12 @@ RegisterFiltersForModeTx
&ActiveFilters->BindRedirectIpv4
);
RFFM_SUCCEED_OR_RETURN
(
RegisterFilterConnectRedirectIpv4Tx(WfpSession),
&ActiveFilters->ConnectRedirectIpv4
);
RFFM_SUCCEED_OR_RETURN
(
RegisterFilterPermitNonTunnelIpv4Tx(WfpSession, &IpAddresses->TunnelIpv4),
@@ -479,6 +529,12 @@ RegisterFiltersForModeTx
&ActiveFilters->BindRedirectIpv6
);
RFFM_SUCCEED_OR_RETURN
(
RegisterFilterConnectRedirectIpv6Tx(WfpSession),
&ActiveFilters->ConnectRedirectIpv6
);
RFFM_SUCCEED_OR_RETURN
(
RegisterFilterPermitNonTunnelIpv6Tx(WfpSession, &IpAddresses->TunnelIpv6),
@@ -501,6 +557,12 @@ RegisterFiltersForModeTx
&ActiveFilters->BindRedirectIpv6
);
RFFM_SUCCEED_OR_RETURN
(
RegisterFilterConnectRedirectIpv6Tx(WfpSession),
&ActiveFilters->ConnectRedirectIpv6
);
RFFM_SUCCEED_OR_RETURN
(
RegisterFilterPermitNonTunnelIpv6Tx(WfpSession, &IpAddresses->TunnelIpv6),
@@ -533,6 +595,13 @@ RegisterFiltersForModeTx
&ActiveFilters->BindRedirectIpv6
);
RFFM_SUCCEED_OR_RETURN
(
RegisterFilterConnectRedirectIpv6Tx(WfpSession),
&ActiveFilters->ConnectRedirectIpv6
);
RFFM_SUCCEED_OR_RETURN
(
RegisterFilterPermitNonTunnelIpv6Tx(WfpSession, &IpAddresses->TunnelIpv6),
@@ -625,6 +694,22 @@ RemoveActiveFiltersTx
);
}
if (ActiveFilters->ConnectRedirectIpv4)
{
RAF_SUCCEED_OR_RETURN
(
RemoveFilterConnectRedirectIpv4Tx(WfpSession)
);
}
if (ActiveFilters->ConnectRedirectIpv6)
{
RAF_SUCCEED_OR_RETURN
(
RemoveFilterConnectRedirectIpv6Tx(WfpSession)
);
}
if (ActiveFilters->BlockTunnelIpv4)
{
RAF_SUCCEED_OR_RETURN
@@ -897,34 +982,20 @@ Initialize
RtlZeroMemory(context, sizeof(*context));
InitializeListHead(&context->PendedBinds.Records);
context->Callbacks = *Callbacks;
context->ProcessEventBroker = ProcessEventBroker;
context->Eventing = Eventing;
auto status = WdfWaitLockCreate(WDF_NO_OBJECT_ATTRIBUTES, &context->IpAddresses.Lock);
auto status = WdfSpinLockCreate(WDF_NO_OBJECT_ATTRIBUTES, &context->IpAddresses.Lock);
if (!NT_SUCCESS(status))
{
DbgPrint("WdfWaitLockCreate() failed 0x%X\n", status);
DbgPrint("WdfSpinLockCreate() failed 0x%X\n", status);
context->IpAddresses.Lock = NULL;
goto Abort;
}
status = WdfWaitLockCreate(WDF_NO_OBJECT_ATTRIBUTES, &context->PendedBinds.Lock);
if (!NT_SUCCESS(status))
{
DbgPrint("WdfWaitLockCreate() failed 0x%X\n", status);
context->PendedBinds.Lock = NULL;
goto Abort_delete_ip_lock;
}
status = WdfWaitLockCreate(WDF_NO_OBJECT_ATTRIBUTES, &context->Transaction.Lock);
if (!NT_SUCCESS(status))
@@ -933,14 +1004,31 @@ Initialize
context->Transaction.Lock = NULL;
goto Abort_delete_bind_lock;
goto Abort_delete_ip_lock;
}
status = pending::Initialize
(
&context->PendedClassifications,
ProcessEventBroker
);
if (!NT_SUCCESS(status))
{
DbgPrint("pending::Initialize failed 0x%X\n", status);
context->PendedClassifications = NULL;
goto Abort_delete_transaction_lock;
}
status = CreateWfpSession(&context->WfpSession);
if (!NT_SUCCESS(status))
{
goto Abort_delete_transaction_lock;
context->WfpSession = NULL;
goto Abort_teardown_pending;
}
status = ConfigureWfpTx(context->WfpSession, context);
@@ -964,21 +1052,10 @@ Initialize
goto Abort_unregister_callouts;
}
status = procbroker::Subscribe(ProcessEventBroker, HandleProcessEvent, context);
if (!NT_SUCCESS(status))
{
goto Abort_teardown_appfilters;
}
*Context = context;
return STATUS_SUCCESS;
Abort_teardown_appfilters:
appfilters::TearDown(&context->AppFiltersContext);
Abort_unregister_callouts:
{
@@ -994,14 +1071,14 @@ Abort_destroy_session:
DestroyWfpSession(context->WfpSession);
Abort_teardown_pending:
pending::TearDown(&context->PendedClassifications);
Abort_delete_transaction_lock:
WdfObjectDelete(context->Transaction.Lock);
Abort_delete_bind_lock:
WdfObjectDelete(context->PendedBinds.Lock);
Abort_delete_ip_lock:
WdfObjectDelete(context->IpAddresses.Lock);
@@ -1034,6 +1111,20 @@ TearDown
CONTEXT **Context
)
{
const bool preConditions =
(
(Context != NULL)
&& (*Context != NULL)
&& ((*Context)->SplittingEnabled == false)
);
NT_ASSERT(preConditions);
if (!preConditions)
{
return STATUS_INVALID_DISPOSITION;
}
auto context = *Context;
*Context = NULL;
@@ -1042,11 +1133,7 @@ TearDown
// Clean up adjacent systems.
//
procbroker::CancelSubscription(context->ProcessEventBroker, HandleProcessEvent);
FailPendedBinds(context);
WdfObjectDelete(context->PendedBinds.Lock);
pending::TearDown(&context->PendedClassifications);
appfilters::TearDown(&context->AppFiltersContext);
@@ -1177,6 +1264,8 @@ EnableSplitting
Context->SplittingEnabled = true;
Context->ActiveFilters = activeFilters;
LogActivatedSplittingMode(Context->IpAddresses.SplittingMode);
return STATUS_SUCCESS;
Abort:
@@ -1345,15 +1434,19 @@ RegisterUpdatedIpAddresses
goto Abort;
}
WdfWaitLockAcquire(Context->IpAddresses.Lock, NULL);
auto intermediateNonPagedAddresses = *IpAddresses;
Context->IpAddresses.Addresses = *IpAddresses;
WdfSpinLockAcquire(Context->IpAddresses.Lock);
Context->IpAddresses.Addresses = intermediateNonPagedAddresses;
Context->IpAddresses.SplittingMode = newMode;
WdfWaitLockRelease(Context->IpAddresses.Lock);
WdfSpinLockRelease(Context->IpAddresses.Lock);
Context->ActiveFilters = newActiveFilters;
LogActivatedSplittingMode(newMode);
return STATUS_SUCCESS;
Abort:

View File

@@ -20,6 +20,14 @@ DEFINE_GUID(ST_FW_CALLOUT_CLASSIFY_BIND_IPV4_KEY,
DEFINE_GUID(ST_FW_CALLOUT_CLASSIFY_BIND_IPV6_KEY,
0x53fb3120, 0xb6a4, 0x462b, 0xbf, 0xfc, 0x69, 0x78, 0xaa, 0xda, 0x1d, 0xa2);
// {A4E010B5-DC3F-474A-B7C2-2F3269945F41}
DEFINE_GUID(ST_FW_CALLOUT_CLASSIFY_CONNECT_IPV4_KEY,
0xa4e010b5, 0xdc3f, 0x474a, 0xb7, 0xc2, 0x2f, 0x32, 0x69, 0x94, 0x5f, 0x41);
// {6B634022-B3D3-4667-88BA-BF5028858F52}
DEFINE_GUID(ST_FW_CALLOUT_CLASSIFY_CONNECT_IPV6_KEY,
0x6b634022, 0xb3d3, 0x4667, 0x88, 0xba, 0xbf, 0x50, 0x28, 0x85, 0x8f, 0x52);
// {33F3EDCC-EB5E-41CF-9250-702C94A28E39}
DEFINE_GUID(ST_FW_CALLOUT_PERMIT_SPLIT_APPS_IPV4_CONN_KEY,
0x33f3edcc, 0xeb5e, 0x41cf, 0x92, 0x50, 0x70, 0x2c, 0x94, 0xa2, 0x8e, 0x39);
@@ -60,6 +68,14 @@ DEFINE_GUID(ST_FW_FILTER_CLASSIFY_BIND_IPV4_KEY,
DEFINE_GUID(ST_FW_FILTER_CLASSIFY_BIND_IPV6_KEY,
0x2f607222, 0xb2eb, 0x443c, 0xb6, 0xe0, 0x64, 0x10, 0x67, 0x37, 0x54, 0x78);
// {4207F127-CC80-477E-ADDF-26F76585E073}
DEFINE_GUID(ST_FW_FILTER_CLASSIFY_CONNECT_IPV4_KEY,
0x4207f127, 0xcc80, 0x477e, 0xad, 0xdf, 0x26, 0xf7, 0x65, 0x85, 0xe0, 0x73);
// {9A87F137-5112-4427-B315-4F87B3E84DCC}
DEFINE_GUID(ST_FW_FILTER_CLASSIFY_CONNECT_IPV6_KEY,
0x9a87f137, 0x5112, 0x4427, 0xb3, 0x15, 0x4f, 0x87, 0xb3, 0xe8, 0x4d, 0xcc);
// {66CED079-C270-4B4D-A45C-D11711C0D600}
DEFINE_GUID(ST_FW_FILTER_PERMIT_SPLIT_APPS_IPV4_CONN_KEY,
0x66ced079, 0xc270, 0x4b4d, 0xa4, 0x5c, 0xd1, 0x17, 0x11, 0xc0, 0xd6, 0x0);

333
src/firewall/logging.cpp Normal file
View File

@@ -0,0 +1,333 @@
#include "logging.h"
#include "../util.h"
#include "../trace.h"
#include "logging.tmh"
namespace firewall
{
void
LogBindRedirect
(
HANDLE ProcessId,
const SOCKADDR_IN *Target,
const IN_ADDR *Override
)
{
char targetString[32];
char overrideString[32];
RtlIpv4AddressToStringA(&Target->sin_addr, targetString);
RtlIpv4AddressToStringA(Override, overrideString);
const auto port = ntohs(Target->sin_port);
DbgPrint
(
"[BIND][%p] Rewriting Non-TCP bind request %s:%d into %s:%d\n",
ProcessId,
targetString,
port,
overrideString,
port
);
}
void
LogBindRedirect
(
HANDLE ProcessId,
const SOCKADDR_IN6 *Target,
const IN6_ADDR *Override
)
{
char targetString[64];
char overrideString[64];
RtlIpv6AddressToStringA(&Target->sin6_addr, targetString);
RtlIpv6AddressToStringA(Override, overrideString);
const auto port = ntohs(Target->sin6_port);
DbgPrint
(
"[BIND][%p] Rewriting Non-TCP bind request [%s]:%d into [%s]:%d\n",
ProcessId,
targetString,
port,
overrideString,
port
);
}
void
LogConnectRedirectPass
(
HANDLE ProcessId,
const IN_ADDR *LocalAddress,
USHORT LocalPort,
const IN_ADDR *RemoteAddress,
USHORT RemotePort
)
{
char localAddrString[32];
char remoteAddrString[32];
RtlIpv4AddressToStringA(LocalAddress, localAddrString);
RtlIpv4AddressToStringA(RemoteAddress, remoteAddrString);
DbgPrint
(
"[CONN][%p] Passing on opportunity to redirect %s:%d -> %s:%d\n",
ProcessId,
localAddrString,
LocalPort,
remoteAddrString,
RemotePort
);
}
void
LogConnectRedirectPass
(
HANDLE ProcessId,
const IN6_ADDR *LocalAddress,
USHORT LocalPort,
const IN6_ADDR *RemoteAddress,
USHORT RemotePort
)
{
char localAddrString[64];
char remoteAddrString[64];
RtlIpv6AddressToStringA(LocalAddress, localAddrString);
RtlIpv6AddressToStringA(RemoteAddress, remoteAddrString);
DbgPrint
(
"[CONN][%p] Passing on opportunity to redirect [%s]:%d -> [%s]:%d\n",
ProcessId,
localAddrString,
LocalPort,
remoteAddrString,
RemotePort
);
}
void
LogConnectRedirect
(
HANDLE ProcessId,
const IN_ADDR *LocalAddress,
USHORT LocalPort,
const IN_ADDR *LocalAddressOverride,
const IN_ADDR *RemoteAddress,
USHORT RemotePort
)
{
char localAddrString[32];
char localAddrOverrideString[32];
char remoteAddrString[32];
RtlIpv4AddressToStringA(LocalAddress, localAddrString);
RtlIpv4AddressToStringA(LocalAddressOverride, localAddrOverrideString);
RtlIpv4AddressToStringA(RemoteAddress, remoteAddrString);
DbgPrint
(
"[CONN][%p] Rewriting connection on %s:%d as %s:%d -> %s:%d\n",
ProcessId,
localAddrString,
LocalPort,
localAddrOverrideString,
LocalPort,
remoteAddrString,
RemotePort
);
}
void
LogConnectRedirect
(
HANDLE ProcessId,
const IN6_ADDR *LocalAddress,
USHORT LocalPort,
const IN6_ADDR *LocalAddressOverride,
const IN6_ADDR *RemoteAddress,
USHORT RemotePort
)
{
char localAddrString[64];
char localAddrOverrideString[64];
char remoteAddrString[64];
RtlIpv6AddressToStringA(LocalAddress, localAddrString);
RtlIpv6AddressToStringA(LocalAddressOverride, localAddrOverrideString);
RtlIpv6AddressToStringA(RemoteAddress, remoteAddrString);
DbgPrint
(
"[CONN][%p] Rewriting connection on [%s]:%d as [%s]:%d -> [%s]:%d\n",
ProcessId,
localAddrString,
LocalPort,
localAddrOverrideString,
LocalPort,
remoteAddrString,
RemotePort
);
}
void
LogPermitConnection
(
HANDLE ProcessId,
const IN_ADDR *LocalAddress,
USHORT LocalPort,
const IN_ADDR *RemoteAddress,
USHORT RemotePort,
bool outgoing
)
{
char localAddrString[32];
char remoteAddrString[32];
RtlIpv4AddressToStringA(LocalAddress, localAddrString);
RtlIpv4AddressToStringA(RemoteAddress, remoteAddrString);
const auto direction = outgoing
? "->"
: "<-";
DbgPrint
(
"[PRMT][%p] %s:%d %s %s:%d\n",
ProcessId,
localAddrString,
LocalPort,
direction,
remoteAddrString,
RemotePort
);
}
void
LogPermitConnection
(
HANDLE ProcessId,
const IN6_ADDR *LocalAddress,
USHORT LocalPort,
const IN6_ADDR *RemoteAddress,
USHORT RemotePort,
bool outgoing
)
{
char localAddrString[64];
char remoteAddrString[64];
RtlIpv6AddressToStringA(LocalAddress, localAddrString);
RtlIpv6AddressToStringA(RemoteAddress, remoteAddrString);
const auto direction = outgoing
? "->"
: "<-";
DbgPrint
(
"[PRMT][%p] [%s]:%d %s [%s]:%d\n",
ProcessId,
localAddrString,
LocalPort,
direction,
remoteAddrString,
RemotePort
);
}
void
LogBlockConnection
(
HANDLE ProcessId,
const IN_ADDR *LocalAddress,
USHORT LocalPort,
const IN_ADDR *RemoteAddress,
USHORT RemotePort,
bool outgoing
)
{
char localAddrString[32];
char remoteAddrString[32];
RtlIpv4AddressToStringA(LocalAddress, localAddrString);
RtlIpv4AddressToStringA(RemoteAddress, remoteAddrString);
const auto direction = outgoing
? "->"
: "<-";
DbgPrint
(
"[BLCK][%p] %s:%d %s %s:%d\n",
ProcessId,
localAddrString,
LocalPort,
direction,
remoteAddrString,
RemotePort
);
}
void
LogBlockConnection
(
HANDLE ProcessId,
const IN6_ADDR *LocalAddress,
USHORT LocalPort,
const IN6_ADDR *RemoteAddress,
USHORT RemotePort,
bool outgoing
)
{
char localAddrString[64];
char remoteAddrString[64];
RtlIpv6AddressToStringA(LocalAddress, localAddrString);
RtlIpv6AddressToStringA(RemoteAddress, remoteAddrString);
const auto direction = outgoing
? "->"
: "<-";
DbgPrint
(
"[BLCK][%p] [%s]:%d %s [%s]:%d\n",
ProcessId,
localAddrString,
LocalPort,
direction,
remoteAddrString,
RemotePort
);
}
void
LogActivatedSplittingMode
(
SPLITTING_MODE Mode
)
{
//
// This only works because SPLITTING_MODE::MODE_1 is defined as 1, etc.
//
NT_ASSERT
(
static_cast<SIZE_T>(SPLITTING_MODE::MODE_1) == 1
&& static_cast<SIZE_T>(SPLITTING_MODE::MODE_9) == 9
);
DbgPrint("Activated splitting mode: %d\n", Mode);
}
}; // namespace firewall

117
src/firewall/logging.h Normal file
View File

@@ -0,0 +1,117 @@
#pragma once
#include "wfp.h"
#include "mode.h"
namespace firewall
{
void
LogBindRedirect
(
HANDLE ProcessId,
const SOCKADDR_IN *Target,
const IN_ADDR *Override
);
void
LogBindRedirect
(
HANDLE ProcessId,
const SOCKADDR_IN6 *Target,
const IN6_ADDR *Override
);
void
LogConnectRedirectPass
(
HANDLE ProcessId,
const IN_ADDR *LocalAddress,
USHORT LocalPort,
const IN_ADDR *RemoteAddress,
USHORT RemotePort
);
void
LogConnectRedirectPass
(
HANDLE ProcessId,
const IN6_ADDR *LocalAddress,
USHORT LocalPort,
const IN6_ADDR *RemoteAddress,
USHORT RemotePort
);
void
LogConnectRedirect
(
HANDLE ProcessId,
const IN_ADDR *LocalAddress,
USHORT LocalPort,
const IN_ADDR *LocalAddressOverride,
const IN_ADDR *RemoteAddress,
USHORT RemotePort
);
void
LogConnectRedirect
(
HANDLE ProcessId,
const IN6_ADDR *LocalAddress,
USHORT LocalPort,
const IN6_ADDR *LocalAddressOverride,
const IN6_ADDR *RemoteAddress,
USHORT RemotePort
);
void
LogPermitConnection
(
HANDLE ProcessId,
const IN_ADDR *LocalAddress,
USHORT LocalPort,
const IN_ADDR *RemoteAddress,
USHORT RemotePort,
bool outgoing
);
void
LogPermitConnection
(
HANDLE ProcessId,
const IN6_ADDR *LocalAddress,
USHORT LocalPort,
const IN6_ADDR *RemoteAddress,
USHORT RemotePort,
bool outgoing
);
void
LogBlockConnection
(
HANDLE ProcessId,
const IN_ADDR *LocalAddress,
USHORT LocalPort,
const IN_ADDR *RemoteAddress,
USHORT RemotePort,
bool outgoing
);
void
LogBlockConnection
(
HANDLE ProcessId,
const IN6_ADDR *LocalAddress,
USHORT LocalPort,
const IN6_ADDR *RemoteAddress,
USHORT RemotePort,
bool outgoing
);
void
LogActivatedSplittingMode
(
SPLITTING_MODE Mode
);
}; // namespace firewall

535
src/firewall/pending.cpp Normal file
View File

@@ -0,0 +1,535 @@
#include "pending.h"
#include "classify.h"
#include "../util.h"
#include "../trace.h"
#include "pending.tmh"
namespace firewall::pending
{
struct PENDED_CLASSIFICATION
{
LIST_ENTRY ListEntry;
// Process that's making the request.
HANDLE ProcessId;
// Timestamp when record was created.
ULONGLONG Timestamp;
// Handle used to trigger re-auth or resume processing.
UINT64 ClassifyHandle;
// Result of classification is recorded here.
FWPS_CLASSIFY_OUT0 ClassifyOut;
// Filter that triggered the classification.
UINT64 FilterId;
// Layer in which classification is occurring.
UINT16 LayerId;
};
struct CONTEXT
{
procbroker::CONTEXT *ProcessEventBroker;
WDFSPINLOCK Lock;
// PENDED_CLASSIFICATION
LIST_ENTRY Classifications;
};
namespace
{
const ULONGLONG RECORD_MAX_LIFETIME_MS = 10000;
bool
AssertCompatibleLayer
(
UINT16 LayerId
)
{
NT_ASSERT
(
LayerId == FWPS_LAYER_ALE_BIND_REDIRECT_V4
|| LayerId == FWPS_LAYER_ALE_BIND_REDIRECT_V6
|| LayerId == FWPS_LAYER_ALE_CONNECT_REDIRECT_V4
|| LayerId == FWPS_LAYER_ALE_CONNECT_REDIRECT_V6
);
if (LayerId == FWPS_LAYER_ALE_BIND_REDIRECT_V4
|| LayerId == FWPS_LAYER_ALE_BIND_REDIRECT_V6
|| LayerId == FWPS_LAYER_ALE_CONNECT_REDIRECT_V4
|| LayerId == FWPS_LAYER_ALE_CONNECT_REDIRECT_V6)
{
return true;
}
DbgPrint("Invalid layer id %d specified in call to 'pending' module\n", LayerId);
return false;
}
const char*
LayerToString
(
UINT16 LayerId
)
{
char *string = "undefined";
switch (LayerId)
{
case FWPS_LAYER_ALE_BIND_REDIRECT_V4:
{
string = "FWPS_LAYER_ALE_BIND_REDIRECT_V4";
break;
}
case FWPS_LAYER_ALE_BIND_REDIRECT_V6:
{
string = "FWPS_LAYER_ALE_BIND_REDIRECT_V6";
break;
}
case FWPS_LAYER_ALE_CONNECT_REDIRECT_V4:
{
string = "FWPS_LAYER_ALE_CONNECT_REDIRECT_V4";
break;
}
case FWPS_LAYER_ALE_CONNECT_REDIRECT_V6:
{
string = "FWPS_LAYER_ALE_CONNECT_REDIRECT_V6";
break;
}
}
return string;
}
NTSTATUS
FailRequest
(
UINT64 FilterId,
UINT16 LayerId,
FWPS_CLASSIFY_OUT0 *ClassifyOut,
UINT64 ClassifyHandle
)
{
//
// There doesn't seem to be any support in WFP for blocking requests in the redirect layers.
// Specifying `FWP_ACTION_BLOCK` will just resume request processing.
// So the best we can do is rewrite the request to do as little harm as possible.
//
PVOID requestData = NULL;
auto status = FwpsAcquireWritableLayerDataPointer0
(
ClassifyHandle,
FilterId,
0,
&requestData,
ClassifyOut
);
if (!NT_SUCCESS(status))
{
DbgPrint("FwpsAcquireWritableLayerDataPointer0() failed\n");
return status;
}
switch (LayerId)
{
case FWPS_LAYER_ALE_BIND_REDIRECT_V4:
case FWPS_LAYER_ALE_BIND_REDIRECT_V6:
{
auto bindRequest = reinterpret_cast<FWPS_BIND_REQUEST0*>(requestData);
//
// This sets the port to 0, as well.
//
INETADDR_SETLOOPBACK((PSOCKADDR)&(bindRequest->localAddressAndPort));
break;
}
case FWPS_LAYER_ALE_CONNECT_REDIRECT_V4:
case FWPS_LAYER_ALE_CONNECT_REDIRECT_V6:
{
auto connectRequest = reinterpret_cast<FWPS_CONNECT_REQUEST0*>(requestData);
INETADDR_SETLOOPBACK((PSOCKADDR)&(connectRequest->localAddressAndPort));
break;
}
};
ClassificationApplyHardPermit(ClassifyOut);
FwpsApplyModifiedLayerData0(ClassifyHandle, requestData, 0);
return STATUS_SUCCESS;
}
void
ReauthPendedRequest
(
PENDED_CLASSIFICATION *Record
)
{
DbgPrint
(
"Requesting re-auth for pended request in layer %s for process %p\n",
LayerToString(Record->LayerId),
Record->ProcessId
);
FwpsCompleteClassify0(Record->ClassifyHandle, 0, NULL);
FwpsReleaseClassifyHandle0(Record->ClassifyHandle);
ExFreePoolWithTag(Record, ST_POOL_TAG);
}
void
FailPendedRequest
(
PENDED_CLASSIFICATION *Record,
bool ReauthOnFailure = true
)
{
DbgPrint
(
"Failing pended request in layer %s for process %p\n",
LayerToString(Record->LayerId),
Record->ProcessId
);
const auto status = FailRequest
(
Record->FilterId,
Record->LayerId,
&Record->ClassifyOut,
Record->ClassifyHandle
);
if (NT_SUCCESS(status))
{
FwpsCompleteClassify0(Record->ClassifyHandle, 0, &Record->ClassifyOut);
FwpsReleaseClassifyHandle0(Record->ClassifyHandle);
}
else
{
DbgPrint("FailRequest() failed 0x%X\n", status);
if (ReauthOnFailure)
{
ReauthPendedRequest(Record);
return;
}
}
ExFreePoolWithTag(Record, ST_POOL_TAG);
}
//
// FailAllPendedRequests()
//
// This function is used during tear down.
// So we don't have the luxury of re-authing requests that can't be failed.
//
void
FailAllPendedRequests
(
CONTEXT *Context
)
{
for (auto rawRecord = Context->Classifications.Flink;
rawRecord != &Context->Classifications;
/* no post-condition */)
{
auto record = (PENDED_CLASSIFICATION*)rawRecord;
rawRecord = rawRecord->Flink;
RemoveEntryList(&record->ListEntry);
FailPendedRequest(record, false);
}
}
void
HandleProcessEvent
(
HANDLE ProcessId,
bool Arriving,
void *Context
)
{
auto context = (CONTEXT*)Context;
auto timeNow = KeQueryInterruptTime();
static const ULONGLONG MS_TO_100NS_FACTOR = 10000;
auto maxAge = RECORD_MAX_LIFETIME_MS * MS_TO_100NS_FACTOR;
//
// Iterate over all pended bind requests.
//
// Fail all requests that are too old.
// Re-auth all requests that belong to the arriving process.
//
WdfSpinLockAcquire(context->Lock);
for (auto rawRecord = context->Classifications.Flink;
rawRecord != &context->Classifications;
/* no post-condition */)
{
auto record = (PENDED_CLASSIFICATION*)rawRecord;
rawRecord = rawRecord->Flink;
auto timeDelta = timeNow - record->Timestamp;
if (timeDelta > maxAge)
{
RemoveEntryList(&record->ListEntry);
FailPendedRequest(record);
continue;
}
if (record->ProcessId != ProcessId)
{
continue;
}
RemoveEntryList(&record->ListEntry);
if (Arriving)
{
ReauthPendedRequest(record);
}
else
{
FailPendedRequest(record, false);
}
}
WdfSpinLockRelease(context->Lock);
}
} // anonymous namespace
NTSTATUS
Initialize
(
CONTEXT **Context,
procbroker::CONTEXT *ProcessEventBroker
)
{
auto context = (CONTEXT*)ExAllocatePoolWithTag(NonPagedPool, sizeof(CONTEXT), ST_POOL_TAG);
if (context == NULL)
{
DbgPrint("ExAllocatePoolWithTag() failed\n");
return STATUS_INSUFFICIENT_RESOURCES;
}
RtlZeroMemory(context, sizeof(*context));
context->ProcessEventBroker = ProcessEventBroker;
auto status = WdfSpinLockCreate(WDF_NO_OBJECT_ATTRIBUTES, &context->Lock);
if (!NT_SUCCESS(status))
{
DbgPrint("WdfSpinLockCreate() failed\n");
goto Abort;
}
InitializeListHead(&context->Classifications);
//
// Everything is initialized.
// Register with process event broker.
//
status = procbroker::Subscribe(ProcessEventBroker, HandleProcessEvent, context);
if (!NT_SUCCESS(status))
{
DbgPrint("Could not register with process event broker\n");
goto Abort_delete_lock;
}
*Context = context;
return STATUS_SUCCESS;
Abort_delete_lock:
WdfObjectDelete(context->Lock);
Abort:
ExFreePoolWithTag(context, ST_POOL_TAG);
return status;
}
void
TearDown
(
CONTEXT **Context
)
{
auto context = *Context;
*Context = NULL;
procbroker::CancelSubscription(context->ProcessEventBroker, HandleProcessEvent);
FailAllPendedRequests(context);
WdfObjectDelete(context->Lock);
ExFreePoolWithTag(context, ST_POOL_TAG);
}
NTSTATUS
PendRequest
(
CONTEXT *Context,
HANDLE ProcessId,
void *ClassifyContext,
UINT64 FilterId,
UINT16 LayerId,
FWPS_CLASSIFY_OUT0 *ClassifyOut
)
{
if (!AssertCompatibleLayer(LayerId))
{
return STATUS_UNSUCCESSFUL;
}
DbgPrint
(
"Pending request in layer %s for process %p\n",
LayerToString(LayerId),
ProcessId
);
auto record = (PENDED_CLASSIFICATION*)
ExAllocatePoolWithTag(NonPagedPool, sizeof(PENDED_CLASSIFICATION), ST_POOL_TAG);
if (record == NULL)
{
DbgPrint("ExAllocatePoolWithTag() failed\n");
return STATUS_INSUFFICIENT_RESOURCES;
}
UINT64 classifyHandle;
auto status = FwpsAcquireClassifyHandle0(ClassifyContext, 0, &classifyHandle);
if (!NT_SUCCESS(status))
{
DbgPrint("FwpsAcquireClassifyHandle0() failed\n");
goto Abort;
}
status = FwpsPendClassify0(classifyHandle, FilterId, 0, ClassifyOut);
if (!NT_SUCCESS(status))
{
DbgPrint("FwpsPendClassify0() failed\n");
FwpsReleaseClassifyHandle0(classifyHandle);
goto Abort;
}
record->ProcessId = ProcessId;
record->Timestamp = KeQueryInterruptTime();
record->ClassifyHandle = classifyHandle;
record->ClassifyOut = *ClassifyOut;
record->LayerId = LayerId;
record->FilterId = FilterId;
WdfSpinLockAcquire(Context->Lock);
InsertTailList(&Context->Classifications, &record->ListEntry);
WdfSpinLockRelease(Context->Lock);
return STATUS_SUCCESS;
Abort:
ExFreePoolWithTag(record, ST_POOL_TAG);
return status;
}
NTSTATUS
FailRequest
(
HANDLE ProcessId,
void *ClassifyContext,
UINT64 FilterId,
UINT16 LayerId,
FWPS_CLASSIFY_OUT0 *ClassifyOut
)
{
if (!AssertCompatibleLayer(LayerId))
{
return STATUS_UNSUCCESSFUL;
}
DbgPrint
(
"Failing request in layer %s for process %p\n",
LayerToString(LayerId),
ProcessId
);
UINT64 classifyHandle = 0;
auto status = FwpsAcquireClassifyHandle0
(
ClassifyContext,
0,
&classifyHandle
);
if (!NT_SUCCESS(status))
{
DbgPrint("FwpsAcquireClassifyHandle0() failed\n");
return status;
}
status = FailRequest(FilterId, LayerId, ClassifyOut, classifyHandle);
if (!NT_SUCCESS(status))
{
return status;
}
FwpsReleaseClassifyHandle0(classifyHandle);
return STATUS_SUCCESS;
}
} // namespace firewall::pending

52
src/firewall/pending.h Normal file
View File

@@ -0,0 +1,52 @@
#pragma once
#include "wfp.h"
#include <wdf.h>
#include "../procbroker/procbroker.h"
//
// This module is currently used for pending redirection classifications,
// but could plausibly be extended to handle other types of classifications,
// as and when the need arises.
//
namespace firewall::pending
{
struct CONTEXT;
NTSTATUS
Initialize
(
CONTEXT **Context,
procbroker::CONTEXT *ProcessEventBroker
);
void
TearDown
(
CONTEXT **Context
);
NTSTATUS
PendRequest
(
CONTEXT *Context,
HANDLE ProcessId,
void *ClassifyContext,
UINT64 FilterId,
UINT16 LayerId,
FWPS_CLASSIFY_OUT0 *ClassifyOut
);
NTSTATUS
FailRequest
(
HANDLE ProcessId,
void *ClassifyContext,
UINT64 FilterId,
UINT16 LayerId,
FWPS_CLASSIFY_OUT0 *ClassifyOut
);
} // namespace firewall::pending

View File

@@ -204,11 +204,13 @@
<ClCompile Include="eventing\builder.cpp" />
<ClCompile Include="eventing\eventing.cpp" />
<ClCompile Include="firewall\appfilters.cpp" />
<ClCompile Include="firewall\asyncbind.cpp" />
<ClCompile Include="firewall\callouts.cpp" />
<ClCompile Include="firewall\classify.cpp" />
<ClCompile Include="firewall\filters.cpp" />
<ClCompile Include="firewall\firewall.cpp" />
<ClCompile Include="firewall\logging.cpp" />
<ClCompile Include="firewall\mode.cpp" />
<ClCompile Include="firewall\pending.cpp" />
<ClCompile Include="ioctl.cpp" />
<ClCompile Include="ipaddr.cpp" />
<ClCompile Include="procbroker\procbroker.cpp" />
@@ -235,14 +237,16 @@
<ClInclude Include="eventing\context.h" />
<ClInclude Include="eventing\eventing.h" />
<ClInclude Include="firewall\appfilters.h" />
<ClInclude Include="firewall\asyncbind.h" />
<ClInclude Include="firewall\callouts.h" />
<ClInclude Include="firewall\classify.h" />
<ClInclude Include="firewall\constants.h" />
<ClInclude Include="firewall\context.h" />
<ClInclude Include="firewall\filters.h" />
<ClInclude Include="firewall\firewall.h" />
<ClInclude Include="firewall\identifiers.h" />
<ClInclude Include="firewall\logging.h" />
<ClInclude Include="firewall\mode.h" />
<ClInclude Include="firewall\pending.h" />
<ClInclude Include="firewall\wfp.h" />
<ClInclude Include="ioctl.h" />
<ClInclude Include="ipaddr.h" />

View File

@@ -33,9 +33,6 @@
<ClCompile Include="procbroker\procbroker.cpp">
<Filter>procbroker</Filter>
</ClCompile>
<ClCompile Include="firewall\asyncbind.cpp">
<Filter>firewall</Filter>
</ClCompile>
<ClCompile Include="firewall\mode.cpp">
<Filter>firewall</Filter>
</ClCompile>
@@ -45,6 +42,15 @@
<ClCompile Include="firewall\appfilters.cpp">
<Filter>firewall</Filter>
</ClCompile>
<ClCompile Include="firewall\logging.cpp">
<Filter>firewall</Filter>
</ClCompile>
<ClCompile Include="firewall\pending.cpp">
<Filter>firewall</Filter>
</ClCompile>
<ClCompile Include="firewall\classify.cpp">
<Filter>firewall</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<Inf Include="mullvad-split-tunnel.inf" />
@@ -128,9 +134,6 @@
<ClInclude Include="procbroker\procbroker.h">
<Filter>procbroker</Filter>
</ClInclude>
<ClInclude Include="firewall\asyncbind.h">
<Filter>firewall</Filter>
</ClInclude>
<ClInclude Include="procmgmt\callbacks.h">
<Filter>procmgmt</Filter>
</ClInclude>
@@ -147,6 +150,15 @@
<Filter>firewall</Filter>
</ClInclude>
<ClInclude Include="trace.h" />
<ClInclude Include="firewall\logging.h">
<Filter>firewall</Filter>
</ClInclude>
<ClInclude Include="firewall\pending.h">
<Filter>firewall</Filter>
</ClInclude>
<ClInclude Include="firewall\classify.h">
<Filter>firewall</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<Filter Include="firewall">