A forum for reverse engineering, OS internals and malware analysis 

Ask your beginner questions here.
 #2806  by xqrzd
 Mon Sep 20, 2010 7:10 am
My question is would a driver to BSOD when ExFreePoolWithTag is called if the driver wrote too much data into the buffer?
This is what I am doing:
1.User-mode app tells driver to do something, driver uses ExAllocatePoolWithTag on a global wchar pointer to store data, then sends back the size of the data
2.User-mode app creates buffer with the data size returned, and calls driver again to get the data in the wchar pointer, then driver frees wchar pointer with ExFreePoolWithTag.
The driver BSODs (BAD_POOL_CALLER) on ExFreePoolWithTag, but I notice if I add 2 more bytes to the initial allocation, (but not the user-mode one) the driver no longer BSODs. My guess is its because of the null terminator on the end, but I'm not sure. If someone could clarify this that would be great, I'm not sure if I fixed the problem or if it just temporarily went away for a while... :?
 #2807  by Alex
 Mon Sep 20, 2010 7:51 am
Hi, could you post a piece of driver's code you are talking about?
 #2810  by xqrzd
 Mon Sep 20, 2010 4:38 pm
Sure.
Global variable:
Code: Select all
WCHAR* dataToSend;
My structure for linked list:
Code: Select all
struct _FILE_FOLDER_INFO {
	WCHAR path[MAX_PATH];
	struct _FILE_FOLDER_INFO* next;
};
typedef struct _FILE_FOLDER_INFO FILE_FOLDER_INFO, *PFILE_FOLDER_INFO;
IOCTL 1:
Code: Select all
INT32 linkListSize = 0;
szBuffer = (WCHAR*)Irp->AssociatedIrp.SystemBuffer;

if (szBuffer)
{
	if (NT_SUCCESS(EnumFiles(szBuffer, &linkListSize)))
		Irp->IoStatus.Information = linkListSize;
	else
		Irp->IoStatus.Information = 0;
}
IOCTL 2:
Code: Select all
if (Irp->AssociatedIrp.SystemBuffer)
{
	INT32 bufferSize = (INT32)loc->Parameters.DeviceIoControl.OutputBufferLength;

	RtlCopyMemory(Irp->AssociatedIrp.SystemBuffer, dataToSend, bufferSize);
	Irp->IoStatus.Information = bufferSize;
	ExFreePoolWithTag(dataToSend, 'tag3');
}
Enumerate files:
Code: Select all
NTSTATUS EnumFiles(WCHAR* directory, INT32* linkListSize)
{
	NTSTATUS status;
	BOOLEAN bIsStarted = TRUE;

	PFILE_BOTH_DIR_INFORMATION pBuffer;
	ULONG dwSize;

	HANDLE hObjectHandle;
	PFILE_OBJECT file;
	OBJECT_ATTRIBUTES ObjAttributes;
	UNICODE_STRING uAllocatedBufferName;
	IO_STATUS_BLOCK IoStatusBlock;

	PFILE_FOLDER_INFO curr;
	PFILE_FOLDER_INFO head = NULL;
	PFILE_FOLDER_INFO free;
	INT32 linkListSizeI = 0;
	//*linkListSize = 0;

	dwSize = (sizeof(FILE_BOTH_DIR_INFORMATION) + MAX_PATH * sizeof(WCHAR));
	RtlInitUnicodeString(&uAllocatedBufferName, directory);

	if (KeGetCurrentIrql() != PASSIVE_LEVEL)
		return STATUS_INVALID_DEVICE_STATE;

	InitializeObjectAttributes(&ObjAttributes,
		&uAllocatedBufferName,
		OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE,
		NULL,
		NULL);

	status = IoCreateFile(&hObjectHandle,
		GENERIC_READ | SYNCHRONIZE,
		&ObjAttributes,
		&IoStatusBlock,
		NULL,
		FILE_ATTRIBUTE_NORMAL,
		FILE_SHARE_READ,
		FILE_OPEN,
		FILE_SYNCHRONOUS_IO_NONALERT,
		NULL,
		0,
		0,
		NULL,
		IO_NO_PARAMETER_CHECKING);

	if (!NT_SUCCESS(status))
		return status;

	status = ObReferenceObjectByHandle(hObjectHandle, FILE_ALL_ACCESS, 0, KernelMode, &file, NULL);

	if (!NT_SUCCESS(status))
	{
		ZwClose(hObjectHandle);
		return status;
	}

	pBuffer = ExAllocatePoolWithTag(PagedPool, dwSize, 'tag1');

	if (pBuffer)
	{
		while (TRUE)
		{
			status = QueryDirectoryFile(file,
				&IoStatusBlock,
				pBuffer,
				dwSize,
				FileBothDirectoryInformation,
				NULL,
				bIsStarted);

			switch (status)
			{
			case STATUS_SUCCESS:
				{
					if (bIsStarted)
						bIsStarted = FALSE;

					curr = ExAllocatePoolWithTag(PagedPool, sizeof(FILE_FOLDER_INFO), 'tag2');
					RtlZeroMemory(curr, sizeof(FILE_FOLDER_INFO));
					curr->path[0] = L'*';

					if ((BOOLEAN)((pBuffer->FileAttributes & FILE_ATTRIBUTE_DIRECTORY) == FILE_ATTRIBUTE_DIRECTORY)) //if (IsFileDirectory(pBuffer->FileAttributes))
						curr->path[1] = L'd'; //directory
					else
						curr->path[1] = L'f'; //file

					RtlStringCchCopyW(curr->path + 2, MAX_PATH, pBuffer->FileName);

					linkListSizeI += pBuffer->FileNameLength + 4; //1 for d or f, and 1 for *, then double since wchar

					curr->next = head;
					head = curr;

					break;
				}

			case STATUS_NO_MORE_FILES:
				{
					//if linkListSize is increased by 2 under STATUS_NO_MORE_FILES, the BSOD does not occur while trying to free memory
					dataToSend = ExAllocatePoolWithTag(PagedPool, linkListSizeI + 2, 'tag3');
					RtlZeroMemory(dataToSend, linkListSizeI + 2);
					//dataToSend[0] = L'\0';
					curr = head;

					while (curr)
					{
						RtlStringCchCatW(dataToSend, linkListSizeI + 2, curr->path);

						free = curr;
						curr = curr->next;
						ExFreePoolWithTag(free, 'tag2');
					}

					*linkListSize = linkListSizeI;

					ExFreePoolWithTag(pBuffer, 'tag1');
					ZwClose(hObjectHandle);
					ObDereferenceObject(file);

					return STATUS_SUCCESS;
				}
			default:
				{
					ExFreePoolWithTag(pBuffer, 'tag1');
					ZwClose(hObjectHandle);
					ObDereferenceObject(file);

					return status;
				}
			}

		}
	}

	ExFreePoolWithTag(pBuffer, 'tag1');
	ZwClose(hObjectHandle);
	ObDereferenceObject(file);

	return status;
}
The BSOD occurs on IOCTL 2 when tag3 memory is freed. Under my EnumFiles function, if I increase size of linkedListSize by 2, it no longer BSODs when freeing the memory.
Edit: I forgot to mention, if I do not add an additional 2 bytes to linkedListSize, it only BSODs sometimes, but not always. I thought I had traced the problem to empty directories, but that didn't seem to be it either.
 #2826  by Alex
 Tue Sep 21, 2010 4:13 pm
I don't see any DbgPrint/KdPrint debug points in you code, it's good to catch some sensitive data. I also don't see appropriate validations, for example this code:
Code: Select all
dataToSend = ExAllocatePoolWithTag(PagedPool, linkListSizeI + 2, 'tag3');
RtlZeroMemory(dataToSend, linkListSizeI + 2);

curr = head;

while (curr)
{
  RtlStringCchCatW(dataToSend, linkListSizeI + 2, curr->path);

  free = curr;
  curr = curr->next;
  ExFreePoolWithTag(free, 'tag2');
}
should looks like that:
Code: Select all
if(linkListSizeI)
{
  KdPrint((" linkListSizeI - %.8X\n", linkListSizeI);
  dataToSend = ExAllocatePoolWithTag(PagedPool, linkListSizeI, 'tag3');

  if(dataToSend)
  {
    KdPrint((" Address of dataToSend - %.8X\n", dataToSend);
    RtlZeroMemory(dataToSend, linkListSizeI);

    curr = head;

    while (curr)
   {
      KdPrint((" Path - %ws\n", curr->path);
      RtlStringCchCatW(dataToSend, linkListSizeI, curr->path);

      free = curr;
      curr = curr->next;
      ExFreePoolWithTag(free, 'tag2');
  }
You should add same modifications in other places of your code. If this will not help, you can try put breakpoints in crucial places (__asm int 3;). Here is also helpful information about this bugcheck code - BAD_POOL_CALLER - it should help you to recognize a reason of BSoDs.

Alex
 #2829  by EP_X0FF
 Tue Sep 21, 2010 11:44 pm
Very likely you calculated wrong size of variable and damaged pointer when working with it next. Try to do as Alex suggested.
 #3668  by gglittle
 Tue Nov 23, 2010 11:13 pm
Just a suggestion, but consider initializing a pointer to NULL when you define it. Thus if you had code that some how in the logic failed to execute the ExAllocateXxxx, your logic to free the code will always encounter either a NULL or a valid pointer. Uninitialized, dataToSend could be garbage and would then still fail (dataToSend) or (NULL != dataToSend).

Gary