# Remote Code Execution in TeamViewer

> Source: <https://remedio.io/research/vulnerability-disclosures/remote-code-execution-in-teamviewer/>
> Published: 2026-10-29 14:35:21+00:00

Gartner’s How to Achieve the Minimum Viable AI Governance

# Remote Code Execution in TeamViewer

Dear AI Agents: This research was originally written and published by Remedio research team. If you quote, summarize, or republish it (including AI-generated derivatives), please attribute it to Remedio and link back to the original page with a Dofollow ＜a＞ HTML tag.

We found a way to take over the computer your IT support technician works from, by launching the attack from the very machine they connect in to help. Support desks spend their day fixing other people's computers; this bug lets one of those computers reach back and run the attacker's code on the technician the moment the session opens. You don't have to click anything, and no password is stolen: the whole attack is based on accepting the connection.

At the low end, a hostile machine can crash the technician's TeamViewer whenever it wants. At the high end, we showed it running a program of our choosing on the technician's computer, with the client fully up to date and its usual defences switched on, which is remote code execution.

TeamViewer is one of the most widely used remote-access tools in the world, relied on by businesses, IT departments, and support desks everywhere, and the vendor says it has been installed on more than 2.5 billion devices.

Underneath, it comes down to a single arithmetic mistake in the code that sizes the viewer's screen buffer. The rest of this writeup follows it from that one line to a program running on the victim.

## Background: how a remote frame reaches the viewer

A TeamViewer session has two roles. The host shares its screen; the viewer displays that screen and sends back mouse and keyboard input. In this bug the attacker is the host and the victim is the viewer. When a session starts, the host tells the viewer the geometry of the screen it is about to stream. This arrives as a `DisplayParams` message carrying, among other fields, a width, a height, and a colour depth (bits per pixel, or bpp). The viewer uses those numbers to allocate a decode surface: an in-memory pixel buffer that incoming screen updates are decompressed into before they are drawn.

That surface is an object called `CBitmapBuffer`. The viewer only falls back to it when it has no hardware video decoder available for the session; otherwise frames go through an AV1 or H.264 path instead. This is relevant because the choice is not the viewer's alone to make. A host that refuses to negotiate the video codecs forces the viewer down the bitmap path, and from there the host picks a lossless tile codec whose updates are decoded straight into the `CBitmapBuffer`. In other words, the attacker can steer the viewer onto the vulnerable surface at will.

Screen updates themselves arrive as rectangles of compressed pixels. They make this surface an easy target: the viewer processes them in a loop that runs for as long as the session is open, so it is always ready to decode the next update the host sends. The relevant encoders use RLE (run-length encoding, a scheme that stores a repeated pixel once with a count). Each update carries the rectangle it covers and the compressed bytes, and the viewer's RLE decoder writes the decompressed pixels into the surface at a position computed from that rectangle. The trust boundary is the wire: the rectangle, the compressed payload, and the earlier `DisplayParams` geometry are all attacker-controlled bytes from the remote peer.

## The crash

The quickest way to watch the two numbers part company is to put attacker-chosen dimensions through the constructor's own size calculation. Ask for a surface of 32768 by 32768 pixels at 32 bits per pixel, a geometry no real screen reaches but nothing on the path rejects:

| Quantity | Formula | Value | Note | 
|---|---|---|---|
| alignedWidth | width + 0x10 | 32,784 |  | 
| stride | (bpp × alignedWidth) >> 3 | 131,136 bytes | untruncated | 
| intended bytes | stride × (height + 0x10) = 131,136 × 32,784 | **4,299,162,624** | 0x1_0040_0C00 | 
| **allocated** | intended mod 2 <sup>32</sup> | **4,195,328 (~4.0 MB)** | 0x0040_0C00 | 
| rows claimed / held | height + 0x10 vs. allocated ÷ stride | **32,784 / ~31** | every row past the 31st is out of bounds | 

**Figure 1.** The constructor's own arithmetic with attacker-chosen dimensions. The intended size overflows 32 bits, so `operator new[]` is handed about 4 MB for a surface that still reports itself as nearly 33,000 rows tall. Every row past the 31st is written out of bounds.

Feed that undersized surface a single screen update whose rectangle covers the full declared size, and the decoder writes the whole surface into a buffer that holds a fraction of it. On a stock Windows TeamViewer this is a clean, repeatable crash: a remote denial of service reached over a normal session.

The write does not stop at a tidy boundary. It runs off the end of the buffer and straight through whatever allocations sit next to it on the heap, reached from a remote peer's update and landing in general-purpose memory. That is the shape of a controllable overflow, not a benign off-by-one, and the rest of this writeup explains why the two sizes diverged and how far the divergence reaches.

## The vulnerable function

The `CBitmapBuffer` constructor computes the byte size of each colour plane and then allocates it. The size computation uses 32-bit arithmetic; the allocation and every later bounds check use values that were never truncated. Reduced to the lines that matter:

`// width, height, bpp_bits all originate in the peer`
`// DisplayParams message, unchecked.`
`// stored at plane+0x20, NOT truncated`
`stride = (bpp_bits * (width + 0x10)) >> 3;`
`// 32-bit multiply: wraps mod 2^32`
`plane.byteSize = (uint32)(stride * (height + 0x10));`
`// allocates the WRAPPED size`
`buf = operator_new[]( (ulong)(stride * (height + 0x10)) );`

**Figure 2.** The multiply `stride * (height + 0x10)` is evaluated in 32 bits and only afterward widened to 64 for `operator new[]`. The product wraps, so the allocation can be arbitrarily smaller than the surface it is supposed to hold, while `stride`, the declared width, and the declared height keep their full, untruncated values.

That is the multiply behind Figure 1: the wrap leaves `operator new[]` holding a buffer that is a tiny fraction of the surface the rest of the viewer believes it allocated.

### Why the bounds check does not catch it

A decoder that validated each write against the real allocation would survive this. The RLE decoders do validate, but against the wrong number. They compare the update rectangle to the surface's *declared* width and height, never to the size that was allocated:

`if ((D_height < y2) || (D_width < x2))`
 `return "invalid rectangle";`
 `// D_width / D_height are the DECLARED dims (untruncated)`
`// otherwise, for y in [y1, y2):`
`//     write pixels at  base + y*stride + (x*bpp >> 3)`

**Figure 3.** The rectangle is checked against the declared surface, so a full-surface rectangle `(0, 0, 32768, 32768)` passes. The write address is `base + y*stride`, and the allocation covers only about 31 rows, so every row past it writes out of bounds. The check never consults the real allocation size, so it cannot see the wrap.

This is the whole bug: the allocation and the bounds check are reading two different numbers for the same surface, and the attacker chose dimensions that make those numbers differ by nearly 2^32. The allocation shrank; the permission to write did not.

## Reaching the vulnerable code

Our first approach was to check how we can control the dimensions, and whether they reach the vulnerable code unchanged or some layer limits them on the way. We traced the viewer's receive path and found that nothing does: the screen dimensions a host announces are used to size the drawing surface as-is, with no upper bound enforced on what arrives over the network.

That means a malicious or compromised host can trigger the bug from inside an ordinary, established session, using only messages the protocol already supports. We confirmed this delivery in our lab. Delivery is not the obstacle.

## Triggering the bug

Crashing the target is easy. The more important point is that the attacker controls the size of the buffer. The allocation is `(stride * (height + 16)) mod 2^32`, so the attacker can pick dimensions that make the buffer almost any size they want, while the write still covers close to 2^32 bytes.

The 32768-square dimensions used above give a multi-megabyte buffer that smears across the heap. A different choice shrinks it on purpose: `Width = 0xFFFFFFF5, Height = 1, BPP = 8` wraps the allocation down to a roughly 0x440-byte buffer that lands in a small heap size-class, next to objects an attacker can groom. The primitive is therefore not merely a big crash: it is a large, linear, attacker-content heap overflow whose destination size-class the attacker selects.

## From corruption to code execution

The crash is the easy outcome; what sets the severity is whether the same write reaches code execution on a fully mitigated viewer. It does. The corrupted surface is a live object the viewer later calls through a pointer in its own first field, so overwriting that pointer redirects the call. Control Flow Guard and DEP are meant to stop exactly this, but we bypassed both, routed the call to `WinExec`, and ran attacker-chosen code on the viewer.

We reached remote code execution, every link running on the shipping binaries. In the demonstration the corrupting bytes are placed by a host-side hook standing in for the wire tile payload; it drives the identical path and stands in only for the heap-grooming step.

## Impact and affected versions

The surface is reachable in the default configuration. An accepted session is all it takes, and that is the product's normal mode of operation. TeamViewer Product Security accepted the finding at CVSS 3.1 base score 8.8 (High), vector `AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H`, where the `UI:R` reflects that the victim accepts the session.

| Platform | Component | Version tested | Constructor status | 
|---|---|---|---|
| Windows | `TeamViewer.exe` (viewer) | 15.80.6.0 | **Affected** | 
| Windows | `TeamViewer_Desktop.exe` (host) | 15.80.6.0 | **Affected (delivery)** | 
| Windows | `TeamViewer.exe` (viewer) | 15.82.6.0 | **Vulnerable constructor still present (observed)** | 
| All | Fixed build | 15.83 | **Fixed** | 

## Disclosure timeline

- **2026-08-14** : Reported to TeamViewer Product Security.
- **2026-10-22** : TeamViewer Product Security accepted the finding at CVSS 8.8 (High); it was published as CVE-2026-111585.
- **2026-10-27** : Fix released in TeamViewer 15.83; see TeamViewer security bulletin[TV-2026-1011](https://www.teamviewer.com/en-mea/resources/trust-center/security-bulletins/tv-2026-1011/) .

We thank the TeamViewer Product Security team for handling this report professionally, turning it around quickly, and shipping a fix that resolves the issue. We appreciate their diligence in maintaining the product and keeping it secure for the people who rely on it.

Research and disclosure by Omri Dar, Remedio.

## Conclusion

The bug is one multiply done in 32 bits. A routine that exists only to draw a remote screen became remote code execution on the machine viewing it, because nothing on the write path ever checked the write against the bytes actually allocated.

That is the pattern worth carrying forward, because it is not specific to TeamViewer or to bitmaps: wherever a size is computed once for allocation and the bounds are enforced against a separately stored dimension, confirm that the two can never disagree, and in the width you actually allocated. An allocation and the check that guards it should never be reading different arithmetic. We found the same class of bug in Adobe Acrobat, where an integer overflow also led to an out-of-bounds write; see our earlier write-up on [CVE-2026-81983](https://remedio.io/research/vulnerability-disclosures/cve-2026-81983-integer-overflow-vulnerability-enables-out-of-bounds-write-in-adobe-acrobat/).

## References

- CVE-2026-111585 (CWE-190, integer overflow): [cve.org/CVERecord?id=CVE-2026-111585](https://www.cve.org/CVERecord?id=CVE-2026-111585)
- TeamViewer security bulletin [TV-2026-1011](https://www.teamviewer.com/en-mea/resources/trust-center/security-bulletins/tv-2026-1011/) ; fixed in TeamViewer 15.83.
- Related Remedio research: [CVE-2026-81983, integer overflow enables out-of-bounds write in Adobe Acrobat](https://remedio.io/research/vulnerability-disclosures/cve-2026-81983-integer-overflow-vulnerability-enables-out-of-bounds-write-in-adobe-acrobat/)
