Skip to content

debug/pe: (*File).ImportedSymbols seeking to virtual address lacks bounds check before using VirtualAddress #76721

@odeke-em

Description

@odeke-em

Go version

go1.26-devel_ce0e803ab9 Mon Aug 11 11:12:55 2025 -0700

Output of go env in your module/workspace:

AR='ar'
CC='clang'
CGO_CFLAGS='-I/Users/emmanuelodeke/Desktop/openSrc/rocksdb/include'
CGO_CPPFLAGS=''
CGO_CXXFLAGS='-O2 -g'
CGO_ENABLED='1'
CGO_FFLAGS='-O2 -g'
CGO_LDFLAGS='-L/Users/emmanuelodeke/Desktop/openSrc/rocksdb -lrocksdb -lstdc++ -lm -lz -lbz2 -lsnappy -llz4 -lzstd'
CXX='clang++'
GCCGO='gccgo'
GO111MODULE=''
GOAMD64='v1'
GOARCH='amd64'
GOAUTH='netrc'
GOBIN='/Users/emmanuelodeke/go/bin'
GOCACHE='/Users/emmanuelodeke/Library/Caches/go-build'
GOCACHEPROG=''
GODEBUG=''
GOENV='/Users/emmanuelodeke/Library/Application Support/go/env'
GOEXE=''
GOEXPERIMENT=''
GOFIPS140='off'
GOFLAGS=''
GOGCCFLAGS='-fPIC -arch x86_64 -m64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -ffile-prefix-map=/var/folders/v3/7z434qpx5v3bw0wh8h2myfpw0000gn/T/go-build3287023084=/tmp/go-build -gno-record-gcc-switches -fno-common'
GOHOSTARCH='amd64'
GOHOSTOS='darwin'
GOINSECURE=''
GOMOD='/Users/emmanuelodeke/Desktop/openSrc/bugs/golang/73548/go.mod'
GOMODCACHE='/Users/emmanuelodeke/go/pkg/mod'
GONOPROXY=''
GONOSUMDB=''
GOOS='darwin'
GOPATH='/Users/emmanuelodeke/go'
GOPRIVATE=''
GOPROXY='https://proxy.golang.org,direct'
GOROOT='/Users/emmanuelodeke/go/src/go.googlesource.com/go'
GOSUMDB='sum.golang.org'
GOTELEMETRY='on'
GOTELEMETRYDIR='/Users/emmanuelodeke/Library/Application Support/go/telemetry'
GOTMPDIR=''
GOTOOLCHAIN='auto'
GOTOOLDIR='/Users/emmanuelodeke/go/src/go.googlesource.com/go/pkg/tool/darwin_amd64'
GOVCS=''
GOVERSION='go1.26-devel_ce0e803ab9 Mon Aug 11 11:12:55 2025 -0700'
GOWORK=''
PKG_CONFIG='pkg-config'

What did you do?

I write here to report an issue that I've found while investigating pe.NewFile and how to crash programs that use it like symbolicators in security scanners to bypass them while doing security research work.

This is my reproduction:

package main

import (
	"bytes"
	"debug/pe"
)

func main() {
	br := bytes.NewReader([]byte("MZ0000000000000000000000000000000000000000000000000000000000\x10\x01\x00\x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000PE\x00\x00d\x86\b\x000000\x00\x00\x00\x000000\xf0\x0000\v\x020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\x10\x00\x00\x00000000000\xb0\x00\x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\x00\x0000000000000000000000\x00\x000000000000000000\x00\x0000000000000000000000\x00\x000000000000000000\x00\x00\x000000000000000000000\x00\x00000000000000000 \x00\x00\x000000000000000000000\x00\x00000000000000000\x00\x00\x0000000000000000000000\x00\x0000000000000000000000\x00\x000\x02\x00\x000\x00\x00\x0000000000\x00\x0000000000000000000000000000000000000000\x00\x0000000000000000000000000000000000000000\x00\x00000000"))
	f, err := pe.NewFile(br)
	if err != nil {
		panic(err)
	}
	defer f.Close()
	f.ImportedSymbols()
}

or via the playground https://go.dev/play/p/TjfLufg_qPp

What did you see happen?

It crashes on the past 3 go versions with:
$ go run main.go

panic: runtime error: slice bounds out of range [32768:560]

goroutine 1 [running]:
debug/pe.(*File).ImportedSymbols(0x95b3d48?)
	/Users/emmanuelodeke/go/src/[go.googlesource.com/go/src/debug/pe/file.go:382](http://go.googlesource.com/go/src/debug/pe/file.go:382) +0x9f5
main.main()
	/Users/emmanuelodeke/Desktop/openSrc/bugs/golang/repro/main.go:15 +0x10f
exit status 2

What did you expect to see?

No crash!

Diagnosis

The issue stems from the fact that inside pe.File.ImportedSymbols, after retrieving the data after the invocation f.Data() we don't check that the slice of data we are dereferencing is within correct bounds and this call happens at

d = d[idd.VirtualAddress-ds.VirtualAddress:]
or:

        // didn't find a section, so no import libraries were found
        if ds == nil {
                return nil, nil     
        }

        d, err := ds.Data()         
        if err != nil {             
                return nil, err
        }

        // seek to the virtual address specified in the import data directory
        d = d[idd.VirtualAddress-ds.VirtualAddress:]

but really we should correctly check the address bounds and return an error before that dereference or even catch it earlier if we can but this is my mitigation:

diff --git a/src/debug/pe/file.go b/src/debug/pe/file.go
index ed63a11cb6..756ff63574 100644
--- a/src/debug/pe/file.go
+++ b/src/debug/pe/file.go

@@ -379,7 +379,11 @@ func (f *File) ImportedSymbols() ([]string, error) {
        }

        // seek to the virtual address specified in the import data directory
-       d = d[idd.VirtualAddress-ds.VirtualAddress:]
+       if seek := idd.VirtualAddress - ds.VirtualAddress; uint64(seek) >= uint64(len(d)) {
+               return nil, errors.New("optional header data directory virtual size doesn't fit within data seek")
+       } else {
+               d = d[seek:]
+       }

Kindly cc-ing @alexbrainman

Metadata

Metadata

Assignees

No one assigned

    Labels

    NeedsInvestigationSomeone must examine and confirm this is a valid issue and not a duplicate of an existing one.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions