I’ve got a FS mini-filter that passes some configuration info back and forth with a user space .NET/C# service using FilterConnectCommunicationPort & FilterSendMessage.
Everything seems to work dandy passing InputBuffers with config info down to the filter using FilterSendMessage. However recently I’ve been trying to add new code to pass diagnostic info from the filter to the .NET app using an OutputBuffer. Try as I might I can’t seem to be able to write more than a few bytes to the OutputBuffer without triggering a System.AccessViolationException in the .NET service.
I’ve tried a bunch of different things, stepping thru the filter code everything looks fine and dandy, I can see a 4096 byte OutputBuffer passed in that the filter then loads up with less than 256 bytes of data. All my try/catch blocks around code that writes to OutputBuffer are happy, and I return STATUS_SUCCESS from my Message handler. But then inevitably shortly thereafter I hit the exception on the .NET side.
Haven’t been able to find the magic answer with google so hoping maybe someone here might have some insight.
Here is what the code looks like on the .NET/C# side:
[DllImport(“fltlib.dll”, CallingConvention = CallingConvention.Winapi, CharSet = CharSet.Unicode)]
static extern uint FilterSendMessage(IntPtr hPort, IntPtr lpInBuffer, uint dwInBufferSize, ref IntPtr lpOutBuffer, uint dwOutBufferSize, out uint lpBytesReturned);
private uint connectCommunicationPort()
{
uint result = ERROR_SUCCESS;
if (hPort == IntPtr.Zero)
{
result = FilterConnectCommunicationPort(@“\DSTWFltPort”, 0, IntPtr.Zero, 0, IntPtr.Zero, ref hPort);
if (result != ERROR_SUCCESS)
{
hPort = IntPtr.Zero;
}
}
return result;
}
public uint FilterDiagnostics(FilterDiagnosticLevel level, ref byte outputBuffer, ref uint outputBufferSize)
{
uint result;
DIAGNOSTICS_DEF diags = new DIAGNOSTICS_DEF(FilterCommands.GetDiagnostics, (uint)level, outputBufferSize);
result = connectCommunicationPort();
if (result == ERROR_SUCCESS)
{
uint bytesReturned;
result = FilterSendMessage(hPort, diags.InBuffer, diags.InBufSize, ref diags.outBuffer, outputBufferSize, out bytesReturned);
if (result == ERROR_SUCCESS)
{
outputBufferSize = bytesReturned;
// Marshal.Copy(diags.outBuffer, outputBuffer, 0, (int)bytesReturned);
}
else
{
outputBufferSize = 0;
}
}
diags.Dispose();
return result;
}
[StructLayout(LayoutKind.Sequential)]
public struct DIAGNOSTICS_DEF : IDisposable
{
private FilterCommands command;
private int inBufSize;
private IntPtr inBuffer;
private int outBufSize;
public IntPtr outBuffer;
public DIAGNOSTICS_DEF(FilterCommands fc, uint level, uint outBufferSize)
{
command = fc;
outBufSize = (int)outBufferSize;
// Calculate the size of the required buffer.
inBufSize = Marshal.SizeOf(typeof(DIAGNOSTICS_DEF_2));
inBuffer = Marshal.AllocHGlobal(inBufSize);
// Put the command in the buffer.
Marshal.WriteInt32(inBuffer, (int)command);
int offset = Marshal.SizeOf(typeof(Int32));
// Reserved field.
Marshal.WriteInt32(inBuffer, offset, 0);
offset += Marshal.SizeOf(typeof(Int32));
// Level field.
Marshal.WriteInt32(inBuffer, offset, (int)level);
offset += Marshal.SizeOf(typeof(Int32));
// Output buffer
outBuffer = Marshal.AllocHGlobal(outBufSize);
}
public void Dispose()
{
Marshal.FreeHGlobal(outBuffer);
outBuffer = IntPtr.Zero;
Marshal.FreeHGlobal(inBuffer);
inBuffer = IntPtr.Zero;
}
public IntPtr InBuffer
{
get
{
return inBuffer;
}
}
public uint InBufSize
{
get
{
return (uint)inBufSize;
}
}
}
[StructLayout(LayoutKind.Sequential)]
public struct DIAGNOSTICS_DEF_2
{
public FilterCommands Command;
public Int32 Reserved;
public Int32 Level;
}
uint outBufferSize = 4096;
byte outBuffer = new byte[outBufferSize];
result = FilterDiagnostics(FilterDiagnosticLevel.FilterPairConfig, ref outBuffer, ref outBufferSize);
Here is the C code on the filter side of things…I’ve tinkered with things in a bunch of different ways so if some of the code looks goofy it’s because of “over-tinkering”.
status = FltCreateCommunicationPort( DSTWFltData.Filter,
&DSTWFltData.ServerPort,
&oa,
NULL,
DSTWConnect,
DSTWDisconnect,
DSTWMessage,
1 );
NTSTATUS
DSTWMessage (
IN PVOID ConnectionCookie,
IN PVOID InputBuffer OPTIONAL,
IN ULONG InputBufferSize,
OUT PVOID OutputBuffer OPTIONAL,
IN ULONG OutputBufferSize,
OUT PULONG ReturnOutputBufferLength
)
{
DSTWFLT_COMMAND command;
PCOMMAND_MESSAGE commandMsg;
DSTWFLTPAIR dstwPair;
DSTWFLTPAIROPTIONS dstwPairOptions;
NTSTATUS status;
ULONG level;
PAGED_CODE();
UNREFERENCED_PARAMETER( ConnectionCookie );
if ((InputBuffer != NULL) &&
(InputBufferSize >= (FIELD_OFFSET(COMMAND_MESSAGE,Command) +
sizeof(DSTWFLT_COMMAND)))) {
try {
//
// Probe and capture input message: the message is raw user mode
// buffer, so need to protect with exception handler
//
commandMsg = (PCOMMAND_MESSAGE) InputBuffer;
command = commandMsg->Command;
// DebugTrace( DEBUG_TRACE_USER_APP,
// (“[DSTWFlt]: DSTWMessage command = %x\n”, command) );
} except( EXCEPTION_EXECUTE_HANDLER ) {
DebugTrace( DEBUG_TRACE_USER_APP | DEBUG_TRACE_ERROR,
(“[DSTWFlt]: DSTWMessage exception while accessing input buffer\n”) );
return GetExceptionCode();
}
switch (command) {
case GetDSTWFltDiagnostics:
//
// Return diagnostic information in OutputBuffer
//
try
{
level = *(ULONG *)commandMsg->Data;
}
except( EXCEPTION_EXECUTE_HANDLER )
{
DebugTrace( DEBUG_TRACE_USER_APP | DEBUG_TRACE_ERROR,
(“[DSTWFlt]: DSTWMessage exception while accessing input buffer\n”) );
return GetExceptionCode();
}
if ((OutputBuffer == NULL) || (OutputBufferSize == 0))
{
status = STATUS_INVALID_PARAMETER;
break;
}
if (!IS_ALIGNED(OutputBuffer,sizeof(ULONG))) {
status = STATUS_DATATYPE_MISALIGNMENT;
break;
}
//
// We want to validate that the given buffer is POINTER
// aligned. But if this is a 64bit system and we want to
// support 32bit applications we need to be careful with how
// we do the check. Note that the way DSTWGetLog is written
// it actually does not care about alignment but we are
// demonstrating how to do this type of check.
//
#if defined(_WIN64)
if (IoIs32bitProcess( NULL )) {
//
// Validate alignment for the 32bit process on a 64bit
// system
//
if (!IS_ALIGNED(OutputBuffer,sizeof(ULONG))) {
status = STATUS_DATATYPE_MISALIGNMENT;
break;
}
} else {
#endif
if (!IS_ALIGNED(OutputBuffer,sizeof(PVOID))) {
status = STATUS_DATATYPE_MISALIGNMENT;
break;
}
#if defined(_WIN64)
}
#endif
status = DSTWGetDiagnostics(level, OutputBuffer, OutputBufferSize, ReturnOutputBufferLength);
break;
default:
status = STATUS_INVALID_PARAMETER;
break;
}
} else {
status = STATUS_INVALID_PARAMETER;
}
return status;
}
NTSTATUS
DSTWGetDiagnostics (
IN ULONG Level,
OUT PUCHAR OutputBuffer,
IN ULONG OutputBufferLength,
OUT PULONG ReturnOutputBufferLength
)
/*++
Routine Description:
This function fills OutputBuffer with diagnostic information that
varies depending on Level.
Arguments:
Level = Determines type of diagnostic information to be returned.
OutputBuffer - The user’s buffer to fill with the diagnostic data
OutputBufferLength - The size in bytes of OutputBuffer
ReturnOutputBufferLength - The amount of data actually written into the
OutputBuffer.
Return Value:
STATUS_SUCCESS if some records were able to be written to the OutputBuffer.
STATUS_BUFFER_TOO_SMALL if the OutputBuffer is too small to
hold the request diagnostic info.
–*/
{
NTSTATUS status;
switch (Level)
{
case GetDSTWFltPairConfig:
status = DSTWGetPairConfiguration(OutputBuffer, OutputBufferLength, ReturnOutputBufferLength);
break;
case GetDSTWFltDump:
status = DSTWGetDump(OutputBuffer, OutputBufferLength, ReturnOutputBufferLength);
break;
case GetDSTWFltStats:
status = DSTWGetStats(OutputBuffer, OutputBufferLength, ReturnOutputBufferLength);
break;
default:
status = STATUS_INVALID_PARAMETER;
break;
}
return status;
}
NTSTATUS
DSTWGetPairConfiguration (
OUT PUCHAR OutputBuffer,
IN ULONG OutputBufferLength,
OUT PULONG ReturnOutputBufferLength
)
{
ULONG bytesWritten = 0;
ULONG i;
if (OutputBufferLength < sizeof(ULONG))
{
return STATUS_BUFFER_TOO_SMALL;
}
try
{
*(ULONG *)OutputBuffer = DSTWFltData.NumPairs;
}
except( EXCEPTION_EXECUTE_HANDLER )
{
return GetExceptionCode();
}
bytesWritten += sizeof(ULONG);
for (i = 0; i < DSTWFltData.NumPairs; i++)
{
if ((OutputBufferLength - bytesWritten) < sizeof(USHORT))
{
return STATUS_BUFFER_TOO_SMALL;
}
try
{
*(USHORT *)(OutputBuffer + bytesWritten) = DSTWFltData.PrimaryRoot[i].Length;
}
except( EXCEPTION_EXECUTE_HANDLER )
{
return GetExceptionCode();
}
bytesWritten += sizeof(USHORT);
if ((OutputBufferLength - bytesWritten) < DSTWFltData.PrimaryRoot[i].Length)
{
return STATUS_BUFFER_TOO_SMALL;
}
try
{
RtlCopyMemory(OutputBuffer + bytesWritten, DSTWFltData.PrimaryRoot[i].Buffer, DSTWFltData.PrimaryRoot[i].Length);
}
except( EXCEPTION_EXECUTE_HANDLER )
{
return GetExceptionCode();
}
bytesWritten += DSTWFltData.PrimaryRoot[i].Length;
if ((OutputBufferLength - bytesWritten) < sizeof(USHORT))
{
return STATUS_BUFFER_TOO_SMALL;
}
try
{
*(USHORT *)(OutputBuffer + bytesWritten) = DSTWFltData.SecondaryRoot[i].Length;
}
except( EXCEPTION_EXECUTE_HANDLER )
{
return GetExceptionCode();
}
bytesWritten += sizeof(USHORT);
if ((OutputBufferLength - bytesWritten) < DSTWFltData.SecondaryRoot[i].Length)
{
return STATUS_BUFFER_TOO_SMALL;
}
try
{
RtlCopyMemory(OutputBuffer + bytesWritten, DSTWFltData.SecondaryRoot[i].Buffer, DSTWFltData.SecondaryRoot[i].Length);
}
except ( EXCEPTION_EXECUTE_HANDLER )
{
return GetExceptionCode();
}
bytesWritten += DSTWFltData.SecondaryRoot[i].Length;
}
*ReturnOutputBufferLength = bytesWritten;
return STATUS_SUCCESS;
}
And finally the stack dump when the .NET exception occurs:
System.AccessViolationException was unhandled
Message=Attempted to read or write protected memory. This is often an indication that other memory is corrupt.
Source=DswIoctlsLib
StackTrace:
at DstwIoctlsLib.FilterDriver.DeleteShare(String name, String guid, Boolean primaryIsUNC, String primary, Boolean shadowIsUNC, String shadow) in F:\dynamic-v4\win\DstwIoctlsLib\FilterDriver.cs:line 257
at DstwIoctlsLib.Ioctls.FilterDeleteShare(String pairName, String pairGuid, Boolean primaryIsUNC, String pairPrimary, Boolean secondaryIsUNC, String pairSecondary) in F:\dynamic-v4\win\DstwIoctlsLib\Ioctls.cs:line 103
at DstwMcpDatabase.PairDatabaseEx.Remove(String guid) in F:\dynamic-v4\win\DstwMcpDatabase\PairDatabaseEx.cs:line 1228
at DstwMcpCore.DstwWebServiceImpl.DstwWSDeletePair(String pairid, OutgoingWebResponseContext& ctx) in F:\dynamic-v4\win\DstwMcpCore\WebServiceImpl.cs:line 2034
at DstwMcpCore.DstwWebServiceImpl.DswWSDeletePair(String pairid) in F:\dynamic-v4\win\DstwMcpCore\WebServiceImpl.cs:line 1976
at SyncInvokeDswWSDeletePair(Object , Object , Object )
at System.ServiceModel.Dispatcher.SyncMethodInvoker.Invoke(Object instance, Object inputs, Object& outputs)
at System.ServiceModel.Dispatcher.DispatchOperationRuntime.InvokeBegin(MessageRpc& rpc)
at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage5(MessageRpc& rpc)
at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage4(MessageRpc& rpc)
at System.ServiceModel.Dispatcher.MessageRpc.Process(Boolean isOperationContextSet)
at System.ServiceModel.Dispatcher.ChannelHandler.DispatchAndReleasePump(RequestContext request, Boolean cleanThread, OperationContext currentOperationContext)
at System.ServiceModel.Dispatcher.ChannelHandler.HandleRequest(RequestContext request, OperationContext currentOperationContext)
at System.ServiceModel.Dispatcher.ChannelHandler.AsyncMessagePump(IAsyncResult result)
at System.ServiceModel.Diagnostics.Utility.AsyncThunk.UnhandledExceptionFrame(IAsyncResult result)
at System.ServiceModel.AsyncResult.Complete(Boolean completedSynchronously)
at System.ServiceModel.Channels.InputQueue1.AsyncQueueReader.Set(Item item) at System.ServiceModel.Channels.InputQueue
1.EnqueueAndDispatch(Item item, Boolean canDispatchOnThisThread)
at System.ServiceModel.Channels.InputQueue1.EnqueueAndDispatch(T item, ItemDequeuedCallback dequeuedCallback, Boolean canDispatchOnThisThread) at System.ServiceModel.Channels.InputQueueChannel
1.EnqueueAndDispatch(TDisposable item, ItemDequeuedCallback dequeuedCallback, Boolean canDispatchOnThisThread)
at System.ServiceModel.Channels.SingletonChannelAcceptor3.Enqueue(QueueItemType item, ItemDequeuedCallback dequeuedCallback, Boolean canDispatchOnThisThread) at System.ServiceModel.Channels.SingletonChannelAcceptor
3.Enqueue(QueueItemType item, ItemDequeuedCallback dequeuedCallback)
at System.ServiceModel.Channels.HttpChannelListener.HttpContextReceived(HttpRequestContext context, ItemDequeuedCallback callback)
at System.ServiceModel.Channels.SharedHttpTransportManager.OnGetContextCore(IAsyncResult result)
at System.ServiceModel.Diagnostics.Utility.AsyncThunk.UnhandledExceptionFrame(IAsyncResult result)
at System.Net.LazyAsyncResult.Complete(IntPtr userToken)
at System.Net.LazyAsyncResult.ProtectedInvokeCallback(Object result, IntPtr userToken)
at System.Net.ListenerAsyncResult.WaitCallback(UInt32 errorCode, UInt32 numBytes, NativeOverlapped* nativeOverlapped)
at System.Threading.ExecutionContext.runTryCode(Object userData)
at System.Runtime.CompilerServices.RuntimeHelpers.ExecuteCodeWithGuaranteedCleanup(TryCode code, CleanupCode backoutCode, Object userData)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
at System.Threading._IOCompletionCallback.PerformIOCompletionCallback(UInt32 errorCode, UInt32 numBytes, NativeOverlapped* pOVERLAP)
InnerException:
Any feedback/suggestions you might have is greatly appreciated. Thanks.