## Thursday, March 3, 2011

### Duplicating User Mode Handles

Among the many new verifier checks in Win7 is a particular one about using user handles in kernel mode. I won't go into the details of why that is potentially bad and instead I'll focus on how to work around the issue. However I'd like to point to some documentation explaining the improvements in Driver Verifier in Win7 in general and this check in particular. There is a PPT "Driver Verifier Advancements In Windows 7" that is pretty good for a high-level view and there is also a more detailed document "Driver Verifier in Windows 7". The verifier bugcheck message in this case is this:

DRIVER_VERIFIER_DETECTED_VIOLATION (c4)
A device driver attempting to corrupt the system has been caught.  This is
because the driver was specified in the registry as being suspect (by the
administrator) and the kernel has enabled substantial checking of this driver.
If the driver attempts to corrupt the system, bugchecks 0xC4, 0xC1 and 0xA will
be among the most commonly seen crashes.
Arguments:
Arg1: 000000f6, Referencing user handle as KernelMode.
Arg2: xxxxxxxx, Handle value being referenced.
Arg3: xxxxxxxx, Address of the current process.
Arg4: xxxxxxxx, Address inside the driver that is performing the incorrect reference.

Before we go into more detail I'd also like to explain what I was trying to do in one case where I ran into this issue. I was writing a driver that had a user mode command line utility that was used to send commands to the driver. One of these commands required sending a user mode file to the driver so it could write logging information into that file. One approach could be to pass in the name of the file and use the driver to open the file, but this is not trivial for various reasons:

• How to get the file name? The user might call the command line utility with a relative path (like "foo.exe -file ..\bar.txt") and so I need to figure out either the full path or to send the current directory path to the driver (yuck!).
• Even using a full path wouldn't be enough because the drive letter might be different depending on the session the user is in. Besides, who knows what a path really points to ? Some followers of this blog might know how much I dislike file names and how I try to avoid them.
• The user might not actually have access to write to that file but the kernel would so I would have to impersonate the user before trying to create the file.
So anyway my decision was to open the file in user mode and then call the driver and tell it the handle to the file and let the driver figure out what the object is and how to use it. However, what I wanted was to use the ZwWriteFile API to write to the file and so I needed a handle to that object. Looking at the OB APIs it's easy to see that we could simply call ObReferenceObjectByHandle followed by ObOpenObjectByPointer to create the new kernel handle. This is what my code looked like:
        status = ObReferenceObjectByHandle( ioctlBuffer->UserHandle,
*IoFileObjectType,
UserMode,
&userFileObject,
NULL );

if (!NT_SUCCESS(status)) {

__leave;
}

ASSERT(FlagOn( userFileObject->Flags, FO_HANDLE_CREATED ) &&
!FlagOn( userFileObject->Flags, FO_CLEANUP_COMPLETE ));

status = ObOpenObjectByPointer( userFileObject,
OBJ_KERNEL_HANDLE,
NULL,
*IoFileObjectType,
KernelMode,
&kernelFileHandle);

This worked pretty well for local files but when I tried to open a file that was on a remote file system it failed to open the kernel handle with STATUS_ACCESS_DENIED . I spent some time tracing through the code and what I found was that ObOpenObjectByPointer in this case always ends up sending an IRP_MJ_QUERY_SECURITY request to the file system. Moreover, this request seemed to always ask for all the security information (which you can see if you disassemble nt!ObpGetObjectSecurity and look at how it sets the SecurityInformation; on my Win7 it's a "mov dword ptr [xxx],1Fh"):
#define OWNER_SECURITY_INFORMATION       (0x00000001L)
#define GROUP_SECURITY_INFORMATION       (0x00000002L)
#define DACL_SECURITY_INFORMATION        (0x00000004L)
#define SACL_SECURITY_INFORMATION        (0x00000008L)
#define LABEL_SECURITY_INFORMATION       (0x00000010L)

3: kd> dt 0xfffff9801a3d8fb8 nt!_IO_STACK_LOCATION Parameters.QuerySecurity.
+0x008 Parameters                :
+0x000 QuerySecurity             :
+0x000 SecurityInformation       : 0x1f
+0x008 Length                    : 0x100

However, while this works well on the local system, it almost always fails over SMB. Looking at the page "2.2.1.3 SECURITY_INFORMATION", there is this table that describes what the caller needs in order to be able to read various information types. For SACL_SECURITY_INFORMATION we see that in fact READ_CONTROL is not enough and that a certain privilege is required. This privilege is unlikely to be granted to any client of the server and so in the general case the IRP_MJ_QUERY_SECURITY issued by nt!ObpGetObjectSecurity will fail with STATUS_ACCESS_DENIED.

 Security information access requested Rights required of caller on server Privileges required of caller on server OWNER_SECURITY_INFORMATION READ_CONTROL Does not apply. GROUP_SECURITY_INFORMATION READ_CONTROL Does not apply. DACL_SECURITY_INFORMATION READ_CONTROL Does not apply. SACL_SECURITY_INFORMATION Does not apply. Security privilege.

So now since this approach was out of the picture, I needed something else. Unfortunately, I have been unable to figure out a documented way to achieve this (pretty much anything I tried called nt!ObpGetObjectSecurity at some point). However, there is one undocumented function that actually does exactly what I wanted, ZwDuplicateObject. So now my code looks something like this:
    //
// duplicate the handle... first get a handle to the system process
// so we can call ZwDuplicateObject on it.
//

status = ObOpenObjectByPointer( PsInitialSystemProcess,
OBJ_KERNEL_HANDLE,
NULL,
NULL,
KernelMode,
&systemProcessHandle );

if (!NT_SUCCESS(status)) {

return status;
}

status = ZwDuplicateObject( NtCurrentProcess(),
ioctlBuffer->UserHandle,
systemProcessHandle,
&kernelFileHandle,