Fwd: Security Vulnerability in nbdkit QCOW2 ZSTD Decompression
----- Forwarded message from Tristan <TristanInSec@gmail.com> ----- Date: Mon, 24 Aug 2026 10:33:21 +0200 From: Tristan <TristanInSec@gmail.com> To: rjones@redhat.com Subject: Security Vulnerability in nbdkit QCOW2 ZSTD Decompression Hello, I am writing to report a security vulnerability in nbdkit's qcow2dec filter, confirmed at HEAD (commit 053f5a3). Summary of findings: ZSTD decompression heap over-read via incorrect buffer size (CVSS 6.5) The zstd_compressed_cluster() function sets the ZSTD input buffer size to cluster_size (uncompressed size) instead of compressed_size (actual allocation size). The equivalent zlib path correctly uses compressed_size, confirming this is a copy-paste bug. In worst case (2MB clusters, 512-byte compressed), this causes a 2MB over-read past a 512-byte allocation. The finding detail is attached. I would appreciate an acknowledgment of receipt. Thank you and please let me know if you need anything else. Regards, Tristan ================================================================= Heap Over-Read in ZSTD Decompression of QCOW2 Compressed Clusters ================================================================= CVSS 3.1: 6.5 (AV:L/AC:L/PR:N/UI:R/S:U/C:L/I:N/A:H) CWE: CWE-125 (Out-of-bounds Read) Auth: None (malicious QCOW2 file) Version: nbdkit 1.42.3, commit 053f5a3 (HEAD, 2026-07-21) File: filters/qcow2dec/qcow2dec.c, zstd_compressed_cluster(), line 712 Root Cause: The ZSTD input buffer size is set to cluster_size (the uncompressed cluster size) instead of compressed_size (the actual allocated buffer size). The equivalent zlib/deflate path correctly uses compressed_size. ZSTD path (INCORRECT) -- line 710-712: ZSTD_outBuffer out = { .dst = buf, .size = cluster_size, .pos = 0 }; ZSTD_inBuffer in = { .src = compressed_cluster, .size = cluster_size, .pos = 0 }; // ^^^^^^^^^^^^ // BUG: should be compressed_size zlib/deflate path (CORRECT) -- line 664-665: strm.next_in = (void *) compressed_cluster; strm.avail_in = compressed_size; // ^^^^^^^^^^^^^^^ correct: uses actual buffer size Buffer allocation -- line 840: compressed_cluster = malloc(compressed_size); Data Flow: 1. qcow2dec filter parses QCOW2 image with ZSTD-compressed clusters 2. L2 entry encodes nr_sectors (line 789), minimum 1 sector (512 bytes) 3. compressed_size = nr_sectors * 512 (line 806) 4. malloc(compressed_size) allocates buffer (line 840) 5. ZSTD input buffer declares .size = cluster_size (line 712) 6. ZSTD_decompressStream() may read up to cluster_size bytes 7. Over-read: cluster_size - compressed_size bytes past allocation Worst Case: cluster_bits=21 (2 MB clusters), single-sector compressed data: - compressed_size = 512 bytes - cluster_size = 2,097,152 bytes - Over-read: up to 2,096,640 bytes past the 512-byte allocation Impact: Crash (DoS) when serving a crafted QCOW2 image via nbdkit. In cloud and virtualization environments where untrusted disk images are served, an attacker can trigger this by providing a crafted QCOW2 image with ZSTD-compressed clusters. Potential heap data disclosure if the process survives the over-read. Fix: Change line 712 from: { .src = compressed_cluster, .size = cluster_size, .pos = 0 }; to: { .src = compressed_cluster, .size = compressed_size, .pos = 0 }; ----- End forwarded message ----- -- Richard Jones, Virtualization Group, Red Hat http://people.redhat.com/~rjones Read my programming and virtualization blog: http://rwmj.wordpress.com virt-builder quickly builds VMs from scratch http://libguestfs.org/virt-builder.1.html
participants (1)
-
Richard W.M. Jones