mirror of
https://github.com/amnezia-vpn/win-split-tunnel.git
synced 2026-05-17 08:16:00 +03:00
Overhaul pending of classifications and make implementation compatible with connect-redirect
This commit is contained in:
@@ -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
|
||||
@@ -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
|
||||
@@ -2,7 +2,7 @@
|
||||
#include "firewall.h"
|
||||
#include "context.h"
|
||||
#include "identifiers.h"
|
||||
#include "asyncbind.h"
|
||||
#include "pending.h"
|
||||
#include "callouts.h"
|
||||
#include "logging.h"
|
||||
#include "../util.h"
|
||||
@@ -315,26 +315,30 @@ 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
|
||||
);
|
||||
|
||||
@@ -343,13 +347,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
|
||||
);
|
||||
}
|
||||
@@ -453,11 +456,12 @@ CalloutClassifyBind
|
||||
}
|
||||
case PROCESS_SPLIT_VERDICT::UNKNOWN:
|
||||
{
|
||||
ClassifyUnknownBind
|
||||
PendClassification
|
||||
(
|
||||
context,
|
||||
context->PendedClassifications,
|
||||
HANDLE(MetaValues->processId),
|
||||
Filter->filterId,
|
||||
FixedValues->layerId,
|
||||
ClassifyContext,
|
||||
ClassifyOut
|
||||
);
|
||||
@@ -513,12 +517,12 @@ LocalAddress(const IN6_ADDR *addr)
|
||||
void
|
||||
RewriteConnection
|
||||
(
|
||||
CONTEXT *Context,
|
||||
const FWPS_INCOMING_VALUES0 *FixedValues,
|
||||
const FWPS_INCOMING_METADATA_VALUES0 *MetaValues,
|
||||
UINT64 FilterId,
|
||||
const void *ClassifyContext,
|
||||
FWPS_CLASSIFY_OUT0 *ClassifyOut,
|
||||
CONTEXT *Context
|
||||
FWPS_CLASSIFY_OUT0 *ClassifyOut
|
||||
)
|
||||
{
|
||||
UNREFERENCED_PARAMETER(MetaValues);
|
||||
@@ -781,18 +785,37 @@ CalloutClassifyConnect
|
||||
|
||||
const auto verdict = callbacks.QueryProcess(HANDLE(MetaValues->processId), callbacks.Context);
|
||||
|
||||
if (verdict == PROCESS_SPLIT_VERDICT::DO_SPLIT)
|
||||
switch (verdict)
|
||||
{
|
||||
RewriteConnection
|
||||
(
|
||||
FixedValues,
|
||||
MetaValues,
|
||||
Filter->filterId,
|
||||
ClassifyContext,
|
||||
ClassifyOut,
|
||||
context
|
||||
);
|
||||
}
|
||||
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
|
||||
);
|
||||
|
||||
break;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
bool IsAleReauthorize
|
||||
|
||||
@@ -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"
|
||||
@@ -18,33 +19,6 @@ struct IP_ADDRESSES_MGMT
|
||||
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.
|
||||
@@ -81,9 +55,7 @@ struct CONTEXT
|
||||
|
||||
IP_ADDRESSES_MGMT IpAddresses;
|
||||
|
||||
PENDED_BIND_MGMT PendedBinds;
|
||||
|
||||
procbroker::CONTEXT *ProcessEventBroker;
|
||||
pending::CONTEXT *PendedClassifications;
|
||||
|
||||
eventing::CONTEXT *Eventing;
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
#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"
|
||||
@@ -982,10 +982,7 @@ Initialize
|
||||
|
||||
RtlZeroMemory(context, sizeof(*context));
|
||||
|
||||
InitializeListHead(&context->PendedBinds.Records);
|
||||
|
||||
context->Callbacks = *Callbacks;
|
||||
context->ProcessEventBroker = ProcessEventBroker;
|
||||
context->Eventing = Eventing;
|
||||
|
||||
auto status = WdfSpinLockCreate(WDF_NO_OBJECT_ATTRIBUTES, &context->IpAddresses.Lock);
|
||||
@@ -999,17 +996,6 @@ Initialize
|
||||
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))
|
||||
@@ -1018,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);
|
||||
@@ -1049,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:
|
||||
|
||||
{
|
||||
@@ -1079,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);
|
||||
@@ -1127,11 +1119,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);
|
||||
|
||||
|
||||
537
src/firewall/pending.cpp
Normal file
537
src/firewall/pending.cpp
Normal file
@@ -0,0 +1,537 @@
|
||||
#include "pending.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;
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
// TODO: replace with call to reusable function.
|
||||
ClassifyOut->actionType = FWP_ACTION_PERMIT;
|
||||
ClassifyOut->rights &= ~FWPS_RIGHT_ACTION_WRITE;
|
||||
|
||||
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
52
src/firewall/pending.h
Normal 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
|
||||
@@ -204,12 +204,12 @@
|
||||
<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\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" />
|
||||
@@ -236,7 +236,6 @@
|
||||
<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\constants.h" />
|
||||
<ClInclude Include="firewall\context.h" />
|
||||
@@ -245,6 +244,7 @@
|
||||
<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" />
|
||||
|
||||
@@ -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>
|
||||
@@ -48,6 +45,9 @@
|
||||
<ClCompile Include="firewall\logging.cpp">
|
||||
<Filter>firewall</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="firewall\pending.cpp">
|
||||
<Filter>firewall</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Inf Include="mullvad-split-tunnel.inf" />
|
||||
@@ -131,9 +131,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>
|
||||
@@ -153,6 +150,9 @@
|
||||
<ClInclude Include="firewall\logging.h">
|
||||
<Filter>firewall</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="firewall\pending.h">
|
||||
<Filter>firewall</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Filter Include="firewall">
|
||||
|
||||
Reference in New Issue
Block a user