Bajo nivel · C++Low-level · C++

Ring -2: el modo que ni el hipervisor puede verRing -2: the mode not even the hypervisor can see

En esta páginaOn this page
  1. El firmware manda primero
  2. Qué es SMM
  3. Para qué se usa
  4. El coste invisible
  5. Por qué es un objetivo tan delicado
  6. WSMT: el firmware declara sus defensas
  7. Compruébalo tú mismo
  8. Cómo se codifican las firmas
  9. Ring -3, en una frase
  10. La escalera completa
  1. Firmware goes first
  2. What SMM is
  3. What it's used for
  4. The invisible cost
  5. Why it's such a sensitive target
  6. WSMT: the firmware declares its defenses
  7. Check it yourself
  8. How the signatures are encoded
  9. Ring -3, in one sentence
  10. The full ladder

El firmware manda primero#

Cuando montas un laboratorio para desarrollar drivers, lo primero que aprendes no tiene nada que ver con el kernel. Tiene que ver con la BIOS. En mi guía de laboratorio lo dejé escrito así: la virtualización se activa en la BIOS/UEFI, no en el SO ni en el hipervisor. Y hay una segunda lección del mismo estilo: con Secure Boot activo, Windows no te deja ni activar el modo de pruebas para drivers. El cambio se rechaza con un mensaje que no admite discusión: protected by Secure Boot policy.

Las dos reglas dicen lo mismo. Las condiciones del juego las fija algo que se ejecuta antes que Windows. Lo que casi nadie cuenta es que ese algo no desaparece cuando arranca el sistema operativo: una parte del firmware sigue ahí, en un modo de la CPU que ni el kernel ni el hipervisor pueden ver.

En esta serie hemos ido bajando escalones. En la primera parte cruzamos de Ring 3 a Ring 0, el kernel. En la segunda encontramos al hipervisor, Ring -1, vigilando al kernel desde abajo. Siempre con la misma pregunta de fondo: cada nivel vigila al de arriba, pero ¿quién vigila al de abajo? Hoy llegamos al último escalón de la CPU: Ring -2, el System Management Mode.

Qué es SMM#

El System Management Mode (SMM) es un modo especial de las CPU x86, reservado al firmware. Existe desde el Intel 386SL, de 1990, y sigue presente en cualquier procesador Intel o AMD actual. Como el VMX root de la entrada anterior, no es un anillo nuevo en sentido estricto: es un modo de funcionamiento distinto. Pero está por debajo de todo lo que hemos visto hasta ahora.

A SMM no se entra con una instrucción normal ni con una syscall. Se entra con una interrupción especial, la SMI (System Management Interrupt). Puede generarla el chipset o el propio hardware (por eventos de energía, de temperatura o de la plataforma) o el software: en muchas plataformas Intel basta con escribir en el puerto de E/S 0xB2.

Cuando llega una SMI, la CPU hace algo que no hace en ningún otro caso:

  1. Guarda todo su estado —registros, modo en el que estaba, por dónde iba— en una zona de memoria especial, la SMRAM.
  2. Pasa a SMM y ejecuta el manejador de SMI del firmware, que también vive en la SMRAM.
  3. Al terminar, el manejador ejecuta la instrucción RSM (Resume from System Management Mode): la CPU restaura el estado guardado y el sistema continúa exactamente donde estaba, sin saber que se detuvo.
RING 3 aplicaciones RING 0 kernel y drivers RING -1 hipervisor (si lo hay) SMI ▼ ▲ RSM SMM ▼ RING -2 manejador de SMI · SMRAM (estado guardado) HARDWARE chipset · SMRR · eventos de la plataforma
Fig. 1 — Una SMI detiene todo lo que hay por encima de la línea, sea cual sea su anillo. RSM lo reanuda como si nada.

La clave de la seguridad de SMM es la SMRAM. Solo es accesible estando en SMM: el chipset y unos registros de la CPU llamados SMRR (System Management Range Registers) la protegen, y el firmware la bloquea antes de ceder el control al sistema operativo. En la configuración habitual, ni el kernel ni el hipervisor pueden leerla, ni pueden interceptar la SMI. El hipervisor, que en la entrada anterior lo veía todo, aquí no ve nada.

En una frase

SMM es un modo de la CPU en el que el firmware toma el control, hace su trabajo en una memoria que nadie más puede ver y devuelve la máquina tal como estaba. Para el sistema operativo, ese tiempo sencillamente no existió.

Para qué se usa#

Suena inquietante, pero SMM existe por razones muy prosaicas. Es el sitio donde el firmware hace trabajo de plataforma que no puede delegar en el sistema operativo, porque no sabe qué sistema operativo habrá ni si se portará bien:

  • Energía y temperatura. Parte de la gestión de energía y de la respuesta a eventos térmicos pasa por manejadores de SMI.
  • Emulación de hardware heredado. El ejemplo clásico: un teclado USB que, durante el arranque, se presenta como un teclado PS/2 para que el software antiguo lo entienda.
  • Servicios del firmware. Proteger la escritura de la flash donde vive la BIOS, o guardar las variables UEFI autenticadas (entre ellas, las bases de datos de claves de las que depende Secure Boot) sin que el sistema operativo pueda alterarlas por su cuenta.

Este último punto cierra el círculo del principio. Las reglas que pone el firmware antes de arrancar Windows se sostienen, en parte, gracias a código que sigue ejecutándose en SMM mientras Windows está en marcha.

El coste invisible#

Hay un precio. Mientras dura una SMI, normalmente todos los núcleos de la CPU entran en SMM, no solo el que la recibió. El sistema operativo no se entera: sus relojes siguen corriendo, pero durante ese tiempo no se ha ejecutado ni una instrucción suya. Simplemente, lo pierde.

Para un escritorio da igual. Para un sistema de tiempo real, donde una tarea tiene que responder en un plazo garantizado, es un problema serio: aparecen pausas que nadie en el sistema ha causado y que ninguna herramienta del sistema puede atribuir a un proceso. Linux incluye un trazador pensado precisamente para eso, hwlat, que ocupa un núcleo dando vueltas y mide los huecos de tiempo que no puede explicar. Es una de las pocas formas de notar SMM desde fuera: no por lo que hace, sino por el tiempo que se lleva.

Por qué es un objetivo tan delicado#

Todo lo que hemos visto en la serie apunta a la misma conclusión. Un fallo en Ring 0 compromete al kernel; el hipervisor está ahí para contenerlo. Un fallo en Ring -1 compromete a todas las máquinas virtuales que están encima. Un fallo en código SMM da control por debajo incluso del hipervisor, en un sitio que ninguna de las defensas de arriba puede ver.

La clase de fallo más conocida en SMM tiene nombre de manual: la del ayudante confundido (confused deputy). El manejador de SMI tiene todos los privilegios, pero a menudo trabaja con datos que le pasa el sistema operativo: un búfer con una petición, un puntero a dónde dejar la respuesta. Si el manejador no valida esos búferes y punteros, el sistema operativo —o un atacante que lo controle— puede engañarlo para que lea o escriba donde no debe, por ejemplo dentro de la propia SMRAM. El ayudante tiene las llaves; el problema es que obedece sin comprobar a quién.

No hace falta entrar en cómo se aprovecha eso. Lo que importa aquí es la respuesta: las mitigaciones que la industria ha ido exigiendo al código SMM atacan justo ese punto.

WSMT: el firmware declara sus defensas#

Microsoft definió una tabla ACPI con un nombre muy descriptivo: la Windows SMM Security Mitigations Table (WSMT). Con ella, el firmware declara que su código SMM aplica tres protecciones:

Bit Protección Qué declara el firmware
0 FIXED_COMM_BUFFERS Solo usa búferes de comunicación en regiones fijas, validadas por el firmware.
1 COMM_BUFFER_NESTED_PTR_PROTECTION También valida los punteros que hay dentro de esos búferes.
2 SYSTEM_RESOURCE_PROTECTION Impide que el sistema operativo reconfigure recursos críticos de la plataforma.

Las dos primeras son la respuesta directa al ayudante confundido: el manejador solo acepta peticiones en sitios conocidos y comprueba cada puntero antes de seguirlo. La tercera evita que el sistema operativo cambie la configuración del hardware de la que depende la protección de SMM.

Windows consulta esta tabla para decidir si confía en el firmware para las funciones de seguridad basada en virtualización (VBS) que vimos en Ring -1. Tiene todo el sentido: de poco sirve que el hipervisor proteja al kernel si por debajo del hipervisor hay código que se lo puede saltar. Los equipos Secured-core van más allá y añaden mecanismos de Intel y AMD que aíslan o supervisan el propio código SMM, para no depender solo de que esté bien escrito.

Una declaración, no una prueba

La WSMT es lo que el firmware dice de sí mismo. Desde el sistema operativo no se puede ver SMM ni comprobar que el código cumple lo que declara. Una tabla con las tres protecciones activadas es una buena señal, no una garantía de que tu equipo sea seguro.

Compruébalo tú mismo#

Windows expone las tablas del firmware a cualquier programa, sin privilegios de administrador, con dos funciones: EnumSystemFirmwareTables lista las que hay y GetSystemFirmwareTable devuelve el contenido de una. Este programa lista las tablas ACPI de tu equipo y lee la WSMT. A diferencia de las partes anteriores, es código solo para Windows:

firmware.cpp
// g++ -std=c++20 firmware.cpp -o firmware.exe   (Windows)
// Lista las tablas ACPI del firmware y lee la WSMT, donde el
// firmware declara qué protecciones aplica a su código SMM.
#include <windows.h>
#include <cstdint>
#include <cstring>
#include <format>
#include <iostream>
#include <string>
#include <vector>

// El proveedor se codifica con el primer carácter en el byte alto...
constexpr DWORD provider(const char s[5]) {
    return DWORD(s[0]) << 24 | DWORD(s[1]) << 16
         | DWORD(s[2]) << 8 | DWORD(s[3]);
}
// ...y la firma de una tabla, con sus bytes en el orden de memoria.
DWORD table(const char s[5]) {
    DWORD v;
    std::memcpy(&v, s, 4);
    return v;
}

int main() {
    const DWORD acpi = provider("ACPI");

    // 1. Qué tablas hay: cada entrada son 4 bytes con su firma.
    std::vector<char> ids(EnumSystemFirmwareTables(acpi, nullptr, 0));
    EnumSystemFirmwareTables(acpi, ids.data(), DWORD(ids.size()));
    std::cout << "ACPI tables:";
    for (size_t i = 0; i + 4 <= ids.size(); i += 4)
        std::cout << (i % 36 ? " " : "\n  ") << std::string(&ids[i], 4);
    std::cout << '\n';

    // 2. WSMT: cabecera ACPI (36 bytes) + ProtectionFlags (4 bytes).
    const DWORD wsmt = table("WSMT");
    DWORD size = GetSystemFirmwareTable(acpi, wsmt, nullptr, 0);
    if (size < 40) {
        std::cout << "WSMT: not present\n";
        return 0;
    }
    std::vector<uint8_t> t(size);
    GetSystemFirmwareTable(acpi, wsmt, t.data(), size);
    uint32_t flags;
    std::memcpy(&flags, t.data() + 36, 4);

    const char* names[] = {"FIXED_COMM_BUFFERS",
                           "COMM_BUFFER_NESTED_PTR_PROTECTION",
                           "SYSTEM_RESOURCE_PROTECTION"};
    std::cout << std::format("WSMT ProtectionFlags = 0x{:X}\n", flags);
    for (int bit = 0; bit < 3; ++bit)
        std::cout << std::format("  {:<34}{}\n", names[bit],
                                 (flags >> bit) & 1 ? "yes" : "no");
}

Esta es la salida real en mi equipo, un Intel Core i9-14900KF con Windows 11:

salidaoutput
ACPI tables:
  DBGP MCFG FACP APIC DMAR HPET FPDT FIDT SSDT
  SSDT SSDT SSDT SSDT UEFI NHLT LPIT SSDT SSDT
  DBG2 SSDT SSDT SSDT SSDT PHAT TPM2 WSMT BGRT
WSMT ProtectionFlags = 0x7
  FIXED_COMM_BUFFERS                yes
  COMM_BUFFER_NESTED_PTR_PROTECTION yes
  SYSTEM_RESOURCE_PROTECTION        yes

ProtectionFlags = 0x7 son los tres bits a uno: el firmware declara las tres protecciones. Pero la lista de tablas merece una segunda mirada, porque en ella aparecen otras dos piezas de la cadena de seguridad:

  • DMAR es la tabla que describe la IOMMU de Intel (VT-d). La IOMMU hace con los dispositivos lo que las tablas EPT hacen con las máquinas virtuales: decide a qué memoria puede acceder cada uno por DMA. Es el hardware en el que se apoya Windows para la protección de DMA del kernel, la que impide que un dispositivo conectado lea o escriba la memoria del sistema a su antojo.
  • TPM2 describe el chip TPM, la raíz de las mediciones del arranque: cada etapa del arranque se mide y se registra en él antes de ejecutarse.

Cómo se codifican las firmas#

El detalle más curioso del código son las dos funciones del principio, que convierten cuatro letras en un DWORD de dos formas distintas. No es un capricho: la API las espera así.

El proveedor ('ACPI') se codifica con el primer carácter en el byte alto, como se escribiría un literal de varios caracteres: 0x41435049, con la A (0x41) delante. La firma de una tabla, en cambio, se pasa con sus bytes en el mismo orden en que aparecen en memoria, que es como está guardada en la propia tabla ACPI. En un x86, que es little-endian, "WSMT" se convierte así en 0x544D5357: la W (0x57) acaba en el byte bajo. Por eso table() usa memcpy, que copia los bytes tal cual, en lugar de desplazamientos. Si te equivocas de orden, no salta ningún error llamativo: GetSystemFirmwareTable busca una tabla que no existe, devuelve 0 y el programa dirá que no hay WSMT aunque la haya.

El resto sigue la anatomía de la WSMT: una cabecera ACPI estándar de 36 bytes, común a todas las tablas, seguida de un único campo de 4 bytes, ProtectionFlags. Por eso el programa exige al menos 40 bytes antes de leer nada y toma los flags del desplazamiento 36. (El 36 del bucle de la lista es otra cosa: 9 firmas de 4 bytes por línea.)

¿Y en Linux?

Aquí no hay equivalente sin privilegios. Linux expone las tablas ACPI en /sys/firmware/acpi/tables/, y la WSMT, si existe, está en /sys/firmware/acpi/tables/WSMT, pero leerla requiere ser root. Lo que encontrarás es la misma estructura: 36 bytes de cabecera y 4 de flags.

Ring -3, en una frase#

Por debajo de SMM ya no queda CPU. Pero fuera de la CPU principal viven microcontroladores con su propio firmware —Intel ME / CSME en Intel, AMD PSP en AMD— que gestionan el arranque seguro, el TPM por firmware y la administración remota, y que pueden seguir funcionando mientras la CPU principal está en reposo. Hay quien los llama Ring -3. Son otro ordenador dentro de tu ordenador, y con eso cerramos la escalera.

La escalera completa#

Nota personal

No he escrito nunca código SMM, y esta entrada no pretende lo contrario. Pero en mis guías de laboratorio para desarrollar drivers e hipervisores dejé por escrito dos avisos que resumen esta entrada: que la virtualización se decide en la BIOS/UEFI y no en el sistema operativo, y que con Secure Boot activo el modo de pruebas de drivers queda bloqueado. Son la misma idea vista desde arriba: antes de que el kernel o el hipervisor decidan nada, el firmware ya ha decidido por ellos.

Tres entradas después, esta es la escalera que hemos bajado:

Nivel Quién vive ahí Quién lo vigila
Ring 3 Tus aplicaciones El kernel
Ring 0 El kernel y los drivers El hipervisor, con VBS y HVCI
Ring -1 El hipervisor El arranque verificado y medido (Secure Boot, TPM)
Ring -2 SMM, el firmware en ejecución Lo que el firmware declara (WSMT) y, en equipos Secured-core, mecanismos de Intel y AMD
Ring -3 Intel ME / CSME, AMD PSP Nadie desde arriba: se confía en el fabricante

La columna de la derecha cuenta la historia de la serie. Cada nivel protege al de arriba, y cada vez es más difícil encontrar a alguien por debajo que proteja al protector. Por eso la seguridad de un sistema moderno no es una muralla, sino una cadena: aplicación, kernel, hipervisor, firmware. Y el eslabón más bajo de esa cadena, el que sostiene a todos los demás, es el firmware.

Si has empezado por esta entrada, la serie empieza en De Ring 3 a Ring 0 y sigue en Ring -1: el hipervisor que vigila a tu kernel.

La próxima vez que entres en la BIOS para activar la virtualización, recuerda con quién estás hablando: con el único nivel que seguirá ahí, sin que nadie lo vea, cuando el sistema operativo crea que manda él.

Firmware goes first#

When you set up a lab for driver development, the first thing you learn has nothing to do with the kernel. It has to do with the BIOS. In my lab guide I wrote it down like this: virtualization is enabled in the BIOS/UEFI, not in the OS or in the hypervisor. And there's a second lesson in the same vein: with Secure Boot on, Windows won't even let you enable test mode for drivers. The change is rejected with a message that leaves no room for argument: protected by Secure Boot policy.

Both rules say the same thing. The rules of the game are set by something that runs before Windows. What almost nobody mentions is that this something doesn't go away when the operating system starts: part of the firmware stays there, in a CPU mode that neither the kernel nor the hypervisor can see.

In this series we've been going down step by step. In part one we crossed from Ring 3 to Ring 0, the kernel. In part two we met the hypervisor, Ring -1, watching over the kernel from below. Always with the same underlying question: each level watches the one above it, but who watches the one below? Today we reach the CPU's last step: Ring -2, System Management Mode.

What SMM is#

System Management Mode (SMM) is a special mode of x86 CPUs, reserved for firmware. It has existed since the Intel 386SL, back in 1990, and it's still present in every current Intel or AMD processor. Like VMX root in the previous post, it isn't strictly a new ring: it's a different operating mode. But it sits below everything we've seen so far.

You don't enter SMM with a normal instruction or a syscall. You enter it through a special interrupt, the SMI (System Management Interrupt). It can be raised by the chipset or by the hardware itself (on power, thermal or platform events) or by software: on many Intel platforms, writing to I/O port 0xB2 is enough.

When an SMI arrives, the CPU does something it does in no other case:

  1. It saves its entire state — registers, the mode it was in, where it was — into a special memory region, SMRAM.
  2. It switches to SMM and runs the firmware's SMI handler, which also lives in SMRAM.
  3. When it's done, the handler executes the RSM instruction (Resume from System Management Mode): the CPU restores the saved state and the system carries on exactly where it was, without knowing it was ever stopped.
RING 3 applications RING 0 kernel and drivers RING -1 hypervisor (if any) SMI ▼ ▲ RSM SMM ▼ RING -2 SMI handler · SMRAM (saved state) HARDWARE chipset · SMRR · platform events
Fig. 1 — An SMI stops everything above the line, whatever its ring. RSM resumes it as if nothing had happened.

The key to SMM's security is SMRAM. It's only accessible while in SMM: the chipset and a set of CPU registers called SMRR (System Management Range Registers) protect it, and the firmware locks it before handing control to the operating system. In the usual configuration, neither the kernel nor the hypervisor can read it, nor can they intercept the SMI. The hypervisor, which saw everything in the previous post, sees nothing here.

In one sentence

SMM is a CPU mode in which the firmware takes over, does its work in memory nobody else can see, and hands the machine back exactly as it was. As far as the operating system is concerned, that time simply never happened.

What it's used for#

It sounds unsettling, but SMM exists for very mundane reasons. It's where the firmware does platform work it can't delegate to the operating system, because it doesn't know which operating system will be running or whether it will behave:

  • Power and thermals. Part of power management and of the response to thermal events goes through SMI handlers.
  • Legacy hardware emulation. The classic example: a USB keyboard that, during boot, presents itself as a PS/2 keyboard so older software can understand it.
  • Firmware services. Protecting writes to the flash chip where the BIOS lives, or storing authenticated UEFI variables (among them the key databases Secure Boot depends on) without the operating system being able to change them on its own.

That last point closes the loop from the beginning. The rules the firmware sets before Windows boots are upheld, in part, by code that keeps running in SMM while Windows is up.

The invisible cost#

There's a price. While an SMI lasts, typically all CPU cores enter SMM, not just the one that received it. The operating system doesn't notice: its clocks keep ticking, but during that time not a single one of its instructions has run. It simply loses that time.

On a desktop it doesn't matter. On a real-time system, where a task has to respond within a guaranteed deadline, it's a serious problem: pauses show up that nothing in the system caused and that no system tool can pin on any process. Linux ships a tracer designed precisely for that, hwlat, which keeps a core spinning and measures the gaps in time it can't explain. It's one of the few ways to notice SMM from outside: not by what it does, but by the time it takes.

Why it's such a sensitive target#

Everything we've seen in this series points to the same conclusion. A bug in Ring 0 compromises the kernel; the hypervisor is there to contain it. A bug in Ring -1 compromises every virtual machine on top of it. A bug in SMM code gives control below even the hypervisor, in a place none of the defenses above can see.

The best-known class of SMM bug has a textbook name: the confused deputy. The SMI handler holds every privilege, but it often works with data the operating system hands it: a buffer with a request, a pointer to where it should leave the answer. If the handler doesn't validate those buffers and pointers, the operating system — or an attacker controlling it — can trick it into reading or writing where it shouldn't, for example inside SMRAM itself. The deputy has the keys; the problem is that it obeys without checking who's asking.

There's no need to go into how that gets exploited. What matters here is the answer: the mitigations the industry has come to require from SMM code target exactly that point.

WSMT: the firmware declares its defenses#

Microsoft defined an ACPI table with a very descriptive name: the Windows SMM Security Mitigations Table (WSMT). With it, the firmware declares that its SMM code applies three protections:

Bit Protection What the firmware declares
0 FIXED_COMM_BUFFERS It only uses communication buffers in fixed regions, validated by the firmware.
1 COMM_BUFFER_NESTED_PTR_PROTECTION It also validates the pointers inside those buffers.
2 SYSTEM_RESOURCE_PROTECTION It prevents the operating system from reconfiguring critical platform resources.

The first two are the direct answer to the confused deputy: the handler only accepts requests in known places and checks every pointer before following it. The third stops the operating system from changing the hardware configuration that SMM's protection depends on.

Windows checks this table to decide whether it trusts the firmware for the Virtualization-Based Security (VBS) features we saw in Ring -1. It makes perfect sense: there's little point in the hypervisor protecting the kernel if, below the hypervisor, there's code that can go around it. Secured-core PCs go further and add Intel and AMD mechanisms that isolate or supervise the SMM code itself, so they don't depend solely on it being well written.

A declaration, not a proof

The WSMT is what the firmware says about itself. From the operating system you can't see SMM or check that the code does what it declares. A table with all three protections set is a good sign, not a guarantee that your machine is secure.

Check it yourself#

Windows exposes firmware tables to any program, without administrator rights, through two functions: EnumSystemFirmwareTables lists the ones available and GetSystemFirmwareTable returns the contents of one. This program lists your machine's ACPI tables and reads the WSMT. Unlike the previous parts, this code is Windows-only:

firmware.cpp
// g++ -std=c++20 firmware.cpp -o firmware.exe   (Windows)
// Lists the firmware's ACPI tables and reads the WSMT, where the
// firmware declares which protections it applies to its SMM code.
#include <windows.h>
#include <cstdint>
#include <cstring>
#include <format>
#include <iostream>
#include <string>
#include <vector>

// The provider is encoded with its first character in the high byte...
constexpr DWORD provider(const char s[5]) {
    return DWORD(s[0]) << 24 | DWORD(s[1]) << 16
         | DWORD(s[2]) << 8 | DWORD(s[3]);
}
// ...and a table's signature, with its bytes in memory order.
DWORD table(const char s[5]) {
    DWORD v;
    std::memcpy(&v, s, 4);
    return v;
}

int main() {
    const DWORD acpi = provider("ACPI");

    // 1. Which tables exist: each entry is its 4-byte signature.
    std::vector<char> ids(EnumSystemFirmwareTables(acpi, nullptr, 0));
    EnumSystemFirmwareTables(acpi, ids.data(), DWORD(ids.size()));
    std::cout << "ACPI tables:";
    for (size_t i = 0; i + 4 <= ids.size(); i += 4)
        std::cout << (i % 36 ? " " : "\n  ") << std::string(&ids[i], 4);
    std::cout << '\n';

    // 2. WSMT: ACPI header (36 bytes) + ProtectionFlags (4 bytes).
    const DWORD wsmt = table("WSMT");
    DWORD size = GetSystemFirmwareTable(acpi, wsmt, nullptr, 0);
    if (size < 40) {
        std::cout << "WSMT: not present\n";
        return 0;
    }
    std::vector<uint8_t> t(size);
    GetSystemFirmwareTable(acpi, wsmt, t.data(), size);
    uint32_t flags;
    std::memcpy(&flags, t.data() + 36, 4);

    const char* names[] = {"FIXED_COMM_BUFFERS",
                           "COMM_BUFFER_NESTED_PTR_PROTECTION",
                           "SYSTEM_RESOURCE_PROTECTION"};
    std::cout << std::format("WSMT ProtectionFlags = 0x{:X}\n", flags);
    for (int bit = 0; bit < 3; ++bit)
        std::cout << std::format("  {:<34}{}\n", names[bit],
                                 (flags >> bit) & 1 ? "yes" : "no");
}

This is the real output on my machine, an Intel Core i9-14900KF running Windows 11:

salidaoutput
ACPI tables:
  DBGP MCFG FACP APIC DMAR HPET FPDT FIDT SSDT
  SSDT SSDT SSDT SSDT UEFI NHLT LPIT SSDT SSDT
  DBG2 SSDT SSDT SSDT SSDT PHAT TPM2 WSMT BGRT
WSMT ProtectionFlags = 0x7
  FIXED_COMM_BUFFERS                yes
  COMM_BUFFER_NESTED_PTR_PROTECTION yes
  SYSTEM_RESOURCE_PROTECTION        yes

ProtectionFlags = 0x7 means all three bits are set: the firmware declares all three protections. But the list of tables deserves a second look, because two more links of the security chain show up in it:

  • DMAR is the table describing Intel's IOMMU (VT-d). The IOMMU does for devices what EPT tables do for virtual machines: it decides which memory each device can reach through DMA. It's the hardware Windows relies on for Kernel DMA Protection, which stops a connected device from reading or writing system memory at will.
  • TPM2 describes the TPM chip, the root of boot measurements: each boot stage is measured and recorded in it before it runs.

How the signatures are encoded#

The most curious detail in the code is the pair of functions at the top, which turn four letters into a DWORD in two different ways. It isn't a whim: that's how the API expects them.

The provider ('ACPI') is encoded with its first character in the high byte, the way a multi-character literal would be written: 0x41435049, with the A (0x41) in front. A table signature, on the other hand, is passed with its bytes in the same order they appear in memory, which is how it's stored in the ACPI table itself. On x86, which is little-endian, "WSMT" therefore becomes 0x544D5357: the W (0x57) ends up in the low byte. That's why table() uses memcpy, which copies the bytes as they are, instead of shifts. Get the order wrong and nothing obvious breaks: GetSystemFirmwareTable looks for a table that doesn't exist, returns 0, and the program reports no WSMT even when there is one.

The rest follows the anatomy of the WSMT: a standard 36-byte ACPI header, shared by every table, followed by a single 4-byte field, ProtectionFlags. That's why the program requires at least 40 bytes before reading anything and takes the flags from offset 36. (The 36 in the listing loop is something else: 9 four-byte signatures per line.)

What about Linux?

There's no unprivileged equivalent here. Linux exposes ACPI tables under /sys/firmware/acpi/tables/, and the WSMT, if present, is at /sys/firmware/acpi/tables/WSMT, but reading it requires root. What you'll find is the same structure: 36 bytes of header and 4 of flags.

Ring -3, in one sentence#

Below SMM there's no CPU left. But outside the main CPU live microcontrollers with their own firmware — Intel ME / CSME on Intel, AMD PSP on AMD — that handle secure boot, the firmware TPM and remote management, and that can keep running while the main CPU is asleep. Some call them Ring -3. They're another computer inside your computer, and with that the ladder is complete.

The full ladder#

Personal note

I've never written SMM code, and this post doesn't pretend otherwise. But in my lab guides for driver and hypervisor development I put down in writing two warnings that sum up this post: that virtualization is decided in the BIOS/UEFI and not in the operating system, and that with Secure Boot on, driver test mode is blocked. They're the same idea seen from above: before the kernel or the hypervisor decide anything, the firmware has already decided for them.

Three posts later, this is the ladder we've climbed down:

Level Who lives there Who watches it
Ring 3 Your applications The kernel
Ring 0 The kernel and drivers The hypervisor, with VBS and HVCI
Ring -1 The hypervisor Verified and measured boot (Secure Boot, TPM)
Ring -2 SMM, the running firmware What the firmware declares (WSMT) and, on Secured-core PCs, Intel and AMD mechanisms
Ring -3 Intel ME / CSME, AMD PSP Nobody from above: you trust the vendor

The right-hand column tells the story of the series. Each level protects the one above it, and each time it gets harder to find someone below to protect the protector. That's why the security of a modern system isn't a wall but a chain: application, kernel, hypervisor, firmware. And the lowest link in that chain, the one holding up all the others, is the firmware.

If you've started with this post, the series starts at From Ring 3 to Ring 0 and continues with Ring -1: the hypervisor watching over your kernel.

Next time you go into the BIOS to turn on virtualization, remember who you're talking to: the only level that will still be there, unseen, while the operating system thinks it's in charge.

CompartirShare

¿Tu proyecto necesita ir por debajo de la capa de aplicación?Does your project need to go below the application layer?

Software de sistemas, rendimiento y seguridad en Windows y Linux: diagnóstico, herramientas a medida y revisión de código de bajo nivel.Systems software, performance and security on Windows and Linux: diagnostics, custom tooling and low-level code review.

HablemosLet's talk→

ComentariosComments

Para comentar necesitas una cuenta de GitHub. Los comentarios se guardan en GitHub Discussions (SoftDryzz/blog-comments) y se moderan: no se publican enlaces promocionales.Commenting requires a GitHub account. Comments live in GitHub Discussions (SoftDryzz/blog-comments) and are moderated: no promotional links.

Sigue leyendoKeep reading

Bajo nivel · Parte 2Low-level · Part 2

Ring -1: el hipervisor que vigila a tu kernelRing -1: the hypervisor watching over your kernel

Qué hay por debajo del kernel: el hipervisor, en modo VMX root (modo host de SVM en AMD). Cómo funciona con VT-x, AMD-V y EPT, por qué Windows usa uno para protegerse a sí mismo y cómo comprobarlo desde C++ con CPUID.What lives below the kernel: the hypervisor, in VMX root mode (SVM host mode on AMD). How it works with VT-x, AMD-V and EPT, why Windows uses one to protect itself, and how to check it from C++ with CPUID.

· 10 min

Bajo nivel · Parte 1Low-level · Part 1

De Ring 3 a Ring 0: qué pasa cuando tu código cruza la frontera del kernelFrom Ring 3 to Ring 0: what happens when your code crosses into the kernel

Un viaje por los anillos de privilegio del x86. Qué es el Ring 0, cómo funciona una syscall, por qué un fallo en el kernel tumba el sistema entero y qué defensas protegen hoy esa frontera.A journey through x86 privilege rings. What Ring 0 is, how a syscall works, why a kernel bug takes down the whole system, and which defenses guard that border today.

· 11 min