La licencia ya está en el firmware

Mucha gente tiene una licencia del Fisher-Price OS (Windows) sin haber visto nunca su clave de producto. Vino con la máquina, y la clave vive en el firmware de esa máquina, en una pequeña tabla ACPI llamada MSDM.

Luego la máquina deja de ser donde se hace el trabajo. O su dueño quiere esa licencia en una VM que ahora te alquila a ti, o la propia máquina se borra, se convierte en un host Proxmox, y se quiere recuperar como VM encima la copia del SO con la que vino.

Mecánicamente, es una opción de QEMU. Ese es el problema. QEMU le pasa cualquier tabla a un invitado sin comprobarla, el invitado se activa con la clave que encuentre, y nada en ningún punto de la pila pregunta si la licencia permite algo de esto. Este artículo cubre qué es la tabla, cómo meterla en una VM sin pasar una corrupta, qué dicen los términos de Microsoft sobre moverla, y por qué una licencia por volumen nunca se acerca a ella.

Qué es la tabla

Desde OEM Activation 3.0, un fabricante de PC ya no imprime la clave en una pegatina debajo de la caja, donde cualquiera con la cámara del móvil puede copiarla, y la clave va en cambio al firmware de la máquina. Las herramientas de fábrica de Microsoft «injects the product keys into the firmware», y su paso de validación comprueba «that the MSDM table exists» y que su cabecera y sus entradas «comply with the correct formats»1. Una máquina preparada así queda «activated by using the OA3 DPK in the firmware»2. MSDM es una tabla ACPI normal, y en Linux se lee directamente del firmware:

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

El portátil en el que se escribió esto lleva una. 85 bytes.

La especificación publicada de Microsoft define la cabecera ACPI estándar de 36 bytes con la firma MSDM, y ahí se para. Todo lo que hay detrás de la cabecera es una «Proprietary data structure that contains all the licensing data necessary to enable Windows activation»3.

BytesContieneSe sabe por
0 a 35la cabecera ACPI estándar: firma MSDM, longitud, checksum, OEM IDla especificación de Microsoft
36 a 5520 bytes de campos: versión, tipo de datos, longitud de datostablas reales, no una especificación publicada
56 a 84la clave de producto de 29 caracteres, cinco grupos de cincotablas reales, no una especificación publicada

QEMU pasa cualquier cosa que le des

El -acpitable file= de QEMU toma la «whole ACPI table from the specified files, including all ACPI headers (possible overridden by other options)»4, y el invitado ve entonces una tabla MSDM exactamente como la presentaría el firmware de un portátil. Se comprobó con una clave falsa a propósito. Los bytes salieron por el otro lado idénticos, de la firma al último carácter.

QEMU no rechaza nada. Esto es lo que hace hw/acpi/core.c con una subida rota5:

Qué le pasa al ficheroQué hace QEMUQué ve el invitado
La longitud de la cabecera no coincide con el ficheroavisa, y luego sobrescribe la longitud con el tamaño realuna cabecera válida
El checksum no suma cerolo recalcula, en cada tabla, cada vezun checksum válido
Clave truncada o destrozadanada, los datos detrás de la cabecera no son cosa suyauna tabla de aspecto válido alrededor de una clave rota

El invitado no tiene forma de notar la diferencia, y tú tampoco hasta que alguien abre una incidencia de soporte. Lo primero que se sabe de ello es un cliente cuya activación ha fallado.

Así que si tus clientes suben estas tablas, compruébalas antes de que QEMU las vea:

#!/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:]}")
CompruebaExige que el fichero cumplaUn fallo significa
Firma, longitud, checksumla cabecera documentada por Microsoftel fichero está roto
Versión, tipo de datos, longitud de datos, forma de la clavela forma que tienen las tablas realesmira esta a mano, no «esto es falso»

Nunca imprime la clave, solo el último grupo. Una clave de producto en un fichero de log es una clave de producto que otro puede usar.

Lanzado contra una tabla buena y tres copias rotas de ella:

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

Dónde va, y quién puede ponerla ahí

De la subida del cliente a la clave con la que se activa el invitadoTabla del clientemsdm.bin, 85 bytesde su propia máquinaValidadorfirma, longitud,checksum, forma de claveGuardada/etc/pve/priv/solo root, en cada nodoargs de la VM-acpitable file=solo la pone rootQEMUreescribe la longitud,recalcula el checksum,no rechaza nadaInvitadoMSDM en ACPI,la activación lee la clave
El validador va delante de QEMU porque QEMU arregla una cabecera rota en vez de rechazarla. La tabla vive en la mitad privada del sistema de ficheros del clúster, así que sigue a la VM a cualquier nodo y solo root puede leerla.

Una clave de producto es un secreto. No pinta nada en ningún sitio donde www-data pueda leerla, y pmxcfs te da exactamente un sitio en /etc/pve donde no puede6:

Dónde podría vivir la tablaQuién puede leerlaEn cada nodo al que la VM puede migrar
/etc/pve/privsolo rootsí
cualquier otro sitio de /etc/pvelegible por el grupo, incluido el www-data de la interfaz websí
un directorio en un nodolo que tú pongasno

Con 85 bytes queda lejísimos del límite de 1 MiB por fichero 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 sustituye la línea entera. Si la VM ya lleva opciones de SMBIOS o de firmware en args, escríbelas todas juntas. Y como args es solo para root, esto es trabajo de tu aprovisionamiento, no algo que un cliente pueda hacer desde la interfaz web. Así es como debe ser. El cliente aporta el fichero, tus herramientas lo comprueban y lo conectan.

La tabla mueve una clave, no un derecho

Aquí es donde la funcionalidad pide la cabeza fría. Mover la tabla MSDM a una VM mueve la clave. No la licencia.

Los propios términos de Microsoft lo dicen, y las líneas que importan caben en una tabla:

CasoLo que dice MicrosoftFuente
Licencia OEM, moverlatransferible a otro usuario «only with the licensed device»términos de licencia OEM8
Licencia OEM, cuántas instalaciones«only one instance of the software for use on one device, whether that device is physical or virtual»términos de licencia OEM8
Licencia OEM, en palabras llanas«locked» al PC original «and cannot be transferred to any other PC»blog de pequeña empresa de Microsoft9
La excepciónlas cláusulas de transferencia «do not apply» cuando el software se adquirió en Alemania o en una lista de otros paísestérminos de licencia OEM8
Alojarla para clientesel Services Provider License Agreement es para «hosted applications to end customers»SPLA10
Ediciones de escritorio como VM alojadas«VMs must be hosted by a Qualified Multitenant Hoster (QMTH)»Microsoft Learn11

La tabla siempre sale de la propia máquina licenciada del cliente, nunca de tus hosts. El caso que cae de lleno dentro de la letra es el más sencillo: la máquina con la que se vendió la licencia, ahora con Proxmox, con esa misma copia del Fisher-Price OS (Windows) movida a una única VM encima. Sigue siendo una instancia en un dispositivo, y los términos OEM lo permiten «whether that device is physical or virtual»8. Una segunda VM, u otra máquina, y ya no.

En ese único caso la máquina del cliente y el hipervisor son la misma caja, así que no hay nada que subir ni nada que copiar. El kernel de Proxmox ya expone la tabla MSDM del firmware bajo /sys/firmware/acpi/tables/, legible solo por root, y QEMU en Proxmox corre como root, así que puede tomar la tabla directamente de ahí:

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

Apuntar a la tabla viva en vez de a una copia tiene dos consecuencias. La ruta se lee en el nodo donde arranque la VM, así que esto va en un host independiente y no en un clúster: migra la VM y o bien recibe la tabla de ese nodo, otra licencia de otra máquina, o no arranca en absoluto, porque QEMU rechaza un fichero que falta con can't open file … No such file or directory. Y es una línea, lo que la hace una línea fácil de pegar en una segunda VM. Esa segunda VM es el caso que los términos no cubren, así que la recibe una VM y ninguna más.

Así que construye la funcionalidad. El mecanismo es sólido, y hay clientes con derecho a usarlo; uno que compró su licencia en Alemania bien puede estar entre ellos. Pero pon el derecho donde le toca: en tus condiciones de servicio, el cliente garantiza que tiene una licencia que cubre ejecutarla en tu VM, y lo hace antes de que el botón de subida haga nada. El validador demuestra que la tabla está bien formada. Nada demuestra que sea suya para usarla.

Las licencias por volumen nunca se acercan a la tabla

¿Puede un cliente con una licencia por volumen usar la misma vía? No. No hay nada que la tabla pueda llevar.

MSDM pertenece al canal OEM y a nada más. La propia guía de planificación de Microsoft dice que la activación OEM «is available only for computers that are purchased through OEM channels and have the Windows operating system preinstalled»12. Las licencias por volumen se activan con uno de otros tres modelos, «Multiple Activation Keys (MAK)», «KMS» y «Active Directory-based activation»12, y en todos ellos la clave se instala en el sistema operativo. Nada la lee del firmware.

MétodoDónde vive la claveCon qué habla el invitadoEn una VM de Proxmox
OEM, OA 3.0la tabla MSDM en el firmwarelos servidores de activación de Microsoftla vía de -acpitable de arriba
MAKinstalada en el invitadoMicrosoft, una vez, descontada de las activaciones de la clavefunciona con acceso a internet o por teléfono
KMSuna clave genérica (GVLK) en el invitadoel host KMS del cliente en TCP 1688necesita ruta hasta él, y 25 clientes o 5 servidores antes de activar nada
Active Directoryuna GVLK en el invitadolos controladores de dominio del cliente, al menos cada 180 díasnecesita la VM unida a su dominio
AVMAuna clave genérica en el invitadola propia licencia Datacenter del host Hyper-Vno está disponible en absoluto

Así que para un cliente con volumen no hay nada que construir en el hipervisor. Su clave va en su imagen, y lo tuyo es el camino de red: el puerto 1688 hasta su host KMS, o una VPN hasta sus controladores de dominio. El hueco de MSDM se queda vacío, y así debe ser.

Dos cosas pillan a la gente. La clave genérica del soporte por volumen no activa nada por sí sola, porque «the GVLK doesn’t work unless a valid KMS host key can be found»12, así que una VM construida desde la ISO Enterprise del cliente sin camino de vuelta a casa se queda sin activar. Y una licencia de escritorio por volumen no es una licencia por sí sola. Los programas por volumen «cover upgrades to Windows client operating systems only», y «an existing retail or OEM operating system license is needed for each computer»12. La licencia por volumen se apoya en una licencia base, muy a menudo la misma OEM de la que iba la sección anterior, y si puede correr en tu VM sigue siendo la pregunta de alojamiento de esa tabla, no una de activación.

AVMA se gana una línea propia, porque es la que parece la respuesta. Es la forma de Microsoft de que un host active sus propios invitados servidor, ligando «the VM activation to the licensed virtualization host», y exige «a Windows Server Datacenter edition with the Hyper-V server host role installed»13. Microsoft son claros con el resto: «AVMA doesn’t work with other server virtualization technologies»13. En Proxmox, los invitados servidor se activan con tus claves SPLA o con el propio KMS del cliente.

Nada en la pila comprueba los papeles

Cada capa aquí hace su trabajo y nada más. El firmware guarda una clave. QEMU copia los bytes y arregla la cabecera. El invitado lee la clave y se activa con ella. Ninguna puede saber si la máquina de debajo es aquella con la que se vendió la licencia, y ninguna se pensó nunca para eso.

Así que la comprobación cae en quien gestiona el hipervisor. Una licencia es un acuerdo entre quien la compró y Microsoft, y tu plataforma no es parte de él, pero es lo que mueve la clave, y eso hace la pregunta tuya la pidieras o no.

Deja la respuesta por escrito antes de que el botón de subida haga nada. Una máquina, una VM, la licencia propia del cliente y su palabra de que cubre esto. No cuesta nada, y te protege a ti tanto como a él. El software moverá cualquier clave que le den. Si debería haberlo hecho es una pregunta que solo una persona puede responder, así que asegúrate de que alguna lo haga.


  1. Microsoft Learn, OA 3.0 on the factory floor — «injects the product keys into the firmware»; /Validate comprueba «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) — tabla 2, desplazamiento 36: «Proprietary data structure that contains all the licensing data necessary to enable Windows activation.» ↩︎

  4. QEMU, System Emulation, Invocation — campos de -smbios por tipo; -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» es un warn_report, y luego se sobrescribe la longitud y se recalcula el checksum. ↩︎

  6. pve-cluster, src/pmxcfs/pmxcfs.c — las rutas bajo priv se enmascaran a 0777700, todo lo demás a 0777750. ↩︎

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

  8. Microsoft, términos de licencia OEM de Windows 11 — «you may transfer the license to use the software directly to another user, only with the licensed device»; las cláusulas de transferencia «do not apply if you acquired the software in Germany» o en los países de la lista. Copia archivada; la página en vivo rechaza las peticiones automáticas. ↩︎ ↩︎ ↩︎ ↩︎

  9. Microsoft, archivo del blog de pequeña empresa — «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 — los tres modelos de activación por volumen; los umbrales de KMS de «at least five computers» para servidores y «at least 25 computers» para clientes, en el puerto TCP 1688; la activación basada en Active Directory, que necesita el dominio «at least once every 180 days»; «the GVLK doesn’t work unless a valid KMS host key can be found»; las licencias por volumen «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.» ↩︎ ↩︎