Device Object Security | |
Copyright © 1998 Mark Russinovich | |
Last
Updated January 26, 1998
|
|
Introduction | The
Windows NT DDK has very little security-related information, documenting
only four functions (SeAccessCheck, SeAssignSecurity,
SeDeassignSecurity, and SeSinglePrivilegeCheck). Its
most glaring omission, however, is related to device objects. Nowhere in
the DDK does it state what default permissions are applied to device
objects upon their creation. The vast majority of device driver
developers assume that the "appropriate" permissions are
enabled, or don't realize that securing device objects is an issue at
all. When I integrated WinObj V2.0
with NT's security editors, a brief tour through the \device object
directory showed me that overlooking device object security could
inadvertently open security holes. The vast majority of device objects
created have the following permissions: Everyone:Read/Write (R/W) (the
Everyone built-in Security Identifier -SID - is also known as the World
SID), System:Full Access, and Administrator:Full Access. Only
Microsoft's file server and file redirector (LanManServer and
LanManRedirector) objects omit Everyone:Write and just allow
Everyone:Read, and physical disks are protected because the device
object directory in which they're located, \device\harddiskxxx, omit
Everyone:R/W. While it makes sense for some objects to have Everyone:R/W, allowing any account to read and write to most devices is not acceptable. Having R/W not only enables an account to perform ReadFile and WriteFile on devices, but execute any device-defined I/O control requests as well. For example, a disk controller that can be directly accessed by a malicious user or Guest could prove disastrous. The release of Microsoft Terminal Server (Hydra), which makes NT a multi-user system, heightens the risk of potential device security breaches. |
IoCreateDevice | Why
does Everyone:R/W show up on most device objects? Because IoCreateDevice,
the kernel-mode API used to create device objects, assigns the
permissions I list above as default permissions - its up to a device
driver to adjust the permissions if necessary, like SRV and RDR
obviously do. Unfortunately, the NT DDK does not document the
kernel-mode functions necessary to remove an Access Control Entry (ACE),
like the one that grants Everyone read and write access, from a device
object's Discretionary Access Control List (DACL). Microsoft has been
made aware of this deficiency, and will be creating and documenting new
kernel-mode APIs for NT 5.0 that will make it easy to create device
objects without weak protection. I'll present these new APIs here when
they are available. Fortunately, the mapping between Win32 security APIs and kernel-mode security APIs is fairly straight-forward. This has enabled me to implement a function, NTIRemoveWorldAce, for use in your own device drivers. This function and its support routines, demonstrated in the secsys sample driver, will fully secure your device objects. Its implementation also serves to show the use of kernel-mode security functions in kernel mode. |
SecDemo | The SecDemo program fully
demonstrates default object security and the use of NTIRemoveWorldAce.
The program consists of the SecDemo Win32 console program and the SecSys
device driver. To see the NTIRemoveWorldAce API remove
Everyone:R/W run SecDemo. It will load SecSys, which creates a
device object named "\device\secsys" using IoCreateDevice
. The program then pauses in order to give you an opportunity to view
its default permissions. To do so, start Winobj,
navigate to the \device directory, and double-click on "secsys".
Then go to the "Security" tab
in the Object Properties dialog box that appears and select "Permissions".
You'll see the following security information, which includes
Everyone:R/W: After you have seen IoCreateDevice's default permissions, press return to let Secdemo continue. It sends an I/O control to the secsys driver that invokes NTIRemoveWorldAce and pauses again to let you view \device\secsys's updated permissions. Close the secsys Object Properties dialog and double-click on secsys again. When you view its permissions at this point you'll see this: Now only members of the built-in Administrators group and the System (Win32 Services and the NT Kernel) will be able to read, write or send IOCTLs to the secsys device object. |
Inside NTIRemoveWorldAce | I've
thoroughly documented the implementation of NTIRemoveWorldAce,
so I'll only provide an overview here. My discussion assumes familiarity
with NT's security model and Win32 security APIs. NTIRemoveWorldAce is prototyped as follows: NTIRemoveWorldAce( PSECURITY_DESCRIPTOR OriginalDescriptor, PSECURITY_DESCRIPTOR *UpdatedDescriptor ); A device object's security descriptor is stored in DeviceObject->SecurityDescriptor, so secsys uses the API as follows: NTIRemoveWorldAce( Device->SecurityDescriptor, &Device->SecurityDescriptor ); NTIRemoveWorldAce first takes the device object's security descriptor, which is in self-relative form, and creates an equivalent security descriptor in absolute form so that the descriptor can be easily manipulated. The support function NTIMakeAbsoluteSD accomplishes this. NTIMakeAbsoluteSD makes use of the following kernel-mode security APIs to create and fill in the absolute security descriptor:
I've included prototypes to all these functions, as well as the others used in the sample, in secsys.h. Next to each prototype I list the functions' equivalent Win32 security API (e.g., RtlValidSecurityDescriptor's equivalent is IsValidSecurityDescriptor), so you can refer to the Win32 SDK's documentation for details on how to use them. NTIRemoveWorldAce obtains a pointer to the absolute descriptor using RtlGetDaclSecurityDescriptor and then initializes a Security Identifier (SID) that represents the built-in World account. The APIs called upon for this operation include:
Next, NTIRemoveWorldAce marches through the DACL's ACE's, searching for one that has the World SID in it. Win32 programs can use GetAce to retrieve a particular ACE from an ACL, but this function is not implemented in kernel-mode. I therefore call my own implementation of GetAce, NTIGetAce, to retrieve pointers to each ACE in the ACL. Using RtlEqualSid I test the SID in an ACE for a match against the World SID that I've initialized. When a match occurs I call NTIDeleteAce, which implements the functionality of the Win32 function DeleteAce. Like GetAce, there is no kernel-mode equivalent provided by NT for DeleteAce. The subsequent steps are to apply the updated DACL to the security descriptor with RtlSetDaclSecurityDescriptor, and to convert the security descriptor back to relative form via RtlAbsoluteToSelfRelativeSD. Finally, the passed in device object security descriptor is deallocated with ExFreePool, and the pointer to receive the new security descriptor (in the case of secsys, the device object's security descriptor pointer) is set to point to the new security descriptor. |
Closing Comments | So is it safe to use these APIs given
that they are undocumented? The answer is yes, at least through NT V5.0.
There are several reasons to feel safe when using them:
In this article and sample program I've only scratched the surface of kernel-mode security APIs and their uses. I'll describe other device driver uses for security, such as privilege checking, protecting private objects, and detecting user access, in an upcoming Dr. Dobb's Journal. The article will also provide a header file, ntsec.h, that I've compiled to include all kernel-mode security APIs, their prototypes, their Win32-equivalents, as well as typedefs and definitions required to use the APIs. |
Download SecDemo (44KB) Download SecDemo Plus Source (101KB) |