From 3fef86f1a9839f79b9efab76080fae0c0a93ebd4 Mon Sep 17 00:00:00 2001 From: gusfromspace Date: Fri, 24 Apr 2026 00:34:50 -0400 Subject: [PATCH] fix: validate ClusterBits and BlocksInImage; fix uint32 overflow in getGDGT Three DoS vulnerabilities in VM disk image parsers, all triggered by parsing a maliciously crafted disk image file. QCOW2 (format.go): ClusterBits is read from the image header with no bounds check. The QCOW2 spec limits valid values to 9-21. Without this check, ClusterBits=62 causes make([]byte, 4EB) which panics the runtime. Validation added to both readL1Table and readL2Table. VMDK (vmdk.go): GDsectors, GTsectors, and GTs are uint32 values. Multiplying them before widening to int64 causes overflow, producing a wrapped totalSectors that bypasses the >1<<31 size check. The gdarr allocation overflows for the same reason. Fixed by casting each operand to int64 before multiplication. VDI (vdi.go): BlocksInImage is a uint32 read directly from the image header with no upper bound. make([]uint32, 0x3FFFFFFF) allocates ~4GB. Added a 2M-block cap (2*1024*1024), generous for any real VDI image. --- extractor/filesystem/embeddedfs/qcow2/format.go | 6 ++++++ extractor/filesystem/embeddedfs/vdi/vdi.go | 3 +++ extractor/filesystem/embeddedfs/vmdk/vmdk.go | 4 ++-- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/extractor/filesystem/embeddedfs/qcow2/format.go b/extractor/filesystem/embeddedfs/qcow2/format.go index 104fd120c..8e749d58d 100644 --- a/extractor/filesystem/embeddedfs/qcow2/format.go +++ b/extractor/filesystem/embeddedfs/qcow2/format.go @@ -191,6 +191,9 @@ func parseHeader(reader io.Reader) (*header, []headerExtension, error) { } func readL1Table(header *header, reader io.ReaderAt) ([]uint64, error) { + if header.ClusterBits < 9 || header.ClusterBits > 21 { + return nil, fmt.Errorf("invalid ClusterBits value: %d", header.ClusterBits) + } l1Table := make([]uint64, header.L1Size) buf := make([]byte, header.L1Size*8) if _, err := reader.ReadAt(buf, int64(header.L1TableOffset)); err != nil { @@ -203,6 +206,9 @@ func readL1Table(header *header, reader io.ReaderAt) ([]uint64, error) { } func readL2Table(l1Entry uint64, header *header, reader io.ReaderAt) ([]uint64, error) { + if header.ClusterBits < 9 || header.ClusterBits > 21 { + return nil, fmt.Errorf("invalid ClusterBits value: %d", header.ClusterBits) + } if l1Entry == 0 { return nil, nil } diff --git a/extractor/filesystem/embeddedfs/vdi/vdi.go b/extractor/filesystem/embeddedfs/vdi/vdi.go index afa3199b8..693ae386d 100644 --- a/extractor/filesystem/embeddedfs/vdi/vdi.go +++ b/extractor/filesystem/embeddedfs/vdi/vdi.go @@ -194,6 +194,9 @@ func convertVDIToRaw(in io.Reader, out io.Writer) error { curPos = int64(hdr.OffsetBmap) } + if hdr.BlocksInImage > 2*1024*1024 { + return fmt.Errorf("BlocksInImage %d exceeds maximum allowed value", hdr.BlocksInImage) + } indices := make([]uint32, hdr.BlocksInImage) if err := binary.Read(in, binary.LittleEndian, &indices); err != nil { return fmt.Errorf("failed to read block map: %w", err) diff --git a/extractor/filesystem/embeddedfs/vmdk/vmdk.go b/extractor/filesystem/embeddedfs/vmdk/vmdk.go index df9919437..dc1175c79 100644 --- a/extractor/filesystem/embeddedfs/vmdk/vmdk.go +++ b/extractor/filesystem/embeddedfs/vmdk/vmdk.go @@ -414,12 +414,12 @@ func getGDGT(hdr sparseExtentHeader) (*gdgtInfo, error) { GTs := uint32((GTEs + uint64(hdr.NumGTEsPerGT) - 1) / uint64(hdr.NumGTEsPerGT)) GDsectors := uint32((uint64(GTs)*4 + SectorSize - 1) / SectorSize) GTsectors := uint32((uint64(hdr.NumGTEsPerGT)*4 + SectorSize - 1) / SectorSize) - totalSectors := int64(GDsectors + GTsectors*GTs) + totalSectors := int64(GDsectors) + int64(GTsectors)*int64(GTs) totalBytes := totalSectors * SectorSize if totalBytes > 1<<31 { return nil, fmt.Errorf("gd/gt allocation too large: %d bytes", totalBytes) } - gdarr := make([]uint32, (GDsectors*SectorSize)/4+(GTsectors*GTs*SectorSize)/4) + gdarr := make([]uint32, (int64(GDsectors)*SectorSize/4)+(int64(GTsectors)*int64(GTs)*SectorSize/4)) info := &gdgtInfo{ GTEs: GTEs, GTs: GTs,