Oops! Microsoft private symbols accidently leaked in Visual Studio 2010 CTP VM image

I downloaded Microsoft’s newly released Visual Studio 2010 CTP virtual machine disk image hoping for a few surprises, but I certainly didn’t expect this…

The Visual Studio 2010 CTP is a huge multi-gigabyte VM running Windows Server 2008. The first thing I did with it is start up Visual C++ 2010, create a Win32 console application and run it in the debugger. I looked at the stack trace and saw the following:

vc10app.exe!wmain(int argc=1, wchar_t * * argv=0x000d1470) Line 8
vc10app.exe!__tmainCRTStartup() Line 564 + 0x19 bytes
vc10app.exe!wmainCRTStartup() Line 392
kernel32.dll!BaseThreadInitThunk(unsigned long RunProcessInit=0, long (void *)* StartAddress=0x00000000, void * Argument=0x7ffdf000) Line 66 + 0x5 bytes
ntdll.dll!__RtlUserThreadStart(long (void *)* StartAddress=0x013b1073, void * Argument=0x7ffdf000) Line 2740
ntdll.dll!_RtlUserThreadStart(long (void *)* StartAddress=0x013b1073, void * Argument=0x7ffdf000) Line 2672 + 0xb bytes

vc10app is the name of my test console application. I go over stack traces on a daily basis so the special thing about this one immediately caught my attention. Notice that the wmain() function of my console application has full debugging information (as expected for something I wrote) and the parameter names argc and argv are visible in the stack trace. Under normal circumstances, only public debugging symbols are available for Microsoft OS components like kernel32 and ntdll. In this CTP VM, however, the StartAddress and Argument parameters were visible as well.

Public debugging symbols are stripped versions of the original private symbols generated by the build process. They do not contain parameter information and do not contain the names of local variables in functions. Note however that for C++ functions, name mangling results in parameter types being visible in public symbols as well. Normally, when running the debugger in a system configured to use Microsoft’s public symbols, names for internal functions are visible in stack traces, but the names of arguments and locals never are.

I opened the Modules tab of the Visual Studio debugger to determine where the debugger is picking up these symbols for kernel32 and ntdll. The debugger was using C:\ppa\symstore as the symbol store. I opened the C:\ppa directory and saw that a Visual Studio Profiler session for a matrix multiplication application was stored there.

Apparently someone with access to Microsoft’s internal symbol store ran a profiling session on this matrix multiplication application, perhaps to ensure profiling is functional on the CTP VM. The private symbols retrieved for the session were persisted in the CTP’s disk and made their way to the public release. To ensure my hypothesis was correct, I installed Windbg on the machine, opened ntdll.dll as a crash dump and loaded symbols from the store directory:

Microsoft (R) Windows Debugger Version 6.9.0003.113 X86
Copyright (c) Microsoft Corporation. All rights reserved.
Loading Dump File [C:\Windows\System32\ntdll.dll]
Symbol search path is: SRV*c:\ppa\symcache;.
Executable search path is:
ModLoad: 77ed0000 77ff7000 C:\Windows\System32\ntdll.dll
eax=00000000 ebx=00000000 ecx=00000000 edx=00000000 esi=00000000 edi=00000000
eip=77ed0000 esp=00000000 ebp=00000000 iopl=0 nv up di pl nz na po nc
cs=0000 ss=0000 ds=0000 es=0000 fs=0000 gs=0000 efl=00000000
77ed0000 4d dec ebp
0:000> .reload /f
Loading unloaded module list
0:000> lm
start end module name
77ed0000 77ff7000 ntdll (private pdb symbols) c:\ppa\symcache\ntdll.pdb\B958B2F91A5A46B889DAFAB4D140CF252\ntdll.pdb
0:000> x ntdll!RtlAllocateHeap
77f358a6 ntdll!RtlAllocateHeap (void *, unsigned long, unsigned long)
0:000> dv /f ntdll!RtlAllocateHeap
@ebp+0x08 HeapHandle
@ebp+0x0c Flags
@ebp+0x10 Size
@ebp+0x08 ExtraSize
@ebp-0x04 AllocationSize
@ebp-0x08 Interceptor
@ebp-0x58 ExceptionRecord

The private PDB for ntdll.dll found in this CTP VM image notes how HeapHandle, Flags, Size and ExtraSize are the parameter names for RtlAllocateHeap. Furthermore, AllocationSize, Interceptor and ExceptionRecord are used as local names in this API.

Private PDBs also feature source information. This is also visible in this case:

0:000> ln ntdll!RtlAllocateHeap
Source Depot: basedepot.sys-ntgroup.ntdev.microsoft.com:2003 //depot/longhorn_rtm/base/ntos/rtl/heap.c#1
(77f358a6) ntdll!RtlAllocateHeap | (77f35997) ntdll!RtlpLowFragHeapFree
Exact matches:
ntdll!RtlAllocateHeap (void *, unsigned long, unsigned long)

The PDB features references to the source file from the Windows source tree for RtlAllocateHeap and the other APIs. Additionally, it appears to contain a custom reference to Microsoft’s internal source control system, Source Depot, presumably to facilitate the debugger retrieving up to date sources automatically when those are not available locally.

It’s interesting how scattered bits of information in a debugging symbols file provide a fascinating insight into Windows. Hope you enjoyed the surprise as much as I did…

Installing filter drivers with DIFxApp and a WiX v3 MSI

The designers of Windows Installer probably did not have driver installation in mind. Indeed, the ServiceInstall MSI database table does not even allow for service types SERVICE_KERNEL_DRIVER and SERVICE_FILE_SYSTEM_DRIVER in the ServiceType column. Originally, Microsoft encouraged driver installation by means of a “setup application”, custom code invoking the SetupAPI. The Setup API functions would be used to examine an .INF file describing required steps for the driver’s installation. Back in Windows NT 4.0, even .INF files were often avoided in lieu of directly setting up the legacy driver service in the system registry with C code.

Enter Windows 2000 with Plug and Play. Now the system uses .INF files it maintains in a store to locate and install drivers for devices on demand. When a new device is inserted, the system looks for an .INF file with a matching hardware identifier. For most hardware devices, .INF based installation becomes a de-facto requirement. However, for other driver categories, such as file system filters, device class filters, etc., an .INF file is but one way of performing the driver installation process.

Microsoft, when confronted by driver setup authors who denounce .INF files as having cryptic, old-fashioned syntax, non-ideal diagnosibility and debuggability and other shortcomings, persists in encouraging their use. They are a WHQL logo requirement and in some cases it is claimed they will be the only way to install drivers in future Windows releases.

Often driver installation is but a part of the installation process of a larger application program. For applications, Microsoft encourages (and mandates as a logo requirement) the use of MSI packages and the Windows Installer service. In order to avoid having every MSI setup author who needs to perform driver installation roll his own custom invocation of the driver’s .INF installation and in a bid to improve driver installation’s synergy with Windows Installer in general, Microsoft created DIFxApp – Driver Install Frameworks for Applications.

MSI and DIFxApp’s .INF based installation make for an odd couple. The Windows Installer model is transactional – describe what changes should be made to the system and let Windows Installer determine how to execute them and importantly, how to roll them back. After having encouraged COM DLLs to encapsulate the details of their installation and registration with the Self-Registration DllRegisterServer export for years, describing the individual elements of the COM registration using an MSI’s Registry table is now encouraged instead. DllUnregisterServer can fail in mysterious ways due to missing dependencies, etc., leaving cruft behind during uninstallation. By contrast, rolling back insertions made from the Registry table is always possible for the MSI service. If COM’s long standing installation architecture was set aside to better align with MSI’s model, one would expect a similar occurance for drivers. However, DIFxApp, the solution for installing drivers with MSI, is the moral equivalent of COM Self-Registration. If a driver’s .INF does not remove all changes it made during installation, the uninstallation process is bound to be imperfect. MSI’s ability to diagnose whether a driver’s installation has been damaged is limited and thwarted.

Be that as it may, .INF files’ tight integration with the Plug and Play system, the fact they are a logo requirement and uniformity considerations are compelling arguments for their use. From this point forward, it shall be assumed that .INF based installation and MSI integration have been chosen.

If you’ve developed a common PnP WDM driver, you probably already have an .INF for installation purposes. That .INF can usually be used as is with DIFxApp, which assumes PnP drivers by default. It is for other driver categories that the process becomes more tricky. I’ll discuss filter drivers of three varieties in particular – file system filters, file system minifilters and device class filters.

For illustration purposes, let’s consider a typical file system minifilter driver, DirFilter, which is presently a pet project of mine that will eventually perform interesting modifications to directory listing I/O operations. The following is DirFilter’s .INF file:

Signature = "$Windows NT$"
Class = "ActivityMonitor"
ClassGuid = {b86dff51-a31e-4bac-b3cf-e8cfe75c9fc2}
Provider = %Koby%
DriverVer = 09/12/2008,
CatalogFile = dirfilter.cat
DriverPackageType = FileSystemMinifilter
DefaultDestDir = %DIRID_DRIVERS%
DirFilter.DriverFiles = %DIRID_DRIVERS%
OptionDesc = %ServiceDescription%
CopyFiles = DirFilter.DriverFiles
AddService = %ServiceName%,,DirFilter.Service
DelFiles = DirFilter.DriverFiles
DelService = %ServiceName%,0x200 ;Ensure service is stopped before deleting
DisplayName = %ServiceName%
Description = %ServiceDescription%
ServiceBinary = %12%\%DriverName%.sys ; DIRID_DRIVERS
Dependencies = "FltMgr"
LoadOrderGroup = "FSFilter Activity Monitor"
AddReg = DirFilter.AddRegistry
dirfilter.sys = 1
1 = %DiskId1%
Koby = "Koby Kahane"
ServiceDescription = "DirFilter minifilter driver"
ServiceName = "DirFilter"
DriverName = "DirFilter"
DiskId1 = "DirFilter minifilter installation media"
; Instance specific information.
Instance1.Name = "DirFilter Instance"
Instance1.Altitude = "370021" ; Real world minifilters should use Microsoft allocated altitude
Instance1.Flags = 0x0 ; Allow automatic attachments

This is, I believe, a pretty typical .INF for a minifilter. A default instance of it is set up at an altitude in an appropriate filter load order group. This is as good a place as any to note the importance of requesting a Minifilter Altitude Allocation from Microsoft for public drivers rather than using some random number which may end up causing conflicts and interoperability issues. From my experience, it takes Microsoft two months or so to respond to an allocation request so make sure to take care of it early in your release cycle.

As a first step towards a DIFxApp-based MSI driver installation, write an .INF file that will work when right clicking it and selecting Install from Explorer’s popup menu. This calls SetupAPI’s InstallHinfSection on the .INF file’s DefaultInstall section. Further, ensure that the uninstallation process works as expected, and leaves no trails behind, by using InstallHinfSection to invoke the DefaultUninstall section of the file.

You may discover that you are encountering difficulty at this stage. I recommend running the ChkINF utility, which analyzes .INF files for errors and mishaps, before testing the .INF file and indeed after every modification to an existing file. ChkINF, while sometimes terse, is nowhere near as cryptic as errors you will receive from SetupAPI. However, if you encounter setup errors even after ChkINF has verified your file, you can utilize verbose SetupAPI logging to further diagnose setup issues.

With a working “right-click Install” .INF file, the time is ripe for integration into an MSI installer. First of all, DIFxApp requires an additional directive in the Version section of your .INF file, the DriverPackageType. The driver package type is used by DIFx to determine what steps are required for the installation of your specific type of driver. For example, if you fail to specify the type of your file system filter, DIFx assumes it is a PnP driver and the “installation” will just import the .INF into the system driver store. No service will be created and success will not be achieved. Unfortunately, the documentation for the DriverPackageType is sorely lacking at best. You will see it only mentions ClassFilter and PlugAndPlay as possible types in MSDN. MSDN proves a disappointment. However, another Microsoft information source, their Requirements for Driver Packages that are Used with the DIFx Tools document, calls for the use of DIFx for Class Filters, Plug and Play drivers, File System & File System Filter drivers, Network drivers, Kernel Service Drivers and Export Drivers. The DriverPackageType values ClassFilter, FileSystem, FileSystemFilter, KernelModule, KernelService, Network and PlugAndPlay are documented there. You may notice that a special type for minifilters is conspicuously absent from the list. At first, I figured perhaps DIFx has no need for a distinction between legacy filters and minifilters. Later, an old post (free registration required) on OSR’s NTFSD mailing list turned my attention to the fact DIFx will require a system restart for a minifilter, even though it can be loaded and unloaded without one. Neal Christiansen, Microsoft’s File System Filter Group Lead, replies (back in 2004) that appropriate support for minifilter installation is forthcoming. I therefore set out to discover whether DIFxApp as available today provides special support for minifilters.

When the Windows Server 2008 WDK is installed to the default location, the x86 versions of the DIFxApp MSI custom action DLLs reside in C:\WINDDK\6001.18000\redist\DIFx\DIFxApp\English\WixLib\x86. I examined them with the Interactive Disassembler and was happy to see Microsoft made available public symbols (.PDBs) for them. To make a long story short, the DriverPackageType field is examined by DIFxApp in the SetupAPI::CDriverPackage::GetDriverType(CRefPtr<SetupAPI::CInf>, DRVPKG_TYPE&) function in DIFxAppA.dll, which references a global table, dubbed DriverTypeStringList, for possible package types:

It is now evident that available driver package types as of DIFx 2.1 are ClassCoInstaller, ClassFilter, DeviceFilter, FileSystem, FileSystemFilter, FileSystemMinifilter, KernelModule, KernelService, NdisImMiniport, Network and PlugAndPlay. With this information, I opted for the FileSystemMinifilter type for DirFilter’s installation .INF file.

Next, a catalog file is required by DIFx. Even though the purpose of the catalog is to be WHQL signed by Microsoft, DIFx can be used to install drivers without WHQL certification. Nevertheless, the catalog still needs to be there. The easy way is to use the Inf2Cat tool for the catalog’s generation. Place the .INF file and the driver files it references in a staging area and you can run it like so:

E:\Projects\dirfilter\staging>inf2cat /driver:E:\Projects\dirfilter\staging /os:Server2008_X86,Vista_X86,Server2003_X86,XP_X86,2000 /verbose

Specifying, of course, the operating systems and platforms appropriate for your driver.

An .INF file, a .CAT file and a .SYS file are the three files required for a DIFxApp driver package. The DIFxApp model requires your MSI to install these files to a source directory, per driver package (i.e., if you have multiple drivers, they’ll need multiple .INFs in multiple directories) and performs the installation from there to the Windows driver store and eventually the drivers directory under system32.

Microsoft distributes a WiX library and documentation for using it with DIFxApp. However, that library targets the older WiX v2.0 release. WiX v3.0 is the actively developed version, and has different schema, semantics and a slightly different integration model with DIFxApp. If you use WiX v2.0, consult Microsoft’s documentation. Otherwise, let’s proceed. The following .wxs file is the sample installer I wrote for installing DirFilter with DIFxApp:

<?xml version='1.0' encoding='windows-1252'?>
<Wix xmlns='http://schemas.microsoft.com/wix/2006/wi'
    <Product Name='DirFilterInstaller' Id='59486bf1-77ad-460f-802d-29dbab0f78be' Language='1033' Codepage='1252' Version='1.0' Manufacturer='KK' UpgradeCode='96fe2ba0-fe8b-45b4-85ef-ea9aca103e6f'>
        <Package Id='*' Keywords='DirFilter' Description='DirFilter Installer' Comments='Installs DirFilter' Manufacturer='KK' InstallerVersion='100' Languages='1033' Compressed='yes' SummaryCodepage='1252' />
        <Media Id='1' Cabinet='DirFilterInstaller.cab' EmbedCab='yes' DiskPrompt='DirFilter Media' />
        <Property Id='DiskPrompt' Value='DirFilter Install Media' />
        <Directory Id='TARGETDIR' Name='SourceDir'>
            <Directory Id='ProgramFilesFolder'>
                <Directory Id='INSTALLDIR' Name='DirFilterApp'>
                    <Directory Id='DirFilterAppDrivers' Name='Drivers'>
                        <Directory Id='DirFilterDir' Name='DirFilter'>
                            <Component Id='DirFilterDriver' Guid='8c64e674-5476-46e4-93cd-ba1ae78622df'>
                                <File Id='DirFilterSYS' Name='DirFilter.sys' DiskId='1' Source='DirFilter.sys' KeyPath='yes' />
                                <File Id='DirFilterINF' Name='DirFilter.inf' DiskId='1' Source='DirFilter.inf' />
                                <File Id='DirFilterCAT' Name='DirFilter.cat' DiskId='1' Source='DirFilter.cat' />
                                <difx:Driver Legacy='yes' />
        <Feature Id='Complete' Level='1'>
            <ComponentRef Id='DirFilterDriver' />
        <UIRef Id="WixUI_Minimal" />

Several points are worth mentioning regarding this installer:

  • The XML schema for the WiX v3 DIFxApp extension is referenced at the top of the file. This is required for the Driver element to work. Note that in WiX v3, the Driver element is used instead of driver-specific attributes that were available for the Component element in the WiX v2 DIFxApp extension.
  • As said before, DIFx requires one driver per directory and one driver per .INF file. The driver component is placed in a directory under the application’s directory. Additional drivers will be placed in additional components under additional directories.
  • The “Legacy” attribute is used in the DIFx Driver element because my driver package is not WHQL signed. Of course the best solution for this is to ensure your driver is up to par and actually get WHQL certification. You should also note that Legacy mode disables integrity checks, allowing the driver installation to appear successful even when critical files are missing, etc.
  • Do NOT copy the GUIDs I used if you end copying and pasting from this example. Make sure you generate your own with uuidgen and replace them.

The .wxs source for this WiX installer is straightforward enough. However, actually compiling it and linking it requires referencing several external dependencies. When running Candle, the WiX compiler, the WixDifxAppExtension should be referenced (and others you may depend on, such as WixUIExtension). When running Light, the WiX linker, you should reference the extension as before. Additionally, in the newer builds of WiX v3.0, multiplatform support for DIFx requires you reference the .wixlib with the DIFxApp custom action DLLs for the appropriate platform:

E:\Projects\dirfilter\staging>%WIX_PATH%\candle -ext %WIX_PATH%\WixUIExtension.dll -ext %WIX_PATH%\WixDifxAppExtension.dll DirFilterInstaller.wxs
Microsoft (R) Windows Installer Xml Compiler version 3.0.4617.0
Copyright (C) Microsoft Corporation. All rights reserved.


E:\Projects\dirfilter\staging>%WIX_PATH%\light -ext %WIX_PATH%\WixUIExtension.dll -
ext %WIX_PATH%\WixDifxAppExtension.dll DirFilterInstaller.wixobj %WIX_PATH%\difx
-o DirFilterInstaller.msi
Microsoft (R) Windows Installer Xml Linker version 3.0.4617.0
Copyright (C) Microsoft Corporation. All rights reserved.

(The WIX_PATH environment variable is set to WiX 3.0’s bin directory on my system.)

Failing to reference WixDifxAppExtension or difxapp_[platform].wixlib will result in assorted compilation or linkage errors during installer build.

When debugging DIFxApp driver installation, usually the interesting things are performed by the SetupAPI directly and therefore the SetupAPI log should be examined first. However, DIFxApp’s custom actions write diagnostic information into the MSI log (which can be enabled with a command-line switch to msiexec) and can provide additional insight into the driver installation process. Here’s an excerpt from the MSI log of a successful DirFilter installation:

MSI (s) (2C:44) [20:22:39:061]: Executing op: ActionStart(Name=MsiInstallDrivers,,)
Action 20:22:39: MsiInstallDrivers.
MSI (s) (2C:44) [20:22:39:077]: Executing op: CustomActionSchedule(Action=MsiInstallDrivers,ActionType=3073,Source=BinaryData,Target=InstallDriverPackages,CustomActionData=2.15{8C64E674-5476-46E4-93CD-BA1AE78622DF}C:\Program Files\DirFilterApp\Drivers\DirFilter\82DirFilterInstallerKK)
MSI (s) (2C:90) [20:22:39:092]: Invoking remote custom action. DLL: C:\WINDOWS\Installer\MSI29.tmp, Entrypoint: InstallDriverPackages
DIFXAPP: ENTER: InstallDriverPackages()
DIFXAPP: INFO: 'CustomActionData' property 'DIFxApp Version' is '2.1'.
DIFXAPP: INFO: 'CustomActionData' property 'UI Level' is '5'.
DIFXAPP: INFO: 'CustomActionData' property 'componentId' is '{8C64E674-5476-46E4-93CD-BA1AE78622DF}'.
DIFXAPP: INFO: 'CustomActionData' property 'componentPath' is 'C:\Program Files\DirFilterApp\Drivers\DirFilter\'.
DIFXAPP: INFO: 'CustomActionData' property 'flags' is 0x8.
DIFXAPP: INFO: 'CustomActionData' property 'installState' is '2'.
DIFXAPP: INFO: 'CustomActionData' property 'ProductName' is 'DirFilterInstaller'.
DIFXAPP: INFO: 'CustomActionData' property 'ManufacturerName' is 'KK'.
DIFXAPP: INFO: user SID of user performing the install is 'S-1-5-21-1417001333-651377827-725345543-1003'.
DIFXAPP: INFO: opening HKEY_USERS\S-1-5-21-1417001333-651377827-725345543-1003\Software\Microsoft\Windows\CurrentVersion\DIFxApp\Components\{8C64E674-5476-46E4-93CD-BA1AE78622DF} (User's SID: 'S-1-5-21-1417001333-651377827-725345543-1003') ...
DIFXAPP: INFO: ENTER: DriverPackageInstallW
DIFXAPP: INFO: Copied 'DirFilter.inf' to driver store...
DIFXAPP: INFO: Copied 'dirfilter.cat' to driver store...
DIFXAPP: INFO: Commiting queue...
DIFXAPP: INFO: Copied file: 'C:\Program Files\DirFilterApp\Drivers\DirFilter\dirfilter.sys' -> 'C:\WINDOWS\system32\DRVSTORE\DirFilter_11E71DD96C3AADF7CDEC882620E8410DECE9B5EF\dirfilter.sys'.
DIFXAPP: INFO: Installing INF file "C:\WINDOWS\system32\DRVSTORE\DirFilter_11E71DD96C3AADF7CDEC882620E8410DECE9B5EF\DirFilter.inf" of Type 4.
DIFXAPP: INFO: Installing File System Driver 'C:\WINDOWS\system32\DRVSTORE\DirFilter_11E71DD96C3AADF7CDEC882620E8410DECE9B5EF\DirFilter.inf'
DIFXAPP: INFO: Service 'DirFilter' was started
DIFXAPP: SUCCESS:Installation completed with code 0x0.
DIFXAPP: INFO: RETURN: DriverPackageInstallW (0x0)

DIFXAPP: INFO: ENTER: DriverPackageGetPathW
DIFXAPP: SUCCESS:Found driver store entry.
DIFXAPP: INFO: RETURN: DriverPackageGetPathW (0x7A)
DIFXAPP: INFO: ENTER: DriverPackageGetPathW
DIFXAPP: SUCCESS:Found driver store entry.
DIFXAPP: INFO: RETURN: DriverPackageGetPathW (0x0)
DIFXAPP: INFO: driver store entry for 'C:\Program Files\DirFilterApp\Drivers\DirFilter\DirFilter.inf' is 'C:\WINDOWS\system32\DRVSTORE\DirFilter_11E71DD96C3AADF7CDEC882620E8410DECE9B5EF\DirFilter.inf'.
DIFXAPP: INFO: The component Id '{8C64E674-5476-46E4-93CD-BA1AE78622DF}' is now set to point to driver store: 'C:\WINDOWS\system32\DRVSTORE\DirFilter_11E71DD96C3AADF7CDEC882620E8410DECE9B5EF\DirFilter.inf'
DIFXAPP: INFO: A reboot is not needed to install the component '{8C64E674-5476-46E4-93CD-BA1AE78622DF}'.
DIFXAPP: RETURN: InstallDriverPackages() 0 (0x0)

Notice how DIFxApp places a copy of your driver package in the system driver store, a behavior which is distinct from “right-click Install” .INF invocation. Notice also it reports whether the installation operation with SetupAPI was successful and will additionally take care of scheduling a reboot if the driver package so requires.

DIFxApp works equally well for installing a legacy file system filter. Simply use the FileSystemFilter DriverPackageType value instead.

Device class filters require special consideration. MSDN has a page about Installing a Filter Driver. For device filters, the process is straightforward – your .INF simply adds an UpperFilter under the appropriate device’s hardware key, using the special “HKR” relative root. However, a device class filter has no hardware key to refer to and indeed may wish to filter several distinct device classes. The MSDN page says one ought to write a custom “setup application” that shall invoke the .INF file for the several device classes of interest, as desired. However, DIFxApp offers no such flexibility and it does not appear that it can be used to drive such an .INF file. However, the fact it has a ClassFilter type clearly implies it has device class filters in mind.

As is often the case, the answer to this dillemma lies in the WDK. The WDK features a sample WiX v2.0 project for illustrating DIFxApp’s use, which in fact installs a class filter. In a default installation, you can find it in C:\WINDDK\6001.18000\src\setup\DIFxApp\WiXLib\ClassFilter.wxs. Nearby, in C:\WINDDK\6001.18000\src\setup\infs\clasfilt\ClasFilt.Inf, resides the sample .INF file. Among other things, the file includes an explicit registration as a class UpperFilter:

; Change {setup-ClassGUID} to the string form of the ClassGUID that you are installing the filter on.
; Change UpperFilters to LowerFilters if this is a lower class filter.
HKLM, System\CurrentControlSet\Control\Class\{setup-ClassGUID}, UpperFilters, 0x00010008, clasfilt

Fair enough. Multiple entries can be included in the registry section for UpperFilter registration directly under the class keys of interest. However, if you opt for this approach you may find to your dismay you no longer fall in ChkINF’s favor:

(W22.1.2213) INF files should not set registry entries under 'HKLM,System\CurrentControlSet\Control\Class'.

I guess you’re damned if you do and damned if you don’t. I suggest not drinking ChkINF’s Kool-Aid, setting up the device class filter registration directly and using DIFxApp rather than iterating with a custom setup application, obviously.

Hopefully this lengthy writeup will prove useful to other authors of MSI-based driver installations.