Oct 8, 2026·6 min read·3 visits
An uncontrolled recursion vulnerability in lz4-java allows remote attackers to trigger a stack overflow and thread termination by supplying compressed streams with repeated empty blocks when stopOnEmptyBlock is disabled.
A Denial of Service (DoS) vulnerability exists in the yawkat fork of lz4-java prior to version 1.11.4. Under specific non-default configurations (stopOnEmptyBlock = false), parsing crafted streams with a large sequence of contiguous empty LZ4 blocks triggers uncontrolled recursion inside the LZ4BlockInputStream.refill() method, causing stack exhaustion and thread termination.
The lz4-java library is a high-performance Java implementation of the LZ4 compression standard, widely integrated into high-throughput backend services and stream-processing frameworks. Within the library, the net.jpountz.lz4.LZ4BlockInputStream class is used to decompress block-based streams containing LZ4 frames. The parser handles input formatting, block verification, and checksum validation before streaming decompressed bytes to applications.\n\nThe stream decoder can be configured using the stopOnEmptyBlock parameter during initialization. When this parameter is set to its default value of true, the decoder treats empty blocks (blocks with a length of zero) as End-Of-Stream (EOF) boundary markers. However, when configured with stopOnEmptyBlock = false, the decoder processes concatenated streams, skipping empty blocks to read the next block in sequence.\n\nThis implementation choice introduces a critical attack surface. If the parser is configured with stopOnEmptyBlock = false, a remote attacker can supply a specially crafted compressed stream containing a dense sequence of valid but empty LZ4 blocks. This triggers uncontrolled recursion in the decompression logic, resulting in a thread-terminating stack overflow.
The technical defect belongs to the CWE-674 (Uncontrolled Recursion) vulnerability class. In the LZ4 block format, each block is preceded by a 21-byte header containing magic bytes, compression metadata tokens, compressed and decompressed lengths, and an optional Adler32 checksum. An empty block is formally defined by setting both the compressed length (compressedLen) and original decompressed length (originalLen) to zero.\n\nIn affected versions of LZ4BlockInputStream, when originalLen equals zero and the decoder has stopOnEmptyBlock disabled, the system attempts to bypass the current empty block and immediately retrieve the next block in the stream. Instead of using an iterative tracking loop, this progression was implemented recursively by invoking refill() from within the refill() function itself.\n\nBecause Java Virtual Machines allocate a distinct frame on the execution stack for each method call, processing continuous empty blocks results in a linear escalation of stack utilization ($O(N)$ depth). The default thread stack size (-Xss) in standard production JVM configurations generally accommodates 10,000 to 100,000 active frames. An attacker-controlled stream carrying tens of thousands of empty block structures will consume the available stack memory, forcing the execution runtime to throw a java.lang.StackOverflowError and terminate the parsing thread.
To understand the vulnerability mechanics, consider the vulnerable implementation of the internal refill() method in LZ4BlockInputStream.java:\n\njava\n// Vulnerable Code Path in net.jpountz.lz4.LZ4BlockInputStream\nprivate void refill() throws IOException {\n if (!tryReadFully(compressedBuffer, HEADER_LENGTH)) {\n if (!stopOnEmptyBlock) {\n finished = true;\n } else {\n throw new EOFException(\"Stream ended prematurely\");\n }\n return;\n }\n // ... [Header Parsing and Validation Logic]\n if (originalLen == 0) {\n if (check != 0) {\n throw new IOException(\"Stream is corrupted\");\n }\n if (!stopOnEmptyBlock) {\n refill(); // <-- UNCONTROLLED RECURSIVE CALL FOR CONSECUTIVE EMPTY BLOCKS\n } else {\n finished = true;\n }\n return;\n }\n // ... [Block Decompression]\n}\n\n\nIn the patched version, the maintainer removed the recursive method invocation. The design was refactored to employ an iterative while loop that handles skipping empty blocks within a flat execution path, maintaining a constant $O(1)$ stack frame complexity:\n\njava\n// Patched Code in net.jpountz.lz4.LZ4BlockInputStream (v1.11.4)\nprivate void refill() throws IOException {\n // Loop rather than recurse over empty blocks so that a long run of them cannot overflow the stack\n while (!readBlock()) {\n // empty block with stopOnEmptyBlock == false, continue with the next block\n }\n}\n\nprivate boolean readBlock() throws IOException {\n final int headerRead = tryReadFully(compressedBuffer, HEADER_LENGTH);\n if (headerRead != HEADER_LENGTH) {\n if (headerRead == 0 && !stopOnEmptyBlock) {\n finished = true;\n return true;\n }\n throw new EOFException(\"Stream ended prematurely\");\n }\n // ... [Metadata Validation]\n if (originalLen == 0) {\n if (check != 0) {\n throw new IOException(\"Stream is corrupted\");\n }\n if (!stopOnEmptyBlock) {\n return false; // <-- Returns false to let the calling loop continue iteratively\n }\n finished = true;\n return true;\n }\n // ... [Decompress and return true]\n return true;\n}\n
An attack requires the remote JVM application to consume LZ4-compressed streams supplied directly by users, and the decoder instance to have been configured with stopOnEmptyBlock = false. If these preconditions are met, the attack can be launched over network boundaries.\n\nAn attacker generates a payload comprising a large series of empty block headers. Since an empty block header is only 21 bytes, an exploit payload containing 150,000 empty blocks is extremely compact, measuring roughly 3.1 megabytes in transit. The generation sequence follows a structured pattern:\n\nmermaid\ngraph LR\n A["Attacker Payload"] --> B["Repeat 150,000x: 21-Byte Header"]\n B --> C["Magic: 'LZ4Block'"]\n C --> D["Token: Raw (0x20)"]\n D --> E["Comp Length: 0x00000000"]\n E --> F["Decomp Length: 0x00000000"]\n F --> G["Checksum: 0x00000000"]\n\n\nWhen the target service ingests this payload, the parser processes each zero-length block in sequence. Because stopOnEmptyBlock is disabled, the parser invokes refill() for every entry. If the number of empty headers exceeds the remaining stack depth of the JVM processing thread, a java.lang.StackOverflowError occurs immediately.\n\nCrucially, because StackOverflowError is an instance of java.lang.Error rather than java.lang.Exception, standard catch blocks targeting java.io.IOException or general exceptions fail to intercept the error. The error propagates up the call stack, abruptly terminating the active worker thread. This leads to thread pool depletion and service denial without leaving traditional error logging metrics behind.
The CVSS v3.1 score for this vulnerability is assessed at 3.7 (Low). The low severity categorization stems from the non-default configuration required to trigger the exploit; applications are only vulnerable if developers explicitly instantiate LZ4BlockInputStream with the stopOnEmptyBlock boolean set to false. Additionally, no remote execution, privilege escalation, or confidentiality compromise occurs.\n\nDespite the low base score, the real-world operational impact on affected targets can be high. In high-performance backend microservices running on thread-per-request architectures (such as Spring MVC, Tomcat, or Netty-based ingest controllers), terminating worker threads causes immediate performance degradation. \n\nAn attacker can repeatedly send the lightweight exploit payload to exhaust available worker threads, resulting in complete denial of service for incoming requests. The vulnerability does not cause memory corruption or native crashes, meaning the JVM itself remains online, but affected routing or processing components will lock up or discard tasks.
To detect vulnerable implementations, static analysis should be performed on the codebase to identify calls to the LZ4BlockInputStream constructor. Specifically, look for calls where the second parameter is configured as false:\n\njava\n// Vulnerable usage pattern\nLZ4BlockInputStream in = new LZ4BlockInputStream(inputStream, false);\n\n\nDependency analysis tools (such as OWASP Dependency-Check or Dependabot) must be updated to audit the dependency coordinates of org.lz4:lz4-java or net.jpountz.lz4. All versions prior to 1.11.4 must be flagged as non-compliant.\n\nThe primary mitigation is to upgrade the lz4-java dependency to version 1.11.4 or above. This completely eliminates the recursive code path. If upgrading is not immediately possible, code must be modified to utilize the default value of stopOnEmptyBlock = true, or upstream application layers must validate the incoming stream format and reject sequences containing consecutive, identical empty block signatures prior to processing.
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:L| Product | Affected Versions | Fixed Version |
|---|---|---|
lz4-java yawkat | < 1.11.4 | 1.11.4 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-674 (Uncontrolled Recursion) |
| Attack Vector | Network (AV:N) |
| Attack Complexity | High (AC:H) |
| CVSS Score | 3.7 (Low) |
| EPSS Score | 0.00339 (Percentile: 25.20%) |
| Impact Status | Denial of Service (Thread Death) |
| Exploit Status | Proof of Concept Available |
| KEV Status | Not Listed |
The code contains a recursive path that does not have an upper limit or maximum recursion depth check bound to available stack limits.
An unauthenticated Server-Side Request Forgery (SSRF) vulnerability exists in Ghost CMS from version 6.54.1 to 6.65.0. The vulnerability stems from a validation bypass in the favicon resolution logic within the bookmark-fetching subsystem, which allows remote, unauthenticated attackers to trigger arbitrary HTTP requests to the local host and internal networks. This bypass circumvents the custom DNS-level IP blocklist controls configured globally in the application.
A resource allocation vulnerability (CWE-770) in lz4-java before version 1.11.4 allows an unauthenticated remote attacker to trigger CPU exhaustion and high garbage collection overhead by streaming empty concatenated LZ4 frames.
CVE-2026-76485 is a critical stack-based buffer overflow vulnerability in the VXLAN OAM (NGOAM) parsing component of Cisco NX-OS Software. The flaw enables an unauthenticated, remote attacker to execute arbitrary code with root privileges or trigger a denial of service on affected Nexus switches. This vulnerability is triggered through crafted packets sent to an IP interface. No workarounds are currently available to mitigate the vulnerability while preserving the NGOAM functionality. Cisco has published software patches to address this flaw.
A local privilege escalation and code execution vulnerability exists in the yawkat fork of lz4-java when extracting its bundled JNI shared library into the system temporary directory. Predictable path derivation and lack of exclusive file creation flags allow a local attacker to hijack library loading via a race condition.
A missing authorization vulnerability in Langflow versions 1.0.0 through 1.10.0 allows authenticated users (and unauthenticated users in versions prior to 1.7.2) to access private workflow structures and execute graph components by targeting deprecated API endpoints.
A critical OS command injection vulnerability exists in Langflow's Model Context Protocol (MCP) server integration using stdio transport, allowing unauthenticated remote command execution under default configurations.