Feb 16, 2026·7 min read·6 visits
Integer overflow in ESPHome's protobuf decoder allows remote DoS via crafted packets. Fixed in version 2025.12.7.
A classic integer overflow vulnerability in the ESPHome API component allows unauthenticated remote attackers to trigger a Denial of Service (DoS) on ESP32, ESP8266, and RP2040 devices. By manipulating the length field of a Protobuf message, an attacker can bypass bounds checking, causing the device to attempt a read from invalid memory, resulting in a system crash and reboot.
If you are into home automation, you know ESPHome. It is the firmware that turns cheap Chinese microcontrollers into obedient servants of your Home Assistant instance. It controls your lights, reads your temperature sensors, and manages your covers. It is the nervous system of the DIY smart home. But as we all know, embedded C++ is a minefield where one wrong move with a pointer blows your leg off.
CVE-2026-23833 is exactly that kind of mine. It resides in the api component—the high-speed, native protocol ESPHome uses to talk to Home Assistant (port 6053). Unlike MQTT, which parses text, the native API uses Protocol Buffers (protobuf). It's faster, lighter, and binary. It's also where the developers decided to implement their own decoding logic.
Now, writing a binary parser from scratch in C++ is a rite of passage for systems programmers. It is also usually where security goes to die. In this case, a fundamental misunderstanding of how computers add numbers allowed a remote attacker to force every smart switch in your house to commit seppuku simultaneously. No authentication required (if you aren't using encryption), just a single malicious packet.
To understand this bug, we have to talk about how computers handle memory and math, specifically on 32-bit architectures like the ESP32 and ESP8266. In C++, when you want to read a piece of data from a buffer, you typically have a pointer to your current position (ptr) and a pointer to the end of the buffer (end).
The vulnerability lies in how the decoder validates incoming fields. The code reads a field_length (a 32-bit unsigned integer) from the network packet. Before reading the data, it must check if the data actually fits in the remaining buffer. The developers wrote a check that looks intuitively correct to a human, but is disastrous to a compiler:
if (ptr + field_length > end) {
return error;
}Here is the catch: ptr is a memory address (essentially a 32-bit integer). field_length is also a 32-bit integer. If ptr is pointing to 0x3FF00000 (a valid heap address on ESP32) and the attacker says the field_length is 0xFFFFFFFF (4GB), the CPU performs the addition.
0x3FF00000 + 0xFFFFFFFF results in an Integer Overflow. The value wraps around zero, landing somewhere near 0x3FEFFFFF (depending on exact alignment). This wrapped-around value is numerically smaller than end. The check if (small_value > end) evaluates to false. The code thinks the check passed. The guard is asleep, and the barbarian is at the gate.
The fix for this vulnerability is a textbook example of "safe coding practices 101" that often gets skipped in embedded development due to performance paranoia. The patch (Commit 69d7b6e) doesn't change the logic of what is being checked, but it changes the arithmetic used to check it.
Here is the side-by-side comparison of components/api/proto.cpp:
// The overflow happens right here in the addition
if (ptr + field_length > end) {
this->error_code_ = ERROR_OUT_OF_BOUNDS;
return false;
}// Safely calculate remaining space first
if (field_length > static_cast<size_t>(end - ptr)) {
this->error_code_ = ERROR_OUT_OF_BOUNDS;
return false;
}Why this works: instead of asking "Does (start + length) go past end?", the fixed code asks "Is the length bigger than the space we have left (end - start)?".
Since end is guaranteed to be greater than or equal to ptr (assuming the loop hasn't already gone off the rails), end - ptr yields the legitimate remaining bytes. Comparing the untrusted field_length against this safe value prevents the overflow entirely. It is a subtle change, effectively just moving ptr to the other side of the inequality inequality algebra-style, but it makes the difference between a stable device and a brick.
Exploiting this is trivially easy if the API is exposed without encryption. The attacker connects to TCP port 6053 and initiates a Protobuf conversation. We don't even need to complete a handshake; we just need the parser to hit our malicious field.
The attack flow looks like this:
192.168.1.x:6053.0x00 byte (signifying no encryption/preamble depending on version).Wire Type 2 (Length Delimited).
0xFFFFFFFF.When the ESPHome device receives this, it calculates the new pointer position. Due to the overflow described above, the bounds check passes. The code then attempts to advance the pointer ptr += field_length.
Since field_length is huge, ptr now points to invalid memory (or wraps around to unmapped territory). The very next operation—trying to read from this location or parsing the next field from void*—triggers a LoadProhibited or StoreProhibited exception. The ESP32 panic handler catches this, dumps a backtrace to the serial console (which you won't see because you are remote), and reboots the device.
> [!NOTE] > While this is primarily a Denial of Service (DoS), on embedded systems without ASLR or memory protection, heap corruption can sometimes lead to RCE. However, given the nature of the crash (reading wild memory), reliable execution is unlikely. It's mostly just a nuisance.
The CVSS score for this is listed as 1.7 (Low), largely due to the AT:P (Attack Complexity/Requirements) vector. This assumes that most users have the native API encryption (Noise protocol) enabled. If encryption is on, the attacker needs the pre-shared key to even get the parser to look at the packet length.
However, many legacy setups or internal LANs run with encryption disabled for simplicity. In those environments, the score is functionally a 7.5 (High).
Imagine a scenario where an attacker gains access to your IoT VLAN. With a simple Python script, they can put every light switch, sensor, and thermostat into a boot loop. The devices will come up, accept a connection, receive the packet, crash, and repeat.
This isn't just about lights flickering. ESPHome is often used for critical triggers: turning on cooling fans for servers, managing aquarium heaters, or controlling garage doors. A persistent DoS on the controller means the logic stops running. The fish die, the server overheats, and the garage door stays open.
The remediation is straightforward, but it requires flashing hardware, which is always a pain at scale.
You need to update the ESPHome dashboard (or command line tool) to version 2025.12.7 or higher. Once updated, you must recompile and upload the firmware to every single device on your network. A dashboard update alone does nothing; the C++ code runs on the chip, not your server.
Even with the patch, parsing untrusted input is risky. The best mitigation is to ensure the parser never processes packets from strangers. Enable the encryption: key in your api: configuration:
api:
encryption:
key: "<generated_base64_key>"This wraps the connection in the Noise protocol. Packets are authenticated before they are parsed. An attacker without the key cannot craft a valid frame, so the vulnerable integer addition code is never reached.
CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:N/VI:N/VA:L/SC:N/SI:N/SA:N/E:U| Product | Affected Versions | Fixed Version |
|---|---|---|
ESPHome ESPHome | >= 2025.9.0, < 2025.12.7 | 2025.12.7 |
| Attribute | Detail |
|---|---|
| CWE | CWE-190 (Integer Overflow) |
| CVSS v4.0 | 1.7 (Low) |
| Vector | CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:N/VI:N/VA:L |
| Attack Vector | Network (TCP/6053) |
| Impact | Denial of Service (Device Crash) |
| EPSS Score | 0.12% (0.00116) |
The software performs a calculation that can produce an integer overflow or wraparound, when the logic assumes that the resulting value will always be larger than the original value.