How One Image Can Break Signal

Author

Stanislav Fort

Date Published

signal-bg

TL;DR: A single maliciously crafted image in your photo gallery could silently break Signal's attachment feature, and stay broken through restarts, cache clears, even full app reinstalls. No error message. No crash dialog. Signal just stops letting you share anything. This zero-day vulnerability, which was unknown to the vendor and unpatched at the time of discovery, hid in production for nearly 5 years. Signal fixed it within 22 hours of our report, directly applying our patch.

signal diagram

Imagine this: you tap the attachment button in Signal. The app silently dumps you back to your conversation list. No error. You try again. Same result. You restart the app; still broken. You reinstall Signal entirely, and it's still broken.

What would you conclude? Probably "Signal is buggy." Not "there's a malicious image hiding in my gallery."

That's exactly what we found.

Signal is the gold standard for private messaging, trusted by journalists, activists, whistleblowers, and millions of privacy-conscious individuals worldwide. With over 100 million downloads on Google Play alone, Signal's security posture matters enormously. For many users, it's critical infrastructure for free speech and human rights.

When I pointed the analyzer at Signal Android's image processing pipeline, it flagged an issue that warranted closer attention. What we found was a zero-day denial-of-service vulnerability that had been sitting in production for nearly 5 years. It could silently render Signal's attachment feature completely unusable. Triggering the bug requires no network access from Signal itself, though the file can be delivered remotely via any app that saves to shared media storage. No suspicious crash dialogs would point to the culprit. The malicious image could simply sit in your gallery, invisible and inert, breaking Signal every time you tried to share anything. It would keep breaking until you figured out, somehow, that that specific image was the problem.

We reported this to Signal's security team on September 8, 2025. Less than 24 hours later, they confirmed our fix and shipped it in version 7.56.5.


The Discovery: When Frustration Becomes Investigation

Aisle's analyzer flagged a risky code path in Signal's APNG decoder. To verify, I built a tiny proof‑of‑concept APNG, sent it to myself in Signal, and saved it to the gallery.

As in the opening scenario, the failure mode is silent and persistent, with no crash dialog, no warning, and no hint of which file is responsible.

At first, it looked like a mundane app bug. I tried the usual fixes.

The behavior persisted across conversations, across restarts, and even after uninstalling and reinstalling the app entirely. That persistence was the tell: app reinstallation should reset everything. Why would the problem survive?

Only later did I connect the dots: saving my proof‑of‑concept APNG had quietly poisoned the gallery. The moment it appeared among the first photos, the picker tried to generate a thumbnail; the decoder trusted the unbounded IHDR dimensions; the image pipeline crashed; and the attachment flow failed every time until that file was removed. The problem wasn’t in Signal’s state at all. It lived in the device’s photo gallery.


The Vulnerability: Death by a Thousand Pixels (That Don't Exist)

Signal Android includes a custom APNG (Animated PNG) decoder for rendering animated images. Unlike the standard PNG decoder provided by Android's BitmapFactory, Signal's implementation reads dimension information directly from the APNG file's IHDR chunk and uses those values to allocate memory buffers.

The problem: there were no bounds checks.

An APNG file can claim to be any size. The IHDR chunk simply contains two 32-bit integers for width and height. A malicious file can declare dimensions of 25,000 × 20,000 pixels while containing virtually no actual image data. When Signal's decoder encountered such a file, it would attempt to allocate a buffer large enough for 500 million pixels, multiplied by 4 bytes per pixel (RGBA). That's 2 gigabytes of memory for a file that's less than 100 bytes on disk.

The result: an immediate OutOfMemoryError, crashing the image loading pipeline.

Here's the vulnerable code path in app/src/main/java/org/signal/glide/load/resource/apng/decode/APNGDecoder.java:

JavaScript
1} else if (chunk instanceof IHDRChunk) {
2 canvasWidth = ((IHDRChunk) chunk).width;
3 canvasHeight = ((IHDRChunk) chunk).height;
4 ihdrData = ((IHDRChunk) chunk).data;
5} else if (!(chunk instanceof IENDChunk)) {
6 otherChunks.add(chunk);
7}
8frameBuffer = ByteBuffer.allocate((canvasWidth * canvasHeight / (sampleSize * sampleSize) + 1) * 4);
9snapShot.byteBuffer = ByteBuffer.allocate((canvasWidth * canvasHeight / (sampleSize * sampleSize) + 1) * 4);

The canvasWidth and canvasHeight values come directly from the untrusted IHDR chunk with no validation. The multiplication canvasWidth * canvasHeight can produce enormous values, and the subsequent ByteBuffer.allocate() call attempts to allocate whatever size is computed, with predictable results.

The same pattern existed in the base class at app/src/main/java/org/signal/glide/common/decode/FrameSeqDecoder.java:

JavaScript
1private void initCanvasBounds(Rect rect) {
2 fullRect = rect;
3 frameBuffer = ByteBuffer.allocate((rect.width() * rect.height() / (sampleSize * sampleSize) + 1) * 4);
4 if (mWriter == null) {
5 mWriter = getWriter();
6 }
7}

This vulnerable code path was introduced on September 1, 2020, when Signal added APNG rendering support. It remained in production for almost exactly 5 years before Aisle's analyzer identified the issue in September 2025.


The Attack Surface: Why This Is More Dangerous Than It Sounds

This vulnerability triggers during thumbnail generation, not just when viewing a full image. Signal's attachment picker needs to render thumbnails for all images in your gallery. The moment it encounters the malicious APNG while building the picker grid, the entire image loading pipeline crashes.

This has several implications:

1. No interaction with the image required

The user doesn't need to tap, preview, or select the malicious image. It only needs to exist in the gallery; opening the attachment picker is enough.

2. The Image Can Arrive Through Multiple Vectors

The malicious APNG doesn't have to come from Signal itself:

  • Third-party apps with auto-download: Many messaging apps automatically save received images to the device gallery. An attacker could send the malicious image via WhatsApp, Telegram, or any other app that auto-saves media.
  • Shared storage: Any app with storage permissions could place the file in a location that Signal's gallery picker enumerates.
  • Signal itself: The image can be received in a Signal chat. In the chat view, it might render fine (or fail silently). But once it's in the gallery, the attachment picker becomes unusable.

3. The Damage Persists Until Manual Remediation

Because the crash happens during gallery enumeration, the only fix is to locate and remove the malicious file from your device. But nothing in Signal's behavior points to a specific image as the culprit. The app just mysteriously breaks every time you try to attach anything.

Our testing showed that:

  • Restarting the app: doesn't help
  • Force-stopping Signal: doesn't help
  • Clearing Signal's cache: doesn't help
  • Uninstalling and reinstalling Signal: doesn't help

The only solution is to find and delete the malicious image from your gallery. But since the image looks like any other PNG file (it might even have a valid thumbnail in your gallery app), there's no obvious way to identify it.


Tested Environment and Reproduction

We verified this vulnerability across multiple devices and documented the behavior with video recordings.

The proof of concept is simple: an APNG whose IHDR declares dimensions in the tens of thousands of pixels while containing minimal image data. Such a file can be tiny on disk yet demand a multi‑gigabyte buffer at decode time. When this file is present in the gallery and the user opens Signal's attachment picker, the app crashes.


Impact Assessment

CVSS v3.1 Score (Aisle assessment): 5.5 (Medium)

Vector: CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:N/I:N/A:H

While the CVSS score is medium, the real-world impact is amplified by the vulnerability's persistence and invisibility to users.

This is a pure denial-of-service vulnerability. There is no evidence of memory corruption or potential for code execution. However, for a secure messaging app with over 100 million Android installs, the ability to silently disable core functionality represents a significant availability impact. Even a small fraction of users encountering this bug could mean millions of people unable to share photos and documents through Signal.

Real-World Attack Scenarios

Targeted harassment: An attacker could send the malicious image to a target through any channel that auto-saves to the device gallery. The target's Signal app becomes partially unusable with no obvious cause, creating frustration and potentially driving them to less secure alternatives.

Protest/journalism disruption: In scenarios where secure communication is critical, disabling a journalist's or activist's ability to share photos and documents through Signal could have serious consequences.

Support ticket flooding: Users experiencing this issue would likely contact Signal support or post in forums about a "bug," consuming support resources while the actual cause remains hidden.


The Fix: Simple, Elegant, Effective

Our proposed fix introduced explicit dimension validation and safe arithmetic before any memory allocation. The patch adds two constants defining reasonable limits:

JavaScript
1public static final int MAX_DIMENSION = 4096;
2public static final long MAX_TOTAL_PIXELS = 64_000_000L;

And a new method that performs safe allocation size calculation:

JavaScript
1public static int getSafeAllocationSize(int width, int height, int sampleSize) throws IOException {
2 if (width <= 0 || height <= 0 || width > MAX_DIMENSION || height > MAX_DIMENSION) {
3 throw new IOException("APNG dimensions exceed safe limits: " + width + "x" + height);
4 }
5
6 int capacity;
7 try {
8 int ss = Math.multiplyExact(sampleSize, sampleSize);
9 int canvasSize = Math.multiplyExact(width, height);
10
11 int pixelCount = canvasSize / ss + 1;
12 if (pixelCount <= 0 || pixelCount > MAX_TOTAL_PIXELS) {
13 throw new IOException("APNG pixel count exceeds safe limits: " + pixelCount);
14 }
15
16 capacity = Math.multiplyExact(pixelCount, 4);
17 } catch (ArithmeticException e) {
18 throw new IOException("Failed to multiply dimensions and sample size: " + width + "x" + height + " @ sample size " + sampleSize + " (overflow?)", e);
19 }
20
21 return capacity;
22}

The key improvements:

  1. Explicit dimension limits: No image larger than 4096 pixels in either dimension
  2. Total pixel cap: Maximum of 64 megapixels regardless of dimensions
  3. Overflow protection: Uses Math.multiplyExact() to catch integer overflow
  4. Clear error reporting: Throws descriptive IOException instead of crashing

Signal's team implemented this fix with minor modifications to the constant values and shipped it in version 7.56.5.


Timeline

Total time from report to confirmed fix: less than 24 hours.

This rapid response demonstrates Signal's commitment to security. The team immediately recognized the validity of the report, accepted our proposed solution, and moved to ship the fix with impressive speed.


Affected and Patched Versions

If you're running Signal Android, ensure you're on version 7.56.5 or later. A patched version (7.56.5+) has been available since September 2025. Given Signal's auto-update behavior, most users should already be protected.

Note on CVE: This vulnerability was not assigned a CVE identifier. Signal's security team informed us that requesting CVE numbers is not their standard practice. The vulnerability is nonetheless documented in the public commit history. The CVSS score and vector shown here are our CVSS v3.1 assessment based on the observed trigger conditions and impact.


Mitigation for Users Who Cannot Update Immediately

If you're unable to update Signal and experience repeated crashes when opening the attachment picker:

  1. Identify suspicious images: Look for recently added PNG files in your gallery, especially any with unusual properties or from untrusted sources.
  2. Clear your gallery of recent additions: If you recently received images through other messaging apps that auto-download media, consider removing those files temporarily.
  3. Use Signal's camera directly: The in-app camera bypasses the gallery picker entirely, allowing you to take and send new photos even while the vulnerability is present.
  4. Update as soon as possible: The fix is simple and the patched version has been available since September 2025.

The Bigger Picture: Invisible Attacks and Autonomous Defense

The severity here is medium. What makes this vulnerability particularly interesting is something else: the invisibility.

Most security vulnerabilities announce themselves. A crash dialog appears. An error is logged. Something looks wrong. This one, by accident of design, was maximally frustrating: it breaks functionality without explanation, survives reinstallation, and provides no clues about its cause.

For a user experiencing this, the natural conclusion is "Signal is buggy," not "someone planted a malicious image in my gallery." The attack surface extends beyond Signal itself into the entire ecosystem of apps that can write to shared media storage.

This is exactly the kind of subtle issue that human code review tends to miss. The vulnerable code looks reasonable at first glance: read dimensions from a file, allocate a buffer. What's wrong with that? The problem only becomes apparent when you consider that the file is untrusted and the dimensions could be adversarial.

Aisle's autonomous analyzer found this because it builds a stack model of the codebase, mapping how data flows from untrusted sources through parsing, allocation, and rendering. It systematically explores trust boundaries, asking: "What happens if this value is larger than expected? What if it's negative? What if it causes an overflow?" These questions are tedious to ask manually across millions of lines of code. They're exactly what autonomous security analysis is designed to do.

This discovery also marks a milestone in Aisle's coverage. Our analyzer has already proven itself against deep infrastructure like OpenSSL, where we discovered three of the four CVEs assigned in 2025. We've found vulnerabilities in middleware like Traefik and Glob. With Signal, we demonstrate the same capability at the application layer: autonomously identifying the vulnerability, building a proof-of-concept to verify it, and proposing a fix that shipped in production. From cryptographic libraries to reverse proxies to encrypted messaging apps, the same system scales across the entire software stack.


Acknowledgments

We extend our appreciation to Signal's security team for their rapid and professional response to this disclosure. Their willingness to credit external researchers and their commitment to shipping fixes quickly demonstrates the kind of security culture that makes Signal worthy of its users' trust.

The fix commit includes the acknowledgment:

"Special thank you to Stanislav Fort of Aisle Research ([email protected]) for finding this issue, bringing it to our attention, and offering a solution!"

Aisle's mission is to secure the world's critical software infrastructure. We're honored to contribute to the security of software that protects so many people's private communications.


Learn More

This issue is one example of how Aisle's autonomous analyzer reasons about untrusted inputs across real-world codebases. Visit us at aisle.com or reach out at [email protected].

Technical References: