A couple hours spent perusing the TC driver source code only deepens
the mystery. CryptDisk, if anything, is more complete than TC
with respect to NT devices. I am responding to every ioctl and IRP as
TC and more.
The differences between TC and CD:
TC alerts the MountMgr (via IRP/ioctl) after creating the virtual
disk device. The volume link is created in two different places:
MountManagerMount() and in response to
IOCTL_MOUNTDEV_QUERY_SUGGESTED_MOUNT_POINT. And finally there is a
comment indicating that MountMgr sometimes fails to create the
link.
TC then responds to the MOUNTDEV IRPs from MountMgr to create a
volume(s) filter for the new disk device. The volume device doesn't
actually do much other than catch power and PnP events.
CD creates the volume link directly through a single call to
IoCreateSymbolicLink(). I tried using MountMgr a long time ago
(Driver1) and tossed it aside as being a mess that didn't provide any
value. Creating the volume myself seems to work and has been
reliable. However, I can't see any other significant difference
between TC and CD. When everything else has been ruled out, what is
left -- no matter how strange or perplexing -- must be the answer.
Software is difficult because testing a theory can sometimes
require writing a lot of code. And sometimes that code is then thrown
away when it disproves the theory. This is especially true for KM,
where creating, maintaining, and using the debug environment can be so
difficult.
I spent about four hours replacing my code that created the volume
link directly with calls to MountMgr, using TrueCrypt as a guide. I
spent about half an hour trying to figure out why MountMgr was always
returning an error from CREATE_POINT, then noticed that TC was
ignoring the return status.
MountMgr IOCTLs sent to the disk device:
560048: IOCTL_VOLUME_IS_DYNAMIC
4D000C: IOCTL_MOUNTDEV_QUERY_SUGGESTED_LINK_NAME
4DC010: IOCTL_MOUNTDEV_LINK_CREATED
4D0010: Same?
56C064: IOCTL_VOLUME_POST_ONLINE
The MountMgr is horrible: Interleaved callbacks, cryptic paths, minimal
documentation. And then it doesn't really do much -- except something
that makes CompactSQL happy.
SUCCESS!!!
I am now able to build the SDF on my CryptDisk volume!!!!!
This was a nasty, nasty bug that required extreme investigation.
I still don't understand exactly why but now I know how.
Rebuilding TC was well worth the trouble. I now have a clear
roadmap to what IOCTLs need to be handled and which do not. At this
point, CD is handling many more IOCTLs than TC, which an optimist
would view as being "more complete". I can also see where the TC
authors also had their own doubts and rabbit holes. In my opinion,
there is a lot of code that isn't necessary. TC doesn't need to create
the volume filters when it is simply acting as a disk device.
On the other hand, there is a lot of code in TC to deal with boot
and hidden partitions. This is a whole area I am intentionally
avoiding for the "Daily Use" version of CD. I will want to revisit the
topic when I start working on CryptDisk Stealth.
October 22 2017
I thought MountMgr was working, but now it is not. It may be that I
am not handling the MOUNTDEV_QUERY_UNIQUE_ID properly.
It appears that MountMgr is creating a volume link using the guid I
provided. Then the CREATE_POINT fails because I am expecting to use
the "CryptDisk01" name not the guid name.
TrueCrypt does not respond to the IOCTL_MOUNTDEV_QUERY_STABLE_GUID
at all. When I disable the response in my code, MountMgr generates a
new guid.
I think MountMgr is expecting to use the guid to track the volume
so it can try to recreate the link points consistently. The guid then
refers to the volume, not the device. So with CryptDisk, the guid
should be tied to the media file. However, I do not want to use guids
since they can be used to identify the computer that generated them.
I use a pseudo-guid instead, generated from the VolumeInfo VolumeID.
(This is a simple timestamp written into a guid struct.)
I split up MountMgr::Mount() into Notify() and Mount(). This lets
me call Mount() with the volume name from the MOUNTDEV_LINK_CREATED
callback. But now I am seeing two volumes created, not sure
what is happening there.
My victory declaration over MountMgr was premature. It worked the
first time, then failed after that.
The loader sends a MOUNT ioctl.
The control device spawns a new \Device\CryptDiskDebug01
disk device.
The control device sends the MOUNT ioctl to the disk device, which
defers it to its worker thread.
The worker thread fetches the MOUNT ioctl.
The disk device opens the media file and validates it.
The disk device calls MountMgr.Notify(\Device\CryptDiskDebug01)
I send IOCTL_MOUNTMGR_VOLUME_ARRIVAL_NOTIFICATION(\Device\CryptDiskDebug01) to the MountMgr.
I receive IOCTL_MOUNTDEV_QUERY_DEVICE_NAME and respond with "\Device\CryptDiskDebug01"
I receive IOCTL_MOUNTDEV_QUERY_UNIQUE_ID and respond with "\Device\CryptDiskDebug01"
I receive IOCTL_MOUNTDEV_QUERY_STABLE_GUID and respond with "{C06A47FA-..."}
I receive IOCTL_MOUNTDEV_QUERY_SUGGESTED_LINK_NAME and respond with "\DosDevices\S:"
I receive IOCTL_MOUNTDEV_LINK_CREATED(\??\Volume{6adc72bd-...})
I receive IOCTL_MOUNTDEV_LINK_CREATED(\??\Volume{c06a47fa-...})
MountMgr returns STATUS_SUCCESS.
The disk device calls MountMgr.Mount(\Device\CryptDiskDebug01,S)
I create the volume name "\DosDevices\S:"
I send IOCTL_MOUNTMGR_CREATE_POINT(\Device\CryptDisk01,\DosDevices\S:) to MountMgr
MountMgr fails with C0000034:NAME_NOT_FOUND
I have tried using \??\Volume{c06a47fa-...} with the same result.
IOCTL_MOUNTDEV_QUERY_DEVICE_NAME: Support for this IOCTL by the mount
manager clients is mandatory. Upon receiving this IOCTL a client
driver must provide the (nonpersistent) device (or target) name for
the volume. The mount manager uses the device name returned by the
client as the target of a symbolic link. An example of a device name
would be "\Device\HarddiskVolume1". typedef struct _MOUNTDEV_NAME {
USHORT NameLength;
WCHAR Name[1];
} MOUNTDEV_NAME, *PMOUNTDEV_NAME;
IOCTL_MOUNTDEV_QUERY_UNIQUE_ID: Support for this IOCTL by mount manager
clients is mandatory. Upon receiving this IOCTL, the mount manager
client must provide a counted byte string identifier that is unique to
the client (that is, the device or the volume). The client cannot
change this unique ID without alerting the mount manager (see
IOCTL_MOUNTDEV_UNIQUE_ID_CHANGE_NOTIFY). typedef struct _MOUNTDEV_UNIQUE_ID {
USHORT UniqueIdLength;
UCHAR UniqueId[1];
} MOUNTDEV_UNIQUE_ID, *PMOUNTDEV_UNIQUE_ID;
IOCTL_MOUNTDEV_QUERY_STABLE_GUID: This is not documented.
The state of the MountMgr "database":
[ImageNotFound:'2172']
[ImageNotFound:'2173']
Note that the link created from my STABLE_GUID "\??\Volume{c06a47fa-...}"
is not listed. The link generated by MountMgr "\??\Volume{6adc72bd-...}"
is present and linked to "\Device\CryptDiskDebug01". There is no entry
for "\DosDevices\S:".
Ideas:
Read OSR:
Drive Letter Assignment. It was written 15 years ago (it mentions
IoMega zip drives! Back when 120MB was a big deal), but not much has
changed.
Read OSRForum: Mount Manager
Mount points are stored in the registry under \HKLM\System\MountedDevices
The discussion ends on a discouraging note.
MountMgr doesn't like the '\??\' prefix. I need to replace it with '\Device\'
The \Device\CryptDiskDebug01 device has not been created in the object directory
yet. I think this is unlikely. The CREATE_POINT worked at least once. This would be
difficult to test.
Start handling the PNP IRPs in the disk device.
Create a volume device to handle PNP messages.
October 29 2017
The SDF glitch is back -- it is the bug that just won't die. After
a lot of experimentation, it seems to be working (again).
UPDATE 20171108: The bug turned out to be another case of C/UNICODE_STRING
confusion. I was including the string terminator in the length given to
MountManager, which was then including the 0-character as part of the literal
text.
IoCreateSymbolicLink("\DosDevices\H:","Device\CryptDiskDebug01")
This will fail if the link has already been created. This is a redundant
step in case the symbolic link was not created by MountMgr.
pDevice->Characteristics|= FILE_DEVICE_IS_MOUNTED;
This is probably not required (probably not correct). It was
added while I was experimenting and seems to be benign.
CREATE_POINT will return 0xC0000034 if DeviceName is invalid. It
will return 0xC000000D if SymbolicName is invalid.
MountMgr:
NTSTATUS nMountMgr::Notify(const WCHAR *DevName) {
NTSTATUS Status= STATUS_SUCCESS;
UINT DevNameLen= StrLenW(DevName);
MOUNTMGR_TARGET_NAME *pInfo= 0;
// NOTE: sizeof(*pInfo) implicitly includes the terminator since
// pInfo->DeviceName[] is declared as length 1.
UINT BufSz= sizeof(*pInfo)+DevNameLen*2;
if(!(pInfo= (MOUNTMGR_TARGET_NAME*)MemAlloc("MountMgr:Notify",BufSz)))
return(Warn(STATUS_NO_MEMORY,"nMountMgr:Notify: NoMem(%d)",BufSz));
pInfo->DeviceNameLength= StrLenW(DevName)*2;
RtlCopyMemory(pInfo->DeviceName,DevName,(DevNameLen+1)*2);
Debug(DBG_MOUNTMGR,"nMountMgr:Notify: VOLUME_ARRIVAL_NOTIFICATION %S",DevName);
if(!NT_SUCCESS(Status= SendMountMgr(IOCTL_MOUNTMGR_VOLUME_ARRIVAL_NOTIFICATION,pInfo,BufSz,0,0)))
Status= Warn(Status,"nMountMgr:Notify: VOLUME_ARRIVAL_NOTIFICATION failed.");
MemFree(pInfo);
return(Status);
}
// Use MountManager to mount the volume.
// This does little more than create the \DosDevices\C: link, but there
// are some weird cases (ie Visual Studio SDF database) that won't work
// properly if I don't use MountManager.
NTSTATUS nMountMgr::Mount(const WCHAR *_DeviceName, WCHAR MountLetter) {
NTSTATUS Status= STATUS_SUCCESS;
NtString DeviceName(_DeviceName);
NtString MountName(false,"\\DosDevices\\%C:",MountLetter);
BYTE *pBuf;
MOUNTMGR_CREATE_POINT_INPUT *pMountPt;
//NOTE: MountPt does not include any string terminators.
UINT BufSz= sizeof(*pMountPt)+DeviceName.ByteCt()+MountName.ByteCt();
if(!(pBuf= (BYTE*)MemAlloc("nMountMgr:MountPt",BufSz)))
return(Warn(STATUS_NO_MEMORY,"nMountMgr:Mount: NoMem(%d)",BufSz));
// Now for some funky point/offset math...
// This would have been *so* much simpler if CREATE_POINT_POINT ended
// with WCHAR text[0] and used null-terminated strings.
pMountPt= (MOUNTMGR_CREATE_POINT_INPUT*)&pBuf[0];
pMountPt->DeviceNameOffset= sizeof(*pMountPt); //Offset is bytes from beginning of pMountPt.
pMountPt->DeviceNameLength= DeviceName.ByteCt(); //Length is in bytes, not including terminator.
pMountPt->SymbolicLinkNameOffset= pMountPt->DeviceNameOffset+pMountPt->DeviceNameLength;
pMountPt->SymbolicLinkNameLength= MountName.ByteCt();
RtlCopyBytes(&pBuf[pMountPt->DeviceNameOffset],DeviceName.Text(),pMountPt->DeviceNameLength);
RtlCopyBytes(&pBuf[pMountPt->SymbolicLinkNameOffset],MountName.Text(),pMountPt->SymbolicLinkNameLength);
if(!NT_SUCCESS(Status= SendMountMgr(IOCTL_MOUNTMGR_CREATE_POINT,pMountPt,BufSz,0,0))) {
Warn(Status,"nMountMgr:Mount: CREATE_POINT(%S,%S) failed.",DeviceName.Text(),MountName.Text());
// I can ignore this error and *most* things will still work. (Except the SDF glitch...)
Status= STATUS_SUCCESS;
} else {
Debug(DBG_MOUNTMGR,"Volume %S mounted as %S",DeviceName.Text(),MountName.Text());
}
// I try to create the volume link just in case MountMgr flakes out.
// This will fail benignly if MountMgr already created the link.
// Otherwise, this will still create a usable drive that will work for 99% of the apps.
IoCreateSymbolicLink(&MountName.GetUnicode(),&DeviceName.GetUnicode());
MemFree(pBuf);
return(Status);
}
NTSTATUS nMountMgr::SendIoctl(UNICODE_STRING &DevName, DWORD IoCode, void *pIn, UINT InSz, void *pOut, UINT OutSz) {
NTSTATUS Status= STATUS_SUCCESS;
FILE_OBJECT *pFileObj;
DEVICE_OBJECT *pDevObj;
KEVENT WaitEvent;
IRP *pIrp;
IO_STATUS_BLOCK IoStatus;
if(!NT_SUCCESS(Status= IoGetDeviceObjectPointer(&DevName,FILE_READ_ATTRIBUTES,&pFileObj,&pDevObj)))
return(Warn(Status,"nMountMgr:SendIoctl: IoGetDeviceObjectPointer(%wZ) failed.",&DevName));
KeInitializeEvent(&WaitEvent,NotificationEvent,FALSE);
if(!(pIrp= IoBuildDeviceIoControlRequest(IoCode,pDevObj,pIn,InSz,pOut,OutSz,0,&WaitEvent,&IoStatus))) {
Status= Warn(STATUS_INSUFFICIENT_RESOURCES,"nMountMgr:SendIoctl: IoBuildDeviceIoControlRequest() failed.");
} else {
IO_STACK_LOCATION *pStack= IoGetNextIrpStackLocation(pIrp);
pStack->FileObject= pFileObj;
if((Status= IoCallDriver(pDevObj,pIrp))==STATUS_PENDING) {
KeWaitForSingleObject(&WaitEvent,Executive,KernelMode,0,0);
Status= IoStatus.Status;
}
}
ObDereferenceObject(pFileObj);
return(Status);
}
March 8 2018
Everything seems to be working. I mount a volume as H: and it is fine. Then
a few seconds later a new E: drive appears, which is an alias for H:. There is
an entry in the registry under \HKLM\System\MountedDevices\ \DosDevices\E:
[ImageNotFound:'2509']
[ImageNotFound:'2510']
If delete the \DosDevices\E: entry and reboot, it will reappear
after I mount H: again. Note that H: is missing from the list.
The debug log shows something unexpected...
Debug:
CryptDriver has arrived!
CryptDriver version 3.1.1130 [VS17] VS17
DriverEntry=FFFFF800B2116A90 pDriver=FFFFDA0EA45C1E60
CryptDisk|DBG DeviceCtrl:Create
CryptDisk|DBG DeviceDisk:Create: \Device\CryptDiskDebug01
CryptDisk|DBG DeviceCtrl:SendIoctl: Waiting... pIrp[FFFFDA0EA7AF7410]
CryptDisk|DBG DeviceDisk:IoctlQueryUniqueID: \Device\CryptDiskDebug01
CryptDisk|DBG DeviceDisk:IoctlQueryVolumeGuid: {C06A47FA-A605-478E-818E-71F0589F503B}
CryptDisk|DBG DeviceDisk:IoctlLinkCreated: \??\Volume{c06a47fa-a605-478e-818e-71f0589f503b}
CryptDisk|DBG DeviceDisk:IoctlLinkCreated: \DosDevices\E:
CryptDisk|ERR WRN[C000000D]: nMountMgr:Mount: CREATE_POINT(\Device\CryptDiskDebug01,\DosDevices\H:) failed.
CryptDisk|DBG DeviceCtrl:SendIoctl: Success[CD01E004]
CryptDisk|ERR WRN[C0000010]: DeviceExt:IrpFail: DeviceDisk:IoctlStorageQueryProperty
CryptDisk|DBG DeviceDisk:IoctlQueryVolumeGuid: {C06A47FA-A605-478E-818E-71F0589F503B}
CryptDisk|ERR WRN[C0000010]: DeviceExt:IrpFail: DeviceDisk:IoctlVolumeQueryAllocationHint
CryptDisk|DBG DeviceDisk:IoctlQueryVolumeGuid: {C06A47FA-A605-478E-818E-71F0589F503B}
CryptDisk|ERR WRN[C0000010]: DeviceExt:IrpFail: DeviceDisk:IoctlStorageQueryProperty
CryptDisk|ERR WRN[C0000023]: DeviceExt:IrpFail: DeviceDisk:IoctlStorageQueryDevice: Buffer too small (8,40)
CryptDisk|ERR WRN[C0000023]: DeviceExt:IrpFail: DeviceDisk:IoctlStorageQueryDevice: Buffer too small (8,40)
CryptDisk|ERR WRN[C0000023]: DeviceExt:IrpFail: DeviceDisk:IoctlStorageQueryDevice: Buffer too small (8,40)
CryptDisk|ERR WRN[C0000023]: DeviceExt:IrpFail: DeviceDisk:IoctlStorageQueryDevice: Buffer too small (8,40)
It appears my old friend MountMgr is acting peculiar again. This is
running on VS17, I probably won't see the same behavior on Win10 where
I can step through the code. So I will need to figure this out the hard
way... This is a tedious process because once the errant E: drive is
created I need to reboot to clear it out.
It appears that MountMgr::Notify() is being called first, which is
when Windows creates the E: drive and notifies me. I try to mount the
volume as H: and that fails, then as a fallback I explictly create the
\DosDevices\H: link to the driver.
I changed the code so Notify() is called after Mount(). That seemed
to fix it.
Comments are moderated.
Anonymous comments are not visible to other users until approved.
The content of comments remains the intellectual property of the poster.
Comments may be removed or reused (but not modified) by this site at
any time without notice.