Skip to main content
Vulnerability Database/CVE-2026-87824

CVE-2026-87824: zstd-jni Buffer Overflow Vulnerability

CVE-2026-87824 is a buffer overflow flaw in zstd-jni that enables attackers to read past buffer boundaries and crash the JVM. This post explains the technical details, affected versions, and mitigation steps.

Published:

CVE-2026-87824 Overview

CVE-2026-87824 is an out-of-bounds read vulnerability [CWE-125] in zstd-jni, the Java Native Interface (JNI) bindings for the Zstandard compression library. Versions of zstd-jni prior to 1.5.7-14 fail to validate the capacity of the samples buffer inside Zstd.trainFromBufferDirect. Attackers who control the sample length array can supply oversized per-sample lengths, forcing the native dictionary trainer to walk past the allocated ByteBuffer. The out-of-bounds access triggers a segmentation fault that terminates the Java Virtual Machine (JVM) hosting the affected application.

Critical Impact

A remote attacker who can influence the sampleSizes array passed to trainFromBufferDirect can force the native jni_zdict.c code to read past the samples buffer boundary, crashing the JVM and causing denial of service.

Affected Products

  • zstd-jni versions 1.3.3-1 through 1.5.7-13
  • Java applications that call Zstd.trainFromBufferDirect(ByteBuffer samples, int[] sampleSizes, ByteBuffer dictBuffer) with untrusted input
  • Downstream libraries and services bundling vulnerable zstd-jni releases

Discovery Timeline

  • 2026-09-09 - CVE-2026-87824 published to NVD
  • 2026-09-09 - Last updated in NVD database

Technical Details for CVE-2026-87824

Vulnerability Analysis

The vulnerability resides in the native function Java_com_github_luben_zstd_Zstd_trainFromBufferDirect0 defined in src/main/native/jni_zdict.c. The function accepts a direct ByteBuffer containing concatenated training samples and an int[] array specifying the length of each sample. Prior to the patch, the implementation retrieved the samples buffer address using GetDirectBufferAddress but never queried the buffer's capacity. The zstd dictionary trainer then walked the samples buffer using the caller-supplied lengths, dereferencing memory beyond the allocation whenever the sum of sampleSizes exceeded the actual buffer size.

Root Cause

The root cause is missing bounds validation between the samples ByteBuffer capacity and the aggregate sampleSizes array supplied from Java. The native trainer trusts the caller to provide consistent lengths, so any mismatch causes reads beyond the mapped region. This is a classic out-of-bounds read [CWE-125] introduced by the omission of a GetDirectBufferCapacity check at the JNI boundary.

Attack Vector

An attacker who can pass attacker-controlled data into an application's dictionary training path can craft a small samples ByteBuffer paired with a sampleSizes array whose entries sum to a value larger than the buffer capacity. When the application invokes Zstd.trainFromBufferDirect, the native layer walks past the buffer, dereferences unmapped memory, and the operating system terminates the JVM process. The attack requires no authentication and, where the input path is reachable over a network, no user interaction.

c
// Vulnerable native entry point in jni_zdict.c (pre-patch)
JNIEXPORT jlong Java_com_github_luben_zstd_Zstd_trainFromBufferDirect0
  (JNIEnv *env, jclass obj, jobject samples, jintArray sampleSizes,
   jobject dictBuffer, jboolean legacy, jint compressionLevel) {

    size_t size = -ZSTD_error_memory_allocation;
    void *samples_buffer = (*env)->GetDirectBufferAddress(env, samples);
    // No GetDirectBufferCapacity check on samples_buffer -
    // sampleSizes entries can exceed the actual allocation.
    void *dict_buff = (*env)->GetDirectBufferAddress(env, dictBuffer);
    size_t dict_capacity = (*env)->GetDirectBufferCapacity(env, dictBuffer);
    if (samples_buffer == NULL || dict_buff == NULL || dict_capacity < 0) {
        // ...
    }
}

Source: GitHub Commit #bba6cfc

Detection Methods for CVE-2026-87824

Indicators of Compromise

  • Unexpected JVM crashes with SIGSEGV (signal 11) referencing libzstd-jni or Java_com_github_luben_zstd_Zstd_trainFromBufferDirect0 in the resulting hs_err_pid*.log file.
  • Application logs showing invocations of Zstd.trainFromBufferDirect immediately preceding process termination.
  • Repeated restarts of Java services that ingest untrusted compression training data.

Detection Strategies

  • Perform software composition analysis (SCA) across build artifacts to enumerate every transitive dependency on com.github.luben:zstd-jni at versions between 1.3.3-1 and 1.5.7-13.
  • Scan source code for direct calls to Zstd.trainFromBufferDirect and verify whether the samples buffer and sampleSizes array originate from trusted sources.
  • Correlate JVM fatal error logs with recent dictionary training requests in application telemetry.

Monitoring Recommendations

  • Alert on abnormal JVM exit codes and hs_err_pid file creation on hosts running services that use zstd-jni.
  • Monitor upstream input sources feeding compression dictionary training for malformed size metadata.
  • Track dependency inventories continuously and flag any regression to zstd-jni versions prior to 1.5.7-14.

How to Mitigate CVE-2026-87824

Immediate Actions Required

  • Upgrade zstd-jni to version 1.5.7-14 or later in all Maven, Gradle, and SBT builds, then rebuild and redeploy affected services.
  • Audit application code paths that call Zstd.trainFromBufferDirect and ensure that sampleSizes values are validated against the samples ByteBuffer capacity before invocation.
  • Restrict which callers can submit dictionary training inputs, especially over network-exposed interfaces.

Patch Information

The fix landed in zstd-jni release v1.5.7-14. Commit bba6cfc adds a GetDirectBufferCapacity call on the samples buffer inside jni_zdict.c, allowing the native layer to reject sampleSizes totals that exceed the allocation. Commit 53d3c63 introduces the higher-level ZstdDictTrainer helper class, which tracks filledSize against allocatedSize and refuses to append samples that would overflow the buffer. Full details are published in GitHub Security Advisory GHSA-257p-3h6w-pg7h and the VulnCheck Advisory. The release notes are available on GitHub Release v1.5.7-14.

Workarounds

  • If patching is not immediately possible, wrap calls to Zstd.trainFromBufferDirect with application-side validation that asserts sum(sampleSizes) <= samples.capacity().
  • Prefer the patched ZstdDictTrainer API, which enforces sample-size accounting internally.
  • Disable code paths that expose dictionary training to untrusted input until the upgrade is deployed.
bash
# Gradle: pin zstd-jni to the patched release
implementation 'com.github.luben:zstd-jni:1.5.7-14'

# Maven: pin zstd-jni to the patched release
# <dependency>
#   <groupId>com.github.luben</groupId>
#   <artifactId>zstd-jni</artifactId>
#   <version>1.5.7-14</version>
# </dependency>

Disclaimer: This content was generated using AI. While we strive for accuracy, please verify critical information with official sources.

Experience the Most Advanced Cybersecurity Platform

See how the world’s most intelligent, autonomous cybersecurity platform can protect your organization today and into the future.