Is it safe to call FltSendMessage PreSetInformation and PostCleanup?

Hello. My query about irql level. So my driver get filename IRP_MJ_CREATE(post op) and allocate handle context (for save *.exe filename) and in PostCleanup i get filename then by FltSendMessage I send filename to my usermode app to scan file(filepath).It works well i tested it with Driver Verifier 4 days. I know post operation may not call PASSIVE_LEVEL but ZwClose documentation says ZwClose called PASSIVE LEVEL.Can I use this method? My second question is I need also scan renamed filename.To checking renamed file in preSetInformation and send filename to usermode app again by FltSendMessage like this.:

FLT_PREOP_CALLBACK_STATUS  ScannerPreSetInformation(
	PFLT_CALLBACK_DATA Data,
	PCFLT_RELATED_OBJECTS FltObjects,
	PVOID CompletionContext
)
{
	NTSTATUS status = FLT_PREOP_SUCCESS_NO_CALLBACK;
	PFILE_RENAME_INFORMATION renameInfo = NULL;
	PFLT_FILE_NAME_INFORMATION FileNameInfo = NULL;

	if (FltObjects->FileObject == NULL)
	{
		return status;
	}

	if (Data->Iopb->Parameters.SetFileInformation.FileInformationClass == FileRenameInformation)
	{
		
		renameInfo = Data->Iopb->Parameters.SetFileInformation.InfoBuffer;

		status = FltGetDestinationFileNameInformation(FltObjects->Instance,
			FltObjects->FileObject,
			renameInfo->RootDirectory,
			renameInfo->FileName,
			renameInfo->FileNameLength,
			FLT_FILE_NAME_NORMALIZED | FLT_FILE_NAME_QUERY_DEFAULT,
			&FileNameInfo);

		if (NT_SUCCESS(status))
		{

			FltParseFileNameInformation(FileNameInfo);

			DbgPrint("%wZ\n", FileNameInfo->Name);   // <<< IN HERE I WANT TO CALL FltSendMessage >>>

			FltReleaseFileNameInformation(FileNameInfo);

		}

	}

	return status;
}

Can it make any problem? Thank you for reading

IIRC… if you want to force PostCleanup to be called at IRQL PASSIVE_LEVEL, you can return FLT_PREOP_SYNCHRONIZE from your PreCleanup. This has been discussed here a few times over the years, and my memory of this is hazy. Perhaps this will trigger somebody else’s memory?

Peter

1 Like

PreSetInformation will be at PASSIVE_LEVEL.

PostCleanup will undoubtedly be passive as well but you can force it as Peter mentioned (IRP_MJ_CLEANUP is synchronous to the app in any case so it doesn’t hurt anything).

Why do you want the name again in PostCleanup? If the fle is delete pending there might not be a name by the time you get PostCleanup. If you mix in POSIX delete it’s worse because the name can be gone in PreCleanup as well (and you’ll be returned some goofy internal NTFS name…$Extend$Deleted IIRC)

1 Like

Thank you for replying. Yes i need filename in post cleanup because of my usermode app. My user mode app designed to scan only filename for this reason i dont have other way. in precleanup routine I send filename to usermode app but i cannot get access this file(CreateFile api) but in postcleanup i can open and read file content. Mr Scott actually it doesnt matter for me if i get as you say gooffy internal name my app doesnt open filename and it doesnt make any problem for me.I scan only newly created exe file. Consider someone Create exe file for WriteAccess in post create i allocate context for this handle and in post cleanup i send filename to usermode app and scan newly created exe file.I also tested PostWrite routine. someone create exe with write access.In postCreate I allocate scanner context for this filename and set context->Rescan =False. In post write i change context->Rescan to true because new data written.Then postCleanup i send filename and scan this file. But there is also problem because in postwrite i call fltgetsetramhandlecontext and again irql problem.Can I use Mr Peter method?Like in pre write i return sync and post write i call any flt function(irql<=APC) ? is it safe? and so sorry for lots of questions but my last question while writing this comment i dont use pre or post write routine.in post create i allocate context and without pre or post write i get this stream handle context in post cleanup.Can it make problem? can i lost some context? And again so sorry if I bored you im newbie in ddk platform)

You don’t want to synchronize write operations because you’ll slow everything down (applications like to have their reads/writes processed asynchronously). You could always just mark it as modified in PreWrite.

The file might be gone by the time you see a PostCleanup. Your scheme also fails if someone uses process doppelgänging.

It’s very difficult to design endpoint scanning things like this…If you’re just fooling around then just assume doing it in PostCleanup is “good enough”. If you’re trying to make a product then you really want to get look into FltCreateSectionForDataScan and assume you have serious work to do. (For completeness, you’ll also inevitably need access to the Windows Threat Intelligence ETW provider so you can properly monitor the endpoint…At a minimum this requires your company to create and certify an ELAM driver, not sure what else you need to do)

1 Like

Yeah my app works like clamav today i tryed Mr Peter and your method it really works well i register pre post create + pre post write and pre post cleanup it works well). in post create i allocate context in pre write if FltGetStreamHandleContext return ok(i have context this is file must scan) I return sync else preop_no_callback . in post write i change Rescan to True . Then pre cleanup again if FltGetstreamhandleContext found i return sync else no_callback in post cleanup i send filename to usermode app user scan file etc. Ok if you say i also will try without sync write.Can i use this method?post create i allocate context , in prewrite i change rescan to true(without postwrite) and pre post cleanup i scan filename?actually i tested this method but driver verifier say me without post operation you will lost context?What is problem? I think this problem comes because i dont have post write?but it also worked well))sorry my bad english

And this is my code: in WinDbg
FILTER VERIFIER WARNING: A filter has set a completion context for a callback data which will be
lost. Probable cause of this is that no post-operation callback was requested
or routine was registered for this operation or the request was being pended
or completed.

BUT WORKS WELL!

FLT_PREOP_CALLBACK_STATUS
ScannerPreCreate(
	_Inout_ PFLT_CALLBACK_DATA Data,
	_In_ PCFLT_RELATED_OBJECTS FltObjects,
	_Flt_CompletionContext_Outptr_ PVOID* CompletionContext
)
{
	UNREFERENCED_PARAMETER(FltObjects);
	UNREFERENCED_PARAMETER(CompletionContext = NULL);

	

	if (IoThreadToProcess(Data->Thread) == ScannerData.UserProcess) {

		return FLT_PREOP_SUCCESS_NO_CALLBACK;
	}

	return FLT_PREOP_SUCCESS_WITH_CALLBACK;
}

FLT_POSTOP_CALLBACK_STATUS ScannerPostOpCreate(PFLT_CALLBACK_DATA Data,
	PCFLT_RELATED_OBJECTS FltObjects,
	PVOID CompletionContext,
	FLT_POST_OPERATION_FLAGS Flags)
{

	PFLT_FILE_NAME_INFORMATION FileNameInfo = NULL;
	POBJECT_NAME_INFORMATION name = NULL;
	NTSTATUS status;
	PSCANNER_NOTIFICATION notification = NULL;
	ULONG replyLen = 0;
	UNICODE_STRING extension = RTL_CONSTANT_STRING(L"exe");
	PSCANNER_CONTEXT scannerContext = NULL;
	

	if (!NT_SUCCESS(Data->IoStatus.Status) ||
		(STATUS_REPARSE == Data->IoStatus.Status)) {
		return FLT_POSTOP_FINISHED_PROCESSING;
	}

	if (ScannerData.ClientPort == NULL)
	{
		return FLT_POSTOP_FINISHED_PROCESSING;
	}

	status = FltGetFileNameInformation(Data, FLT_FILE_NAME_NORMALIZED | FLT_FILE_NAME_QUERY_DEFAULT, &FileNameInfo);
	if (!NT_SUCCESS(status))
	{
		return FLT_POSTOP_FINISHED_PROCESSING;
	}

	status = FltParseFileNameInformation(FileNameInfo);
	if (!NT_SUCCESS(status))
	{
		FltReleaseFileNameInformation(FileNameInfo);
		return FLT_POSTOP_FINISHED_PROCESSING;
	}

	if (FileNameInfo->Extension.Length == 0)
	{
		FltReleaseFileNameInformation(FileNameInfo);
		return FLT_POSTOP_FINISHED_PROCESSING;
	}

	if (RtlCompareUnicodeString(&extension, &FileNameInfo->Extension, TRUE) != 0)
	{
		FltReleaseFileNameInformation(FileNameInfo);
		return FLT_POSTOP_FINISHED_PROCESSING;
	}

	FltReleaseFileNameInformation(FileNameInfo);

	if (FltObjects->FileObject->WriteAccess)
	{

		status = IoQueryFileDosDeviceName(FltObjects->FileObject, &name);
		if (NT_SUCCESS(status))
		{

			status = FltAllocateContext(ScannerData.Filter,
				FLT_STREAMHANDLE_CONTEXT,
				sizeof(SCANNER_CONTEXT),
				NonPagedPool,
				&scannerContext);

			if (NT_SUCCESS(status))
			{
				RtlCopyMemory(scannerContext->Path, name->Name.Buffer, name->Name.MaximumLength);
				scannerContext->Len = name->Name.MaximumLength;
				scannerContext->Rescan = FALSE;

				status = FltSetStreamHandleContext(FltObjects->Instance,
					FltObjects->FileObject,
					FLT_SET_CONTEXT_REPLACE_IF_EXISTS,
					scannerContext,
					NULL);

				if (!NT_SUCCESS(status))
				{
					DbgPrint("Error set setram context");
					FltReleaseContext(scannerContext);
					ExFreePool(name);
					return FLT_POSTOP_FINISHED_PROCESSING;
				}

				DbgPrint("Context yaradildi\n");

				FltReleaseContext(scannerContext);

			}
			else {
				DbgPrint("Cant allocate memory\n");
			}

			

			ExFreePool(name);
		}

	}

	return FLT_POSTOP_FINISHED_PROCESSING;
}

FLT_PREOP_CALLBACK_STATUS
ScannerPreWrite(
	_Inout_ PFLT_CALLBACK_DATA Data,
	_In_ PCFLT_RELATED_OBJECTS FltObjects,
	_Flt_CompletionContext_Outptr_ PVOID* CompletionContext
)
{

	NTSTATUS status;
	PSCANNER_CONTEXT scannerContext = NULL;
	/*POBJECT_NAME_INFORMATION name = NULL;*/

	status = FltGetStreamHandleContext(FltObjects->Instance, FltObjects->FileObject, &scannerContext);
	if (NT_SUCCESS(status))
	{

		if (scannerContext->Rescan == FALSE)
		{
			scannerContext->Rescan = TRUE;

			FltSetStreamHandleContext(FltObjects->Instance, FltObjects->FileObject, FLT_SET_CONTEXT_REPLACE_IF_EXISTS, scannerContext, NULL);

			FltReleaseContext(scannerContext);
			
			return FLT_PREOP_SUCCESS_WITH_CALLBACK;
		}

		FltReleaseContext(scannerContext);

	}

	return FLT_PREOP_SUCCESS_NO_CALLBACK;
}

FLT_PREOP_CALLBACK_STATUS
ScannerPreCleanup(
	_Inout_ PFLT_CALLBACK_DATA Data,
	_In_ PCFLT_RELATED_OBJECTS FltObjects,
	_Flt_CompletionContext_Outptr_ PVOID* CompletionContext
)
{
	NTSTATUS status;
	PSCANNER_CONTEXT scannerContext = NULL;

	status = FltGetStreamHandleContext(FltObjects->Instance, FltObjects->FileObject, &scannerContext);
	if (!NT_SUCCESS(status))
	{
		return FLT_PREOP_SUCCESS_NO_CALLBACK;
	}
	

	FltReleaseContext(scannerContext);

	return FLT_PREOP_SYNCHRONIZE;
}

FLT_POSTOP_CALLBACK_STATUS ScannerPostCleanup(PFLT_CALLBACK_DATA Data,
	PCFLT_RELATED_OBJECTS FltObjects,
	PVOID CompletionContext,
	FLT_POST_OPERATION_FLAGS Flags)
{
	NTSTATUS status;
	PSCANNER_CONTEXT scannerContext = NULL;
	PSCANNER_NOTIFICATION notification = NULL;
	ULONG replyLen = 0;

	if (ScannerData.ClientPort == NULL)
	{
		return FLT_POSTOP_FINISHED_PROCESSING;
	}

	status = FltGetStreamHandleContext(FltObjects->Instance, FltObjects->FileObject, &scannerContext);
	if (NT_SUCCESS(status))
	{
		if (scannerContext->Rescan == TRUE)
		{
			notification = ExAllocatePoolWithTag(NonPagedPool, sizeof(SCANNER_NOTIFICATION), 'LFC');
			if (notification == NULL)
			{
				FltReleaseContext(scannerContext);
				return FLT_POSTOP_FINISHED_PROCESSING;;
			}

			replyLen = sizeof(SCANNER_REPLY);

			RtlCopyMemory(notification->PathName, scannerContext->Path, scannerContext->Len);

			status = FltSendMessage(ScannerData.Filter,
				&ScannerData.ClientPort,
				notification,
				sizeof(SCANNER_NOTIFICATION),
				notification,
				&replyLen,
				NULL
			);

			if (!NT_SUCCESS(status))
			{
				DbgPrint("Error FltSendMessage\n");
			}

			ExFreePoolWithTag(notification, 'LFC');

		}

		scannerContext->Rescan = FALSE;

		FltSetStreamHandleContext(FltObjects->Instance, FltObjects->FileObject, FLT_SET_CONTEXT_REPLACE_IF_EXISTS, scannerContext, NULL);

		FltReleaseContext(scannerContext);
	}

	return FLT_POSTOP_FINISHED_PROCESSING;
}

FLT_PREOP_CALLBACK_STATUS  ScannerPreSetInformation(
	PFLT_CALLBACK_DATA Data,
	PCFLT_RELATED_OBJECTS FltObjects,
	PVOID CompletionContext
)
{
	NTSTATUS status = FLT_PREOP_SUCCESS_NO_CALLBACK;
	PFILE_RENAME_INFORMATION renameInfo = NULL;
	PFLT_FILE_NAME_INFORMATION FileNameInfo = NULL;
	PSCANNER_CONTEXT scannerContext = NULL;
	PSCANNER_NOTIFICATION notification = NULL;
	ULONG replyLen = 0;
	UNICODE_STRING extension = RTL_CONSTANT_STRING(L"exe");

	if (FltObjects->FileObject == NULL)
	{
		return status;
	}

	if (Data->Iopb->Parameters.SetFileInformation.FileInformationClass == FileRenameInformation ||
		Data->Iopb->Parameters.SetFileInformation.FileInformationClass == FileRenameInformationEx)
	{

		renameInfo = Data->Iopb->Parameters.SetFileInformation.InfoBuffer;

		status = FltGetDestinationFileNameInformation(FltObjects->Instance,
			FltObjects->FileObject,
			renameInfo->RootDirectory,
			renameInfo->FileName,
			renameInfo->FileNameLength,
			FLT_FILE_NAME_NORMALIZED | FLT_FILE_NAME_QUERY_DEFAULT,
			&FileNameInfo);

		if (NT_SUCCESS(status))
		{

			FltParseFileNameInformation(FileNameInfo);

			if (FileNameInfo->Extension.Length == 0)
			{
				FltReleaseFileNameInformation(FileNameInfo);
				return FLT_PREOP_SUCCESS_NO_CALLBACK;
			}

			if (RtlCompareUnicodeString(&extension, &FileNameInfo->Extension, TRUE) != 0)
			{
				FltReleaseFileNameInformation(FileNameInfo);
				return FLT_PREOP_SUCCESS_NO_CALLBACK;
			}

			/*DbgPrint("%wZ\n", FileNameInfo->Name);*/

			notification = ExAllocatePoolWithTag(NonPagedPool, sizeof(SCANNER_NOTIFICATION), 'LFC');
			if (notification == NULL)
			{
				FltReleaseFileNameInformation(FileNameInfo);
				return FLT_PREOP_SUCCESS_NO_CALLBACK;
			}

			replyLen = sizeof(SCANNER_REPLY);

			RtlCopyMemory(notification->PathName, FileNameInfo->Name.Buffer, FileNameInfo->Name.MaximumLength);

			status = FltSendMessage(ScannerData.Filter,
				&ScannerData.ClientPort,
				notification,
				sizeof(SCANNER_NOTIFICATION),
				notification,
				&replyLen,
				NULL
			);

			if (!NT_SUCCESS(status))
			{
				DbgPrint("Error FltSendMessage\n");
			}

			ExFreePoolWithTag(notification, 'LFC');
			FltReleaseFileNameInformation(FileNameInfo);

		}

	}

	return FLT_PREOP_SUCCESS_NO_CALLBACK;
}