Back to Blog
Espressif's New ESP-TEE Already Has a 9.3-Severity Out-of-Bounds Write Bug

Espressif's New ESP-TEE Already Has a 9.3-Severity Out-of-Bounds Write Bug

September 13, 2026
11 min read
3 views
Share:

Espressif's Trusted Execution Environment for the ESP32-C5/C6/C61/H2 shipped with an unchecked-pointer bug (CVE-2026-45328, CVSS 9.3) that lets ordinary application code corrupt memory inside the very boundary the TEE exists to protect.

esp_tee_ptr_in_ree() was supposed to check every pointer crossing from ordinary application code into Espressif's new Trusted Execution Environment before letting a hardware peripheral touch it. It didn't check all of them. Some pointers — output buffers, pointers buried inside structs, the tail end of length-bounded buffers — were allowed straight through, and the range math that did run didn't account for a pointer wrapping past the top of a 32-bit address space. The result is CVE-2026-45328, a critical out-of-bounds write in the exact subsystem Espressif built to stop this class of attack.

I read the advisory twice because it felt almost too on the nose. A chip vendor spends over a year building and marketing hardware-backed trust for its cheapest microcontrollers, and the first serious bug in that framework isn't a side-channel attack or a cryptographic weakness — it's the single most ordinary mistake in embedded C: a pointer nobody bothered to fully validate at the one boundary where validation was the entire point.


What ESP-TEE Was Supposed to Fix

Espressif announced the ESP-TEE framework in early 2025 for the ESP32-C6, and has since extended it to the C5, C61, and H2. The pitch is straightforward: split the chip into a Rich Execution Environment (REE), where your application code and whatever libraries it drags in all run, and a Trusted Execution Environment (TEE), a separate, higher-privilege execution context that holds secrets — attestation keys, OTA signing material, secure storage — and mediates access to sensitive hardware peripherals like AES, SHA, ECC, HMAC, SPI, the MMU, and the watchdog timer.

The REE never gets to touch that hardware directly. It has to go through secure-service wrappers — functions in esp_secure_services.c and esp_secure_services_iram.c that accept arguments from REE code, validate them, and then perform the privileged operation on the caller's behalf. For an eight-dollar Wi-Fi microcontroller, that's a genuinely useful security primitive: it means a compromised or buggy application can't just walk over to the crypto engine and read out a signing key.

Assuming, of course, that the wrappers actually check what they're handed.

The Bug, in Plain Terms

They didn't — not consistently. Several wrappers called esp_tee_ptr_in_ree() on some of the pointers a caller supplied, but not all of them. Output pointers where results get written back, pointers nested inside caller-supplied structs, and the end address of length-bounded buffers could all slip through unchecked. On top of that, the range checks that did exist didn't guard against 32-bit address overflow: a buffer that wrapped past the top of the address space could pass validation while still spanning memory that belonged to the TEE.

That would be a moderate bug on its own, except for one detail that turns it critical: the peripherals these wrappers front — AES, SHA, ECC, HMAC, SPI — run in RISC-V machine mode, the highest privilege level on the chip, with full address-space visibility. They don't know or care about the REE/TEE split. If a secure-service wrapper tells one of them to write output to a given address, it writes there, TEE-resident or not. The wrapper was the only thing standing between an untrusted pointer and privileged hardware, and in enough cases it wasn't standing there at all.

"An unprivileged REE application can direct ESP-TEE hardware peripherals to write attacker-influenced content into TEE-resident DRAM, breaking the REE/TEE isolation boundary." — GitHub Security Advisory GHSA-mmgp-73p4-92xp

How the Attack Chain Actually Runs

  1. Baseline: the attacker already has ordinary code execution in the REE — this could come from a compromised application dependency, a memory-safety bug in the app layer, or physical/UART access to a device that lets them push firmware.
  2. The attacker's REE code calls a legitimate secure-service function — say, one that drives the AES or ECC peripheral — supplying a pointer argument that the wrapper doesn't fully validate (an output buffer, a struct-embedded field, or a buffer end address).
  3. That pointer is crafted to either point directly into TEE-resident DRAM, or to wrap around the top of the 32-bit address space so a naive range check reads it as "in bounds" when it actually spans TEE memory.
  4. esp_tee_ptr_in_ree() either never runs against that specific argument, or its overflow-blind math waves it through.
  5. The call proceeds to the underlying peripheral, which executes in M-mode with no concept of the REE/TEE boundary and simply writes the attacker-supplied data where it was told to.
  6. Attacker-controlled bytes land inside TEE DRAM — the same region that, prior to this fix, also held the secure service call dispatch table (esp_secure_service_table.c), the function-pointer table that routes every future secure-service call.
  7. If the corrupted region includes that dispatch table, the next secure-service call jumps to an address of the attacker's choosing — arbitrary code execution inside the TEE, with the REE attacker now running with TEE privileges.

No TEE privileges are required to start this chain, and the CVSS vector reflects that: AV:L/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H, a 9.3 critical. Local access, low complexity, no privileges, no user interaction, and a scope change from REE to TEE with full loss of confidentiality, integrity, and availability once you're in.

It Has a Quieter Sibling: An Information-Leak Oracle

Espressif disclosed a second, related advisory alongside this one: CVE-2026-45329 (GHSA-w82j-7q63-7pqm), an out-of-bounds read in the same wrapper functions. Same root cause, mirror-image impact: some wrappers left input pointer arguments unchecked, so a caller could point a peripheral at TEE memory and have it read from there instead of writing to it. Depending on which wrapper you hit, the REE gets back raw TEE bytes, a computed function of TEE memory recoverable through repeated calls, or a single bit per call that works as an oracle for incrementally exfiltrating TEE-resident secrets — keys, internal pointers, the works.

Put the two together and you get a textbook combination: the read bug leaks the addresses and data you need to make the write bug reliable, and the write bug gives you code execution once you know where to aim. Both were fixed by the same underlying change.

DetailCVE-2026-45328 (Write)CVE-2026-45329 (Read)
GHSA IDGHSA-mmgp-73p4-92xpGHSA-w82j-7q63-7pqm
CWECWE-20, CWE-787CWE-20, CWE-125
CVSS 3.19.3 (Critical)Also Critical-range
Primary impactMemory corruption → TEE code executionTEE secret disclosure (keys, pointers)
Affected chipsESP32-C5, ESP32-C6, ESP32-C61, ESP32-H2
Vulnerable ESP-IDFv5.5.4, v6.0.1 (and earlier releases carrying ESP-TEE)
Fixed ESP-IDFv5.5.5, v6.0.2
ReporterEun0us (EspilonOrg)
PublishedMay 13, 2026

The Fix: Check Everything, and Make the Dispatch Table Immutable

Espressif's patch does two things, and both are worth understanding because they tell you what "fixed" actually means here. First, it introduces a new helper, esp_tee_buf_in_ree(), which performs overflow-safe range verification — correctly handling the 32-bit wraparound case — on every pointer argument crossing a secure-service boundary, not just some of them. Every wrapper now runs every pointer argument, including output pointers, struct-embedded pointers, and buffer end addresses, through this helper with ESP_FAULT_ASSERT() enforcement. Any range that touches a TEE-resident address gets rejected before it ever reaches the peripheral.

Second, and I think this is the more interesting hardening move: Espressif relocated the secure-service call dispatch table from TEE DRAM — which is readable and writable at runtime — to TEE IRAM, protected as read/execute-only under the chip's Physical Memory Attributes. Even if some future bug lets an attacker write into TEE memory again, the single highest-value target in that memory — the table that decides where every secure-service call jumps to — is no longer sitting in writable memory waiting to be hit. That's defense in depth done right: fix the immediate bug, and also remove the blast radius of the next one you haven't found yet.

There is no runtime workaround. Espressif's guidance is to upgrade to v5.5.5 or v6.0.2, or cherry-pick the patch commits onto a downstream branch if you can't move the whole SDK version yet.


My Take

I've spent enough years around network segmentation and privilege boundaries in enterprise infrastructure to recognize this pattern immediately, because it's not an embedded-systems problem, it's a trust-boundary problem, and I've watched the same shape of mistake happen in firewalls, hypervisors, and IAM policies that were supposed to be the hard perimeter. You draw a line, you build a gate to cross it, and then the gate itself turns out to have a hole because someone validated the arguments they thought about and missed the ones they didn't. TrustZone has had this exact class of bug. SGX has had it. Now ESP-TEE has it, on hardware that costs less than a coffee and ships in everything from smart plugs to industrial sensors.

What actually bothers me isn't that the bug existed — new security boundaries almost always have early bugs, and coordinated disclosure plus a same-cycle fix is the system working the way it's supposed to. What bothers me is how ordinary the bug was. This wasn't a novel side-channel or a cryptographic subtlety that took a PhD to spot. It was CWE-20, improper input validation — the same bug class that's been in the OWASP and MITRE top lists for two decades — sitting inside the one piece of code whose entire job description was input validation. If you're marketing a feature as "hardware root of trust," the wrapper code enforcing that trust needs to be held to a higher bar than average firmware, not the same bar, because every gap in it isn't a bug, it's a hole straight through the thing customers are paying extra silicon and extra engineering effort to get.

The part I'd genuinely credit Espressif for is the dispatch-table relocation. Patching the specific bug and stopping there is the common move; moving the highest-value target out of writable memory entirely is the move that assumes there's a next bug you haven't found yet. That's the mindset I want to see more of in embedded security teams, and it's rarer than it should be given how tight the margins are on this class of hardware.

What You Should Do Right Now

  • Inventory first. Find every board in your fleet or product line built on ESP32-C5, C6, C61, or H2 with ESP-TEE enabled, and check the exact ESP-IDF version each one was built against (idf.py --version, or check your build's sdkconfig and manifest pins).
  • Upgrade, don't patch around it. Move to ESP-IDF v5.5.5 or v6.0.2. If a full version bump isn't feasible on a given branch, cherry-pick the exact commits listed in GHSA-mmgp-73p4-92xp for your branch — there is no config flag or runtime mitigation that substitutes for the code fix.
  • Audit your own secure services, not just Espressif's. If you've extended esp_secure_services.c with custom TEE-side functionality for your product, check every pointer argument in your own wrappers against the same failure mode: are you validating output pointers and struct-embedded pointers, or just the obvious top-level input buffer? Copy the pattern in esp_tee_buf_in_ree(), don't reinvent it.
  • Treat any device that ran unpatched firmware as potentially exposed on the read side too. Because CVE-2026-45329 can leak keys and internal addresses, any fielded device that was running vulnerable firmware with REE-side code execution achievable (compromised app, malicious OTA, physical access) should be considered a candidate for key rotation on attestation or secure-storage material, not just a firmware bump.
  • Stop treating "it runs in the TEE" as a substitute for input validation everywhere else. This bug is a reminder that a TEE moves the trust boundary, it doesn't eliminate the need to defend it. Every REE-facing entry point into privileged hardware needs to assume its caller is hostile, full stop.
  • If you build firmware CI, add a fuzz pass against secure-service call arguments from the REE side specifically targeting pointer and length fields — this exact bug class is cheap to catch with targeted fuzzing and expensive to find any other way.

This article was researched and drafted with AI assistance as part of an experiment in building a faster tech-writing workflow, then reviewed and edited before publishing.


Primary source: Out-of-Bounds Write in ESP-TEE Secure Service Wrappers (GHSA-mmgp-73p4-92xp) — GitHub Security Advisories / espressif/esp-idf, May 13, 2026

Secondary sources: Out-of-Bounds Read in ESP-TEE Secure Service Wrappers (GHSA-w82j-7q63-7pqm) — GitHub Security Advisories / espressif/esp-idf; CVE-2026-45328 — OSV.dev; Announcing ESP-TEE Framework for ESP32-C6 — Espressif Developer Portal, February 2025

Comments