When you accept a voice or video call on your phone, the interface barely changes. The ringtone stops, the connection comes up, and audio starts. Underneath that is a lot of machinery: network signaling, cryptographic negotiation, media encoding, and hardware-specific audio processing. Most of it runs outside the managed app code that developers and users normally see. It lives in optimized native libraries built for low latency and high throughput, and that performance-critical layer is where severe bugs tend to sit until a malformed sequence of bytes arrives.
On October 29, 2026, the security research startup NebuSec disclosed a critical vulnerability in Telegram for Android. It affects versions 8.6.2 through 12.10.3 and, according to NebuSec, lets an attacker execute code remotely on a target device by starting a voice call that the victim then answers. The bug sits in tgcalls, Telegram’s shared native calling library, in the parser that handles a signaling message called MediaState. Telegram had already fixed the underlying logic error in version 12.10.4, released in late September. This article covers how the flaw works, what is and is not publicly known about the exploit, and what to do about it.
What is the Telegram Android MediaState vulnerability?
The MediaState flaw is a field-validation bug in the native C++ code that parses call signaling. The parser confirmed that one value was present, then read a second, independent value without checking that it existed too. NebuSec says it turned that mistake into remote code execution on ARM64 Android devices after the victim answered a crafted call.
The affected range is Telegram for Android 8.6.2 through 12.10.3. Version 12.10.4 and later contain the fix.
Signaling, media, and the native boundary in VoIP apps
A calling app has to solve two related problems. Signaling is the control plane: it negotiates session parameters, exchanges cryptographic keys, tracks call state, and announces changes in media capabilities while the call is live. Media processing is the data plane: it handles the constant stream of encoded audio and video, including hardware-accelerated decoding, jitter buffering, and rendering.
Telegram uses tgcalls for both jobs. The library is shared across its Android, iOS, and desktop clients. Real-time media has strict timing requirements and needs direct access to platform codecs, so writing this layer in Java or Kotlin is usually impractical. C++ gives the control over memory allocation, thread synchronization, and hardware interaction that the job demands.
That control has a cost. A managed runtime enforces bounds checking, garbage collection, and safe object lifetimes. Native C++ leaves memory safety entirely to the developer. An invalid pointer dereference or a miscalculated array index does not raise a catchable exception. It produces undefined behavior, which can show up as silent data corruption, a crash, or, in skilled hands, arbitrary code execution.
A MediaState message crosses a trust boundary on its way in. It starts at a remote peer, travels over the network, passes through Telegram’s transport and encryption layers, and lands in the native tgcalls parser, which turns the encoded representation into internal C++ structures. A parser in that position has to assume the peer is hostile or at least badly broken. Peers can send incomplete messages, truncate payloads, or supply values that violate the spec. The parser’s job is to protect the application’s internal state from all of that, and in this case a small logical oversight let bad input through.
How the MediaState validation bug works
NebuSec’s finding comes down to a mismatch between the fields the parser validated and the fields the application then used. During a call, MediaState carries information about the media session, such as whether the remote user has turned on video and how the outgoing video should be rotated to match the device’s orientation. The wire encoding follows Telegram’s internal signaling format, but the requirement is the same in any format: confirm that a field exists in the payload before reading its value.
In versions before 12.10.4, the native code verified that a rotation-related value was present and then read a separate video-state field without checking that this second field was present as well. Modern C++ often wraps optional fields in std::optional or reads them from custom serialization buffers. Assuming that one field’s presence implies another’s is a logic error, and in native code it can be a fatal one.
📬 Stay Ahead of Cyber Threats
Get the latest cybersecurity news, critical vulnerabilities, threat intelligence, tutorials, and exclusive giveaways delivered straight to your inbox. No spam. Unsubscribe anytime.
Subscribe to the Newsletter →The following example models the structure of the flaw without reproducing Telegram’s proprietary source:
cpp
<optional>struct MediaState { std::optional<int> rotation; std::optional<int> video_state;};void processMediaState(const MediaState& state) { // The parser correctly validates the presence of the rotation field. if (!state.rotation.has_value()) { return; } // FATAL FLAW: The presence of 'rotation' does not mathematically // or logically guarantee the presence of 'video_state'. int videoState = *state.video_state; // The application now operates on uninitialized or out-of-bounds memory, // leading to undefined behavior and potential memory corruption.}
Dereferencing an empty std::optional, or reading past the end of a serialized buffer, does not fall back to anything safe. Depending on memory layout, compiler optimizations, and the allocator, the program might read adjacent heap metadata, treat uninitialized stack memory as a valid integer, or corrupt the internal state of the tgcalls engine. Whether a parsing error becomes a working remote code execution exploit depends on what sits next to the missing field and how the application uses the corrupted data afterward.
The fix is field-by-field validation that treats all external data as untrusted. In 12.10.4, each optional field is checked against the protocol’s presence rules before its value is extracted. If the protocol marks a field as mandatory, a missing field means the whole message is rejected, and the parser never reaches the vulnerable code path.
From a parsing error to ARM64 code execution
NebuSec claims it demonstrated remote code execution on ARM64 Android devices after a victim answered a crafted call. Getting from a field-validation error to reliable code execution on a hardened mobile OS is hard work. The source code confirms the parsing defect, but the exact memory corruption and the rest of the exploit chain have not been independently verified in public.
It helps to look at what an attacker faces on ARM64 (AArch64). Modern Android devices stack several defenses meant to make memory corruption bugs unexploitable. Address Space Layout Randomization (ASLR) randomizes where code and data live on every run, so hardcoded addresses stop working. Many ARM64 processors also support Pointer Authentication Codes (PAC) and the Memory Tagging Extension (MTE), which sign pointers and tag allocations so that overwriting a function pointer or return address is likely to trigger a hardware exception.
If the MediaState bug produces an out-of-bounds read, the attacker’s first task is turning that read into an ASLR bypass. Reading adjacent heap memory might leak a pointer into a known library or the heap, which undoes the randomization for later stages. If the missing field instead throws off the length calculation for a following payload, the result could be a heap buffer overflow. An overflow lets the attacker overwrite the metadata of neighboring heap chunks or corrupt C++ virtual method tables (vtables). The next virtual call on a corrupted object then redirects execution to attacker-controlled shellcode or a Return-Oriented Programming (ROP) chain.
The victim has to answer the call, which adds a timing constraint. The vulnerable parser likely runs only after the cryptographic handshake finishes and the call moves into an active media-exchange state. So the attacker cannot fire a malformed packet at a dormant device. They need a legitimate session, a victim who picks up, and a malformed signaling message injected just as the native library updates its media state. That requirement keeps the bug out of the “zero-click” category, but it is still dangerous. Answering a call is routine, and nothing on screen tells the user that the signaling protocol is being used against their device.
Telegram’s other native attack surface: rlottie and the Rust migration
The disclosure landed amid a wider debate over the security of native media and content parsers in messaging apps. On October 15, 2026, two weeks earlier, TrendAI’s Zero Day Initiative (ZDI) published an advisory for CVE-2026-111105. It covers rlottie, the library Telegram and many other apps use to render vector-based Lottie animations. The advisory describes a write-before-start buffer flaw caused by insufficient validation of user-supplied data. It carries a CVSS score of 7.0 and offers a path to code execution if a victim opens a malicious file or visits a compromised page.
The two bugs differ in how they are triggered. The rlottie flaw needs the victim to interact with a particular file type, while the MediaState flaw sits in the core call protocol. They share a root cause, though: memory-unsafe languages processing untrusted input. Messaging apps today are also rendering engines, media decoders, and real-time communication hubs. Each feature, whether animated stickers, media players, or VoIP signaling, adds thousands of lines of native C or C++ that can each become a memory corruption vector.
Telegram has started moving toward memory-safe code. It introduced tlottie, a Rust-based implementation for animated content, and between Android versions 12.8.1 and 12.10.0 the client began replacing rlottie with it. That should cut the risk of classic buffer overflows and use-after-free bugs in the animation pipeline. Rust’s ownership model and compile-time borrow checker enforce memory safety in safe code without a garbage collector, which suits performance-critical native libraries.
A partial migration has limits, though. It removes one class of bugs from the parts that were rewritten and leaves the rest of the C++ untouched, so tgcalls and its signaling engine are no safer for it. Rust code can also reintroduce memory risk through unsafe blocks, which are common where it talks to legacy C APIs or does low-level hardware work. tlottie is a good engineering direction, but securing a large multi-platform codebase takes continuous triage, refactoring, and adversarial testing.
AI-assisted vulnerability discovery: what GPT-5.4 did and didn’t do
NebuSec says it found the MediaState bug with its proprietary Code Security product, which uses large language models to audit codebases. The company named OpenAI’s GPT-5.4 as the model behind it. That puts this bug among a growing set of findings made with AI help.
Language models are well suited to large, poorly documented native libraries like tgcalls. They can scan millions of lines quickly and flag suspicious constructs, inconsistent validation, and departures from secure coding conventions. A model trained on secure C++ practice would spot the gap here easily: the code checks rotation and then dereferences video_state with no has_value() check. That saves a human researcher a lot of time locating the defect.
Spotting a static anomaly is different from proving a dynamic vulnerability, however. A model can say the code breaks safe memory access rules. It cannot confirm on its own that the flaw is exploitable in a live environment. It does not know the heap layout, which compiler mitigations are active, or which exact packet sequence reaches the vulnerable path without crashing the app first. Going from a static-analysis flag to a demonstrated ARM64 RCE takes human expertise in binary exploitation, memory forensics, and protocol reverse engineering. The NebuSec case shows AI working well as a discovery and triage tool, while people still do the work of weaponizing a bug and understanding what it means.
How to prevent parser bugs like this: structure-aware fuzzing and sanitizers
Functional testing is a poor way to secure a network-facing parser. Standard QA checks that an application behaves correctly on valid inputs produced by its own ecosystem. Security testing asks what happens when the input is malformed and built to break the parser’s assumptions.
Teams should run structure-aware fuzzing in their CI pipelines. Naive fuzzers inject random bytes, and those usually get rejected by the outer cryptographic or framing layers before reaching the vulnerable logic. A structure-aware fuzzer knows the protocol grammar. It can generate syntactically valid messages with semantic problems: mandatory fields omitted, optional fields duplicated, values far outside the allowed ranges.
A fuzzing campaign for a signaling parser should enforce these invariants:
- A missing optional field never triggers an unchecked memory access.
- A missing required field leads to a defined rejection or a fallback to a documented default state.
- Out-of-range values are caught before they reach any operation that assumes valid input.
- Repeated or conflicting fields are resolved strictly according to the format’s specification.
- A malformed message never causes invalid memory access, unbounded resource use, or an unexpected state transition, and a failed parse leaves no partially updated call state behind.
Native builds should also run continuously under runtime instrumentation such as AddressSanitizer (ASan) and UndefinedBehaviorSanitizer (UBSan). These tools watch allocations and pointer operations and catch out-of-bounds accesses, use-after-free conditions, and undefined behavior that would otherwise appear in production as silent corruption or erratic crashes. Fuzzing a parser as a hostile boundary, with sanitizers on, is how field-validation mismatches like this one get found before attackers find them.
How to fix it: update Telegram for Android to 12.10.4 or later
Users and administrators have one effective remediation: update Telegram for Android to version 12.10.4 or later. The flaw lives in the native tgcalls library, so changing app permissions, restricting network access, or adjusting Java-level settings will not mitigate it. The vulnerable code runs with the full privileges of the Telegram process, which makes the patch the only reliable defense.
To check your version, open Settings and look at the number at the bottom of the screen. Enterprise mobility management (EMM) administrators should run compliance checks for devices still on the 8.6.2 through 12.10.3 release trains and force updates where needed.
Why the Android patch came months after the fix: the shared library sync problem
The disclosure also exposes a release-management problem common to cross-platform software. According to the report, the fix for the camera rotation logic behind the MediaState flaw was committed to the shared tgcalls repository in April 2026. iOS picked it up in version 12.7 in May, and the desktop client in version 7.0.1 in July. Android did not get the updated library until version 12.10.4 in late September.
That gap left a window in which the defect was known and fixed in the central repository while Android users stayed exposed. Platform-specific build pipelines, dependency management delays, and staggered rollouts all contribute to this. A shared codebase is efficient, but it needs a security-first release process so critical memory-safety patches reach every supported platform at about the same time. Until a fix is compiled, signed, and delivered to a user’s device, the vulnerability stays exploitable no matter what the source repository says.
Conclusion
The MediaState bug started with a single wrong assumption about an optional field in a native C++ parser. No cryptographic break or architectural failure was involved. The whole path from an ordinary voice call to a possible device compromise runs through the point where external data enters the application’s internal state.
NebuSec has kept the details of its ARM64 exploitation chain private. What is established is the parsing defect and the fix in 12.10.4. Messaging apps keep absorbing OS-level functionality, including real-time media, complex rendering, and decentralized protocols, so their native attack surface will keep growing. Moving to Rust helps, but it does not remove the need to audit, fuzz, and defend the large body of legacy C++ that still carries global communications. Any parser that takes remote input should treat every incoming message as hostile until it has been validated.









