Updated INF Verification on Microsoft Attestation Signing

Just FYI, and in case anyone sees any good wisdom to share:

Sometime between December 2022 and now, the INF Verification involved in the Attestation signing process was updated to apply new requirements.

Specifically the one that seems to have bitten us is that when setting registry values, now it’s not allowed to reference INF directory IDs using the strings syntax. e.g. “%11%\system.dll”

The error message from INF Verification helpfully suggests that one should use “%%SystemRoot%%” or “%%ProgramFiles%%” instead of the directory IDs. Note the implication here is that the registry value now MUST be REG_EXPAND_SZ, and the substitution with the actual path now happens at runtime when the registry value is read instead of during the .INF processing.

Unfortunately “does the consumer of this registry value actually support REG_EXPAND_SZ?” is a limitation as well, since REG_EXPAND_SZ behavior is not transparently applied by the registry APIs. We were lucky that all the registry values, save one, did already support REG_EXPAND_SZ.

But it appears that Microsoft NetSetupShim.dll’s reading of the “ComponentDll” value does not support REG_EXPAND_SZ, at least on Windows 11, Windows 10 and Windows 7 platforms.

This was one of many DLLs we had intentionally moved into a product-specific folder name space, seemingly in line with Microsoft’s intentions that we not all install and contend over the SYSTEM32 folder namespace. Our “successfully installing until now” ComponentDll syntax in out NetClient-class INF had been referencing a DLL in our Program Files folder:

HKR,"Ndi","ComponentDll",,"%16422%\CompanyName\ProductName\Notification.dll"

But adding the 0x20000 flag to write the value as REG_EXPAND_SZ causes attempts to install from the .INF fail with a form of “invalid data type” or “data type mismatch”. Exactly which error code will be returned varies depending upon which Windows platform you are on. The call stack as identified by Process Monitor says it’s NetSetupShim.dll reading this value, successfully, but then apparently internally decides the value must be REG_SZ type to succeed.

So we’re in the process of changing the INF to install this DLL into the SYSTEM32 folder instead, so that no explicit path will need to be specified. Given that now neither “%11%” nor “%%ProgramFiles%%” will be supported for this “ComponentDll” value.

System32 will only be allowed for another couple years. Use %13% to just leave all the files in the driver store, you can use them directly from there. I’ll check on netsetup not resolving these

Thanks for the info. Indeed, NetSetupShim honoring REG_EXPAND_SZ is probably good for the future. For what it’s worth the call stack seen here was when NetSetupShim!DriverInstallUninstall::LoadNetCfgDriver called into NetSetupShim!ParseDriverRegistry, and used NetSetupShim!RegistryKey::GetValueString to consume the “ComponentDll” value.

And thanks for the idea of putting “Notification.dll” into directory ID 13. We will investigate doing that, but an initial reading of ID 13 and “run from Driver Store” discussion, I think our INF likely has a significant conflict to using ID 13. Because we have significant renaming required within the INF CopyFiles sections, because on 64-bit platforms we need to deliver both 64-bit application library support and 32-bit application library support, with library files that have the same name for both architectures.

i.e. The same way in which Microsoft delivers both C:\Windows\System32\NETAPI32.DLL and C:\Windows\SysWOW64\NETAPI32.DLL, our INF needs to deliver application support libraries to be available in that same manner. But it was impossible to make a single INF to deliver two different CPU architectures of the same file name without having the files renamed within your driver installation package & resolving those renames back to the intended name during CopyFiles. So for example the “Notification.dll” being referenced here as ComponentDll was delivered on 64-bit platforms as:

Notification.dll,Notification_x64.dll ; to 16422,CompanyName\ProductName
Notification.dll,Notification_x86.dll ; to 16426,CompanyName\ProductName

I also don’t know what if anything ID 13 might have returned on Windows 7 platforms, and will have to investigate this as well. Presumably it may require that we begin split-sourcing our Windows 7 platform INF versus Windows 8 and later platform INF, which up until now had been the same binary files and same INF across Windows 7 to Windows 11.

Perhaps it was inevitable that one day split-sourcing the INFs would become necessary, but its just one more can of worms to be opening up. Maybe the right section decoration incantations can keep us successfully single-sourced.

Got it. We have some upcoming INF syntax that will help with this renaming as well so you would be able to use %13% in the future

For what it’s worth, I did speak too soon regarding “We were lucky that all the registry values, save one, did already support REG_EXPAND_SZ.”

The ImagePath for kernel-mode drivers indeed “were already being set as REG_EXPAND_SZ”, but this REG_EXPAND_SZ behavior is not actually supported for kernel-mode driver loading. Which does seem to make sense, because “What is an environment string table?” that early in the load process. ImagePath does support REG_EXPAND_SZ, but apparently only when SERVICES.EXE is doing user-mode service loading.

I do see drivers use a syntax like “\SystemRoot\SYSTEM32\drivers\foo.sys” which is supported, as some kind of “REG_EXPAND_SZ without REG_EXPAND_SZ.” Using “\ProgramFiles\CompanyName\ProductName\foo.sys” doesn’t work, unless anyone knows of another alias that would work for Program Files, like the non-environment-based “SystemRoot” reference does.

So this does seem to be another ramification of “the directory reference is now resolved at runtime when the registry value is read”, instead of being established during the INF processing itself.

We did try to use “%13%”, just for delivery of the image being used in the ComponentDll value which used to be in Program Files. But unfortunately as soon as a single item in the INF referenced directory ID 13, INF Verification flagged every other destination reference as “error: not isolated to directory ID 13”. So that’s understandable, but it must be “all or nothing”, and we have too many conflicts for “all”.

I can get around the ComponentDll hurdle by copying it to SYSTEM32 for now, and cross the “no more SYSTEM32” bridge when we get to it. But the ImagePath for drivers which used to be “%16422%\CompanyName\ProductName\foo.sys” can’t use “%ProgramFiles%” as the replacement (since no REG_EXPAND_SZ support in kernel driver loading). And we have yet to find a syntax to replace that directory ID 16422 reference.

I don’t think I can just “let the ImagePath values be ‘wrong’, and fix-up the values in our product installer after performing the .INF-based portion of the installation before rebooting.” Because Windows itself is going to want to re-process our .INF during Windows upgrades and similar, when our umbrella installer won’t be around to fix-up the values. Can think of other more persistent ways to perform the fix-up, but the approach seems too brittle.

Which makes it seem like in response to this INF Verification change, these drivers are just going to have to move out of the product-specific folder into the common \system32\drivers\ folder, and delete them from their Program Files location. Not that INF Verification seems particularly happy about having a DelFiles operation during installation, either.

The problem exists in registry values - we can’t anticipate what your usage of those registry values is going to be, so we can’t be smart about it. In the ImagePath of a service (where you’re using AddService and the ServiceBinary directive), we know exactly what this is for so you can use %11%, %12%, %13%, etc. and we will handle it correctly - the directive tells us the context and we act appropriately.

Thanks for the additional input. I’m sure that makes sense, just not within what I know. If it’s a short enough answer, what would “%11%” or “%16422%” mean which would be “ambiguous” depending on the registry value it was written to? From my outside view it’s expected to be expanded to the path .INF processing would use if directory ID 11 or directory ID 16422 were used in a DestinationDirs section, which to my view does not “change depending on usage context.”

Meaning it’s not that we’re saying “%11%” or “%16422%” is going to be written “into the literal registry value” to be interpreted later at runtime, and “what might 11 or 16422 have meant at the time INF processing was occurring?” The same thing the running instance of INF processing knows directory ID 11 to be for any other purpose is the value we want “%11%” expanded to when the .INF parsing is composing the value to be written to the registry. We end up with literally “C:\Windows\SYSTEM32” or “C:\Program Files” written in the registry value by the .INF processing, which to me seems unambiguous. Same as we would if we were making a references to an entry in the [Strings] table; there isn’t obviously “something else it could have meant” from my limited perspective since directory IDs seem pretty unambiguous when actually in the midst of .INF processing.

Just an academic question I guess, since obviously the INF Verification and everything is already in place. Just trying to understand more about the reasons. Thanks.

Fair question -

Say you mount a windows image with DISM to E:\mountdir, so the mounted windows directory is E:\mountdir\windows. Now you add a driver with DISM to that image, and we write out the registry value at that time: %11% now maps to E:\mountdir\windows\system32, and that’s what you’ll see in the registry. When you are using %11% to copy a file, that was actually the directory you wanted to put the file, so that path is correct at that time.