cwindowsdriverwfp

Windows WFP Driver: Getting BSOD when processing packets in ClassifyFn callback


I am trying to code a simple firewall application which can allow or block network connection attempts made from userlevel processes.

To do so, following the WFPStarterKit tutorial, I created a WFP Driver which is set to intercept data at FWPM_LAYER_OUTBOUND_TRANSPORT_V4 layer.

The ClassifyFn callback function is responsible for intercepting the connection attempt, and either allow or deny it.

Once the ClassifyFn callback gets hit, the ProcessID of the packet is sent, along with a few other info, to a userlevel process through the FltSendMessage function.

The userlevel process receives the message, checks the ProcessID, and replies a boolean allow/deny command to the driver.

While this approach works when blocking a first packet, in some cases (expecially when allowing multiple packets) the code generates a BSOD with the INVALID_PROCESS_ATTACH_ATTEMPT error code. The error is triggered at the call to FltSendMessage.

While I am still unable to pinpoint the exact problem, it seems that making the callout thread wait (through FltSendMessage) for a reply from userlevel can generate this BSOD error on some conditions.

I would be very grateful if you can point me to the right direction.

Here is the function where I register the callout:

    NTSTATUS register_example_callout(DEVICE_OBJECT * wdm_device)
{
    NTSTATUS status = STATUS_SUCCESS;
    FWPS_CALLOUT s_callout = { 0 };
    FWPM_CALLOUT m_callout = { 0 };
    FWPM_DISPLAY_DATA display_data = { 0 };

    if (filter_engine_handle == NULL)
        return STATUS_INVALID_HANDLE;

    display_data.name = EXAMPLE_CALLOUT_NAME;
    display_data.description = EXAMPLE_CALLOUT_DESCRIPTION;

    // Register a new Callout with the Filter Engine using the provided callout functions
    s_callout.calloutKey = EXAMPLE_CALLOUT_GUID;
    s_callout.classifyFn = example_classify;
    s_callout.notifyFn = example_notify;
    s_callout.flowDeleteFn = example_flow_delete;
    status = FwpsCalloutRegister((void *)wdm_device, &s_callout, &example_callout_id);
    if (!NT_SUCCESS(status)) {
        DbgPrint("Failed to register callout functions for example callout, status 0x%08x", status);
        goto Exit;
    }

    // Setup a FWPM_CALLOUT structure to store/track the state associated with the FWPS_CALLOUT
    m_callout.calloutKey = EXAMPLE_CALLOUT_GUID;
    m_callout.displayData = display_data;
    m_callout.applicableLayer = FWPM_LAYER_OUTBOUND_TRANSPORT_V4;
    m_callout.flags = 0;
    status = FwpmCalloutAdd(filter_engine_handle, &m_callout, NULL, NULL);
    if (!NT_SUCCESS(status)) {
        DbgPrint("Failed to register example callout, status 0x%08x", status);
    }
    else {
        DbgPrint("Example Callout Registered");
    }

Exit:
    return status;
}

Here is the callout function:

    /*************************
ClassifyFn Function
**************************/
void example_classify(
    const FWPS_INCOMING_VALUES * inFixedValues,
    const FWPS_INCOMING_METADATA_VALUES * inMetaValues,
    void * layerData,
    const void * classifyContext,
    const FWPS_FILTER * filter,
    UINT64 flowContext,
    FWPS_CLASSIFY_OUT * classifyOut)
{
    UNREFERENCED_PARAMETER(layerData);
    UNREFERENCED_PARAMETER(classifyContext);
    UNREFERENCED_PARAMETER(flowContext);
    UNREFERENCED_PARAMETER(filter);
    UNREFERENCED_PARAMETER(inMetaValues);

    NETWORK_ACCESS_QUERY AccessQuery;
    BOOLEAN SafeToOpen = TRUE;
    classifyOut->actionType = FWP_ACTION_PERMIT;

    AccessQuery.remote_address = inFixedValues->incomingValue[FWPS_FIELD_OUTBOUND_TRANSPORT_V4_IP_REMOTE_ADDRESS].value.uint32;
    AccessQuery.remote_port = inFixedValues->incomingValue[FWPS_FIELD_OUTBOUND_TRANSPORT_V4_IP_REMOTE_PORT].value.uint16;

    // Get Process ID
    AccessQuery.ProcessId = (UINT64)PsGetCurrentProcessId();

    if (!AccessQuery.ProcessId) 
    {
        return;
    }

    // Here we connect to our userlevel application using FltSendMessage.
    // Some checks are done and the SafeToOpen variable is populated with a BOOLEAN which indicates if to allow or block the packet.
    // However, sometimes, a BSOD is generated with an INVALID_PROCESS_ATTACH_ATTEMPT error on the FltSendMessage call
    QueryUserLevel(QUERY_NETWORK, &AccessQuery, sizeof(NETWORK_ACCESS_QUERY), &SafeToOpen, NULL, 0);

    if (!SafeToOpen) {
        classifyOut->actionType = FWP_ACTION_BLOCK;
    }

    return;
}

Solution

  • The problem was that sometimes the ClassifyFn callback function can be called at IRQL DISPATCH_LEVEL. FltSendMessage does not support DISPATCH_LEVEL, as it can only be run at IRQL <= APC_LEVEL. Running at DISPATCH_LEVEL can cause this function to generate a BSOD.

    I solved the problem by invoking FltSendMessage from a worker thread which runs at IRQL PASSIVE_LEVEL.

    The worker thread can be created using IoQueueWorkItem.