CVE-2021-46951 - Linux Kernel TPM2 Driver Integer Underflow Vulnerability (Exploit & Fix Explained)

A dangerous vulnerability was found in the Linux Kernel TPM (Trusted Platform Module) EFI (Extensible Firmware Interface) support, specifically in how log size calculation was handled in the tpm_read_log_efi function. This issue, now cataloged as CVE-2021-46951, allowed local users to trigger an integer underflow and possibly cause kernel memory corruption or a system crash by repeatedly loading and unloading a TPM2 driver.

Below, I’ll explain in plain language what went wrong, how someone could trigger this bug, and how the problem was fixed. I’ll include code snippets, a sample dmesg crash log, and useful links for further reading.

What Happened? The Root of the Vulnerability

In the Linux kernel (up to 5.13), a global variable named efi_tpm_final_log_size was used to track the total size of the TPM event log in EFI systems. Each time tpm_read_log_efi() was called, it subtracted a variable called final_events_preboot_size from efi_tpm_final_log_size.

The issue? If tpm_read_log_efi() was called multiple times (such as by repeatedly loading and unloading the vTPM/TPM2 driver), the subtraction happened each time, making efi_tpm_final_log_size steadily decrease – eventually becoming negative, which, in unsigned math, wraps around to a gigantic value. This causes invalid memory accesses (kernel panics, crashes, or worse).

Simple analogy:
Imagine a counter tracking how many cookies you have, but every time you look in the jar, you eat a cookie and forget to put them back before checking again. Eventually, the number of cookies becomes negative, and strange things start happening in your kitchen!

The Exploit: How Attackers Could Crash Your Machine

To exploit this bug, an attacker simply needs to load and unload a TPM2 driver (like tpm_vtpm_proxy) enough times. Each event subtracts from efi_tpm_final_log_size. After enough operations, it underflows, and a later attempt to use the value results in out-of-bounds memory access.

Here’s a pseudocode sequence to trigger the bug

# Loop 50 times: remove and add the vTPM driver
for i in $(seq 1 50); do
    modprobe tpm_vtpm_proxy   # Load the driver
    rmmod tpm_vtpm_proxy      # Unload the driver
done

On vulnerable systems, this might soon produce kernel log errors and can cause a crash. Here’s a real crash snippet:

Mar  8 15:35:12 hibinst kernel: tpm_read_log_efi+x152/x1a7
Mar  8 15:35:12 hibinst kernel: tpm_bios_log_setup+xc8/x1c
Mar  8 15:35:12 hibinst kernel: tpm_chip_register+x8f/x260
Mar  8 15:35:12 hibinst kernel: vtpm_proxy_work+x16/x60 [tpm_vtpm_proxy]
Mar  8 15:35:12 hibinst kernel: process_one_work+x1b4/x370
Mar  8 15:35:12 hibinst kernel: worker_thread+x53/x3e

And a key part of the oops

RIP: 001:__memcpy+x12/x20
RAX: ffff88f878cefed5 RBX: ffff88f878ce900 RCX: 1ffffffffffffef
...
CR2: ffff9ac4c003c000 CR3: 00000001785a6004 CR4: 000000000006ee

This means the kernel tried to copy an absurdly large chunk of memory – leading to a fatal crash or possible further memory corruption.

The Fix: Use a Local (Not Global) Variable

Instead of subtracting from a global variable every time, the correct way is to use a local variable inside the function (tpm_read_log_efi()), compute the result once per call, and keep the global variable intact.

Before (Vulnerable)

/* Vulnerable code! */
efi_tpm_final_log_size -= final_events_preboot_size;
memcpy(..., efi_tpm_log, efi_tpm_final_log_size);

After (Fixed/Patched)

/* Patched code */
size_t local_final_log_size = efi_tpm_final_log_size - final_events_preboot_size;
memcpy(..., efi_tpm_log, local_final_log_size);

Now, no matter how many times you initialize/unload the driver, the size calculation is always correct and doesn’t affect other calls.

References and Patch

- Kernel.org fix commit
- LKML Patch discussion
- Red Hat Bugzilla 1934695

TL;DR: What Should You Do?

- If you maintain kernels, make sure to update to a version patched after March 2021, or manually backport the commit linked above.
- If you run Linux on servers with TPM2/EFI, avoid manually toggling TPM drivers until patched!
- If you are a developer, always use function-local variables for calculations that should not persist between calls — especially when handling hardware or global state.

Final Thoughts

This type of vulnerability is a classic kernel mistake: using global state where local logic should be used. With the Linux kernel’s many contributors and complex hardware interactions, it’s easy to see how things slip through. Luckily, diligent auditing and community reporting bring these issues to light.

For the curious, here’s more reading on kernel memory safety and TPM in Linux.

Stay safe, and always keep your kernel up to date!

CVE-2021-46951 disclosure date: March 2021
Patched in kernels: v5.13+ (and many maintained LTS/Enterprise kernels)


*This post was written for educational purposes. Do not attempt to exploit vulnerabilities on systems you do not own or have explicit permission to test!*

Timeline

Published on: 02/27/2024 19:04:06 UTC
Last modified on: 04/10/2024 20:15:55 UTC