210 likes | 466 Views
Avoiding Driver Security Pitfalls . Matt Miller Senior Software Security Engineer SWI Security Science mamill@microsoft.com. Agenda. Introduction Why driver security is important Principles of driver security Common driver security pitfalls & how to avoid them
E N D
Avoiding Driver Security Pitfalls Matt Miller Senior Software Security Engineer SWI Security Science mamill@microsoft.com
Agenda • Introduction • Why driver security is important • Principles of driver security • Common driver security pitfalls & how to avoid them • Interacting with user-mode data • Object security • Device security • Additional tips for helping to secure your drivers
Why Driver Security Is Important • Device drivers play a critical role in keeping the kernel secure • Kernel mode drivers share the keys to the castle • Driver flaws can allow the entire system to be compromised • Drivers are becoming an increased target of public security scrutiny • Wireless drivers • Video drivers • We must work together to keep customers (and the kernel) secure
Principles of Driver Security • Uphold established security guarantees • Separation of kernel and user • Isolation of processes • Enforcement of granted privileges and rights • It pays to be paranoid • Do not trust any user-mode data, network data, file data, etc. • Assumptions are the root of all evil • Do not assume security is provided elsewhere • Do not assume security checks will kill performance
Checking Buffer Lengths case IOCTL_SET_DATA: // METHOD_BUFFERED IOCTL PUCHAR InBuffer = Irp->AssociatedIrp.SystemBuffer; PUCHAR OutBuffer = Irp->AssociatedIrp.SystemBuffer; RtlCopyMemory(GlobalData, InBuffer, Stack->Param…InputBufferLength); RtlCopyMemory(OutBuffer, &GlobalDataStats, sizeof(GlobalDataStats)); case IOCTL_SET_DATA: // METHOD_BUFFERED IOCTL PUCHAR InBuffer = Irp->AssociatedIrp.SystemBuffer; PUCHAR OutBuffer = Irp->AssociatedIrp.SystemBuffer; if (Stack->Param…InputBufferLength > sizeof(GlobalData)) break; if (Stack->Param…OutputBufferLength != sizeof(GlobalDataStats)) break; RtlCopyMemory(GlobalData, InBuffer, Stack->Param…InputBufferLength); RtlCopyMemory(OutBuffer, &GlobalDataStats, sizeof(GlobalDataStats)); • Pitfall: Failing to check the length of buffers provided by user mode • User mode can pass buffers that are smaller or larger than you expect • Can lead to buffer overflows or kernel data leakage • Solution: Always check the lengths associated with untrusted buffers • WdfRequestRetrieveXxxBuffer takes MinimumRequiredSize
Integer Wraps When Using Untrusted Values case IOCTL_SET_NAME: PNAMEBUF Buf = Irp->AssociatedIrp.SystemBuffer; ULONG BufLen = Stack->Parameters.DeviceIoControl.InputBufferLength; if ((BufLen != sizeof(NAMEBUF)) || (Buf->NameLength + sizeof(CHAR) > 256)) break; RtlCopyMemory(GlobalName, Buf->Name, Buf->NameLength); case IOCTL_SET_NAME: // METHOD_BUFFERED IOCTL PNAMEBUF Buf = Irp->AssociatedIrp.SystemBuffer; ULONG BufLen = Stack->Parameters.DeviceIoControl.InputBufferLength; if (BufLen != sizeof(NAMEBUF)) break; Status = RtlULongAdd(Buf->NameLength, sizeof(CHAR), &Length); if (!NT_SUCCESS(Status) || Length > 256 ) break; RtlCopyMemory(GlobalName, Buf->Name, Buf->NameLength); typedefstruct { ULONG NameLength; CHAR Name[256]; } *PNAMEBUF; • Pitfall: Using untrusted integers can lead to integer wraps • Conditionals with integer wrapping problems can lead to overflows • Passing wrapped integers to allocation routines causes small allocations • Solution: Use safe integer manipulation routines to catch wraps • Routines can be found in ntintsafe.h (RtlULongAdd, RtlULongMult, …) NameLength = -1 -1 + 1 = 0 < 256
Probing User-Mode Buffers case IOCTL_SET_DATA: // METHOD_NEITHER IOCTL PUCHAR InBuffer = Stack->Parameters.DeviceIoControl.Type3InputBuffer; PUCHAR OutBuffer = Irp->UserBuffer; … // Buffer sizes are checked try { ProbeForRead(InBuffer, Stack->Param…InputBufferLength, 1); ProbeForWrite(OutBuffer, Stack->Param…OutputBufferLength, 1); } except(EXCEPTION_EXECUTE_HANDLER) { break; } RtlCopyMemory(OutBuffer, GlobalData, Stack->Param…OutputBufferLength); RtlCopyMemory(GlobalData, InBuffer, Stack->Param…InputBufferLength); case IOCTL_SET_DATA: // METHOD_NEITHER IOCTL PUCHAR InBuffer = Stack->Parameters.DeviceIoControl.Type3InputBuffer; PUCHAR OutBuffer = Irp->UserBuffer; … // Buffer sizes are checked RtlCopyMemory(OutBuffer, GlobalData, Stack->Param…OutputBufferLength); RtlCopyMemory(GlobalData, InBuffer, Stack->Param…InputBufferLength); • Pitfall: Not ensuring that user-mode pointers are valid • User-mode pointers can come from many places (IOCTLs, FSCTLs, embedded in structures, etc.) • Can allow kernel memory to be indirectly referenced from user mode • Solution: Always probe user-mode pointers • Probing must occur inside a try/except block!
Accessing User-Mode Buffers case IOCTL_SET_DATA: // METHOD_NEITHER IOCTL PUCHAR InBuffer = Stack->Parameters.DeviceIoControl.Type3InputBuffer; PUCHAR OutBuffer = Irp->UserBuffer; … // Buffer sizes are checked try { ProbeForRead(InBuffer, Stack->Param…InputBufferLength, 1); ProbeForWrite(OutBuffer, Stack->Param…OutputBufferLength, 1); } except(EXCEPTION_EXECUTE_HANDLER) { break; } RtlCopyMemory(OutBuffer, GlobalData, Stack->Param…OutputBufferLength); RtlCopyMemory(GlobalData, InBuffer, Stack->Param…InputBufferLength); case IOCTL_SET_DATA: // METHOD_NEITHER IOCTL PUCHAR InBuffer = Stack->Parameters.DeviceIoControl.Type3InputBuffer; PUCHAR OutBuffer = Irp->UserBuffer; … // Buffer sizes are checked try { ProbeForRead(InBuffer, Stack->Param…InputBufferLength, 1); ProbeForWrite(OutBuffer, Stack->Param…OutputBufferLength, 1); RtlCopyMemory(OutBuffer, GlobalData, Stack->Param…OutputBufferLength); RtlCopyMemory(GlobalData, InBuffer, Stack->Param…InputBufferLength); } except(EXCEPTION_EXECUTE_HANDLER) { break; } • Pitfall: Accessing user-mode memory in a non-recoverable fashion • A fault can occur at any time (e.g. another thread could unmap memory) • Solution: Always wrap user-mode buffer access in try/except • Any fault that occurs can be handled and an error returned
Capturing User Mode Buffers case IOCTL_SET_NAME: // METHOD_NEITHER IOCTL PNAMEBUF UserBuf = Stack->Parameters…Type3InputBuffer; ULONG UserBufLen = Stack->Parameters…InputBufferLength; try { ProbeForRead(UserBuf, UserBufLen, 1); if ((UserBufLen != sizeof(NAMEBUF)) || (UserBuf->NameLength > 256)) break; RtlCopyMemory(Name, UserBuf->Name, UserBuf->NameLength); } except(EXCEPTION_EXECUTE_HANDLER) { break; } case IOCTL_SET_NAME: // METHOD_NEITHER IOCTL NAMEBUF CapturedBuf; volatile NAMEBUF* UserBuf = Stack->Parameters…Type3InputBuffer; ULONG UserBufLen = Stack->Parameters…InputBufferLength; try { ProbeForRead(UserBuf, UserBufLen, 1); if (UserBufLen != sizeof(NAMEBUF)) break; CapturedBuf = *UserBuf; if (CapturedBuf.NameLength > 256)) break; RtlCopyMemory(Name, CapturedBuf.Name, CapturedBuf.NameLength); } except(EXCEPTION_EXECUTE_HANDLER) { break; } typedefstruct { ULONG NameLength; CHAR Name[256]; } NAMEBUF, *PNAMEBUF; • Pitfall: Failing to capture data from user memory • The contents and layout of user-mode memory may change asynchronously • Leads to time-of-check, time-of-use issues • Solution: Always capture user-mode data prior to checking or using it • User-mode memory changes do not affect captured buffer • Be aware of the need for the volatile keyword! NameLength could change after this check
Accessing Pointers within User Mode Buffers case IOCTL_SET_NAME: // METHOD_NEITHER IOCTL NAMEBUF CapturedBuf; volatile NAMEBUF* UserBuf = Stack->Parameters…Type3InputBuffer; ULONG UserBufLen = Stack->Parameters…InputBufferLength; try { ProbeForRead(UserBuf, UserBufLen, 1); if (UserBufLen != sizeof(NAMEBUF)) break; CapturedBuf = *UserBuf; if (CapturedBuf.NameLength > 256)) break; RtlCopyMemory(Name, CapturedBuf.Name, CapturedBuf.NameLength); } except(EXCEPTION_EXECUTE_HANDLER) { break; } case IOCTL_SET_NAME: // METHOD_NEITHER IOCTL NAMEBUF CapturedBuf; volatile NAMEBUF* UserBuf = Stack->Parameters…Type3InputBuffer; ULONG UserBufLen = Stack->Parameters…InputBufferLength; try { ProbeForRead(UserBuf, UserBufLen, 1); if (UserBufLen != sizeof(NAMEBUF)) break; CapturedBuf = *UserBuf; if (CapturedBuf.NameLength > 256)) break; ProbeForRead(CapturedBuf.Name, CapturedBuf.NameLength, 1); RtlCopyMemory(Name, CapturedBuf.Name, CapturedBuf.NameLength); } except(EXCEPTION_EXECUTE_HANDLER) { break; } typedefstruct { ULONG NameLength; PCHAR Name; } NAMEBUF, *PNAMEBUF; case IOCTL_SET_NAME: // METHOD_NEITHER IOCTL PNAMEBUF UserBuf = Stack->Parameters…Type3InputBuffer; ULONG UserBufLen = Stack->Parameters…InputBufferLength; try { ProbeForRead(UserBuf, UserBufLen, 1); if ((UserBufLen != sizeof(NAMEBUF)) || (UserBuf->NameLength > 256)) break; RtlCopyMemory(Name, UserBuf->Name, UserBuf->NameLength); } except(EXCEPTION_EXECUTE_HANDLER) { break; } • Pitfall: Accessing pointers contained within user memory unsafely • Pointer fields in structures obtained from user mode cannot be trusted • Could be invalid or kernel-mode addresses as mentioned previously • Solution: Follow access rules for all pointers obtained from user mode • Probe, check lengths, capture containing buffer (if needed), probe field, then access
Getting the Object Associated with a Handle case IOCTL_UPDATE_FILE: // METHOD_BUFFER IOCTL … FileHandle = *(PHANDLE)Irp->AssociatedIrp.SystemBuffer; Status = ObReferenceObjectByHandle( FileHandle, // Handle FILE_READ_DATA, // Desired access NULL, // Object type KernelMode, // Access mode &FileObject, // Object pointer NULL); // Object handle information case IOCTL_UPDATE_FILE: // METHOD_BUFFER IOCTL … FileHandle = *(PHANDLE)Irp->AssociatedIrp.SystemBuffer; Status = ObReferenceObjectByHandle( FileHandle, // Handle FILE_READ_DATA, // Desired access *IoFileObjectType, // Object type Irp->RequestorMode, // Access mode &FileObject, // Object pointer NULL); // Object handle information FileHandle could be associated with a non-file object FileHandle could be a kernel handle Caller may not be granted desired rights • Pitfall: Passing incorrect parameters when getting a handle’s object • A NULL ObjectType disables object type validation • Passing KernelMode as the AccessMode disables security access checks • Solution: Always pass a valid ObjectType and correct AccessMode
Creating a Handle to an Object OBJECT_ATTRIBUTES Attributes; InitializeObjectAttributes( &Attributes, NULL, OBJ_KERNEL_HANDLE, NULL, NULL); Status = ZwCreateSection( &SectionHandle, SECTION_ALL_ACCESS, &Attributes, …); OBJECT_ATTRIBUTES Attributes; InitializeObjectAttributes( &Attributes, NULL, 0, NULL, NULL); Status = ZwCreateSection( &SectionHandle, SECTION_ALL_ACCESS, &Attributes, …); • Pitfall: Creating a non-kernel handle to an object • Allows the calling user-mode process to modify the object associated with the handle • User mode can close the handle and open a new handle to a different resource • Solution: Use OBJ_KERNEL_HANDLE when creating a handle to an object • Includes ObOpenObjectByPointer, ZwCreateFile, ZwCreateSection, etc
Device Object Creation Status = IoCreateDevice(DriverObject, // Driver object 0, // Device extension &DeviceName, // Device name FILE_DEVICE_UNKNOWN, // Device type0, // Device characteristics FALSE, // Exclusive &NewDeviceObject); // Device object Status = IoCreateDevice(DriverObject, // Driver object 0, // Device extension &DeviceName, // Device name FILE_DEVICE_UNKNOWN, // Device typeFILE_DEVICE_SECURE_OPEN, // Device characteristics FALSE, // Exclusive &NewDeviceObject); // Device object • Pitfall: Inadvertently disabling device namespace open access checks • ACLs checked when opening \Device\Foobut not \Device\Foo\Bar • Allows device object to be opened by nonprivileged callers • Solution: Pass FILE_DEVICE_SECURE_OPEN to IoCreateDevice • WdfDeviceCreate automatically sets this characteristic
Tools and Tips for Improving Driver Security • Use Driver Verifier • Contains a handful of checks capable of detecting security issues • Test teams should “fuzz” driver interfaces • Write tools to provide intentionally bogus input to the driver • Targets for fuzzing include IOCTLs, network I/O, file I/O, etc • Peer or industry review of driver code • Play devil’s advocate!
Driver Security Recap • Interacting with user-mode data • Encapsulate: Always encapsulate access to user-mode memory in try/except • Probe: Always probe pointers obtained from user mode • Capture: Always capture user-mode memory that is accessed multiple times • Validate: Always check the lengths of user-mode buffers before accessing • Object security • Use correct object type and access mode when getting a handle’s object • Use OBJ_KERNEL_HANDLE when creating handles • Device security • Use FILE_DEVICE_SECURE_OPEN when creating a device
Call to Action • Apply what you’ve learned • Review your driver code for the security issues discussed • Be security conscious while developing your drivers • Challenge security assumptions • Limit your exposure to untrusted data (reduce attack surface) • Only you can prevent driver security issues!
Resources • Driver security information on WHDC Web Sitehttp://www.microsoft.com/whdc/driver/security/default.mspx • Windows Security Model: What Every Driver Writer Needs to Knowhttp://go.microsoft.com/fwlink/?LinkID=56770 • Common Driver Reliability Issueshttp://go.microsoft.com/fwlink/?LinkID=56774 • Driver verifier • Driver verifier on WHDC Web sitehttp://www.microsoft.com/whdc/DevTools/tools/DrvVerifier.mspx • DDC 2008: Driver Verifier Advancements in Windows 7Mon. 5:15-6:15 and Wed. 1:30-2:30