BitLocker I/O Control

The kernel-mode device driver, FVEVOL.SYS, for BitLocker extends each filtered volume’s Device I/O Control interface. Without BitLocker, the volume could be opened, e.g., as \\.\c: from user mode, and be found to respond to some set of I/O Control (IOCTL) codes. With BitLocker, the same volume responds to more IOCTL codes. For Windows Vista and Windows 7, this addition of roughly a dozen IOCTL codes as if supported by the underlying device driver was the whole of BitLocker’s API.

Note that a filter driver written by hands other than Microsoft’s, as for full-volume encryption (FVE) products in an openly competitive market that pre-dated BitLocker, could not prudently extend the interface this way. A Microsoft driver that filters the I/O to and from the underlying device drivers for storage volumes can add IOCTL codes because Microsoft, as owner of these underlying device drivers, controls what IOCTL codes these drivers respond to, both documented and not, both now and forever. Were non-Microsoft filter drivers to add IOCTL codes of their own to whatever devices they filter, they would risk conflict among themselves and with Microsoft. They instead create at least one separate control device with a name that can reasonably be taken as unique to them so that the IOCTL codes supported by these control devices are theirs to define without risk of any conflict (that they need feel responsible for). Clearly, BitLocker started with the advantage of a simpler interface. One perspective to this is that simplicity is a natural benefit for everyone from building full-volume encryption into the operating system.

Another perspective is that the security of this extended interface is natively that of user-mode permissions for accessing the filtered volume as a file. Such access is needed by many processes for many purposes unrelated to BitLocker. If all software that can open the volume is not also to have access to BitLocker secrets, then FVEVOL in these early versions would have to work at being particular about who calls each BitLocker IOCTL. Since FVEVOL goes no further than distinguishing kernel-mode and user-mode callers, any user-mode process that can open a protected volume for read access when running on these versions can call FVEVOL to learn the VMK (for instance). Were the Device I/O Control interface instead supported only through a separate control device, the necessary permissions would be those for opening the control device, not the volume. The interface then could be restricted to just the SYSTEM account and a specific service account, such that no other process can even open the device, let alone try any IOCTL code. There actually are merits, even for Microsoft, to doing what the documentation recommends.

Control Device

For whatever reason, Windows 8 changed to having FVEVOL create a separate control device. Two of the original IOCTL codes continue to be responded to when sent to the filtered volume, another is added (and two more in later versions, but most of the original IOCTL codes and most new codes must be sent instead to the control device object (CDO).

CDO Paths

Having taken its time about implementing a Device I/O Control interface in more like the same way that non-Microsoft drivers must, Microsoft of course went to town with the technique. There is much elaboration. Though the CDO itself presents as \\.\BitLocker in user mode, this path introduces a multi-level namespace.

In effect, the BitLocker control device has a small file system. What a user-mode or kernel-mode caller sees of BitLocker’s I/O Control interface depends on which file the caller opened and each file has its own security. This file system runs three levels deep:



The CDO itself is \Device\BitLocker. It has very wide exposure: GENERIC_ALL for the SYSTEM account, unsurprisingly, but GENERIC_READ and GENERIC_WRITE for AuthorizedUsers. But it answers to only two IOCTL codes, just enough for navigating the CDO’s file system (see IOCTL code 0x4C4210CC). Moreover, these relatively broad permissions are only what the kernel applies before asking the device if it wants to be opened. The device itself is more fussy: if any write access is sought, even just FILE_WRITE_ATTRIBUTES, then the device declines to open for any user-mode process.

Volume Type

The voltype at the first level can be Unsupported, Volume or CsvVolume. The last follows such well-established abominations as PIN number, CSV standing for Cluster Shared Volume. The name voltype is here taken from the recurrence of VolType in the names of relevant internal routines as known from public symbol files for FVEVOL, but this level may alternatively be thought of by Microsoft as some sort of device type: public symbol files for the user-mode FVEAPI.DLL show that routines written in C++ take among their arguments an enumeration named FVE_DEVICE_TYPE (formally _FVE_DEVICE_TYPE) whose values 0, 1 and 2 select from Unsupported, Volume and CsvVolume, respectively, for composing a CDO path.

Volume and CsvVolume are for volumes whose “physical” device object is implemented by drivers named volmgr and csvbus, respectively. Unsupported is for all others.

The device gives the SYSTEM account and Administrators group all access to the files at this level. User-mode processes on other accounts are allowed only what access they have to the root, meaning in practice that they are granted read access but not write access. Again, this suffices for navigation.

Volume Number

The volnum is an unsigned decimal. It is a sequence number that FVEVOL itself assigns when attaching to the volume. Each of the three types of volume has its own sequence. Removing and reinserting a removable volume changes its volume number. For these reasons, the volnum in a path beneath BitLocker’s control device need not correspond directly to other numbering of volumes. The correspondence can instead be discovered by opening a volume and sending an IOCTL to learn the CDO path (see (0x455610D4).

For files at this level, the device allows only the same user-mode access as for the root. This means that none can open these files for write access. Again, read access suffices for navigation.


The longest possible CDO paths end with SEI, HEI or SYS. The first is what must be opened for the vast majority of BitLocker interaction through the Device I/O Control interface. The HEI file responds to fewer IOCTL codes but in variant implementations that are specialised for hardware encryption. It is here guessed that SEI and HEI stand respectively for Software and Hardware Encryption Interface. The SYS file responds to only two IOCTL codes, but this is not as limited as it may sound: they take input that in turn selects from many non-trivial actions to perform.

Roughly speaking, user-mode callers are allowed to open SEI and HEI files with the same access that they have to the corresponding volume. At the other extreme, the SYS file can be opened only by the SYSTEM account, which is granted all access.

I/O Control Codes

Microsoft is not known to document any of these IOCTL codes that BitLocker either adds to the usual interface for storage volumes or implements through a control device. Microsoft’s names for the codes are presumably defined by macro and are not known to survive into any public symbol files or as any sort of text message, e.g., for debug output. It would not surprise if Microsoft’s names turned out to be IOCTL_FVE_GET_DATASET, etc., but it seems safer not to guess. For the summary tables below, a more or less plain-language Description of each IOCTL code is instead adapted as directly as seems helpful from the names of corresponding internal routines as known from matching public symbol files to the binaries. For instance and most straightforwardly, for the IOCTL code that is handled by a routine named IoctlFveGetDataset, the summary Description is naturally “get dataset”. One reason for further adaptation is that the straightforward extraction is not grammatical (to a native English speaker), as for the internal routine named IoctlFveUnbindAllDataVolume. Another is that the same routine handles more than one IOCTL code, as with IoctlFveGetKey, and then the further adaptation is of “get key” to tell of which key.

The tables below do not list the IOCTL codes in increasing order of their whole numerical values. It is well documented that the Windows kernel interprets IOCTL codes in four fields. From most significant to least, numerically, these are a 16-bit device type, two access bits, a 12-bit function number and two bits for the buffering method. The table instead lists IOCTL codes in increasing order just of the function number. Note especially that some IOCTL codes change their access bits between versions.

I/O Control Code Description Applicability Versions
0x45565083 (6.0);
get dataset filtered volume 6.0 to 6.1
6.2 and higher
0x4556D087 set dataset filtered volume 6.0 to 6.1
6.2 and higher
0x45561088 get status filtered volume 6.0 to 6.1
SEI 6.2 and higher
0x4556508F get FVEK filtered volume 6.0 to 6.1
SEI 6.2 and higher
0x45565093 get VMK filtered volume 6.0 to 6.1
SEI 6.2 and higher
0x4556D097 (6.0);
provide VMK filtered volume 6.0 to 6.1
6.2 and higher
0x4556D0A0 perform action filtered volume 6.0 to 6.1
6.2 and higher
0x4556D0A7 bind data volume filtered volume 6.0 to 6.1
SEI 6.2 and higher
0x4556D0AB unbind data volume filtered volume 6.0 to 6.1
SEI 6.2 and higher
0x4556D0AC (6.0);
0x455650AC (6.1);
verify bind data volume filtered volume 6.0 to 6.1
SEI 6.2 and higher
0x4556D0B0 (6.0);
0x455650B0 (6.1 to 1607);
check unlock info for data volume filtered volume 6.0 to 6.1
SEI 6.2 and higher
0x4556D0B7 unbind all data volumes filtered volume 6.0 to 6.1
SEI 6.2 and higher
0x4556D0BB prepare hibernate filtered volume 6.0 to 6.1
0x4556D0BF cancel hibernate filtered volume 6.0 to 6.1
0x4556D0C3 register context filtered volume 6.0 and higher
0x4556D0C7 unregister context filtered volume 6.0 and higher
0x455610C8 perform action filtered volume 6.1 only
6.2 and higher
0x4C4210CC query child information list all CDO files 6.2 and higher
0x4C4290D0 get namespace reference all CDO files 6.2 and higher
0x455610D4 get CDO path filtered volume 6.2 and higher
0x455610D8 lock driver filtered volume 10.0 and higher
0x455610DC unlock driver filtered volume 10.0 and higher

The two IOCTL codes between 0x455610C8 and 0x455610D4 change their device type, i.e., the high word, to 0x4C42 (presumably for the B and L of BitLocker). They are answered for all files in the CDO’s file system, including the CDO itself.

The FT Series

For Windows 8.1, Microsoft gave FVEVOL a substantial facility for self-testing (or had it all the while but now chose to leave it in the released builds). This testing is configurable through registry settings and is driven by new IOCTL codes. Their function numbers are disjoint from the others and the device type in the high word differs too. It is not known if this device type is specific to BitLocker’s feature tests.

I/O Control Code Description Applicability Versions
0x5446D804 test RW start SEI 6.3 and higher
0x5446D808 test RW query SEI 6.3 and higher
0x5446D80C test RW stop SEI 6.3 and higher
0x5446D810 test ICE SEI 1709 and higher

Some might want that ICE, from the name FveTestIce of the internal routine that handles the IOCTL 0x5446D810, stands for the encryption algorithm named Information Concealment Engine. There seems instead to be no relationship. What is known is that the internal routine FveCheckIceSupport (in 1703 and higher) queries the storage class driver for its cryptography capability. This is done through IOCTL_STORAGE_QUERY_PROPERTY, asking for the StorageAdapterCryptoProperty. Microsoft had squeezed this property into the STORAGE_PROPERTY_ID enumeration for the 1703 release (shifting the numerical values of three older properties). What FVEVOL seeks is support for either algorithm StorageCryptoAlgorithmXTSAES or StorageCryptoAlgorithmBitlockerAESCBC. BitLocker’s use of (and simulation of) hardware cryptography is plausibly worth detailed study.