FILE_COMPLETE_IF_OPLOCKED not working on windows 10

Which build of Win10 are you testing on? Also, which file system(s)?

I have tried win-10 1803, 1903 and 1909 with ntfs volume where it returns STATUS_SUCCESS instead of STATUS_OPLOCK_BREAK_IN_PROGRESS.
win-7 with ntfs volume returns STATUS_OPLOCK_BREAK_IN_PROGRESS.

One of my colleague tried on win 8.1 with ntfs volume, with same result as win-10.

FWIW, I have seen HCK’s IFS Test fail oplock break test on W7 and Srv08/12
with a vanilla system (no third party apps or drivers).

I never trust it as a result.

Not sure if I understand correctly. Which of the behavior is expected, one I am seeing on win7 or on win 10.
If win 10 behavior is expected, then what is the way to detect an oplock is already acquired in order to skip any file io after opening that file again.

The one you observe on win7 is expected. But often it does not materialize.
I have seen it not happen on vanilla Windows 7, Server 2008 and 2012,
as I mentioned. I.e. without ANY third party filters or software.

As to how to detect it - no idea. I gave up that almost a decade ago.

Dejan.

Not sure if I understand correctly. Which of the behavior is expected, one I
am seeing on win7 or on win 10.

If win 10 behavior is expected, then what is the way to detect an oplock is
already acquired in order to skip any file io after opening that file
again.

Thank you Dejan for clarifying.

FILE_OPEN_REQUIRING_OPLOCK is a weird flag because it doesn’t actually grant you an oplock. Instead it puts the stream into an “oplock like” state in the file system so that no one can touch the file until you send your FSCTL_REQUEST_OPLOCK. If you have an FSCTL_REQUEST_OPLOCK pending when the second handle does a read/write do you get the oplock break acknowledgment on the first handle?

Also, can you post your code someplace? I just did the following on Win 10 1909 and a local NTFS volume:

    status = NtCreateFile(&handle, 
                          GENERIC_READ | GENERIC_WRITE, 
                          &objAttr, 
                          &iosb, 
                          NULL, 
                          FILE_ATTRIBUTE_NORMAL, 
                          0, 
                          FILE_OPEN_IF, 
                          FILE_OPEN_REQUIRING_OPLOCK, 
                          NULL, 
                          0);

    if (!NT_SUCCESS(status)) {
        return 1;
    }

    status = NtCreateFile(&handle2, 
                          GENERIC_READ | GENERIC_WRITE, 
                          &objAttr, 
                          &iosb, 
                          NULL, 
                          FILE_ATTRIBUTE_NORMAL, 
                          0, 
                          FILE_OPEN_IF, 
                          FILE_COMPLETE_IF_OPLOCKED, 
                          NULL, 
                          0);

The second open fails with sharing violation. This is different than what you’re describing so I’d be interested to see your exact combination of values.

IIRC, FILE_OPEN_IF is handled differently from FILE_OPEN in a lot of cases.

Hence you get a sharing violation.

Hi Scott,

I have tried the flags that you have used and it does fail with sharing violation. Then I removed FILE_OPEN_REQUIRING_OPLOCK and FILE_COMPLETE_IF_OPLOCKED flags from first and second create calls and it still failed with sharing violation. Most likely the reason for sharing violation is that you have provided GENERIC_READ | GENERIC_WRITE in desired access but have not provided FILE_SHARE_READ | FILE_SHARE_WRITE, so second open request cannot be allowed to succeed.

Here is the relevant code snippet:

`status = ZwCreateFile(&hFile1,
FILE_WRITE_DATA | SYNCHRONIZE,
&oa,
&ioStatus,
NULL,
FILE_ATTRIBUTE_NORMAL,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
FILE_OPEN_IF,
FILE_NON_DIRECTORY_FILE | FILE_RANDOM_ACCESS | FILE_OPEN_REQUIRING_OPLOCK,
NULL,
0);
if (status != 0)
{
return status;
}

status = ZwCreateFile(&hFile2,
        FILE_WRITE_DATA | SYNCHRONIZE | DELETE,
        &oa,
        &ioStatus,
        NULL,
        FILE_ATTRIBUTE_NORMAL,
        FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
        FILE_OPEN,
        FILE_NON_DIRECTORY_FILE | FILE_COMPLETE_IF_OPLOCKED,
        NULL,
        0);
if (status != 0)
{
    ....
}`

Also attached the source file to this thread (since it won’t let me attach a cpp file, renamed its extension to .txt)

Thanks,
Arun

Hi Scott,

My previous comment went missing, so please forgive me if this is a duplicate post.

Your second request would have failed because it requests GENERIC_READ | GENERIC_WRITE but no sharing was allowed by the first and second open.
I have tried the flags used in your sample and was able to reproduce the sharing violation behavior you are seeing, then added FILE_SHARE_READ and FILE_SHARE_WRITE in both open requests and it gave STATUS_SUCCESS in both requests.
Also, if I remove FILE_OPEN_REQUIRING_OPLOCK and FILE_COMPLETE_IF_OPLOCKED it doesn’t change the behavior.

Here is relevant code snippet which I had originally used:

status = ZwCreateFile(&hFile1,
FILE_WRITE_DATA | SYNCHRONIZE,
&oa,
&ioStatus,
NULL,
FILE_ATTRIBUTE_NORMAL,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
FILE_OPEN_IF,
FILE_NON_DIRECTORY_FILE | FILE_RANDOM_ACCESS | FILE_OPEN_REQUIRING_OPLOCK,
NULL,
0);
if (status != 0)
{
return status;
}

status = ZwCreateFile(&hFile2,
FILE_WRITE_DATA | SYNCHRONIZE | DELETE,
&oa,
&ioStatus,
NULL,
FILE_ATTRIBUTE_NORMAL,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
FILE_OPEN,
FILE_NON_DIRECTORY_FILE | FILE_COMPLETE_IF_OPLOCKED,
NULL,
0);
if (status != 0)
{

}

Also attached the source file here (since it won’t let me attach a .cpp file, added .txt extension to it)

Thanks,
Arun

Hi Scott,

Somehow, I am not able to post with the code/attachment.

In your case sharing violation is returned because you have not provided FILE_SHARE_READ | FILE_SHARE_WRITE while desired access are requesting GENERIC_READ | GENERIC_WRITE. Oplock flags have no bearing on these calls. Please add the sharing flags and you will see the same result of STATUS_SUCCESS in the second call.

Thanks,
Arun

Cool, presumably we’re now looking at the same thing and now we’re on to something…

If I do this:

    status = NtCreateFile(&handle, 
                          GENERIC_READ, 
                          &objAttr, 
                          &iosb, 
                          NULL, 
                          FILE_ATTRIBUTE_NORMAL, 
                          FILE_SHARE_READ|FILE_SHARE_WRITE, 
                          FILE_OPEN_IF, 
                          FILE_OPEN_REQUIRING_OPLOCK, 
                          NULL, 
                          0);

    if (!NT_SUCCESS(status)) {
        return 1;
    }

    status = NtCreateFile(&handle2, 
                          GENERIC_WRITE, 
                          &objAttr, 
                          &iosb, 
                          NULL, 
                          FILE_ATTRIBUTE_NORMAL, 
                          FILE_SHARE_READ|FILE_SHARE_WRITE, 
                          FILE_OPEN_IF, 
                          FILE_COMPLETE_IF_OPLOCKED,
                          NULL, 
                          0);

The second create completes immediately with STATUS_SUCCESS. This is reasonable though because the first open doesn’t actually have an oplock yet (how would the file system even know what level to give you?). Like I said before it’s in an “oplock like” state though so things that might require breaking an oplock will hang.

The contract with FILE_OPEN_REQUIRING_OPLOCK is that you then need to actually request an oplock. So, if I change the code to something like this:

    status = NtCreateFile(&handle, 
                          GENERIC_READ, 
                          &objAttr, 
                          &iosb, 
                          NULL, 
                          FILE_ATTRIBUTE_NORMAL, 
                          FILE_SHARE_READ|FILE_SHARE_WRITE, 
                          FILE_OPEN_IF, 
                          FILE_OPEN_REQUIRING_OPLOCK, 
                          NULL, 
                          0);

    if (!NT_SUCCESS(status)) {
        return 1;
    }

    oplockInput.StructureVersion     = REQUEST_OPLOCK_CURRENT_VERSION;
    oplockInput.StructureLength      = sizeof(REQUEST_OPLOCK_INPUT_BUFFER);
    oplockInput.RequestedOplockLevel = (OPLOCK_LEVEL_CACHE_READ | OPLOCK_LEVEL_CACHE_WRITE);
    oplockInput.Flags                = REQUEST_OPLOCK_INPUT_FLAG_REQUEST;

    status = NtFsControlFile(handle,
                             event,
                             NULL,
                             NULL,
                             &iosb,
                             FSCTL_REQUEST_OPLOCK,
                             &oplockInput,
                             sizeof(REQUEST_OPLOCK_INPUT_BUFFER),
                             &oplockOutput,
                             sizeof(REQUEST_OPLOCK_OUTPUT_BUFFER));

    if (!NT_SUCCESS(status)) {
        return 1;
    }

    status = NtCreateFile(&handle2, 
                          GENERIC_WRITE, 
                          &objAttr, 
                          &iosb, 
                          NULL, 
                          FILE_ATTRIBUTE_NORMAL, 
                          FILE_SHARE_READ|FILE_SHARE_WRITE, 
                          FILE_OPEN_IF, 
                          FILE_COMPLETE_IF_OPLOCKED,
                          NULL, 
                          0);

Then I get STATUS_OPLOCK_BREAK_IN_PROGRESS on the second open.

I know you said you’re not seeing this behavior, so I’d still like to see you code and figure out what’s different. Can you put it on Dropbox or OneDrive?

Hi Scott,

Thanks for lot for your inputs,

I was able to find the issue. Difference which mattered is the use of OPLOCK_LEVEL_CACHE_WRITE. I had that flag missing in my code in which case second open is successful. On adding that flag, I do get back STATUS_OPLOCK_BREAK_IN_PROGRESS in second open.

So the question becomes, Is it necessary to provide OPLOCK_LEVEL_CACHE_WRITE, when the first handle only means to cache the reads ? Trying to attach my source code in .txt format, lets see if it works this time.

Thanks,
Arun

You’re missing the part where you get OPLOCK_BREAK_IN_PROGRESS on Windows 7.
If it is only your applications’ interop that you are testing, the
below may work (until a build of Windows decides to break it, or some
filter, often Windows Defender, breaks it).

But other applications, that did not explicitly request the oplock
through FSCTL call, will not change most likely. And you simply won’t
get the status you are expecting.

Hi Scott,

Thanks for lot for your inputs,

I was able to find the issue. Difference which mattered is the use of
OPLOCK_LEVEL_CACHE_WRITE. I had that flag missing in my code in which case
second open is successful. On adding that flag, I do get back
STATUS_OPLOCK_BREAK_IN_PROGRESS in second open.

So the question becomes, Is it necessary to provide
OPLOCK_LEVEL_CACHE_WRITE, when the first handle only means to cache the
reads ? Trying to attach my source code in .txt format, lets see if it works
this time.

STATUS_OPLOCK_BREAK_IN_PROGRESS means, “your IRP_MJ_CREATE broke the oplock (based on our byzantine rules that are impossible to remember). We didn’t block waiting for acknowledgement though. You must not proceed to do I/O on this handle until you an FSCTL_OPLOCK_BREAK_NOTIFY completes successfully, which means all involved parties have acknowledged the oplock break.”

However, not all oplocks break on IRP_MJ_CREATE. Imagine everyone is caching read data and you want to open for write. There’s no need to break the oplock because you haven’t actually written anything yet and we’ll lose if you open for write, break all the oplocks, and then just close the handle. The oplock WILL need to break on write though, so that’s why you get the inifinite hang on I/O if an oplock hasn’t actually been requested on the file yet (the write can’t proceed until the oplock breaks, but the oplock hasn’t even been requested yet).

Sooooo this brings me to what I should have asked in the first place: what are you trying to accomplish?

Hi Dejan/Scott,

Sooooo this brings me to what I should have asked in the first place: what are you trying to accomplish?
I started this thread because we were getting a hang with another filter driver which was opening the file but not requesting oplock through FSCTL. For some reason our filter wanted to delete that file in PreCleanup and it was opening the file again and tried to delete it where it got hung.
I know we don’t need to open the file again and file can be deleted with existing file object (for most practical cases, and we may not care about other case where we can’t) . So that part has been fixed. However I was intrigued why use FILE_COMPLETE_IF_OPLOCKED didn’t result in STATUS_OPLOCK_BREAK_IN_PROGRESS.

But other applications, that did not explicitly request the oplock through FSCTL call, will not change most likely. And you simply won’t get the status you are expecting.
Atleast in that case, I can tell the third party filter driver developers that they are not playing by the rules and they must obtain the oplock if they want to use FILE_OPEN_REQUIRING_OPLOCK.

Also, If I understand correctly, if anyone only wants to use OPLOCK_LEVEL_CACHE_READ and not OPLOCK_LEVEL_CACHE_WRITE, they must skip FILE_SHARE_WRITE and FILE_SHARE_DELETE flags.

I think I got all the answers I was looking for. Thanks for all the help, much appreciated.

Thanks,
Arun

On a non-technical note again (and this is directed to anyone reading,
with sincerely good intentions in mind), since interop testing took
over half of my “development” time in the past…
You might get the developer to change their filter to “conform” to
Windows 10 Oplocks, but you will still need to get all the customers
to switch to a new version (which is not doable if that requires a new
license often).

What I am trying to say is: consider a different approach, if at all
possible. Unless you are Symantec, McAffee, in case of South Korea
AHN, or some other really big name, you cannot afford to chase other
filters :frowning:

I believe we had interop issues with literally every AV company on
this list, apart from G-Data. We only got TWO of them to work with us
on the issue.
All the issues were their bugs or incorrect I/O handling. But the
customer will not see it that way.

I started this thread because we were getting a hang with another filter
driver which was opening the file but not requesting oplock through FSCTL.
For some reason our filter wanted to delete that file in PreCleanup and it
was opening the file again and tried to delete it where it got hung.

Hi Dejan,

I do agree with your comment and thats what we did, used an alternate way (which should have been used in the first place)
This post was mostly for my own understanding.

Thanks,
Arun

Hi Scott,

My previous comment went missing, so please forgive me if this is a duplicate post.

Your second request would have failed because it requests GENERIC_READ | GENERIC_WRITE but no sharing was allowed by the first and second open.
I have tried the flags used in your sample and was able to reproduce the sharing violation behavior you are seeing, then added FILE_SHARE_READ and FILE_SHARE_WRITE in both open requests and it gave STATUS_SUCCESS in both requests.
Also, if I remove FILE_OPEN_REQUIRING_OPLOCK and FILE_COMPLETE_IF_OPLOCKED it doesn’t change the behavior.

Here is relevant code snippet which I had originally used:

status = ZwCreateFile(&hFile1,
FILE_WRITE_DATA | SYNCHRONIZE,
&oa,
&ioStatus,
NULL,
FILE_ATTRIBUTE_NORMAL,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
FILE_OPEN_IF,
FILE_NON_DIRECTORY_FILE | FILE_RANDOM_ACCESS | FILE_OPEN_REQUIRING_OPLOCK,
NULL,
0);
if (status != 0)
{
return status;
}

status = ZwCreateFile(&hFile2,
FILE_WRITE_DATA | SYNCHRONIZE | DELETE,
&oa,
&ioStatus,
NULL,
FILE_ATTRIBUTE_NORMAL,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
FILE_OPEN,
FILE_NON_DIRECTORY_FILE | FILE_COMPLETE_IF_OPLOCKED,
NULL,
0);
if (status != 0)
{

}

Also attached the source file here (since it won’t let me attach a .cpp file, added .txt extension to it)

Thanks,
Arun

Hi Scott,

My previous comment went missing, so please forgive me if this is a duplicate post.

Your second request would have failed because it requests GENERIC_READ | GENERIC_WRITE but no sharing was allowed by the first and second open.
I have tried the flags used in your sample and was able to reproduce the sharing violation behavior you are seeing, then added FILE_SHARE_READ and FILE_SHARE_WRITE in both open requests and it gave STATUS_SUCCESS in both requests.
Also, if I remove FILE_OPEN_REQUIRING_OPLOCK and FILE_COMPLETE_IF_OPLOCKED it doesn’t change the behavior.

Here is relevant code snippet which I had originally used:

status = ZwCreateFile(&hFile1,
FILE_WRITE_DATA | SYNCHRONIZE,
&oa,
&ioStatus,
NULL,
FILE_ATTRIBUTE_NORMAL,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
FILE_OPEN_IF,
FILE_NON_DIRECTORY_FILE | FILE_RANDOM_ACCESS | FILE_OPEN_REQUIRING_OPLOCK,
NULL,
0);
if (status != 0)
{
return status;
}

status = ZwCreateFile(&hFile2,
        FILE_WRITE_DATA | SYNCHRONIZE | DELETE,
        &oa,
        &ioStatus,
        NULL,
        FILE_ATTRIBUTE_NORMAL,
        FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
        FILE_OPEN,
        FILE_NON_DIRECTORY_FILE | FILE_COMPLETE_IF_OPLOCKED,
        NULL,
       0);

if (status != 0)
{

}

Also attached the source file here (since it won’t let me attach a .cpp file, added .txt extension to it)

Thanks,
Arun