Sample Code

windows driver samples/ Windows Filtering Platform Stream Edit Sample/ C++/ sys/ oob_edit.c/

/*++

Copyright (c) Microsoft Corporation. All rights reserved

Abstract:

    Stream Edit Callout Driver Sample.
    
    This sample demonstrates Out-of-band (OOB) stream inspection/editing 
    via the WFP stream API.

Environment:

    Kernel mode

--*/

#include <ntddk.h>

#pragma warning(push)
#pragma warning(disable:4201)       // unnamed struct/union

#include <fwpsk.h>

#pragma warning(pop)

#include <fwpmk.h>

#include "inline_edit.h"
#include "oob_edit.h"
#include "stream_callout.h"

#define STREAM_EDITOR_OUTGOING_DATA_TAG 'doeS'
#define STREAM_EDITOR_MDL_DATA_TAG 'dmeS'

void* gThreadObj;

KSTART_ROUTINE StreamOobEditWorker;

NTSTATUS
OobEditInit(
   _Out_ STREAM_EDITOR* streamEditor
   )
{
   NTSTATUS status = STATUS_SUCCESS;

   HANDLE threadHandle;
   
   streamEditor->editInline = FALSE;

   KeInitializeSpinLock(&streamEditor->oobEditInfo.editLock);   

   KeInitializeEvent(
      &streamEditor->oobEditInfo.editEvent,
      NotificationEvent,
      FALSE
      );

  streamEditor->oobEditInfo.busyThreshold = 32 * 1024;
  streamEditor->oobEditInfo.editState = OOB_EDIT_IDLE;

  InitializeListHead(&streamEditor->oobEditInfo.outgoingDataQueue);


   status = PsCreateSystemThread(
               &threadHandle,
               THREAD_ALL_ACCESS,
               NULL,
               NULL,
               NULL,
               StreamOobEditWorker,
               &gStreamEditor
               );

   if (!NT_SUCCESS(status))
   {
      goto Exit;
   }

   status = ObReferenceObjectByHandle(
               threadHandle,
               0,
               NULL,
               KernelMode,
               &gThreadObj,
               NULL
               );
   NT_ASSERT(NT_SUCCESS(status));

   ZwClose(threadHandle);

Exit:

   return status;
}

void
OobEditShutdown(
   _Out_ STREAM_EDITOR* streamEditor
   )
{
   KLOCK_QUEUE_HANDLE editLockHandle;

   KeAcquireInStackQueuedSpinLock(
      &streamEditor->oobEditInfo.editLock,
      &editLockHandle
      );

   streamEditor->oobEditInfo.shuttingDown = TRUE;

   switch (streamEditor->oobEditInfo.editState)
   {
      case OOB_EDIT_IDLE:
      {
         streamEditor->oobEditInfo.editState = OOB_EDIT_SHUT_DOWN;

         KeSetEvent(
            &gStreamEditor.oobEditInfo.editEvent,
            IO_NO_INCREMENT, 
            FALSE
            );

         break;
      }
      default:
         break;
   };

   KeReleaseInStackQueuedSpinLock(&editLockHandle);

   NT_ASSERT(gThreadObj != NULL);

   KeWaitForSingleObject(
      gThreadObj,
      Executive,
      KernelMode,
      FALSE,
      NULL
      );

   ObDereferenceObject(gThreadObj);
}

__inline
NET_BUFFER_LIST* 
TailOfNetBufferListChain(
   _In_ NET_BUFFER_LIST* netBufferListChain
   )
{
   NT_ASSERT(netBufferListChain != NULL);

   while (netBufferListChain->Next != NULL)
   {
      netBufferListChain = netBufferListChain->Next;
   }

   return netBufferListChain;
}

void 
NTAPI 
StreamOobInjectCompletionFn(
   _Inout_ void* context,
   _Inout_ NET_BUFFER_LIST* netBufferList,
   BOOLEAN dispatchLevel
   )
/* ++

   Injection completion function for injecting an NBL created using 
   FwpsAllocateNetBufferAndNetBufferList. This function frees up 
   resources allocated during StreamOobReinjectData().

-- */
{
   MDL* mdl = (MDL*)context;

   UNREFERENCED_PARAMETER(dispatchLevel);

   FwpsFreeNetBufferList(netBufferList);

   if (mdl != NULL)
   {
      IoFreeMdl(mdl);

      //
      // The MDL mapped over a pool alloc which we need to free here.
      //

      ExFreePoolWithTag(
         mdl->MappedSystemVa, 
         STREAM_EDITOR_MDL_DATA_TAG
         );
   }
}

void 
NTAPI StreamOobInjectCloneCompletionFn(
   _Inout_ void* context,
   _Inout_ NET_BUFFER_LIST* netBufferList,
   BOOLEAN dispatchLevel
   )
/* ++

   Injection completion function for injecting one of the NBLs cloned
   via FwpsCloneStreamData.
   
   FwpsCloneStreamData can return a chain of cloned NBLs; each NBL will 
   complete separately.

-- */
{
   UNREFERENCED_PARAMETER(context);
   UNREFERENCED_PARAMETER(dispatchLevel);

   FwpsFreeCloneNetBufferList(netBufferList, 0);
}

NTSTATUS
StreamOobQueueUpIncomingData(
   _Inout_ STREAM_EDITOR* streamEditor,
   _Inout_ FWPS_STREAM_DATA* streamData
   )
/* ++

   This function clones the indicated stream data into a NBL chain and 
   appends the chain at the end of the existing chain (if exists) inside
   the streamEditor.
   
   This function assumes that the oobEditInfo lock inside streamEditor is
   being held.

-- */
{
   NTSTATUS status;

   NET_BUFFER_LIST* clonedNetBufferListChain;

   status = FwpsCloneStreamData(
               streamData,
               NULL,
               NULL,
               0,
               &clonedNetBufferListChain
               );

   if (!NT_SUCCESS(status))
   {
      goto Exit;
   }

   //
   // TCP Fin (EOF) is indicated by an empty NBL with disconnect flag
   // set, since it does not contain any data we queue it up separately.
   //

   if ((streamData->flags & FWPS_STREAM_FLAG_SEND_DISCONNECT) || 
       (streamData->flags & FWPS_STREAM_FLAG_RECEIVE_DISCONNECT))
   {
      NT_ASSERT(streamEditor->oobEditInfo.noMoreData);
      NT_ASSERT(streamEditor->oobEditInfo.nblEof == NULL);
      NT_ASSERT(streamData->dataLength == 0);

      streamEditor->oobEditInfo.nblEof = clonedNetBufferListChain;

      status = STATUS_SUCCESS;
      goto Exit;
   }

   if (streamEditor->oobEditInfo.nblTail != NULL)
   {
      NT_ASSERT(streamEditor->oobEditInfo.nblHead != NULL);
      NT_ASSERT(streamEditor->oobEditInfo.nblTail->Next == NULL);

      streamEditor->oobEditInfo.nblTail->Next = clonedNetBufferListChain;
   }
   else
   {
      NT_ASSERT(streamEditor->oobEditInfo.nblHead == NULL);

      streamEditor->oobEditInfo.nblHead = clonedNetBufferListChain;
   }

   streamEditor->oobEditInfo.nblTail = 
      TailOfNetBufferListChain(clonedNetBufferListChain);

   streamEditor->oobEditInfo.totalDataLength += streamData->dataLength;

   streamEditor->oobEditInfo.streamFlags = streamData->flags;

Exit:

   return status;
}

void
StreamOobEdit(
   _Inout_ STREAM_EDITOR* streamEditor,
   const FWPS_INCOMING_VALUES* inFixedValues,
   const FWPS_INCOMING_METADATA_VALUES* inMetaValues,
   const FWPS_FILTER* filter,
   _Inout_ FWPS_STREAM_DATA* streamData,
   _Inout_ FWPS_STREAM_CALLOUT_IO_PACKET* ioPacket,
   _Inout_ FWPS_CLASSIFY_OUT* classifyOut
   )
/* ++

   This function queues up incoming data and notifies the worker thread
   to process them. The incoming data is blocked and removed from the
   stream while data is pending.

   If the editor is shutdown (e.g. during driverUnload) as indicated 
   by OOB_EDIT_SHUT_DOWN state, it permits the indicated data inline after 
   flushing all pended data (to be carried out by the caller).

-- */

{
   NTSTATUS status;

   KLOCK_QUEUE_HANDLE editLockHandle;

   KeAcquireInStackQueuedSpinLock(
      &streamEditor->oobEditInfo.editLock,
      &editLockHandle
      );

   if (streamEditor->oobEditInfo.nblEof != NULL)
   {
      //
      // A new flow arrives before we finish processing an earlier flow. Production
      // code should create 1:1 between streamEditor and flow to handle this
      // condition. See the "MSN Monitor sample" for how that can be implemented. 
      //

      ioPacket->streamAction = FWPS_STREAM_ACTION_DROP_CONNECTION;
      classifyOut->actionType = FWP_ACTION_NONE;

      goto Exit;
   }

   if (classifyOut->flags & FWPS_CLASSIFY_OUT_FLAG_NO_MORE_DATA)
   {
      NT_ASSERT(streamEditor->oobEditInfo.nblEof == NULL);

      streamEditor->oobEditInfo.noMoreData = TRUE;
   }

   // 
   // Record needed flow information etc for data (re-)injection.
   //

   streamEditor->oobEditInfo.calloutId = filter->action.calloutId;
   streamEditor->oobEditInfo.flowId = inMetaValues->flowHandle;
   streamEditor->oobEditInfo.layerId = inFixedValues->layerId;

   switch (streamEditor->oobEditInfo.editState)
   {
      case OOB_EDIT_PROCESSING:
      {
         if ((streamEditor->oobEditInfo.totalDataLength + streamData->dataLength) > 
            streamEditor->oobEditInfo.busyThreshold)
         {
            ioPacket->streamAction = FWPS_STREAM_ACTION_DEFER;
            classifyOut->actionType = FWP_ACTION_NONE;

            streamEditor->oobEditInfo.editState = OOB_EDIT_BUSY;
         }
         else
         {
            status = StreamOobQueueUpIncomingData(
                        streamEditor,
                        streamData
                        );

            if (!NT_SUCCESS(status))
            {
               streamEditor->oobEditInfo.editState = OOB_EDIT_ERROR;

               ioPacket->streamAction = FWPS_STREAM_ACTION_DROP_CONNECTION;
               classifyOut->actionType = FWP_ACTION_NONE;
            }
            else
            {
               //
               // State remains at OOB_EDIT_PROCESSING state. Since the worker thread
               // is active there is no need to set the event (to wake it up)
               //

               ioPacket->streamAction = FWPS_STREAM_ACTION_NONE;
               ioPacket->countBytesEnforced = 0;
               classifyOut->actionType = FWP_ACTION_BLOCK;
               classifyOut->rights &= ~FWPS_RIGHT_ACTION_WRITE;
            }
         }

         break;
      }
      case OOB_EDIT_IDLE:
      {
         status = StreamOobQueueUpIncomingData(
                     streamEditor,
                     streamData
                     );

         if (!NT_SUCCESS(status))
         {
            streamEditor->oobEditInfo.editState = OOB_EDIT_ERROR;

            ioPacket->streamAction = FWPS_STREAM_ACTION_DROP_CONNECTION;
            classifyOut->actionType = FWP_ACTION_NONE;
         }
         else
         {
            streamEditor->oobEditInfo.editState = OOB_EDIT_PROCESSING;

            //
            // The worker thread is idle waiting for more work, now wake it up.
            //
            KeSetEvent(
               &streamEditor->oobEditInfo.editEvent, 
               0, 
               FALSE
               );

            ioPacket->streamAction = FWPS_STREAM_ACTION_NONE;
            ioPacket->countBytesEnforced = 0;
            classifyOut->actionType = FWP_ACTION_BLOCK;
            classifyOut->rights &= ~FWPS_RIGHT_ACTION_WRITE;
         }

         break;
      }
      case OOB_EDIT_SHUT_DOWN:
      {
         ioPacket->streamAction = FWPS_STREAM_ACTION_NONE;
         ioPacket->countBytesEnforced = 0;
         classifyOut->actionType = FWP_ACTION_PERMIT;

         if (filter->flags & FWPS_FILTER_FLAG_CLEAR_ACTION_RIGHT)
         {
            classifyOut->rights &= ~FWPS_RIGHT_ACTION_WRITE;
         }

         break;
      }
      case OOB_EDIT_ERROR:
      {
         ioPacket->streamAction = FWPS_STREAM_ACTION_DROP_CONNECTION;
         classifyOut->actionType = FWP_ACTION_NONE;
         break;
      }
      default:
         NT_ASSERT(FALSE);
   };

Exit:

   KeReleaseInStackQueuedSpinLock(&editLockHandle);
}

NTSTATUS
StreamOobQueueUpOutgoingData(
   _Inout_ STREAM_EDITOR* streamEditor,
   _Inout_ NET_BUFFER_LIST* netBufferList,
   BOOLEAN isClone,
   size_t dataLength,
   DWORD streamFlags,
   _In_opt_ MDL* mdl
   )
/* ++

   This function queues up processed data (either sections of the indicated
   data or newly created data) such that they can be (re-)injected back to
   the data stream during the following context.

      1. Before FWP_ACTION_BLOCK is returned from the ClassifyFn, or
      2. After EOF is indicated.

   Under the conditions above, the incoming data (which we pend) and the 
   outgoing data (which we (re-)inject) can be synchronized properly).

-- */
{
   NTSTATUS status = STATUS_SUCCESS;
   KLOCK_QUEUE_HANDLE editLockHandle;
   OUTGOING_STREAM_DATA* outgoingStreamData;

   outgoingStreamData = (OUTGOING_STREAM_DATA*) ExAllocatePoolWithTag(
                                                   NonPagedPool,
                                                   sizeof(OUTGOING_STREAM_DATA),
                                                   STREAM_EDITOR_OUTGOING_DATA_TAG
                                                   );

   if (outgoingStreamData == NULL)
   {
      status = STATUS_NO_MEMORY;
      return status;
   }

   RtlZeroMemory(outgoingStreamData, sizeof(OUTGOING_STREAM_DATA));

   outgoingStreamData->netBufferList = netBufferList;
   outgoingStreamData->isClone = isClone;
   outgoingStreamData->dataLength = dataLength;
   outgoingStreamData->streamFlags = streamFlags;
   outgoingStreamData->mdl = mdl;

   KeAcquireInStackQueuedSpinLock(
      &streamEditor->oobEditInfo.editLock,
      &editLockHandle
      );

   InsertTailList(
      &streamEditor->oobEditInfo.outgoingDataQueue,
      &outgoingStreamData->listEntry
      );

   KeReleaseInStackQueuedSpinLock(&editLockHandle);

   return status;
}

NTSTATUS
StreamOobFlushOutgoingData(
   _Inout_ STREAM_EDITOR* streamEditor
   )
{
   NTSTATUS status = STATUS_SUCCESS;

   KLOCK_QUEUE_HANDLE editLockHandle;
   OUTGOING_STREAM_DATA* outgoingStreamData = NULL;

   for(;;)
   {
      KeAcquireInStackQueuedSpinLock(
         &streamEditor->oobEditInfo.editLock,
         &editLockHandle
         );

      if (!IsListEmpty(&streamEditor->oobEditInfo.outgoingDataQueue))
      {
         LIST_ENTRY* listEntry = 
            RemoveHeadList(&streamEditor->oobEditInfo.outgoingDataQueue);
         
         outgoingStreamData = CONTAINING_RECORD(
                                 listEntry, 
                                 OUTGOING_STREAM_DATA,
                                 listEntry
                                 );
      }

      KeReleaseInStackQueuedSpinLock(&editLockHandle);

      if (outgoingStreamData == NULL)
      {
         break;
      }

      status = FwpsStreamInjectAsync(
                  gInjectionHandle,
                  NULL,
                  0,
                  streamEditor->oobEditInfo.flowId,
                  streamEditor->oobEditInfo.calloutId,
                  streamEditor->oobEditInfo.layerId,
                  outgoingStreamData->streamFlags,
                  outgoingStreamData->netBufferList,
                  outgoingStreamData->dataLength,
                  outgoingStreamData->isClone ? StreamOobInjectCloneCompletionFn :
                                                StreamOobInjectCompletionFn,
                  outgoingStreamData->mdl
                  );

      if (!NT_SUCCESS(status))
      {
         goto Exit;
      }

      ExFreePoolWithTag(
         outgoingStreamData,
         STREAM_EDITOR_OUTGOING_DATA_TAG
         );

      outgoingStreamData = NULL;
   }
  
Exit:

   if (outgoingStreamData != NULL)
   {
      NT_ASSERT(!NT_SUCCESS(status));

      if (outgoingStreamData->isClone)
      {
         FwpsDiscardClonedStreamData(
            outgoingStreamData->netBufferList,
            0,
            FALSE
            );
      }
      else
      {
         FwpsFreeNetBufferList(outgoingStreamData->netBufferList);

         if (outgoingStreamData->mdl != NULL)
         {
            IoFreeMdl(outgoingStreamData->mdl);

            ExFreePoolWithTag(
               outgoingStreamData->mdl->MappedSystemVa, 
               STREAM_EDITOR_MDL_DATA_TAG
               );
         }
      }

      ExFreePoolWithTag(
         outgoingStreamData,
         STREAM_EDITOR_OUTGOING_DATA_TAG
         );
   }

   return status;
}


NTSTATUS
StreamOobReinjectData(
   _Inout_ STREAM_EDITOR* streamEditor,
   UINT32 streamFlags,
   const void* data,
   size_t length
   )
/* ++

   This function injects a section of the original indicated data back
   to the data stream.

   An MDL is allocated to describe the data section.

-- */
{
   NTSTATUS status;

   void* dataCopy = NULL;
   MDL* mdl = NULL;
   NET_BUFFER_LIST* netBufferList = NULL;

   dataCopy = ExAllocatePoolWithTag(
                  NonPagedPool,
                  length,
                  STREAM_EDITOR_MDL_DATA_TAG
                  );

   if (dataCopy == NULL)
   {
      status = STATUS_NO_MEMORY;
      goto Exit;
   }

   RtlCopyMemory(dataCopy, data, length);

   mdl = IoAllocateMdl(
            dataCopy,
            (ULONG)length,
            FALSE,
            FALSE,
            NULL
            );
   if (mdl == NULL)
   {
      status = STATUS_NO_MEMORY;
      goto Exit;
   }

   MmBuildMdlForNonPagedPool(mdl);

   status = FwpsAllocateNetBufferAndNetBufferList(
                  gNetBufferListPool,
                  0,
                  0,
                  mdl,
                  0,
                  length,
                  &netBufferList
                  );
   if (!NT_SUCCESS(status))
   {
      goto Exit;
   }

   NT_ASSERT(!(streamFlags & FWPS_STREAM_FLAG_SEND_DISCONNECT) && 
          !(streamFlags & FWPS_STREAM_FLAG_RECEIVE_DISCONNECT));

   status = StreamOobQueueUpOutgoingData(
               streamEditor,
               netBufferList,
               FALSE,
               length,
               streamFlags,
               mdl
               );
               
   if (!NT_SUCCESS(status))
   {
      goto Exit;
   }

   dataCopy = NULL;
   mdl = NULL;
   netBufferList = NULL;

Exit:

   if (netBufferList != NULL)
   {
      FwpsFreeNetBufferList(netBufferList);
   }
   if (mdl != NULL)
   {
      IoFreeMdl(mdl);
   }
   if (dataCopy != NULL)
   {
      ExFreePoolWithTag(
         dataCopy, 
         STREAM_EDITOR_MDL_DATA_TAG
         );
   }
   return status;
}

NTSTATUS
StreamOobInjectReplacement(
   _Inout_ STREAM_EDITOR* streamEditor,
   UINT32 streamFlags,
   _In_opt_ MDL* data,
   size_t length
   )
/* ++

   This function injects a section of replacement data (in place of data
   removed from the stream) into the data stream.

   The MDL describes the replacement data is allocated during DriverEntry
   and does not need to be freed during injection completion.

-- */
{
   NTSTATUS status;

   NET_BUFFER_LIST* netBufferList = NULL;

   status = FwpsAllocateNetBufferAndNetBufferList(
               gNetBufferListPool,
               0,
               0,
               data,
               0,
               length,
               &netBufferList
               );

   if (!NT_SUCCESS(status))
   {
      goto Exit;
   }

   NT_ASSERT(!(streamFlags & FWPS_STREAM_FLAG_SEND_DISCONNECT) && 
          !(streamFlags & FWPS_STREAM_FLAG_RECEIVE_DISCONNECT));

   status = StreamOobQueueUpOutgoingData(
               streamEditor,
               netBufferList,
               FALSE,
               length,
               streamFlags,
               NULL
               );

   if (!NT_SUCCESS(status))
   {
      FwpsFreeNetBufferList(netBufferList);
      goto Exit;
   }

   netBufferList = NULL;

Exit:

   if (netBufferList != NULL)
   {
      FwpsFreeNetBufferList(netBufferList);
   }

   return status;
}

NTSTATUS
StreamOobCopyDataToFlatBuffer(
   _Inout_ STREAM_EDITOR* streamEditor,
   _Inout_ NET_BUFFER_LIST* netBufferListChain,
   size_t totalDataLength,
   DWORD streamFlags
   )
/* ++

   This function copies the data described by NBL(s) into a flat buffer. 
   
   It reuses the FwpsCopyStreamDataToBuffer API (via StreamCopyDataForInspection)
   by creating a FWPS_STREAM_DATA struct.

-- */
{
   NTSTATUS status = STATUS_SUCCESS;

   FWPS_STREAM_DATA streamData = {0};

   if (totalDataLength > 0)
   {
      streamData.netBufferListChain = netBufferListChain;
      streamData.dataLength =  totalDataLength;
      streamData.flags = streamFlags;

      streamData.dataOffset.netBufferList = netBufferListChain;
      streamData.dataOffset.netBuffer = 
         NET_BUFFER_LIST_FIRST_NB(streamData.dataOffset.netBufferList);
      streamData.dataOffset.mdl = 
         NET_BUFFER_CURRENT_MDL(streamData.dataOffset.netBuffer);
      streamData.dataOffset.mdlOffset = 
         NET_BUFFER_CURRENT_MDL_OFFSET(streamData.dataOffset.netBuffer);

      if (StreamCopyDataForInspection(
            streamEditor,
            &streamData
            ) == FALSE)
      {
         status = STATUS_NO_MEMORY;
      }
   }

   return status;
}

NTSTATUS
StreamOobEditData(
   _Inout_ STREAM_EDITOR* streamEditor,
   _Inout_ NET_BUFFER_LIST* netBufferListChain,
   size_t totalDataLength,
   DWORD streamFlags
   )
/* ++

   This function first copies the stream data into a flat inspection buffer;
   it then parses the buffer looking for the matching pattern. For
   non-matching sections it re-injects the data back; for a match it skips 
   over and injects an replacement section.

   If a match can not be determined due to lack of data, it injects the
   non-matching section back and moves the potential match to the beginning
   of the inspection buffer.

   When an EOF is reached, it flushes all processed stream sections back
   and re-injects the FIN back to end the stream.

-- */
{
   NTSTATUS status = STATUS_SUCCESS;

   UINT i = 0;
   BOOLEAN streamModified = FALSE;
   BOOLEAN potentialMatch = FALSE;

   BYTE* dataStart;

   UINT findLength = (UINT) strlen(configStringToFind);

   status = StreamOobCopyDataToFlatBuffer(
               streamEditor,
               netBufferListChain,
               totalDataLength,
               streamFlags
               );
   if (!NT_SUCCESS(status))
   {
      goto Exit;
   }
    
   dataStart =  (BYTE*)streamEditor->scratchBuffer + streamEditor->dataOffset;

   for (; i < streamEditor->dataLength; ++i)
   {
      if (i + findLength <= streamEditor->dataLength)
      {
         if (RtlCompareMemory(
               dataStart + i,
               configStringToFind,
               findLength
               ) == findLength)
         {
            if (i != 0)
            {
               status = StreamOobReinjectData(
                              streamEditor,
                              streamFlags, 
                              dataStart,
                              i
                              );

               if (!NT_SUCCESS(status))
               {
                  goto Exit;
               }

               streamEditor->dataOffset += i;
               streamEditor->dataLength -= i;

               i = 0;
            }

            status = StreamOobInjectReplacement(
                        streamEditor,
                        streamFlags, 
                        gStringToReplaceMdl,
                        strlen(configStringToReplace)
                        );

            if (!NT_SUCCESS(status))
            {
               goto Exit;
            }

            streamEditor->dataOffset += findLength;
            streamEditor->dataLength -= findLength;

            streamModified = TRUE;

            if (streamEditor->dataLength > 0)
            {
               dataStart =  (BYTE*)streamEditor->scratchBuffer + streamEditor->dataOffset;

               --i;               

               continue;
            }
            else
            {
               streamEditor->dataOffset = 0;
            }
         }
      }
      else
      {
         if (streamEditor->oobEditInfo.noMoreData)
         {
            break;
         }

         if (RtlCompareMemory(
               dataStart + i,
               configStringToFind,
               streamEditor->dataLength - i
               ) == streamEditor->dataLength - i)
         {
            potentialMatch = TRUE;  // this is a partial find

            status = StreamOobReinjectData(
                           streamEditor,
                           streamFlags, 
                           dataStart,
                           i
                           );

            if (!NT_SUCCESS(status))
            {
               goto Exit;
            }

            RtlMoveMemory(
               (BYTE*)streamEditor->scratchBuffer,
               dataStart + i,
               streamEditor->dataLength - i
               );

            streamEditor->dataOffset = 0;
            streamEditor->dataLength = streamEditor->dataLength - i;

            break;
         }
      }
   }

   if (streamModified && streamEditor->dataLength > 0)
   {
      status = StreamOobReinjectData(
                     streamEditor,
                     streamFlags, 
                     dataStart,
                     streamEditor->dataLength
                     );

      if (!NT_SUCCESS(status))
      {
         goto Exit;
      }

      streamEditor->dataOffset = 0;
      streamEditor->dataLength = 0;
   }

   if (!streamModified && !potentialMatch)
   {
      if (totalDataLength > 0)
      {
         NT_ASSERT(!(streamFlags & FWPS_STREAM_FLAG_SEND_DISCONNECT) && 
                !(streamFlags & FWPS_STREAM_FLAG_RECEIVE_DISCONNECT));

         status = StreamOobQueueUpOutgoingData(
                     streamEditor,
                     netBufferListChain,
                     TRUE,
                     totalDataLength,
                     streamFlags,
                     NULL
                     );

         if (!NT_SUCCESS(status))
         {
            goto Exit;
         }

         netBufferListChain = NULL;
      }
      else if (streamEditor->dataLength > 0)
      {
         status = StreamOobReinjectData(
                        streamEditor,
                        streamFlags, 
                        dataStart,
                        streamEditor->dataLength
                        );

         if (!NT_SUCCESS(status))
         {
            goto Exit;
         }
      }

      streamEditor->dataOffset = 0;
      streamEditor->dataLength = 0;
   }

   if (streamEditor->oobEditInfo.nblEof != NULL)
   {
      status = StreamOobFlushOutgoingData(streamEditor);

      if (!NT_SUCCESS(status))
      {
         goto Exit;
      }

      status = FwpsStreamInjectAsync(
                  gInjectionHandle,
                  NULL,
                  0,
                  streamEditor->oobEditInfo.flowId,
                  streamEditor->oobEditInfo.calloutId,
                  streamEditor->oobEditInfo.layerId,
                  streamFlags | (configInspectionOutbound ? FWPS_STREAM_FLAG_SEND_DISCONNECT :
                                                            FWPS_STREAM_FLAG_RECEIVE_DISCONNECT),
                  streamEditor->oobEditInfo.nblEof,
                  0,
                  StreamOobInjectCompletionFn,
                  NULL
                  );

      if (!NT_SUCCESS(status))
      {
         goto Exit;
      }

      streamEditor->oobEditInfo.nblEof = NULL;
      streamEditor->oobEditInfo.noMoreData = FALSE;     
   }

Exit:

   if (netBufferListChain != NULL)
   {
      FwpsDiscardClonedStreamData(
         netBufferListChain,
         0,
         FALSE
         );
   }

   if (streamEditor->oobEditInfo.nblEof != NULL)
   {
      FwpsDiscardClonedStreamData(
         streamEditor->oobEditInfo.nblEof,
         0,
         FALSE
         );

      streamEditor->oobEditInfo.nblEof = NULL;
   }

   return status;
}

_IRQL_requires_same_
_Function_class_(KSTART_ROUTINE)
void
StreamOobEditWorker(
   _In_ void* StartContext
   )
/* ++

   This function waits for an event which gets signalled when there is data
   waiting to be inspected.

   Once awaken, the worker thread edits the stream until all stream data is
   processed (and then it waits for more work again).

   When requested to shutdown, it will finish the editing task and enters
   "shutdown" state.

-- */
{
   NTSTATUS status = STATUS_SUCCESS;

   NET_BUFFER_LIST* netBufferListChain = NULL;
   size_t totalDataLength;
   STREAM_EDITOR* streamEditor = (STREAM_EDITOR*)StartContext;
   DWORD streamFlags;

   for(;;)
   {
      KLOCK_QUEUE_HANDLE editLockHandle;

      KeWaitForSingleObject(
         &streamEditor->oobEditInfo.editEvent,
         Executive, 
         KernelMode, 
         FALSE, 
         NULL
         );

      if (streamEditor->oobEditInfo.editState == OOB_EDIT_ERROR ||
          streamEditor->oobEditInfo.editState == OOB_EDIT_SHUT_DOWN)
      {
         break;
      }

      KeAcquireInStackQueuedSpinLock(
         &streamEditor->oobEditInfo.editLock,
         &editLockHandle
         );

      NT_ASSERT(streamEditor->oobEditInfo.editState == OOB_EDIT_PROCESSING ||
             streamEditor->oobEditInfo.editState == OOB_EDIT_BUSY);

      netBufferListChain = streamEditor->oobEditInfo.nblHead;
      totalDataLength = streamEditor->oobEditInfo.totalDataLength;
      streamFlags = streamEditor->oobEditInfo.streamFlags;

      streamEditor->oobEditInfo.nblHead = NULL;
      streamEditor->oobEditInfo.nblTail = NULL;
      streamEditor->oobEditInfo.totalDataLength = 0;

      KeReleaseInStackQueuedSpinLock(&editLockHandle);

      _Analysis_assume_(netBufferListChain != NULL);
      status = StreamOobEditData(
                  streamEditor,
                  netBufferListChain,
                  totalDataLength,
                  streamFlags
                  );

      if (!NT_SUCCESS(status))
      {
         streamEditor->oobEditInfo.editState = OOB_EDIT_ERROR;
         break;
      }

      if (streamEditor->oobEditInfo.editState == OOB_EDIT_BUSY)
      {
         NTSTATUS streamContinueStatus;
         streamEditor->oobEditInfo.editState = OOB_EDIT_PROCESSING;
         
         streamContinueStatus = FwpsStreamContinue(
                                   streamEditor->oobEditInfo.flowId, 
                                   streamEditor->oobEditInfo.calloutId,
                                   streamEditor->oobEditInfo.layerId,
                                   streamEditor->oobEditInfo.streamFlags
                                   );

         if (!NT_SUCCESS(streamContinueStatus))
         {
            streamEditor->oobEditInfo.editState = OOB_EDIT_ERROR;
            break;
         }
      }

      KeAcquireInStackQueuedSpinLock(
         &streamEditor->oobEditInfo.editLock,
         &editLockHandle
         );

      if (streamEditor->oobEditInfo.nblHead == NULL)
      {
         if (!streamEditor->oobEditInfo.shuttingDown)
         {
            streamEditor->oobEditInfo.editState = OOB_EDIT_IDLE;
            KeClearEvent(&streamEditor->oobEditInfo.editEvent);
         }
         else
         {
            streamEditor->oobEditInfo.editState = OOB_EDIT_SHUT_DOWN;
         }
      }

      KeReleaseInStackQueuedSpinLock(&editLockHandle);
   }

   PsTerminateSystemThread(status);
}

#if(NTDDI_VERSION >= NTDDI_WIN7)

void 
NTAPI
StreamOobEditClassify(
   _In_ const FWPS_INCOMING_VALUES* inFixedValues,
   _In_ const FWPS_INCOMING_METADATA_VALUES* inMetaValues,
   _Inout_ void* layerData,
   _In_ const void* classifyContext,
   _In_ const FWPS_FILTER* filter,
   _In_ UINT64 flowContext,
   _Inout_ FWPS_CLASSIFY_OUT* classifyOut
   )

#else

void 
NTAPI
StreamOobEditClassify(
   _In_ const FWPS_INCOMING_VALUES* inFixedValues,
   _In_ const FWPS_INCOMING_METADATA_VALUES* inMetaValues,
   _Inout_ void* layerData,
   _In_ const FWPS_FILTER* filter,
   _In_ UINT64 flowContext,
   _Inout_ FWPS_CLASSIFY_OUT* classifyOut
   )

#endif /// (NTDDI_VERSION >= NTDDI_WIN7)
/* ++

   This is the ClassifyFn function registered by the OOB stream edit callout.

   An OOB stream modification callout blocks all indicated data after cloning
   them for processing by a kernel mode worker thread (or marshalling the data
   to user mode for inspection); the resultant/edited data will then be put
   back to the stream via the stream injection API.

   For such a callout, the processed data must be (re-)injected back to the 
   stream from within the ClassifyFn.

-- */
{
   FWPS_STREAM_CALLOUT_IO_PACKET* ioPacket;
   FWPS_STREAM_DATA* streamData;

   UINT findLength = (UINT) strlen(configStringToFind);

   ioPacket = (FWPS_STREAM_CALLOUT_IO_PACKET*)layerData;
   NT_ASSERT(ioPacket != NULL);

   streamData = ioPacket->streamData;
   NT_ASSERT(streamData != NULL);

#if(NTDDI_VERSION >= NTDDI_WIN7)
   UNREFERENCED_PARAMETER(classifyContext);
#endif /// (NTDDI_VERSION >= NTDDI_WIN7)
   UNREFERENCED_PARAMETER(flowContext);

   RtlZeroMemory(classifyOut, sizeof(FWPS_CLASSIFY_OUT));

   //
   // Let go the traffic that the editor does not care about.
   //

   if ((configInspectionOutbound  && (streamData->flags & FWPS_STREAM_FLAG_RECEIVE)) ||
       (!configInspectionOutbound && (streamData->flags & FWPS_STREAM_FLAG_SEND)))
   {
      ioPacket->streamAction = FWPS_STREAM_ACTION_NONE;
      classifyOut->actionType = FWP_ACTION_PERMIT;

      if (filter->flags & FWPS_FILTER_FLAG_CLEAR_ACTION_RIGHT)
      {
         classifyOut->rights &= ~FWPS_RIGHT_ACTION_WRITE;
      }

      goto Exit;
   }

   //
   // In this sample we don't edit TCP urgent data
   //

   if ((streamData->flags & FWPS_STREAM_FLAG_SEND_EXPEDITED) ||
       (streamData->flags & FWPS_STREAM_FLAG_RECEIVE_EXPEDITED))
   {
      ioPacket->streamAction = FWPS_STREAM_ACTION_NONE;
      classifyOut->actionType = FWP_ACTION_PERMIT;

      if (filter->flags & FWPS_FILTER_FLAG_CLEAR_ACTION_RIGHT)
      {
         classifyOut->rights &= ~FWPS_RIGHT_ACTION_WRITE;
      }

      goto Exit;
   }

   if ((streamData->dataLength < findLength) && 
       !(classifyOut->flags & FWPS_CLASSIFY_OUT_FLAG_NO_MORE_DATA))
   {
      ioPacket->streamAction = FWPS_STREAM_ACTION_NEED_MORE_DATA;
      ioPacket->countBytesRequired = findLength;

      classifyOut->actionType = FWP_ACTION_NONE;
      goto Exit;
   }

   StreamOobEdit(
      &gStreamEditor,
      inFixedValues,
      inMetaValues,
      filter,
      streamData,
      ioPacket,
      classifyOut
      );

   if (classifyOut->actionType == FWP_ACTION_BLOCK || 
       classifyOut->actionType == FWP_ACTION_PERMIT)
   {
      if (!(streamData->flags & FWPS_STREAM_FLAG_SEND_DISCONNECT) && 
          !(streamData->flags & FWPS_STREAM_FLAG_RECEIVE_DISCONNECT))
      {
         NTSTATUS status = StreamOobFlushOutgoingData( &gStreamEditor);
         if (!NT_SUCCESS(status))
         {
            ioPacket->streamAction = FWPS_STREAM_ACTION_DROP_CONNECTION;
            classifyOut->actionType = FWP_ACTION_NONE;
         }
      }
   }

Exit:

   return;
}

Our Services

  • What our customers say about us?

© 2011-2025 All Rights Reserved. Joya Systems. 4425 South Mopac Building II Suite 101 Austin, TX 78735 Tel: 800-DEV-KERNEL

Privacy Policy. Terms of use. Valid XHTML & CSS