Geoff Chappell, Software Analyst
The LdrQueryImageFileKeyOption function queries a value from among the Image File Execution Options in a given registry key.
NTSTATUS LdrQueryImageFileKeyOption ( HANDLE hKey, PCWSTR lpszOption, ULONG dwType, PVOID lpData, ULONG cbData, ULONG *lpcbData);
The hKey argument is a handle to a registry key that contains Image File Execution Options.
The lpszOption argument names the one option whose value is sought.
The dwType argument specifies the type for the value as produced in the output buffer. This is typically also, but need not be, the required type for the value as stored in the registry.
The optional lpData and cbData arguments are respectively the address and size of the output buffer that is to receive the value. These arguments can be NULL and zero to query for the option’s existence or size.
The optional lpcbData argument provides the address of a variable that is to receive the size, in bytes, of the value that the successful function has put into the buffer and which may receive the size that the failed function might have put into the buffer (had the buffer been large enough). This argument can be NULL if the caller does not want to know how much data is produced or is available.
The function returns STATUS_SUCCESS if successful, else a negative error code.
Of particular importance is STATUS_BUFFER_OVERFLOW, which is the function’s indication that the buffer, if any, is too small for the whole value and that a size that would have sufficed has been set into the variable, if any, that was specified through the lpcbData argument.
The LdrQueryImageFileKeyOption function is exported by name from NTDLL.DLL in version 5.2 starting from Windows Server 2003 SP1, and higher.
In version 6.0 and higher, the name LdrQueryImageFileKeyOption exists just for export. Were the function not exported, it would be an internal routine whose only name is RtlQueryImageFileKeyOption. As usual for Run Time Library (RTL) routines, the code is written for both kernel and user modes. The kernel has this routine since version 6.0 and exports it without the name change starting with the 1709 release of version 10.0.
The LdrQueryImageFileKeyOption function is not documented. Neither is it declared in any C-language header that Microsoft is known to have published in any development kit for either user-mode or kernel-mode software. While Microsoft’s names and types for the function’s arguments are not known, this article uses inventions.
This function’s introduction for Windows Server 2003 SP1 comes from NTDLL exporting separate functions (that had developed as internal routines in version 5.1) for opening the key and for querying its potentially numerous values. The LdrOpenImageFileOptionsKey function opens the key. This LdrQueryImageFileKeyOption function queries the values. The caller is expected to close the key when done. This separation leaves the ancient LdrQueryImageFileExecutionOptions and the relatively new LdrQueryImageFileExecutionOptionsEx as compounds which open the key, query one value and close the key.
The function trusts its caller. That there actually is valid memory at and beyond lpszOption up to and including some null character is just assumed. Similarly, it is just assumed that there are cbData bytes of writable memory at lpData. This is here taken to mean that the function can as well assume that lpData isn’t NULL unless cbData is zero. That lpcbData, if not NULL, addresses a writable dword is just assumed. Behaviour when these assumptions are not satisfied is the caller’s problem and is not the business of these notes.
The function’s essential task is to query the given option as a registry value in the given registry key. If the value’s name at lpszOption is too long for representation in a UNICODE_STRING, the function returns STATUS_NAME_TOO_LONG. Failure at querying the value for all its data, after allowing for retrying into sufficient memory, is failure for the function. The error code if new memory is needed but cannot be obtained is STATUS_NO_MEMORY.
Note that there is no rule that the hKey argument actually was obtained through the LdrOpenImageFileOptionsKey function. Yet the function plainly does not exist to query an arbitrary value from an arbitrary registry key. Instead, it enforces some expectations that are particular to Image File Execution Options. Notably, there are restrictions on the value’s type and there’s some capability for translating from one type to another.
Before version 5.0, when the function was not yet separated from LdrQueryImageFileExecutionOptions even as an internal routine, the only supported type for an Image File Execution Option as stored in the registry was REG_SZ but callers could ask for REG_DWORD, get a conversion, and perhaps never know (or care) that their dword of data was actually in the registry as a string. Though this behaviour is clearly deliberate, and the conversion is done still, the purpose may be lost to history. After all, Microsoft’s documentation in the late 90s never abounded with examples, let alone with explanation. It’s not unthinkable that the conversion from string to dword was intended less as a convenience for callers than as some small defence against setting options inadvertently or ignorantly: a dword option could usefully be set only by tools or users who knew to set it as a string.
Even for version 10.0, to query this function is to ask for the option if its type as a registry value is either dwType or REG_SZ. Since version 6.0, however, if an option is in the registry as any supported type other than REG_SZ, then it can be successfully queried only by asking for that exact type. In all versions, if the registry value is present as an unsupported type or if its type does not agree sufficiently well with the dwType argument, the function returns STATUS_OBJECT_TYPE_MISMATCH.
|Type Stored||Type Asked||Applicable Versions|
|REG_SZ||any||5.2 and higher|
|REG_BINARY||6.0 and higher|
|REG_DWORD||REG_DWORD||5.2 and higher|
|REG_MULTI_SZ||REG_MULTI_SZ||6.0 and higher|
|REG_QWORD||REG_QWORD||6.2 and higher|
If the registry value has the REG_DWORD or REG_QWORD type, then the caller must ask not only for this type but also for the right size. If cbData is not four or eight, respectively—or if the data from the registry is not exactly four or eight bytes—the function returns STATUS_INFO_LENGTH_MISMATCH.
If the registry value has the REG_SZ type but dwType is REG_DWORD, then the function parses the string as a 32-bit integer. Just as if the value had been in the registry as a dword, the caller must supply exactly four bytes for this output, i.e., cbData must be four, else the function returns STATUS_INFO_LENGTH_MISMATCH. Version 6.0 introduces a requirement that lpData be dword-aligned, else the function returns STATUS_DATATYPE_MISALIGNMENT.
The conversion from string to dword is done by RtlUnicodeStringToInteger with zero for its Base argument. Perhaps the most notable consequence in practice is that this allows C-language hexadecimal representation. Likely users of Image File Execution Options surely will find this easier than decimal for those options, such as the ancient GlobalFlag, whose values can be combined from bit flags. As a lesser consequence, string data that doesn’t parse to a dword is not an error but evaluates as zero.
Whatever other conversions may ever have been intended, translation of string data to dword output is all that ever got implemented. For all other types of data as stored in the registry and types as requested, if the value’s data is produced as output at all, it is produced as is.
In general, if an output buffer is not provided or if what’s provided is too small, i.e., if lpData is NULL or if the amount of data available from the registry exceeds cbData, then the function sets the amount available into the variable, if any, at lpcbData and returns STATUS_BUFFER_OVERFLOW. If, however, the value has the REG_SZ type and dwType is not REG_DWORD, the function ignores lpData when testing whether the output buffer is big enough to copy to. It is not clear whether this difference for this particular case is by design.
It cannot be stressed enough that except for the historical case of conversion from string to dword, data that has the REG_SZ type as stored in the registry is copied to the output buffer as is. This has two implications. One is that the data need not have the form that was sought. For instance, provide eight bytes to receive REG_QWORD data, and even if you are told that eight bytes are produced they may be just the raw bytes of a small Unicode string. The other implication is no mere nuisance for interpretation but a trap for the caller’s integrity, albeit a trap that affects most functions that query the registry for string data: the output from a successful query for REG_SZ (or REG_MULTI_SZ) does not necessarily end with a null nor even comprise whole Unicode characters.
Version 5.2 retains logic from when the function was an internal routine and even from when it was not yet separated from LdrQueryImageFileExecutionOptions.
Querying for the value is done by asking the NtQueryValueKey function for KeyValuePartialInformation, initially using 0x0400 bytes on the stack for the output buffer (including a KEY_VALUE_PARTIAL_INFORMATION at its start). The implementation has varied in its technique for assessing how much memory to ask for from the process heap when repeating a query that failed with STATUS_BUFFER_OVERFLOW as the error code. The technique that Microsoft has always documented, if only in the Device Driver Kit (DDK) for ZwQueryValueKey as a kernel export, is that the function’s last argument is the address of a variable that the function sets to the size that would have sufficed for the output buffer. Version 5.2 does not use this, however. It instead relies on the function to have set at least the DataLength member of the KEY_VALUE_PARTIAL_INFORMATION at the start of the output buffer when returning STATUS_BUFFER_OVERFLOW. This alternative technique is here thought to have been reliable all along—not just for setting DataLength but for filling the buffer with as much as fits—but no reason is known why Microsoft’s programmers ever used it rather than the documented technique.
If the registry value has the REG_SZ type and dwType is REG_DWORD, then version 5.2 ignores the last two bytes of the string data (presumably on the grounds that these bytes are ordinarily the terminating null character of a Unicode string).
If the registry value has the REG_DWORD type but cbData is not four or the data from the registry is not exactly four bytes, then where later versions return STATUS_INFO_LENGTH_MISMATCH, version 5.2 returns STATUS_BUFFER_OVERFLOW and indicates the data’s size (which may, confusingly, be less than cbData).
Version 5.2 can set the variable, if any, at lpcbData even when the returned error code is not STATUS_BUFFER_OVERFLOW.