## Devctl – Device path exerciser

[This is preliminary documentation and subject to change.]

### SUMMARY

The Devctl program is designed to crash drivers by calling them through various user-mode I/O interfaces. It does not test the functionality but rather the robustness of drivers. Drivers should be resilient to bad data from user mode in exactly the same way that kernel entry points have to be. If they are not resilient then they open up denial of service attacks and in some cases a mechanism to bypass system security. This program identifies drivers that do not handle the following calls properly:

1. Unexpected entry points into the driver, such as file system query functions directed to a sound card
2. Query functions with buffers that are too small to contain all the data to be returned
3. IOCTL/FSCTL functions with missing buffers, buffers that are too small, or buffers that contain meaningless information
4. IOCTL/FSCTL with direct I/O or type3 buffers with data changing asynchronously
5. IOCTL/FSCTL with bad pointers for type3 requests
6. IOCTL/FSCTL and fast path query functions where the user buffer mapping may change asynchronously, causing pages to become unreadable at arbitrary execution points
7. Relative opens with strange file names or opens to strange device objects like the direct device open or file system devices
8. Issues requests both synchronously and asynchronously to the device

Devctl checks to make sure that the above calls are handled properly, and do not result in any of the following events:

1. System crash occurred.
2. System memory pools are corrupted.
3. Memory has leaked.
4. IRPs are not completed correctly.

While Devctl runs, it writes lines to the file crashn.log. After each line is written, this log file is flushed. As a result, if a crash occurs, the offending operation is easy to determine even if the system proves difficult to debug. The flush operation is expensive so in some places only one portion of a block of operations is logged. For example, only logs for IOCTL for a particular function value are logged rather than each IOCTL. When the Devctl program is restarted, it takes the last line from crashn.log and places it in the file crash.log. The Devctl program assumes the operation in line to be a failed operation. Operations within crash.log are not performed again, by default.

After each operation is performed, the program performs a check on consumed system memory. Devctl queries the pool tag database and look-aside information. If this operation caused either one of these memory sources to be depleted, the operation is repeated. Note that this increase in memory consumption may have had nothing to do with the request that was just performed. Typically, the second call does not see an increase and the program continues. Real memory leaks arise as a vast number of repeated calls and the tag in question raised to the top of the poolmon display sorted by difference (d).

For calls that would typically be buffered I/O operations, the program tracks the number of exceptions the operating system has dispatched. If the number of exceptions increases, the operation is also repeated. Although buffered I/O operations may also reference the user's address space, this is rare. Note that exception dispatching may signal an internal error that is being handled by an exception handler. Such a situation may hide a true parameter or handle validation problem. Devctl can run with the bottom hardware page mapped so that NULL pointer dereferences do not raise exceptions but rather return meaningless information.

Candidates for pool leaks and exceptions are logged to a file, diags.txt, which may be useful in the event that the leak does not crash the system or the many repeated calls are missed by the user. The program's basic execution passes are:

1. Issue synchronous, asynchronous, direct, and relative opens, using various filenames. These catch drivers are unaware of the relative open semantics and the fact that they have to perform their own security checks. This also shows us drivers that may have word integer overflow problems manipulating file names.
2. Issue queries for possible items the driver might understand.
3. Issue miscellaneous functions with file handles like: flush, create section, read, and write (for asynchronous handles only). Reads and writes for obscure file offsets are performed. These include: append writes where the file offset is a 2^64-1, and reads and writes where the offset is an unsigned 63-bit quantity that wraps to a signed 64-bit value when the length is added on. These catch read and write paths that do not recognize the full semantics of file offsets or have overflow problems in their validation.
4. Issue miscellaneous handleless functions such as NtDeletefile, NtQueryFullAttributesFile, and so on. These functions exercise some of the fast open paths used for network queries and so on.
5. Issue an IOCTL and FSCTL pass with zero length input and output buffers. This pass detects the most common driver error of not checking buffer length properly.
6. Issue random IOCTL and FSCTL calls with random size buffers. For buffered I/O requests, the buffers are valid but contain random data. For direct I/O requests, the buffers are valid, contain random data that changes asynchronously with this threads execution, and are butted to the ends of H/W pages to make it more likely for references beyond buffer end to fail.
7. For type 3 buffers, the pointers are random.

After IOCTL passes, Devctl issues a cancel request to the driver as part of a "miscellaneous function calls pass". IRPs that were lost (never completed by calling IoCompleteRequest or passed to other drivers but finished by the device) will cause the process to hang. Some execution paths may lead to dialog boxes appearing from the I/O manager warning that IRPs did not cancel within an allotted time. These lost IRPs are easily debugged by issuing "!process 0 0" from the debugger. Select the Devctl process via a "!process " where xxx is the Client ID (CID). The IRP (possibly more that one) will show up queued to one of the Devctl's threads. Use "‘!IRP " where xxx is the IRP address to determine what IRP was lost. Occasionally IOCTL or FSCTL requests will pass parameter validation by a driver and will be pending for a synchronous request (for instance, some type of notify request to a driver). Pressing CTRL+C will cause the program to terminate in this case, but not in a lost IRP case.

The basic syntax for the program is:

devctl [/i] [/l] [/il nn] [/iu mm] [devnam]
/and + enable options, - disables options
/a
Examine all devices in system. Don't prompt for yes/no.
/al Alert the main thread periodically.
/c
Enable or disable skipping operations that aborted or crashed.
/dd Enable or disable the direct device open paths.
/dl nn
Sets maximum limit for device type portion of IOCTL/FSCTL code, default zero.
/du nn
Sets minimum limit for device type portion of IOCTL/FSCTL code, default 200.
/e
Enable or disable zero length EA's, needed on checked builds.
/f
Enable or disable all FSCTL paths.
/fi
Enable or disable turning on failure injection in the driver verifier.
/fn
Enable or disable FSCTL paths with null buffers.
/fr
Enable or disable FSCTL paths with random buffers.
/fl nn
Sets maximum limit for function portion of IOCTL and FSCTL code, default zero.
/fu nn
Sets minimum limit for function portion of IOCTL and FSCTL code, default 200.
/g c h
Grabs a handle from another process.
/h /?
Prints this message.
/i
Enable or disable all IOCTL paths.
/if
Enable or disable all FSCTL and IOCTL paths.
/in
Enable or disable IOCTL paths with null buffers.
/il nnn
Set lower input buffer size.
/iu nnn
Set upper input buffer size.
/im
Enable or disable the impersonation of a non-admin during the test.
/ir
Enable or disable IOCTL paths with random buffers.
/j
Enable or disable relative stream opens for file systems.
/k
Enable or disable synchronous handles.
/l
Enable or disable logging and skipping failing functions.
/m
Enable or disable miscellaneous functions.
/n
Map zero page so that NULL pointer dereferences do not raise.
/ol nnn
Set lower output buffer size.
/ou nnn
Set upper output buffer size.
/p
Enable or disable the checks on pool usage through tags and look-aside lists.
/pd
Print out device objects and symbolic links and exit.
/pr
Enable or disable protection change tests.
/ps sss
Set prefix string for use with /pd.
/q
Enable or disable the normal handle query functions.
/r
Enable or disable skipping operations that are already logged as done.
/rd
Select a random device object or symbolic link for testing.
/s
Enable or disable the sub or relative opens to obtain handles.
/sd
Enable or disable the query and set security functions.
/sl
Enable or disable the opening of symbolic links.
/se nnn
Set session ID to "nnn."
/t nn
Set maximum limit for IOCTL/FSCTL calls made with random buffers, default 100000.
/tt nn
Set maximum limit for tailored calls made for discovered IOCTLs/FSCTLs, default 10000.
/v
Enable or disable the printing of error status values for calls.
/w
Enable or disable the Winsock TransmitFile test.
/y
Enable or disable touching disk devices.
Defaults: devctl -a -al +c +dd +dl 0 +du 200 +e +fn +fr +fl 0 fu 200 +im +il 0 +in +iu 512 +ir -j -k +l +m -n +ol 0 +ou 512 +p -pr +q +s +sd +sl +t 100000 +tt 10000 -v -w +y
Devnam is the device to open to issue requests. It must be in native object tree format like ’\device\null’. If this is omitted the program prompts for each device in turn. You can skip to a particular device by typing ‘/’ where prefix is the first few characters of the device you want to match. Here is a typical run:

E:\devctl>obj\i386\devctl \device\null
Listen socket on port 1192 address 172.31.236.194
Trying to open device \device\null synchronous
Opened crashn.log for reading
Lookaside: PooL, size 128 up 2
Pool: Mdl , Paged up 0, NonPaged up 128
\device\null Open synchronous
Opened file \device\null with access 1f03ff
Pool: File, Paged up 0, NonPaged up 192
Pool: CcBc, Paged up 0, NonPaged up 160
\device\null NtQueryObject ObjectNameInformation
NtQueryObject failed c0000004
NtQueryObject failed c0000004
Lookaside: Pool, size 128 up 1
NtQueryObject failed c0000004
\device\null NtQueryInformationFile FileBasicInformation
\device\null NtQueryInformationFile FileStandardInformation
\device\null NtQueryInformationFile FileInternalInformation
… … E:\devctl>obj\i386\devctl
Listen socket on port 1194 address 172.31.236.194
Open device Cdfs? /null
Matching "null" against "Cdfs"
Matching "null" against "000126"
Matching "null" against "ASYNCMAC"
Matching "null" against "Afd"
Matching "null" against "Beep"
Matching "null" against "CdRom0"
Matching "null" against "DmLoader"
Matching "null" against "Floppy0"
Matching "null" against "FloppyPDO0"
Matching "null" against "FsWrap"
Matching "null" against "FtControl"
Matching "null" against "Gpc"
Matching "null" against "Hal Pci 0"
Matching "null" against "IdeDeviceP0T0L0"
Matching "null" against "IdeDeviceP1T1L0"
Matching "null" against "IdeFdo809a1288Channel0"
Matching "null" against "IdeFdo809a1288Channel1"
Matching "null" against "IdePort0"
Matching "null" against "IdePort1"
Matching "null" against "Ip"
Matching "null" against "KeyboardClass0"
Matching "null" against "KsecDD"
Matching "null" against "LanmanDatagramReceiver"
Matching "null" against "LanmanRedirector"
Matching "null" against "LanmanServer"
Matching "null" against "Mailslot"
Matching "null" against "MountPointManager"
Matching "null" against "Mup"
Matching "null" against "NTPNP_PCI0000"
Matching "null" against "NamedPipe"
Matching "null" against "Ndis"
Matching "null" against "Null"
Open device Null? y
Trying to open device Null synchronous
Opened crashn.log for reading
Lookaside: Pool, size 32 up 4
Pool: AfdC, Paged up 0, NonPaged up 192
\Device\Null Open synchronous
Opened file Null with access 1f03ff
Pool: File, Paged up 0, NonPaged up 192
\Device\Null NtQueryObject ObjectNameInformation
NtQueryObject failed c0000004
NtQueryObject failed c0000004
Lookaside: Pool, size 128 up 1
Lookaside: Pool, size 192 up 1
NtQueryObject failed c0000004
\Device\Null NtQueryInformationFile FileBasicInformation
\Device\Null NtQueryInformationFile FileStandardInformation
\Device\Null NtQueryInformationFile FileInternalInformation
\Device\Null NtQueryInformationFile FileEaInformation
\Device\Null NtQueryInformationFile FileAccessInformation
\Device\Null NtQueryInformationFile FileNameInformation
\Device\Null NtQueryInformationFile FileModeInformation
\Device\Null NtQueryInformationFile FileAlignmentInformation
Lookaside: PooL, size 128 up 1
Pool: Sect, Paged up 128, NonPaged up 0
Pool: CcSc, Paged up 0, NonPaged up 320
\Device\Null NtQueryInformationFile FileAllInformation
\Device\Null NtQueryInformationFile FileStreamInformation
Pool: VadS, Paged up 0, NonPaged up 32
\Device\Null NtQueryInformationFile FilePipeInformation
\Device\Null NtQueryInformationFile FilePipeLocalInformation
\Device\Null NtQueryInformationFile FilePipeRemoteInformation
E:\devctl>

Here is a sample leak:

C:\>g:\nt\nttest\security\tiger\devctl\obj\i386\devctl -if
…
Open device \Device\000167? /dfs
…
Open device \Dfs? y
…
\Dfs Open synchronous
Opened file Dfs with access 1f03ff
…
\Dfs NtQueryVolumeInformationFile FileFsVolumeInformation
Pool: Mup , Paged up 0, NonPaged up 131136
Lookaside: Scs$, size 64 up 2
Pool: Mup , Paged up 0, NonPaged up 131136
Pool: Hal , Paged up 0, NonPaged up 288
Pool: Irp , Paged up 0, NonPaged up 384
Pool: Mup , Paged up 0, NonPaged up 131136
Pool: Mup , Paged up 0, NonPaged up 131136
Pool: Mup , Paged up 0, NonPaged up 131136
Pool: Mup , Paged up 0, NonPaged up 131136
Pool: Mup , Paged up 0, NonPaged up 131136
Lookaside: Ntfi, size 264 up 1
Pool: Mup , Paged up 0, NonPaged up 130944
Lookaside: Ntfi, size 264 up 2
Lookaside: Scs$, size 64 up 1
Pool: Mup , Paged up 0, NonPaged up 131008
Pool: Mup , Paged up 0, NonPaged up 129536
Pool: Mup , Paged up 0, NonPaged up 131136
Pool: Mup , Paged up 0, NonPaged up 131136
Pool: Mup , Paged up 0, NonPaged up 131136
Pool: Mup , Paged up 0, NonPaged up 131008
Lookaside: Ntfi, size 264 up 1
Lookaside: Scs$, size 64 up 1
Pool: Mup , Paged up 0, NonPaged up 131136
Pool: Mup , Paged up 0, NonPaged up 130752
Pool: Mup , Paged up 0, NonPaged up 130944
Pool: Mup , Paged up 0, NonPaged up 131008
Pool: Mup , Paged up 0, NonPaged up 131136
Pool: Mup , Paged up 0, NonPaged up 131136
Pool: Mup , Paged up 0, NonPaged up 131136
Pool: Mup , Paged up 0, NonPaged up 131136
Pool: Mup , Paged up 0, NonPaged up 131136
Pool: Mup , Paged up 0, NonPaged up 131136
Pool: Mup , Paged up 0, NonPaged up 131136
Lookaside: PooL, size 256 up 1
Pool: Mup , Paged up 0, NonPaged up 131136
Pool: Mup , Paged up 0, NonPaged up 131136
Pool: Mup , Paged up 0, NonPaged up 131136
Pool: Mup , Paged up 0, NonPaged up 131136
Pool: Mup , Paged up 0, NonPaged up 131136
Pool: Mup , Paged up 0, NonPaged up 131136
Pool: Mup , Paged up 0, NonPaged up 131136
Pool: Mup , Paged up 0, NonPaged up 131136
Lookaside: Scs$, size 64 up 2
Pool: Mup , Paged up 0, NonPaged up 131136
Pool: Hal , Paged up 0, NonPaged up 288
Pool: Irp , Paged up 0, NonPaged up 768
^C

Memory: 65080K Avail: 3424K PageFlts: 0 InRam Krnl: 4632K P: 1916K
Commit: 52972K Limit: 666632K Peak: 108844K Pool N: 6312K P:10324K
Tag Type Allocs Frees Diff Bytes Per Alloc
Mup Nonp 84551 ( 0) 9870 ( 0) 74681 4779584 ( 0) 64
SYSA Paged 2227 ( 0) 750 ( 0) 1477 67136 ( 0) 45
CM Paged 6542 ( 0) 5854 ( 0) 688 8877504 ( 0) 12903
Vad Nonp 1460 ( 0) 1000 ( 0) 460 29440 ( 0) 64
File Nonp 3434 ( 0) 3137 ( 0) 297 57024 ( 0) 192
C:\>type diags.txt
\Dfs NtQueryVolumeInformationFile FileFsAttributeInformation Pool: Mup , Paged up 0, NonPaged up 65600

Note that the IOCTL and FSCTL passes take a very long time since the space is huge. You can get much quicker coverage if you know the range of IOCTLs that the driver accepts. Here is the procedure with IPNAT:
//
// NAT-supported IOCTL constant declarations
//
#define IOCTL_IP_NAT_SET_GLOBAL_INFO \
_IP_NAT_CTL_CODE(0, METHOD_BUFFERED, FILE_WRITE_ACCESS)
#define IOCTL_IP_NAT_CREATE_INTERFACE \
_IP_NAT_CTL_CODE(2, METHOD_BUFFERED, FILE_WRITE_ACCESS)
… … #define IOCTL_IP_NAT_DELETE_REDIRECT \
_IP_NAT_CTL_CODE(13, METHOD_BUFFERED, FILE_WRITE_ACCESS)

The first number in this macro is the function number. You can limit Devctl to just cover this range with a command like devctl +fl 0 +fu 13. This limits both the IOCTL and FSCTL zero-length and random buffer calls. This will run very quickly; consider increasing the number of random buffers that the program gives the driver by +t nnnn, for instance.

Top of page

© 1999 Microsoft Corporation