La licence est déjà dans le firmware

Beaucoup de gens possèdent une licence du Fisher-Price OS (Windows) sans en avoir jamais vu la clé de produit. Elle est venue avec la machine, et la clé vit dans le firmware de cette machine, dans une petite table ACPI appelée MSDM.

Puis la machine cesse d’être l’endroit où se fait le travail. Soit son propriétaire veut cette licence dans une VM qu’il vous loue désormais, soit la machine elle-même est effacée, transformée en hôte Proxmox, et la copie de l’OS livrée avec elle est voulue de retour sous forme de VM par-dessus.

Mécaniquement, c’est une seule option QEMU. C’est bien le problème. QEMU transmet n’importe quelle table à un invité sans la vérifier, l’invité s’active avec la clé qu’il trouve, et rien, nulle part dans la pile, ne demande si la licence permet quoi que ce soit de tout cela. Ce billet couvre ce qu’est la table, comment la faire entrer dans une VM sans en transmettre une corrompue, ce que disent les conditions de Microsoft sur son déplacement, et pourquoi une licence en volume ne s’en approche jamais.

Ce qu’est la table

Depuis OEM Activation 3.0, un fabricant de PC n’imprime plus la clé sur une étiquette sous le boîtier, où n’importe qui avec un téléphone peut la photographier, et la clé va dans le firmware de la machine à la place. L’outillage d’usine de Microsoft « injects the product keys into the firmware », et son étape de validation vérifie « that the MSDM table exists » et que son en-tête et ses entrées « comply with the correct formats »1. Une machine préparée ainsi est « activated by using the OA3 DPK in the firmware »2. MSDM est une table ACPI ordinaire, et sous Linux elle se lit directement depuis le firmware :

sudo cat /sys/firmware/acpi/tables/MSDM > msdm.bin

Le portable sur lequel ce billet a été écrit en porte une. 85 octets.

La spécification publiée par Microsoft définit l’en-tête ACPI standard de 36 octets avec la signature MSDM, puis s’arrête. Tout ce qui suit l’en-tête est une « Proprietary data structure that contains all the licensing data necessary to enable Windows activation »3.

OctetsContientConnu par
0 à 35l’en-tête ACPI standard : signature MSDM, longueur, somme de contrôle, OEM IDla spécification de Microsoft
36 à 5520 octets de champs : version, type de données, longueur des donnéesde vraies tables, pas une spécification publiée
56 à 84la clé de produit de 29 caractères, cinq groupes de cinqde vraies tables, pas une spécification publiée

QEMU transmettra tout ce que vous lui donnez

Le -acpitable file= de QEMU prend la « whole ACPI table from the specified files, including all ACPI headers (possible overridden by other options) »4, et l’invité voit alors une table MSDM exactement comme le firmware d’un portable la présenterait. Cela a été vérifié avec une clé délibérément fausse. Les octets sont ressortis de l’autre côté identiques, de la signature au dernier caractère.

QEMU ne refuse rien. Voici ce que fait hw/acpi/core.c d’un fichier cassé5 :

Ce qui ne va pas dans le fichierCe que fait QEMUCe que voit l’invité
La longueur de l’en-tête ne correspond pas au fichieravertit, puis écrase la longueur avec la taille réelleun en-tête valide
La somme de contrôle ne donne pas zérola recalcule, sur chaque table, à chaque foisune somme de contrôle valide
Clé tronquée ou abîméerien, les données après l’en-tête ne le regardent pasune table d’apparence valide autour d’une clé cassée

L’invité n’a aucun moyen de faire la différence, et vous non plus tant que quelqu’un n’a pas ouvert un ticket de support. La première fois que quiconque en entend parler, c’est un client dont l’activation a échoué.

Si des clients les téléversent, vérifiez-les donc avant que QEMU les voie :

#!/usr/bin/env python3
"""Refuse anything that is not a well-formed MSDM table before QEMU sees it."""
import re, struct, sys

t = open(sys.argv[1], 'rb').read()
fail = lambda why: sys.exit(f"{sys.argv[1]}: {why}")

if len(t) < 56:                       fail(f"{len(t)} bytes, too short for an MSDM table")
sig, length = t[:4], struct.unpack_from('<I', t, 4)[0]
if sig != b'MSDM':                    fail(f"signature is {sig!r}, not MSDM")
if length != len(t):                  fail(f"header says {length} bytes, file is {len(t)}")
if sum(t) & 0xff:                     fail("checksum does not sum to zero")
ver, _, dtype, _, dlen = struct.unpack_from('<5I', t, 36)
if (ver, dtype) != (1, 1):            fail(f"version {ver}, data type {dtype}: expected 1 and 1")
if dlen != 29 or 56 + dlen != length: fail(f"data length {dlen}: expected 29")
key = t[56:].decode('ascii', 'replace')
if not re.fullmatch(r'([0-9A-Z]{5}-){4}[0-9A-Z]{5}', key): fail("data is not a 5x5 product key")
print(f"OK  OEM {t[10:16].decode(errors='replace').strip()!r}  key *****-*****-*****-*****-{key[-5:]}")
VérifieTient le fichier àUn échec veut dire
Signature, longueur, somme de contrôlel’en-tête documenté par Microsoftle fichier est cassé
Version, type de données, longueur des données, forme de la cléla forme que portent les vraies tablesregardez celui-ci à la main, pas « c’est un faux »

Il n’affiche jamais la clé, seulement le dernier groupe. Une clé de produit dans un fichier journal est une clé de produit que quelqu’un d’autre peut utiliser.

Lancé contre une bonne table et trois copies cassées de celle-ci :

OK  OEM 'EXMPLE'  key *****-*****-*****-*****-EEEEE
bad-sum.bin: checksum does not sum to zero
bad-trunc.bin: header says 85 bytes, file is 70
bad-sig.bin: signature is b'SLIC', not MSDM

Où elle va, et qui peut l’y mettre

Du fichier téléversé par le client à la clé avec laquelle l'invité s'activeTable du clientmsdm.bin, 85 octetsde sa propre machineValidateursignature, longueur,somme, forme de la cléStockée/etc/pve/priv/root seul, chaque nœudargs de la VM-acpitable file=défini par root seulQEMUréécrit la longueur,recalcule la somme,ne refuse rienInvitéMSDM dans l'ACPI,l'activation lit la clé
Le validateur se place devant QEMU parce que QEMU répare un en-tête cassé plutôt que de le refuser. La table vit dans la moitié privée du système de fichiers du cluster, si bien qu’elle suit la VM sur n’importe quel nœud et que seul root peut la lire.

Une clé de produit est un secret. Elle n’a rien à faire là où www-data peut la lire, et pmxcfs vous donne exactement un endroit dans /etc/pve où il ne le peut pas6 :

Où la table pourrait vivreQui peut la lireSur chaque nœud où la VM peut migrer
/etc/pve/privroot seulementoui
n’importe où ailleurs dans /etc/pvelisible par le groupe, www-data de l’interface web comprisoui
un répertoire sur un seul nœudce que vous avez défininon

À 85 octets, elle est très loin de la limite de 1 MiB par fichier de pmxcfs7 :

mkdir -p /etc/pve/priv/msdm
check-msdm.py upload.bin && cp upload.bin /etc/pve/priv/msdm/9000.bin
qm set 9000 --args "-acpitable file=/etc/pve/priv/msdm/9000.bin"

qm set --args remplace la ligne entière. Si la VM porte déjà des options SMBIOS ou de firmware dans args, écrivez-les toutes ensemble. Et comme args est réservé à root, c’est un travail pour votre provisionnement, pas quelque chose qu’un client peut faire depuis l’interface web. C’est le bon sens. Le client fournit le fichier, vos outils le vérifient et l’attachent.

La table déplace une clé, pas un droit

C’est ici que la fonctionnalité demande la tête froide. Déplacer la table MSDM dans une VM déplace la clé. Pas la licence.

Les conditions de Microsoft elles-mêmes le disent, et les lignes qui comptent tiennent dans une table :

CasCe que dit MicrosoftSource
Licence OEM, la déplacertransférable à un autre utilisateur « only with the licensed device »conditions de licence OEM8
Licence OEM, combien d’installations« only one instance of the software for use on one device, whether that device is physical or virtual »conditions de licence OEM8
Licence OEM, en clair« locked » au PC d’origine « and cannot be transferred to any other PC »blog Microsoft pour les petites entreprises9
L’exceptionles dispositions de transfert « do not apply » lorsque le logiciel a été acquis en Allemagne ou dans une liste d’autres paysconditions de licence OEM8
L’héberger pour des clientsle Services Provider License Agreement est destiné aux « hosted applications to end customers »SPLA10
Éditions de bureau en VM hébergées« VMs must be hosted by a Qualified Multitenant Hoster (QMTH) »Microsoft Learn11

La table vient toujours de la machine sous licence du client lui-même, jamais de vos hôtes. Le cas qui tombe carrément dans le texte est le plus simple : la machine avec laquelle la licence a été vendue, qui fait maintenant tourner Proxmox, avec cette même copie du Fisher-Price OS (Windows) déplacée dans une seule VM dessus. C’est toujours une instance sur un appareil, et les conditions OEM l’autorisent « whether that device is physical or virtual »8. Une deuxième VM, ou une autre machine, et ce n’est plus le cas.

Dans ce cas précis, la machine du client et l’hyperviseur sont la même boîte, il n’y a donc rien à téléverser et rien à copier. Le noyau de Proxmox expose déjà la table MSDM du firmware sous /sys/firmware/acpi/tables/, lisible par root seulement, et QEMU sur Proxmox tourne en root, il peut donc prendre la table directement là :

qm set 100 --args "-acpitable file=/sys/firmware/acpi/tables/MSDM"

Pointer vers la table vivante plutôt que vers une copie a deux conséquences. Le chemin est lu sur le nœud où la VM démarre, quel qu’il soit, ceci a donc sa place sur un hôte isolé et pas dans un cluster : migrez la VM et soit elle reçoit la table de ce nœud, une autre licence sur une autre machine, soit elle ne démarre pas du tout, parce que QEMU refuse un fichier manquant avec can't open file … No such file or directory. Et c’est une ligne, ce qui en fait une ligne à coller sur une deuxième VM. Cette deuxième VM est le cas que les conditions ne couvrent pas, alors une seule VM la reçoit et aucune autre.

Construisez donc la fonctionnalité. Le mécanisme est solide, et il y a des clients qui ont le droit de s’en servir ; celui qui a acheté sa licence en Allemagne pourrait bien en faire partie. Mais mettez le droit à sa place : dans vos conditions de service, le client garantit qu’il détient une licence qui couvre son exécution dans votre VM, et il le fait avant que le bouton de téléversement fasse quoi que ce soit. Le validateur prouve que la table est bien formée. Rien ne prouve qu’elle est à lui.

Les licences en volume ne s’approchent jamais de la table

Un client avec une licence en volume peut-il prendre le même chemin ? Non. La table n’a rien à porter.

MSDM appartient au canal OEM et à rien d’autre. Le guide de planification de Microsoft lui-même dit que l’activation OEM « is available only for computers that are purchased through OEM channels and have the Windows operating system preinstalled »12. Les licences en volume s’activent par l’un de trois autres modèles, « Multiple Activation Keys (MAK) », « KMS » et « Active Directory-based activation »12, et dans chacun d’eux la clé est installée dans le système d’exploitation. Rien ne la lit depuis le firmware.

MéthodeOù vit la cléÀ quoi parle l’invitéDans une VM Proxmox
OEM, OA 3.0la table MSDM dans le firmwareles serveurs d’activation de Microsoftla voie -acpitable ci-dessus
MAKinstallée dans l’invitéMicrosoft, une fois, décomptée des activations de la clémarche avec un accès internet ou par téléphone
KMSune clé générique (GVLK) dans l’invitél’hôte KMS du client sur TCP 1688il faut une route vers lui, et 25 clients ou 5 serveurs avant qu’il n’active quoi que ce soit
Active Directoryune GVLK dans l’invitéles contrôleurs de domaine du client, au moins tous les 180 joursil faut que la VM soit jointe à leur domaine
AVMAune clé générique dans l’invitéla licence Datacenter propre de l’hôte Hyper-Vpas disponible du tout

Pour un client en volume, il n’y a donc rien à construire sur l’hyperviseur. Sa clé va dans son image, et votre part est le chemin réseau : le port 1688 vers son hôte KMS, ou un VPN vers ses contrôleurs de domaine. L’emplacement MSDM reste vide, et c’est normal.

Deux choses piègent les gens. La clé générique des médias en volume n’active rien toute seule, parce que « the GVLK doesn’t work unless a valid KMS host key can be found »12, si bien qu’une VM construite depuis l’ISO Enterprise du client sans route vers chez lui reste non activée. Et une licence en volume pour poste de travail n’est pas une licence à elle seule. Les programmes en volume « cover upgrades to Windows client operating systems only », et « an existing retail or OEM operating system license is needed for each computer »12. La licence en volume se pose sur une licence de base, très souvent la même licence OEM dont parlait la section précédente, et savoir si elle peut tourner dans votre VM reste la question d’hébergement de cette table, pas une question d’activation.

AVMA mérite une ligne à part, parce que c’est celle qui ressemble à la réponse. C’est le moyen que Microsoft donne à un hôte pour activer ses propres invités serveur, en liant « the VM activation to the licensed virtualization host », et il exige « a Windows Server Datacenter edition with the Hyper-V server host role installed »13. Microsoft est clair sur le reste : « AVMA doesn’t work with other server virtualization technologies »13. Sur Proxmox, les invités serveur s’activent par vos clés SPLA ou par le KMS du client.

Rien dans la pile ne vérifie les papiers

Chaque couche ici fait son travail et rien de plus. Le firmware stocke une clé. QEMU copie les octets et range l’en-tête. L’invité lit la clé et s’active avec. Aucune ne peut dire si la machine en dessous est celle avec laquelle la licence a été vendue, et aucune n’a jamais été faite pour.

La vérification retombe donc sur celui qui fait tourner l’hyperviseur. Une licence est un accord entre la personne qui l’a achetée et Microsoft, et votre plateforme n’en est pas partie, mais c’est elle qui déplace la clé, et cela fait de la question la vôtre, que vous l’ayez demandé ou non.

Écrivez la réponse avant que le bouton de téléversement fasse quoi que ce soit. Une machine, une VM, la propre licence du client et sa parole qu’elle couvre ceci. Ça ne coûte rien, et ça vous protège autant que lui. Le logiciel déplacera n’importe quelle clé qu’on lui tend. Savoir s’il aurait dû est une question à laquelle seule une personne peut répondre, alors assurez-vous qu’une personne y ait répondu.


  1. Microsoft Learn, OA 3.0 on the factory floor — « injects the product keys into the firmware » ; /Validate vérifie « that the MSDM table exists ». ↩︎

  2. Microsoft Learn, Validate an OEM Activation key — « is activated by using the OA3 DPK in the firmware ». ↩︎

  3. Microsoft, Microsoft Software Licensing Tables (SLIC and MSDM) — table 2, décalage 36 : « Proprietary data structure that contains all the licensing data necessary to enable Windows activation. » ↩︎

  4. QEMU, System Emulation, Invocation — les champs de -smbios par type ; -acpitable « For file=, take whole ACPI table from the specified files, including all ACPI headers » ; -boot « Currently Seabios for X86 system support it. » ↩︎

  5. QEMU v11.0.3, hw/acpi/core.c — « ACPI table has wrong length » est un warn_report, puis la longueur est écrasée et la somme de contrôle recalculée. ↩︎

  6. pve-cluster, src/pmxcfs/pmxcfs.c — les chemins sous priv sont masqués en 0777700, tout le reste en 0777750. ↩︎

  7. pve-cluster, src/pmxcfs/memdb.h — #define MEMDB_MAX_FILE_SIZE (1024 * 1024) // 1 MiB. ↩︎

  8. Microsoft, Windows 11 OEM licence terms — « you may transfer the license to use the software directly to another user, only with the licensed device » ; les dispositions de transfert « do not apply if you acquired the software in Germany » ou dans les pays listés. Copie archivée ; la page en ligne refuse les requêtes automatisées. ↩︎ ↩︎ ↩︎ ↩︎

  9. Microsoft, small business blog archive — « the OEM Windows license is ’locked’ to the original PC it comes with and cannot be transferred to any other PC. » ↩︎

  10. Microsoft, Services Provider License Agreement — « for service providers and software development companies licensing eligible Microsoft products to provide software services and hosted applications to end customers. » ↩︎

  11. Microsoft Learn, Windows subscription activation for VDA — « VMs must be hosted by a Qualified Multitenant Hoster (QMTH). » ↩︎

  12. Microsoft Learn, Plan for volume activation — les trois modèles d’activation en volume ; les seuils KMS de « at least five computers » pour les serveurs et « at least 25 computers » pour les clients, sur le port TCP 1688 ; l’activation par Active Directory qui a besoin du domaine « at least once every 180 days » ; « the GVLK doesn’t work unless a valid KMS host key can be found » ; les licences en volume « cover upgrades to Windows client operating systems only ». ↩︎ ↩︎ ↩︎ ↩︎

  13. Microsoft Learn, Automatic Virtual Machine Activation in Windows Server — « AVMA requires a Windows Server Datacenter edition with the Hyper-V server host role installed » ; « AVMA doesn’t work with other server virtualization technologies. » ↩︎ ↩︎