Skip to content

BSODs after DokanMounter service restart #47

Description

@don-Pardon

Hi everyone.
Here is the thing (on Win8.1 Pro, x64, latest available dokanx build):

  1. Install dokanx.
  2. Run "mirrorfs /t 1 /r c: /l z" (I tested on 1-thread fs-app, and BSOD logs where collected for 1-thread app, but I had also multi-threaded mirrorfs caused BSOD as well).
  3. Restart DokanMounter service.
  4. Kill mirrorfs.
    After some time a BSOD will occur. If it didn't, run mirrorfs again.

I tried to detect what the hell happened several times and BSODs where different. Here are BSODs full descriptions (I didn't figure how to attach a txt file here, so external link, sorry). Here is the list of most "popular" BSODs:

  • UNEXPECTED_KERNEL_MODE_TRAP
  • SYSTEM_SERVICE_EXCEPTION
  • PAGE_FAULT_IN_NONPAGED_AREA
  • BAD_POOL_CALLER
  • UNEXPECTED_KERNEL_MODE_TRAP

Most of them point out to the memory problem, attempt to delete what was already deleted or attempt get access to what it shouldn't, etc., yet no one points onto dokanx directly.

Frankly speaking, I'm not that good at driver development neither at BSOD analyze, so I tried to analyze what led to such behavior. And here is what I found out:

  1. When mirrorfs (or any other fs-app) calls DokanMain function, dokanx_mount.exe receives a DOKAN_CONTROL_MOUNT event, it inserts mount entry into it's entry list (mouter.cpp):
if (DokanControlMount(Control->MountPoint, Control->DeviceName)) {
    Control->Status = DOKAN_CONTROL_SUCCESS;
    InsertMountEntry(Control);
} else {
    Control->Status = DOKAN_CONTROL_FAIL;
}

And when fs-app detaches, dokanx_mount.exe receives DOKAN_CONTROL_UNMOUNT event, tries to find mount record and umount mount point:

mountEntry = FindMountEntry(Control);
if (mountEntry == NULL) {
    if (Control->Option == DOKAN_CONTROL_OPTION_FORCE_UNMOUNT &&
        DokanControlUnmount(Control->MountPoint)) {
            Control->Status = DOKAN_CONTROL_SUCCESS;
            break;
    }
    Control->Status = DOKAN_CONTROL_FAIL;
    break;
}
if (DokanControlUnmount(mountEntry->MountControl.MountPoint)) {
    Control->Status = DOKAN_CONTROL_SUCCESS;
    if (wcslen(Control->DeviceName) == 0) {
        wcscpy_s(Control->DeviceName, sizeof(Control->DeviceName) / sizeof(WCHAR),
            mountEntry->MountControl.DeviceName);
    }
    RemoveMountEntry(mountEntry);
} else {
    mountEntry->MountControl.Status = DOKAN_CONTROL_FAIL;
    Control->Status = DOKAN_CONTROL_FAIL;
}

In normal case the record is found and DokanControlUnmount is called and DefineDosDevice(DDD_REMOVE_DEFINITION, drive, NULL) is executed. And previously mounted volume is detached. Everyone's happy. In normal case. But when DokanMounter service is being restarted it looses mount entry list and when DOKAN_CONTROL_UNMOUNT event comes, dokanx_mount.exe doesn't know what to do with such mount entry (unless DOKAN_CONTROL_OPTION_FORCE_UNMOUNT option is specified, but it can be specified only if unmount was performed via dokanx_control utility).
The point is that after DokanMounter service is being restarted, unmounting fails (unless force unmount) and after some time something memory-incorrect happens in kernel and BSOD occurs.

Again, I have very few knowledge about drivers and it would take lots of time for me to discover what and why happening in kernel mode, so if anyone willing to dig up the truth - you are welcome.
For now I have some solution-like-thoughts :

  1. Why do we need DOKAN_CONTROL_OPTION_FORCE_UNMOUNT? If we remove check for that option and will try to unmount everything that being requested, what problems will it cause? Something like this:
mountEntry = FindMountEntry(Control);
if (mountEntry == NULL) {
    if (DokanControlUnmount(Control->MountPoint)) {
            Control->Status = DOKAN_CONTROL_SUCCESS;
            break;
    }
    Control->Status = DOKAN_CONTROL_FAIL;
    break;
}
if (DokanControlUnmount(mountEntry->MountControl.MountPoint)) {
    Control->Status = DOKAN_CONTROL_SUCCESS;
    if (wcslen(Control->DeviceName) == 0) {
        wcscpy_s(Control->DeviceName, sizeof(Control->DeviceName) / sizeof(WCHAR),
            mountEntry->MountControl.DeviceName);
    }
    RemoveMountEntry(mountEntry);
} else {
    mountEntry->MountControl.Status = DOKAN_CONTROL_FAIL;
    Control->Status = DOKAN_CONTROL_FAIL;
}

I tried this approach and everything went fine (no BSODs) except for BOOL SendReleaseIRP(LPCWSTR DeviceName) function fails because of GetRawDeviceName fails, because there were no entry list and therefore required entry wasn't found. The device wasn't release as I understood, how bad is that?
2. Another approach - is to create some kind of mount entry list serialization/deserialization for DokanMounter service, so when it is being shut down, it would dump this list to somewhere and when it comes back alive it would know all about mount entries. This approach is kinda row yet, it should take into account situation when fs-app detaches when service is still down, or when new fs-app being attached. Maybe combine those two approaches...

That is it. Thanks for reading. I would like you guys to talk about these matter. Please fill free to correct me, give advices, any kind of thoughts. For now I'll use first approach, but I thinks it is not fully correct and I'm not going to pull it yet.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions