Issues in the disk encryption filter

Hi,

I’m writing a basic kmdf-based upper disk filter that will encrypt vhds. (This is my first attempt at writing non-fs minifilter driver, so please bear with me).

I’ve reached to a point where I can attach to a test vhd and can see all the READs, WRITEs and IOCTLs happening on it. So far so good. Now I want to introduce encryption (during WRITE) and decryption piece (during POST READ) to this. For this I need to obviously first access data buffers for these IO requests which I do using WdfRequestRetrieveInputMemory / WdfRequestRetrieveOutputMemory functions. Couple of issues I’ve come across are:

  1. Keeping the encryption part aside, I seem to be getting STATUS_ACCESS_VOILATION while copying data back from my local buffer to InputMemory. So in the write event callback,

    • WdfRequestRetrieveInputMemory(Request, &InputMemory);
      WdfMemoryGetBuffer(InputMemory, &BufferSize);
      LocalBuffer = ExAllocatePoolWithTag(NonPagedPoolNx, BufferSize, POOL_TAG);
      WdfMemoryCopyToBuffer(InputMemory, 0, LocalBuffer, BufferSize);
      //
      // This is where I would add encryption part, could be as simple as LocalBuffer[i]++ to begin with
      //
      WdfMemoryCopyFromBuffer(InputMemory, 0, (PVOID)LocalBuffer, BufferSize); → I get STATUS_ACCESS_VOILATION at this call. Any idea why ?
      WdfRequestSend(Request, Target, &options);
  2. Also, I understand that even for full-disk encryption, certain parts of the disk should not be subject to encryption/decryption process. E.g.
    (https://community.osr.com/discussion/64507/problems-with-a-disk-encryption-filter-driver) IoReadPartitionTable is not subject to filtering. It ignores the disk upper filters.
    So what are the sectors (I know about sector 0) I should watch out for and refrain from encrypting?

For testing, I create and attach a vhd manually using disk management. At this point my filter gets attached and receives all the READ, WRITE notifications.
The goal is to have encryption / decryption working transparently during vhd’s lifetime and have operations such as creating and formatting volume with NTFS, and using it for fs operations succeed.

Thanks

The input buffer for a write request is only guaranteed to be readable. Remember that you are using a kernel-mapped pointer to the user’s memory pages. You don’t want to screw with the user’s memory. You’re going to have allocate your own buffer to contain your encrypted data.

Hi Tim, Thanks for your reply. I do allocate my own buffer and perform encryption on that but how do I tell wdf to consider this newly encrypted buffer instead of the original input buffer and pass that on to next lower driver ? I thought WdfMemoryCopyFromBuffer() will do that for me. Or perhaps I should just assign this new buffer to InputMemory instead of copying contents over. Thanks Nik

I suppose you could fetch the IRP and manipulate the fields directly, but as far as I know, the approved method is to create a new WDFREQUEST and supply the buffer there. I don’t think you can change the buffer in an existing request.

Hi Tim,

Sorry I couldn’t post here earlier as I was pulled away into some other issues and thanks for pointing in the right direction.

While your suggestion makes sense, I also found another relevant thread (https://community.osr.com/discussion/216562/wdfrequest-buffer) where Doron mentioned,
“Then all you need to do is allocate a WDFMEMORY with the new expanded size, for a write operation copy the caller’s buffer content into the new buffer, format the request with your WDFMEMORY and adjusted size, and send the request down the stack.”

which is an easier route. But I for the life of me can’t make out what’s going wrong below:

VOID
FilterEvtIoWrite(
IN WDFQUEUE Queue,
IN WDFREQUEST OrgRequest,
IN size_t Length
)
{
NTSTATUS status = STATUS_SUCCESS;
WDFDEVICE Device;
WDFMEMORY EncryptedMemory = NULL;
PVOID EncryptedDataBuffer = NULL;

WDF_REQUEST_COMPLETION_PARAMS EncryptedParams;
WDF_REQUEST_COMPLETION_PARAMS_INIT(&EncryptedParams);

try
{
	Device = WdfIoQueueGetDevice(Queue);

	WDF_REQUEST_PARAMETERS non_irp_params;

	WDF_REQUEST_PARAMETERS_INIT(&non_irp_params);

	WdfRequestGetParameters(OrgRequest, &non_irp_params);

	DbgPrint("KmdfDiskFilter: IO -> ORIGINAL WRITE; DeviceOffset -> %lld; Length -> %llu; \n", non_irp_params.Parameters.Write.DeviceOffset, Length);

	if (Length != 0)
	{
		WDFMEMORY InputMemory;

		status = WdfRequestRetrieveInputMemory(OrgRequest, &InputMemory);
		if (!NT_SUCCESS(status))
		{
			DbgPrint("KmdfDiskFilter: WdfRequestRetrieveInputMemory failed, Error Code: [0x%x]\n", status);

			leave;
		}

		size_t BufferSize;
		
		PVOID InputBuffer = WdfMemoryGetBuffer(InputMemory, &BufferSize);

		WDF_OBJECT_ATTRIBUTES EncryptedMemoryAttributes;
		WDF_OBJECT_ATTRIBUTES_INIT(&EncryptedMemoryAttributes);

		// Allocate WDF memory object.
		status = WdfMemoryCreate(&EncryptedMemoryAttributes, NonPagedPoolNx, POOL_TAG, BufferSize, &EncryptedMemory, &EncryptedDataBuffer);
		if (!NT_SUCCESS(status))
		{
			DbgPrint("KmdfDiskFilter: WdfMemoryCreate failed, Error Code: [0x%x]\n", status);

			leave;
		}

		// Encryption function.
		//for (size_t i = 0; i < EncryptedDataBuffer; i++)
		//{
		//	((PUCHAR)EncryptedDataBuffer)[i]++;
		//}

		// Copying original data
		status = WdfMemoryCopyFromBuffer(EncryptedMemory, 0, InputBuffer, BufferSize);
		if (!NT_SUCCESS(status))
		{
			DbgPrint("KmdfDiskFilter: WdfMemoryCopyFromBuffer failed, Error Code: [0x%x]\n", status);

			leave;
		}

		status = WdfIoTargetFormatRequestForWrite(WdfDeviceGetIoTarget(Device), OrgRequest, EncryptedMemory, NULL, NULL);
		if (!NT_SUCCESS(status))
		{
			DbgPrint("KmdfDiskFilter: WdfIoTargetFormatRequestForWrite failed, Error Code: [0x%x]\n", status);

			leave;
		}

		WdfRequestSetCompletionRoutine(OrgRequest, FilterEncryptedWriteComplete, (WDFCONTEXT)EncryptedMemory);
	}
}
finally
{ }

if (!WdfRequestSend(OrgRequest, WdfDeviceGetIoTarget(Device), WDF_NO_SEND_OPTIONS))
{
	status = WdfRequestGetStatus(OrgRequest);

	DbgPrint("KmdfDiskFilter: WdfRequestSend failed, Error Code: [0x%x]\n", status);
}

return;

}

VOID
FilterEncryptedWriteComplete(
IN WDFREQUEST Request,
IN WDFIOTARGET Target,
IN PWDF_REQUEST_COMPLETION_PARAMS Params,
IN WDFCONTEXT Context
)
{
UNREFERENCED_PARAMETER(Target);

WDFMEMORY EncryptedMemory = (WDFMEMORY)Context;

if (EncryptedMemory)
{
	WdfObjectDelete(EncryptedMemory);
}

DbgPrint("KmdfDiskFilter: IO -> ENCRYPTED WRITE; BytesWritten -> %llu; Status -> [0x%x]\n", Params->IoStatus.Information, Params->IoStatus.Status);

WdfRequestComplete(Request, Params->IoStatus.Status);

}

I’m trying to format a vhd (using NTFS) with my upper filter attached but it always comes back saying it was unable to format disk.
What makes it worse is its not even encrypted data - its just another copy of the same buffer that I received in the first place.

There must be something obvious that I’m missing or doing something stupid.

Thanks

Or it could be the case that the steps are fine and its just that the test-case of formatting a vhd (using NTFS) requires some special / additional handling?