Die Lizenz steckt schon in der Firmware

Viele Leute besitzen eine Lizenz für das Fisher-Price OS (Windows), ohne je ihren Produktschlüssel gesehen zu haben. Sie kam mit der Maschine, und der Schlüssel lebt in der Firmware dieser Maschine, in einer kleinen ACPI-Tabelle namens MSDM.

Dann ist die Maschine nicht mehr der Ort, an dem die Arbeit passiert. Entweder will ihr Besitzer diese Lizenz in einer VM, die er jetzt bei dir mietet, oder die Maschine selbst wird gelöscht, zu einem Proxmox-Host gemacht, und die Kopie des Betriebssystems, mit der sie ausgeliefert wurde, soll als VM obendrauf zurück.

Technisch ist es eine QEMU-Option. Genau das ist das Problem. QEMU reicht jede Tabelle ungeprüft an einen Gast weiter, der Gast aktiviert mit dem Schlüssel, den er findet, und nirgends im ganzen Stapel fragt irgendetwas, ob die Lizenz das alles erlaubt. Dieser Beitrag behandelt, was die Tabelle ist, wie man sie in eine VM bringt, ohne eine kaputte weiterzureichen, was Microsofts Bedingungen über den Umzug sagen, und warum eine Volumenlizenz nie in ihre Nähe kommt.

Was die Tabelle ist

Seit OEM Activation 3.0 druckt ein PC-Hersteller den Schlüssel nicht mehr auf einen Aufkleber unten am Gehäuse, wo jeder mit einer Handykamera ihn abschreiben kann, sondern der Schlüssel kommt stattdessen in die Firmware der Maschine. Microsofts Fabrikwerkzeug „injects the product keys into the firmware“, und sein Prüfschritt kontrolliert, „that the MSDM table exists“ und dass Header und Einträge „comply with the correct formats“1. Eine so eingerichtete Maschine ist „activated by using the OA3 DPK in the firmware“2. MSDM ist eine gewöhnliche ACPI-Tabelle, und unter Linux liest man sie direkt aus der Firmware:

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

Der Laptop, auf dem dieser Text geschrieben wurde, trägt eine. 85 Byte.

Microsofts veröffentlichte Spezifikation definiert den üblichen ACPI-Header mit 36 Byte und der Signatur MSDM, und hört dann auf. Alles nach dem Header ist eine „Proprietary data structure that contains all the licensing data necessary to enable Windows activation“3.

ByteEnthältBekannt aus
0 bis 35der übliche ACPI-Header: Signatur MSDM, Länge, Prüfsumme, OEM-IDMicrosofts Spezifikation
36 bis 5520 Byte an Feldern: Version, Datentyp, Datenlängeechte Tabellen, keine veröffentlichte Spezifikation
56 bis 84der Produktschlüssel mit 29 Zeichen, fünf Gruppen zu fünfechte Tabellen, keine veröffentlichte Spezifikation

QEMU reicht alles weiter, was du ihm gibst

-acpitable file= in QEMU nimmt die „whole ACPI table from the specified files, including all ACPI headers (possible overridden by other options)“4, und der Gast sieht dann eine MSDM-Tabelle genau so, wie die Firmware eines Laptops sie präsentieren würde. Das wurde mit einem absichtlich gefälschten Schlüssel geprüft. Die Byte kamen auf der anderen Seite identisch heraus, von der Signatur bis zum letzten Zeichen.

QEMU lehnt nichts ab. Das macht hw/acpi/core.c mit einem kaputten Upload5:

Was an der Datei falsch istWas QEMU tutWas der Gast sieht
Länge im Header passt nicht zur Dateiwarnt, dann überschreibt es die Länge mit der echten Größeeinen gültigen Header
Prüfsumme ergibt nicht nullrechnet sie neu, bei jeder Tabelle, jedes Maleine gültige Prüfsumme
Schlüssel abgeschnitten oder verstümmeltnichts, die Daten nach dem Header gehen es nichts aneine gültig aussehende Tabelle um einen kaputten Schlüssel

Der Gast kann den Unterschied nicht erkennen, und du auch nicht, bis jemand ein Support-Ticket aufmacht. Das Erste, was man davon hört, ist ein Kunde, dessen Aktivierung fehlgeschlagen ist.

Wenn Kunden diese Tabellen also hochladen, prüf sie, bevor QEMU sie je zu sehen bekommt:

#!/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:]}")
PrüftMisst die Datei anEin Fehlschlag heißt
Signatur, Länge, PrüfsummeMicrosofts dokumentiertem Headerdie Datei ist kaputt
Version, Datentyp, Datenlänge, Form des Schlüsselsder Form, die echte Tabellen habensieh dir diese von Hand an, nicht „die ist gefälscht“

Er gibt nie den Schlüssel aus, nur die letzte Gruppe. Ein Produktschlüssel in einer Logdatei ist ein Produktschlüssel, den jemand anderes benutzen kann.

Gegen eine gute Tabelle und drei kaputte Kopien davon laufen gelassen:

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

Wo sie hinkommt, und wer sie dort hinlegen darf

Vom Upload des Kunden bis zum Schlüssel, mit dem der Gast aktiviertTabelle des Kundenmsdm.bin, 85 Bytevon seiner MaschineValidatorSignatur, Länge,Prüfsumme, SchlüsselAbgelegt/etc/pve/priv/nur root, jeder KnotenVM-args-acpitable file=nur root setzt esQEMUschreibt die Länge um,rechnet die Prüfsumme,lehnt nichts abGastMSDM in ACPI,Aktivierung liest Schlüssel
Der Validator sitzt vor QEMU, weil QEMU einen kaputten Header zurechtbiegt, statt ihn abzulehnen. Die Tabelle lebt in der privaten Hälfte des Cluster-Dateisystems, sie folgt der VM also auf jeden Knoten, und nur root kann sie lesen.

Ein Produktschlüssel ist ein Geheimnis. Er gehört nirgends hin, wo www-data ihn lesen kann, und pmxcfs gibt dir in /etc/pve genau einen Ort, an dem das nicht geht6:

Wo die Tabelle liegen könnteWer sie lesen kannAuf jedem Knoten, auf den die VM migrieren kann
/etc/pve/privnur rootja
irgendwo sonst in /etc/pvefür die Gruppe lesbar, www-data der Weboberfläche eingeschlossenja
ein Verzeichnis auf einem Knotenwas immer du einstellstnein

Mit 85 Byte ist sie nirgends in der Nähe der Dateigrenze von 1 MiB in 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 ersetzt die ganze Zeile. Trägt die VM schon SMBIOS- oder Firmware-Optionen in args, schreib sie alle zusammen aus. Und weil args nur root setzen darf, ist das eine Aufgabe für deine Provisionierung, nichts, was ein Kunde aus der Weboberfläche tun kann. So herum ist es richtig. Der Kunde liefert die Datei, deine Werkzeuge prüfen sie und hängen sie an.

Die Tabelle bewegt einen Schlüssel, kein Recht

Hier braucht die Funktion einen klaren Kopf. Die MSDM-Tabelle in eine VM zu bringen bewegt den Schlüssel. Nicht die Lizenz.

Microsofts eigene Bedingungen sagen das, und die Zeilen, auf die es ankommt, passen in eine Tabelle:

FallWas Microsoft sagtQuelle
OEM-Lizenz, Umzugan einen anderen Nutzer übertragbar „only with the licensed device“OEM-Lizenzbedingungen8
OEM-Lizenz, wie viele Installationen„only one instance of the software for use on one device, whether that device is physical or virtual“OEM-Lizenzbedingungen8
OEM-Lizenz, in einfachen Worten„locked“ an den ursprünglichen PC „and cannot be transferred to any other PC“Microsofts Blog für kleine Unternehmen9
Die Ausnahmedie Übertragungsbestimmungen „do not apply“, wenn die Software in Deutschland oder einem von mehreren anderen Ländern erworben wurdeOEM-Lizenzbedingungen8
Für Kunden hostendas Services Provider License Agreement ist für „hosted applications to end customers“SPLA10
Desktop-Editionen als gehostete VMs„VMs must be hosted by a Qualified Multitenant Hoster (QMTH)“Microsoft Learn11

Die Tabelle kommt immer von der eigenen lizenzierten Maschine des Kunden, nie von deinen Hosts. Der Fall, der klar innerhalb des Wortlauts liegt, ist der einfachste: die Maschine, mit der die Lizenz verkauft wurde, jetzt mit Proxmox, und dieselbe Kopie des Fisher-Price OS (Windows) in eine einzige VM darauf umgezogen. Das ist immer noch eine Instanz auf einem Gerät, und die OEM-Bedingungen erlauben das „whether that device is physical or virtual“8. Eine zweite VM, oder eine andere Maschine, und es ist das nicht mehr.

In diesem einen Fall sind die Maschine des Kunden und der Hypervisor dieselbe Kiste, es gibt also nichts hochzuladen und nichts zu kopieren. Der Proxmox-Kernel legt die MSDM-Tabelle der Firmware schon unter /sys/firmware/acpi/tables/ offen, nur für root lesbar, und QEMU läuft unter Proxmox als root, kann die Tabelle also direkt von dort nehmen:

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

Auf die Live-Tabelle zu zeigen statt auf eine Kopie hat zwei Folgen. Der Pfad wird auf dem Knoten gelesen, auf dem die VM startet, das gehört also auf einen einzelnen Host und nicht in einen Cluster: Migrierst du die VM, bekommt sie entweder die Tabelle dieses Knotens, eine andere Lizenz auf einer anderen Maschine, oder sie startet gar nicht, weil QEMU eine fehlende Datei mit can't open file … No such file or directory ablehnt. Und es ist eine Zeile, also ist es eine Zeile, die man in eine zweite VM einfügen kann. Diese zweite VM ist der Fall, den die Bedingungen nicht abdecken, also bekommt eine VM es und keine andere.

Bau die Funktion also. Der Mechanismus ist solide, und es gibt Kunden, die ihn nutzen dürfen; einer, der seine Lizenz in Deutschland gekauft hat, kann durchaus darunter sein. Aber leg die Berechtigung dorthin, wo sie hingehört: in deine Nutzungsbedingungen. Der Kunde sichert zu, dass er eine Lizenz hat, die den Betrieb in deiner VM abdeckt, und er tut das, bevor der Upload-Knopf irgendetwas tut. Der Validator beweist, dass die Tabelle wohlgeformt ist. Nichts beweist, dass er sie benutzen darf.

Volumenlizenzen kommen nie in die Nähe der Tabelle

Kann ein Kunde mit einer Volumenlizenz denselben Weg nehmen? Nein. Es gibt nichts, was die Tabelle tragen könnte.

MSDM gehört zum OEM-Kanal und sonst nirgends hin. Microsofts eigener Planungsleitfaden sagt, OEM-Aktivierung „is available only for computers that are purchased through OEM channels and have the Windows operating system preinstalled“12. Volumenlizenzen aktivieren über eines von drei anderen Modellen, „Multiple Activation Keys (MAK)“, „KMS“ und „Active Directory-based activation“12, und bei jedem davon wird der Schlüssel im Betriebssystem installiert. Nichts liest ihn aus der Firmware.

MethodeWo der Schlüssel lebtMit wem der Gast sprichtIn einer Proxmox-VM
OEM, OA 3.0die MSDM-Tabelle in der FirmwareMicrosofts Aktivierungsserverder Weg über -acpitable oben
MAKim Gast installiertMicrosoft, einmal, gezählt gegen die Aktivierungen des Schlüsselsgeht mit Internetzugang oder per Telefon
KMSein generischer Schlüssel (GVLK) im Gastder KMS-Host des Kunden auf TCP 1688braucht eine Route dorthin, und 25 Clients oder 5 Server, bevor er irgendetwas aktiviert
Active Directoryein GVLK im Gastdie Domänencontroller des Kunden, mindestens alle 180 Tagebraucht die VM in seiner Domäne
AVMAein generischer Schlüssel im Gastdie eigene Datacenter-Lizenz des Hyper-V-Hostsgar nicht verfügbar

Für einen Volumenkunden gibt es auf dem Hypervisor also nichts zu bauen. Sein Schlüssel kommt in sein Image, und dein Teil ist der Netzwerkpfad: Port 1688 zu seinem KMS-Host, oder ein VPN zu seinen Domänencontrollern. Der MSDM-Platz bleibt leer, und so soll es sein.

Zwei Dinge erwischen die Leute. Der generische Schlüssel auf Volumenmedien aktiviert für sich allein nichts, denn „the GVLK doesn’t work unless a valid KMS host key can be found“12, eine VM, die aus dem Enterprise-ISO des Kunden ohne Route nach Hause gebaut wurde, bleibt also unaktiviert. Und eine Desktop-Volumenlizenz ist für sich allein keine Lizenz. Volumenprogramme „cover upgrades to Windows client operating systems only“, und „an existing retail or OEM operating system license is needed for each computer“12. Die Volumenlizenz sitzt auf einer Basislizenz, sehr oft derselben OEM-Lizenz, um die es im letzten Abschnitt ging, und ob sie in deiner VM laufen darf, ist immer noch die Hosting-Frage aus dieser Tabelle, keine Aktivierungsfrage.

AVMA verdient eine eigene Zeile, weil es so aussieht, als wäre es die Antwort. Es ist Microsofts Weg, wie ein Host seine eigenen Server-Gäste aktiviert, indem er „the VM activation to the licensed virtualization host“ bindet, und es verlangt „a Windows Server Datacenter edition with the Hyper-V server host role installed“13. Microsoft sind beim Rest deutlich: „AVMA doesn’t work with other server virtualization technologies“13. Unter Proxmox aktivieren Server-Gäste über deine SPLA-Schlüssel oder über den eigenen KMS des Kunden.

Nichts im Stapel prüft die Papiere

Jede Schicht hier tut ihre Arbeit und nichts darüber hinaus. Die Firmware speichert einen Schlüssel. QEMU kopiert die Byte und räumt den Header auf. Der Gast liest den Schlüssel und aktiviert damit. Keine davon kann sagen, ob die Maschine darunter die ist, mit der die Lizenz verkauft wurde, und keine davon sollte das je können.

Die Prüfung landet also bei dem, der den Hypervisor betreibt. Eine Lizenz ist eine Vereinbarung zwischen dem, der sie gekauft hat, und Microsoft, und deine Plattform ist keine Partei davon, aber sie ist das, was den Schlüssel bewegt, und das macht die Frage zu deiner, ob du sie haben wolltest oder nicht.

Schreib die Antwort auf, bevor der Upload-Knopf irgendetwas tut. Eine Maschine, eine VM, die eigene Lizenz des Kunden und sein Wort, dass sie das abdeckt. Kostet nix, und es schützt dich genauso wie ihn. Die Software bewegt jeden Schlüssel, den man ihr gibt. Ob sie das hätte tun sollen, ist eine Frage, die nur ein Mensch beantworten kann, also sorg dafür, dass einer es tut.


  1. Microsoft Learn, OA 3.0 on the factory floor — „injects the product keys into the firmware“; /Validate prüft, „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) — Tabelle 2, Offset 36: „Proprietary data structure that contains all the licensing data necessary to enable Windows activation.“ ↩︎

  4. QEMU, System Emulation, Invocation — -smbios-Felder pro Typ; -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“ ist ein warn_report, danach wird die Länge überschrieben und die Prüfsumme neu berechnet. ↩︎

  6. pve-cluster, src/pmxcfs/pmxcfs.c — Pfade unter priv werden auf 0777700 maskiert, alles andere auf 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“; die Übertragungsbestimmungen „do not apply if you acquired the software in Germany“ oder in den aufgeführten Ländern. Archivierte Kopie; die Live-Seite verweigert automatisierte Anfragen. ↩︎ ↩︎ ↩︎ ↩︎

  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 — die drei Modelle der Volumenaktivierung; die KMS-Schwellen von „at least five computers“ für Server und „at least 25 computers“ für Clients, auf TCP-Port 1688; Active Directory-based Activation braucht die Domäne „at least once every 180 days“; „the GVLK doesn’t work unless a valid KMS host key can be found“; Volumenlizenzen „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.“ ↩︎ ↩︎