Geoff Chappell, Software Analyst
The RtlConvertSidToUnicodeString function takes a SID in binary form as input and creates a string representation as output, optionally in new memory.
NTSTATUS RtlConvertSidToUnicodeString ( PUNICODE_STRING UnicodeString, PSID Sid, BOOLEAN AllocateDestinationString);
The UnicodeString argument is the address of a structure that may on input describe a buffer in which to create the string and will on successful output describe the created string. Whether the buffer is provided as input or is only produced as output depends on the AllocateDestinationString argument.
The Sid argument provides the address of the SID for which a string form is sought.
The AllocateDestinationString argument is FALSE if memory that is to receive the string is already described by UnicodeString. The MaximumLength and Buffer members are respectively the size in bytes and the address. The function does not change them. If AllocateDestinationString is not FALSE, the function obtains memory for the output, and sets MaximumLength and Buffer to the size and address of this memory. Either way, the Length on output is the size of the SID in string form, not including a terminating null.
The function returns STATUS_SUCCESS if successful, else a negative error code.
The RtlConvertSidToUnicodeString function is exported by name from both the kernel and NTDLL in all Windows versions, i.e., in 3.10 and higher. It provides the low-level support for the documented high-level API function ConvertSidToStringSid, which ADVAPI32 exports by name in both ANSI and Unicode forms in versions 5.0 and higher.
The RtlConvertSidToUnicodeString function is documented but has not always been. User-mode documentation came first, apparently in 2002 for Microsoft’s anti-trust settlement. Perhaps because its functionality was not from the start exposed through a higher-level API function, it was used by the Internet Explorer programmers. So were many other low-level functions, of course, and not all got documented for the settlement. The connection here, however, seems clear: to this day the C-language declaration of RtlConvertSidToUnicodeString in the Software Development Kit (SDK) is in WINTERNL.H, which came directly from the settlement.
Documentation of RtlConvertSidToUnicodeString for kernel-mode use is not known before the Windows Driver Kit (WDK) for Windows 7. It is then said to be “Available in Windows 2000 and later Windows operating systems.” It is declared in NTIFS.H, suggesting strongly that any earlier disclosure will have been limited to the Installable File System (IFS) Kit, but it is not in that kit’s inclusion with the WDK for Windows Vista. The declaration also requires Windows 2000 or higher as the target operating system.
If whatever’s addressed at Sid is not valid as a SID or if the RevisionNumber is not SID_REVISION (1), the function returns STATUS_INVALID_SID.
Note that a valid SID has no more than 15 subauthorities. This limits how much output the function can generate: in modern versions, the output cannot exceed 184 characters, including a terminating null. At least to the 1803 release of Windows 10, the function prepares the string representation in a 256-character buffer on the stack. Some kernel-mode programmers would regard this as excessive. A comment in NTIFS.H, just in advance of defining MAX_UNICODE_STACK_BUFFER_LENGTH as 256, even suggests that the caller may want that the output also is on the stack—and there goes a kilobyte!
This string representation has several pieces to it:
The authority identifier is formally an array of six bytes, but ending with the least significant. If the first two bytes are zero, the least significant four are represented as an unsigned decimal. Otherwise, though rare in practice, all six bytes are represented in hexadecimal, with a C-language “0x” prefix and no leading zeroes.
If AllocateDestinationString argument is FALSE, the function copies its output to the caller-supplied memory described by the UnicodeString argument, i.e., MaximumLength bytes at Buffer. If the composed string with its terminating null is too large to fit, the function returns STATUS_BUFFER_OVERFLOW.
If instead AllocateDestinationString is not FALSE, the function copies its output to new memory. This is done through RtlCreateUnicodeString and so the new memory is sought from the paged pool (with tag 'GrtS') or the process heap in kernel and user modes respectively. If the functon cannot get this memory, it returns STATUS_NO_MEMORY.
The requirement of 1 for the RevisionNumber dates from version 5.0. In earlier versions, the 1 in “S-1-” is the RevisionNumber as an unsigned decimal. These versions prepare the string representation in ANSI but use no less stack, since they have two 256-byte buffers, one in which to prepare each component in its turn and another to which successive components get appended. This result is then converted to Unicode as the function’s output.
A change to RtlValidSid for version 6.1 means that the validity check in kernel mode nowadays includes that Sid must not be in user-mode address space. This is not even hinted at in the documentation. Any SID from user mode should, of course, be captured to kernel-mode address space before trying to form a string representation.
The kernel-mode implementation is in paged memory and must not be called at DISPATCH_LEVEL or higher. Microsoft’s documentation explicitly permits APC_LEVEL, but beware that it is just as explicit that the called function RtlCreateUnicodeString is restricted to PASSIVE_LEVEL. Both cannot be correct!