In an ideal world vendors could use the same GUID value for hardware matching in Windows and Linux firmware. When installing firmware and drivers in Windows vendors can always use some generated HardwareID GUIDs that match useful things like the BIOS vendor and the product SKU. It would make sense to use the same scheme as Microsoft. There are a few issues in an otherwise simple plan.
The first, solved with a simple kernel patch I wrote, exposes a few more SMBIOS fields into /sys/class/dmi/id
that are required for the GUID calculation.
The second problem is a little more tricky. We don’t actually know how Microsoft joins the strings, what encoding is used, or more importantly the secret namespace UUID used to seed the GUID. The only thing we have got is the closed source ComputerHardwareIds.exe
program in the Windows DDK. This, luckily, runs in Wine although Wine isn’t able to get the system firmware data itself. This can be worked around, and actually makes testing easier.
So, some research. All we know from the MSDN page is that Each hardware ID string is converted into a GUID by using the SHA-1 hashing algorithm which actually tells us quite a bit. Generating a GUID from a SHA-1 hash means this has to be a type 5 UUID.
The reference code for a type-5 UUID is helpfully available in the IETF RFC document so it’s quite quick to get started with research. From a few minutes of searching online, the most likely symbols the program will be using are the BCrypt*
set of functions. From the RFC code, we call the checksum generation update function with first the encoded namespace (aha!) and then the encoded joined string (ahaha!). For Win32 programs, BCryptHashData
is the function we want to trace.
So, to check:
wine /home/hughsie/ComputerHardwareIds.exe /mfg "To be filled by O.E.M."
…matches the reference HardwareID-14
output from Microsoft. So onto debugging, using +relay
shows all the calling values and return values from each Win32 exported symbol:
WINEDEBUG=+relay winedbg --gdb ~/ComputerHardwareIds.exe
Wine-gdb> b BCryptHashData
Wine-gdb> r ~/ComputerHardwareIds.exe /mfg "To be filled by O.E.M." /family "To be filled by O.E.M."
005b:Call bcrypt.BCryptHashData(0011bab8,0033fcf4,00000010,00000000) ret=0100699d
Breakpoint 1, 0x7ffd85f8 in BCryptHashData () from /lib/wine/bcrypt.dll.so
Wine-gdb>
Great, so this is the secret namespace. The first parameter is the context, the second is the data address, the third is the length (0x10 as a length is indeed SHA-1) and the forth is the flags — so lets print out the data so we can see what it is:
Wine-gdb> x/16xb 0x0033fcf4
0x33fcf4: 0x70 0xff 0xd8 0x12 0x4c 0x7f 0x4c 0x7d
0x33fcfc: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
Using either the uuid
in python, or uuid_unparse
in libuuid, we can format the namespace to 70ffd812-4c7f-4c7d-0000-000000000000
— now this doesn’t look like a randomly generated UUID to me! Onto the next thing, the encoding and joining policy:
Wine-gdb> c
005f:Call bcrypt.BCryptHashData(0011bb90,00341458,0000005a,00000000) ret=010069b3
Breakpoint 1, 0x7ffd85f8 in BCryptHashData () from /lib/wine/bcrypt.dll.so
Wine-gdb> x/90xb 0x00341458
0x341458: 0x54 0x00 0x6f 0x00 0x20 0x00 0x62 0x00
0x341460: 0x65 0x00 0x20 0x00 0x66 0x00 0x69 0x00
0x341468: 0x6c 0x00 0x6c 0x00 0x65 0x00 0x64 0x00
0x341470: 0x20 0x00 0x62 0x00 0x79 0x00 0x20 0x00
0x341478: 0x4f 0x00 0x2e 0x00 0x45 0x00 0x2e 0x00
0x341480: 0x4d 0x00 0x2e 0x00 0x26 0x00 0x54 0x00
0x341488: 0x6f 0x00 0x20 0x00 0x62 0x00 0x65 0x00
0x341490: 0x20 0x00 0x66 0x00 0x69 0x00 0x6c 0x00
0x341498: 0x6c 0x00 0x65 0x00 0x64 0x00 0x20 0x00
0x3414a0: 0x62 0x00 0x79 0x00 0x20 0x00 0x4f 0x00
0x3414a8: 0x2e 0x00 0x45 0x00 0x2e 0x00 0x4d 0x00
0x3414b0: 0x2e 0x00
Wine-gdb> q
So there we go. The encoding looks like UTF-16 (as expected, much of the Windows API is this way) and the joining character seems to be &
. An extra nugget of information found through bitter experience is that the string has to be stripped of leading and trailing spaces.
I’ve written some code in fwupd so that this happens:
$ fwupdmgr hwids # use /usr/libexec/fwupd/fwupdtool hwids for fwupd > 1.1.1
Computer Information
--------------------
BiosVendor: LENOVO
BiosVersion: GJET75WW (2.25 )
Manufacturer: LENOVO
Family: ThinkPad T440s
ProductName: 20ARS19C0C
ProductSku: LENOVO_MT_20AR_BU_Think_FM_ThinkPad T440s
EnclosureKind: 10
BaseboardManufacturer: LENOVO
BaseboardProduct: 20ARS19C0C
Hardware IDs
------------
{c4159f74-3d2c-526f-b6d1-fe24a2fbc881} <- Manufacturer + Family + ProductName + ProductSku + BiosVendor + BiosVersion + BiosMajorRelease + BiosMinorRelease
{ff66cb74-5f5d-5669-875a-8a8f97be22c1} <- Manufacturer + Family + ProductName + BiosVendor + BiosVersion + BiosMajorRelease + BiosMinorRelease
{2e4dad4e-27a0-5de0-8e92-f395fc3fa5ba} <- Manufacturer + ProductName + BiosVendor + BiosVersion + BiosMajorRelease + BiosMinorRelease
{3faec92a-3ae3-5744-be88-495e90a7d541} <- Manufacturer + Family + ProductName + ProductSku + BaseboardManufacturer + BaseboardProduct
{660ccba8-1b78-5a33-80e6-9fb8354ee873} <- Manufacturer + Family + ProductName + ProductSku
{8dc9b7c5-f5d5-5850-9ab3-bd6f0549d814} <- Manufacturer + Family + ProductName
{178cd22d-ad9f-562d-ae0a-34009822cdbe} <- Manufacturer + ProductSku + BaseboardManufacturer + BaseboardProduct
{da1da9b6-62f5-5f22-8aaa-14db7eeda2a4} <- Manufacturer + ProductSku
{059eb22d-6dc7-59af-abd3-94bbe017f67c} <- Manufacturer + ProductName + BaseboardManufacturer + BaseboardProduct
{0cf8618d-9eff-537c-9f35-46861406eb9c} <- Manufacturer + ProductName
{f4275c1f-6130-5191-845c-3426247eb6a1} <- Manufacturer + Family + BaseboardManufacturer + BaseboardProduct
{db73af4c-4612-50f7-b8a7-787cf4871847} <- Manufacturer + Family
{5e820764-888e-529d-a6f9-dfd12bacb160} <- Manufacturer + EnclosureKind
{f8e1de5f-b68c-5f52-9d1a-f1ba52f1f773} <- Manufacturer + BaseboardManufacturer + BaseboardProduct
{6de5d951-d755-576b-bd09-c5cf66b27234} <- Manufacturer
Which basically matches the output of ComputerHardwareIds.exe
on the same hardware. I’ve merged the fwupd branch to master and vendors are already using the Microsoft HardwareID GUID values.