A forum for reverse engineering, OS internals and malware analysis 

Forum for discussion about kernel-mode development.
 #31032  by myid
 Sat Nov 25, 2017 8:35 pm
Hi, everyone, I wrote some code like this:
Code: Select all
KEVENT event;
PIRP irp;
IO_STATUS_BLOCK iosb;
KeInitializeEvent(&event, NotificationEvent, FALSE);
irp = BuildIrp(devObj, ..., &event, &iosb);
status = IoCallDriver(devObj, irp);
if (status == STATUS_PENDING)
{
	//Is there any problem if I delete this function call?
	//KeWaitForSingleObject(&event, Executive, KernelMode, FALSE, NULL);
	status = iosb.Status;
}
return status;
I am worry about if sometime the code is continue executing, but the EVENT variable is no longer valid.
I don't know the Windows build-in drivers will check the pointer of EVENT variable before use, or use it directly.

Is there any problem if I do not call KeWaitForSingleObject and return directly?
 #31034  by Vrtule
 Sat Nov 25, 2017 9:26 pm
If you do not intend to wait for the IRP completion, you do not need the event object at all.

If you have a reason for having the event object, you may allocate from nonpaged memory, or in your driver binary (by declaring it as a global variable).

If you allocate the event object on the stack it remains valid until the block of code it is declared in is active. After that, the stack location may be deallocated and may be occupied by another local variable(s). You definitely do not want to pass such an "temporary" event object to the o I/O Manager.
 #31038  by myid
 Sun Nov 26, 2017 3:50 am
Vrtule wrote:If you do not intend to wait for the IRP completion, you do not need the event object at all.

If you have a reason for having the event object, you may allocate from nonpaged memory, or in your driver binary (by declaring it as a global variable).

If you allocate the event object on the stack it remains valid until the block of code it is declared in is active. After that, the stack location may be deallocated and may be occupied by another local variable(s). You definitely do not want to pass such an "temporary" event object to the o I/O Manager.
I know what you said, but the real situation is:
(1) I allocate the EVENT variable from NonPagedPool;
(2) I wait 3000 ms but KeWaitForSingleObject return STATUS_TIMEOUT;
(3) Should I free the memory of EVENT variable? If I free it, I/O Manager will use the invalid memory; if not, the memory will be wasted after the IRP is completed.
 #31045  by myid
 Sun Nov 26, 2017 5:12 pm
Vrtule wrote:You may set a completion routine for the IRP and free the allocated event object there. You can pass address of the object as a the context parameter.
Thanks, that's a very good idea. I almost forgot the IoSetCompletionRoutine function.
 #31052  by myid
 Mon Nov 27, 2017 7:04 am
Vrtule wrote:You may set a completion routine for the IRP and free the allocated event object there. You can pass address of the object as a the context parameter.
Hi, I use IoSetCompletionRoutine to set a callback function (before call IoCallDriver).
I try to free the pointer of EVENT variable in the callback, but the system will BSOD sometimes. CODE: PAGE_FAULT_IN_NONPAGED_AREA.
The code looks like:
Code: Select all
NTSTATUS CompletionRoutine
(
	PDEVICE_OBJECT DeviceObject,
	PIRP Irp,
	PVOID Context
)
{
	if(Context && MmIsAddressValid(Context))
	{
		ExFreePool(Context);
	}
	//return STATUS_MORE_PROCESSING_REQUIRED;
	return STATUS_SUCCESS;
}

void do_sth()
{
	PKEVENT event = ExAllocatePool(NonPagedPool, sizeof(KEVENT));
	PIRP irp;
	IO_STATUS_BLOCK iosb;
	KeInitializeEvent(event, NotificationEvent, FALSE);
	irp = BuildIrp(devObj, ..., event, &iosb);
	IoSetCompletionRoutine(irp, CompletionRoutine, event, TRUE, TRUE, TRUE);
	status = IoCallDriver(devObj, irp);
	if (status == STATUS_PENDING)
	{
		LARGE_INTEGER interval = {0};
		interval.QuadPart = -10000;
		interval.QuadPart *= 3000;
		KeWaitForSingleObject(event, Executive, KernelMode, FALSE, &interval);
		status = iosb.Status;
	}
	return status;
}
The BSOD have no relationship of the code in completion routine. it will BSOD if I set the completion routine (even if the completion routine is an empty function).
I don't know what to do next.
 #31055  by Vrtule
 Mon Nov 27, 2017 9:05 am
How are you building the IRP?

Also, if KeWaitForSingleObject returns STATUS_TIMEOUT, you should not use the iosb.Status value since the IRP is not complete (and hence, this value is not initialized by the completing driver).
 #31058  by myid
 Mon Nov 27, 2017 9:08 am
Vrtule wrote:How are you building the IRP?

Also, if KeWaitForSingleObject returns STATUS_TIMEOUT, you should not use the iosb.Status value since the IRP is not complete (and hence, this value is not initialized by the completing driver).
Thanks, but this is not the key point. It still BSOD after I delete this line.
 #31060  by Vrtule
 Mon Nov 27, 2017 10:31 am
Hmmm, I think the problem is that the event object may be freed even when a thread is waiting for it. The following scenario leads to the issue:

1) you allocate the event object,
2) you build the IRP,
3) you pass the IRP to the target driver (IoCallDriver),
4) you start waiting for the event object
5) the target driver completes the IRP, so the completion routine is invoked
6) the completion routine frees the event object,
7) the thread waiting for the (now freed) event corrupts the memory.

To avoid this, you need to track number of references to the event object. Either manually (you allocate a variable together with the event object, set it to 2 and decrement it in the completion routine and after the wait ends), or via Object Manager (create the event object via Object Manager and get the pointer to it (ZwCreateEvent, ObReferenceObjectByHandle, ZwClose). Then increase the reference count (ObReferenceObject) when registering the completion routine and decrease it (ObDereferenceObject) after the wait and in the completion routine). If you utilize the Object Manager approach, the event object is automatically freed when its reference count reaches zero. Otherwise, you have to do it yourself.
 #31070  by myid
 Tue Nov 28, 2017 7:27 am
Vrtule wrote:Hmmm, I think the problem is that the event object may be freed even when a thread is waiting for it. The following scenario leads to the issue:

1) you allocate the event object,
2) you build the IRP,
3) you pass the IRP to the target driver (IoCallDriver),
4) you start waiting for the event object
5) the target driver completes the IRP, so the completion routine is invoked
6) the completion routine frees the event object,
7) the thread waiting for the (now freed) event corrupts the memory.

To avoid this, you need to track number of references to the event object. Either manually (you allocate a variable together with the event object, set it to 2 and decrement it in the completion routine and after the wait ends), or via Object Manager (create the event object via Object Manager and get the pointer to it (ZwCreateEvent, ObReferenceObjectByHandle, ZwClose). Then increase the reference count (ObReferenceObject) when registering the completion routine and decrease it (ObDereferenceObject) after the wait and in the completion routine). If you utilize the Object Manager approach, the event object is automatically freed when its reference count reaches zero. Otherwise, you have to do it yourself.
Thanks, current problem has been resolved. But I have another question:
If IRP owned cancel routine, can we call IoCancelIrp first, then free the event object?