Go version
go1.24.3 linux/amd64
What did you do?
Called DWARF() on a crafted PE or Mach-O binary containing a ZLIB-compressed debug section with a large dlen value in the 12-byte ZLIB header.
What did you expect to see?
An error returned, similar to how debug/elf handles oversized allocations after commit 6064169 (Fixes #78611, For #47653) applied saferio.SliceCap hardening.
What did you see instead?
panic: runtime error: makeslice: len out of range — the process terminates.
Details
debug/pe and debug/macho read an attacker-controlled uint64 from ZLIB-compressed debug section headers and pass it directly to make([]byte, dlen) without size validation.
File 1 — src/debug/pe/file.go around line 240 (inside File.DWARF() → sectionData closure):
if len(b) >= 12 && string(b[:4]) == "ZLIB" {
dlen := binary.BigEndian.Uint64(b[4:12]) // from section data
dbuf := make([]byte, dlen) // no saferio validation
File 2 — src/debug/macho/file.go around line 649 (same pattern):
if len(b) >= 12 && string(b[:4]) == "ZLIB" {
dlen := binary.BigEndian.Uint64(b[4:12])
dbuf := make([]byte, dlen)
Note: debug/macho already imports internal/saferio (line 26) but does not use it in the ZLIB path. debug/pe/file.go does not currently import internal/saferio.
Reproducer
Minimal PE binary generator (Python):
import struct
dos = bytearray(64)
dos[0:2] = b'MZ'
struct.pack_into('<I', dos, 60, 64)
pe_sig = b'PE\x00\x00'
coff = bytearray(20)
struct.pack_into('<H', coff, 0, 0x14c) # i386
struct.pack_into('<H', coff, 2, 1) # 1 section
struct.pack_into('<I', coff, 8, 128+512) # symbol table offset
sec = bytearray(40)
sec[0:8] = b'/4\x00\x00\x00\x00\x00\x00' # string table ref
struct.pack_into('<I', sec, 16, 512) # SizeOfRawData
struct.pack_into('<I', sec, 20, 128) # PointerToRawData
zlib_data = bytearray(512)
zlib_data[0:4] = b'ZLIB'
struct.pack_into('>Q', zlib_data, 4, 0xFFFFFFFFFFFFFFFF)
strtab = struct.pack('<I', 17) + b'.zdebug_info\x00'
with open('malicious.pe', 'wb') as f:
f.write(dos + pe_sig + coff + sec + zlib_data + strtab)
Trigger:
package main
import (
"debug/pe"
"os"
)
func main() {
f, _ := pe.Open(os.Args[1])
if f != nil {
f.DWARF() // panics
}
}
Confirmed output (Go 1.24.3)
debug/pe:
panic: runtime error: makeslice: len out of range
goroutine 1 [running]:
debug/pe.(*File).DWARF.func2(0xc0001001e0)
/usr/local/go/src/debug/pe/file.go:240 +0xd8
debug/macho (same pattern with __zdebug_info Mach-O section):
panic: runtime error: makeslice: len out of range
goroutine 1 [running]:
debug/macho.(*File).DWARF.func2(0xc0000c8100)
/usr/local/go/src/debug/macho/file.go:649 +0xab
Suggested fix
Apply saferio.SliceCap + saferio.ReadData, consistent with the approach used in debug/elf (commit 6064169):
debug/pe/file.go:
+import "internal/saferio"
if len(b) >= 12 && string(b[:4]) == "ZLIB" {
dlen := binary.BigEndian.Uint64(b[4:12])
- dbuf := make([]byte, dlen)
- r, err := zlib.NewReader(bytes.NewBuffer(b[12:]))
- if err != nil {
- return nil, err
- }
- if _, err := io.ReadFull(r, dbuf); err != nil {
- return nil, err
+ c := saferio.SliceCap[byte](dlen)
+ if c < 0 {
+ return nil, fmt.Errorf("PE ZLIB section too large: %d", dlen)
}
+ r, err := zlib.NewReader(bytes.NewBuffer(b[12:]))
+ if err != nil {
+ return nil, err
+ }
+ defer r.Close()
+ dbuf, err := saferio.ReadData(r, dlen)
+ if err != nil {
+ return nil, err
+ }
Same change for debug/macho/file.go (saferio is already imported).
Related issues
I understand debug/* packages are outside the scope of https://go.dev/security/policy. This is a hardening request for consistency with the saferio work already applied to debug/elf.
Go version
go1.24.3 linux/amd64
What did you do?
Called
DWARF()on a crafted PE or Mach-O binary containing a ZLIB-compressed debug section with a largedlenvalue in the 12-byte ZLIB header.What did you expect to see?
An error returned, similar to how
debug/elfhandles oversized allocations after commit 6064169 (Fixes #78611, For #47653) appliedsaferio.SliceCaphardening.What did you see instead?
panic: runtime error: makeslice: len out of range— the process terminates.Details
debug/peanddebug/machoread an attacker-controlleduint64from ZLIB-compressed debug section headers and pass it directly tomake([]byte, dlen)without size validation.File 1 —
src/debug/pe/file.goaround line 240 (insideFile.DWARF()→sectionDataclosure):File 2 —
src/debug/macho/file.goaround line 649 (same pattern):Note:
debug/machoalready importsinternal/saferio(line 26) but does not use it in the ZLIB path.debug/pe/file.godoes not currently importinternal/saferio.Reproducer
Minimal PE binary generator (Python):
Trigger:
Confirmed output (Go 1.24.3)
debug/pe:
debug/macho (same pattern with
__zdebug_infoMach-O section):Suggested fix
Apply
saferio.SliceCap+saferio.ReadData, consistent with the approach used indebug/elf(commit 6064169):debug/pe/file.go:
Same change for
debug/macho/file.go(saferio is already imported).Related issues
debug/elf: oom in NewFile(fix commit6064169eapplied saferio.SliceCap)debug/peOOM (related fuzzing-found issue)debug/macho: oom in NewFatFileI understand
debug/*packages are outside the scope of https://go.dev/security/policy. This is a hardening request for consistency with the saferio work already applied todebug/elf.