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.
| Bytes | Contiene | Se sabe por |
|---|---|---|
| 0 a 35 | la cabecera ACPI estándar: firma MSDM, longitud, checksum, OEM ID | la especificación de Microsoft |
| 36 a 55 | 20 bytes de campos: versión, tipo de datos, longitud de datos | tablas reales, no una especificación publicada |
| 56 a 84 | la clave de producto de 29 caracteres, cinco grupos de cinco | tablas 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 fichero | Qué hace QEMU | Qué ve el invitado |
|---|---|---|
| La longitud de la cabecera no coincide con el fichero | avisa, y luego sobrescribe la longitud con el tamaño real | una cabecera válida |
| El checksum no suma cero | lo recalcula, en cada tabla, cada vez | un checksum válido |
| Clave truncada o destrozada | nada, los datos detrás de la cabecera no son cosa suya | una 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:]}")
| Comprueba | Exige que el fichero cumpla | Un fallo significa |
|---|---|---|
| Firma, longitud, checksum | la cabecera documentada por Microsoft | el fichero está roto |
| Versión, tipo de datos, longitud de datos, forma de la clave | la forma que tienen las tablas reales | mira 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í
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 tabla | Quién puede leerla | En cada nodo al que la VM puede migrar |
|---|---|---|
/etc/pve/priv | solo root | sí |
cualquier otro sitio de /etc/pve | legible por el grupo, incluido el www-data de la interfaz web | sí |
| un directorio en un nodo | lo que tú pongas | no |
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:
| Caso | Lo que dice Microsoft | Fuente |
|---|---|---|
| Licencia OEM, moverla | transferible 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ón | las cláusulas de transferencia «do not apply» cuando el software se adquirió en Alemania o en una lista de otros países | términos de licencia OEM8 |
| Alojarla para clientes | el 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étodo | Dónde vive la clave | Con qué habla el invitado | En una VM de Proxmox |
|---|---|---|---|
| OEM, OA 3.0 | la tabla MSDM en el firmware | los servidores de activación de Microsoft | la vía de -acpitable de arriba |
| MAK | instalada en el invitado | Microsoft, una vez, descontada de las activaciones de la clave | funciona con acceso a internet o por teléfono |
| KMS | una clave genérica (GVLK) en el invitado | el host KMS del cliente en TCP 1688 | necesita ruta hasta él, y 25 clientes o 5 servidores antes de activar nada |
| Active Directory | una GVLK en el invitado | los controladores de dominio del cliente, al menos cada 180 días | necesita la VM unida a su dominio |
| AVMA | una clave genérica en el invitado | la propia licencia Datacenter del host Hyper-V | no 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.
Microsoft Learn, OA 3.0 on the factory floor — «injects the product keys into the firmware»;
/Validatecomprueba «that the MSDM table exists». ↩︎Microsoft Learn, Validate an OEM Activation key — «is activated by using the OA3 DPK in the firmware». ↩︎
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.» ↩︎
QEMU, System Emulation, Invocation — campos de
-smbiospor tipo;-acpitable«For file=, take whole ACPI table from the specified files, including all ACPI headers»;-boot«Currently Seabios for X86 system support it.» ↩︎QEMU v11.0.3,
hw/acpi/core.c— «ACPI table has wrong length» es unwarn_report, y luego se sobrescribe la longitud y se recalcula el checksum. ↩︎pve-cluster,
src/pmxcfs/pmxcfs.c— las rutas bajoprivse enmascaran a0777700, todo lo demás a0777750. ↩︎pve-cluster,
src/pmxcfs/memdb.h—#define MEMDB_MAX_FILE_SIZE (1024 * 1024) // 1 MiB. ↩︎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. ↩︎ ↩︎ ↩︎ ↩︎
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.» ↩︎
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.» ↩︎
Microsoft Learn, Windows subscription activation for VDA — «VMs must be hosted by a Qualified Multitenant Hoster (QMTH).» ↩︎
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». ↩︎ ↩︎ ↩︎ ↩︎
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.» ↩︎ ↩︎