go ``` debug/pe: 致命错误:运行时:在 NewFile 上内存不足 ```

myzjeezk  于 4个月前  发布在  Go
关注(0)|答案(9)|浏览(55)

你正在使用哪个版本的Go( go version )?

$ go version
go version go1.15.7 linux/amd64

这个问题在最新版本中是否会重现?

是的

你正在使用什么操作系统和处理器架构( go env )?

go env 输出

$ go env
GO111MODULE=""
GOARCH="amd64"
GOBIN=""
GOCACHE="/home/falce/.cache/go-build"
GOENV="/home/falce/.config/go/env"
GOEXE=""
GOFLAGS=""
GOHOSTARCH="amd64"
GOHOSTOS="linux"
GOINSECURE=""
GOMODCACHE="/home/falce/.gvm/pkgsets/go1.15.7/global/pkg/mod"
GONOPROXY=""
GONOSUMDB=""
GOOS="linux"
GOPATH="/home/falce/.gvm/pkgsets/go1.15.7/global"
GOPRIVATE=""
GOPROXY="https://proxy.golang.org,direct"
GOROOT="/home/falce/.gvm/gos/go1.15.7"
GOSUMDB="sum.golang.org"
GOTMPDIR=""
GOTOOLDIR="/home/falce/.gvm/gos/go1.15.7/pkg/tool/linux_amd64"
GCCGO="gccgo"
AR="ar"
CC="gcc"
CXX="g++"
CGO_ENABLED="1"
GOMOD="/home/falce/vchain/vcn/src/go.mod"
CGO_CFLAGS="-g -O2"
CGO_CPPFLAGS=""
CGO_CXXFLAGS="-g -O2"
CGO_FFLAGS="-g -O2"
CGO_LDFLAGS="-g -O2"
PKG_CONFIG="pkg-config"
GOGCCFLAGS="-fPIC -m64 -pthread -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build610876847=/tmp/go-build -gno-record-gcc-switches"

你做了什么?

为了重现问题,请下载并解压 000182.zip

package main

import (
	"debug/pe"
	"log"
	"os"
)

func main() {
	var file, err = os.OpenFile("path-to-the-unzipped/000182.sst", os.O_RDWR, 0644)
	if err!=nil {
		log.Fatal(err)
	}
	defer file.Close()

	_, err = pe.NewFile(file)
	if err!=nil {
		log.Fatal(err)
	}
}

你期望看到什么?

我期望该方法返回一个有效的 pe.FIle

你看到了什么?

runtime stack:
runtime.throw(0x51b52c, 0x16)
        /home/usr/.gvm/gos/go1.15.7/src/runtime/panic.go:1116 +0x72
runtime.sysMap(0xc004000000, 0x1400000000, 0x5dd3f8)
        /home/usr/.gvm/gos/go1.15.7/src/runtime/mem_linux.go:169 +0xc6
runtime.(*mheap).sysAlloc(0x5c3020, 0x1400000000, 0xd400800000, 0xc000200000)
        /home/usr/.gvm/gos/go1.15.7/src/runtime/malloc.go:727 +0x532
runtime.(*mheap).grow(0x5c3020, 0x9ffffb, 0x0)
        /home/usr/.gvm/gos/go1.15.7/src/runtime/mheap.go:1344 +0xa5
runtime.(*mheap).allocSpan(0x5c3020, 0x9ffffb, 0x410100, 0x5dd408, 0x18100)
        /home/usr/.gvm/gos/go1.15.7/src/runtime/mheap.go:1160 +0x665
runtime.(*mheap).alloc.func1()
        /home/usr/.gvm/gos/go1.15.7/src/runtime/mheap.go:907 +0x65
runtime.(*mheap).alloc(0x5c3020, 0x9ffffb, 0x7fe4066c0101, 0x7fe42d000c20)
        /home/usr/.gvm/gos/go1.15.7/src/runtime/mheap.go:901 +0x85
runtime.largeAlloc(0x13ffff4fd4, 0x7ffda0430101, 0x7fe42d000c20)
        /home/usr/.gvm/gos/go1.15.7/src/runtime/malloc.go:1177 +0xa8
runtime.mallocgc.func1()
        /home/usr/.gvm/gos/go1.15.7/src/runtime/malloc.go:1071 +0x46
runtime.systemstack(0x0)
        /home/usr/.gvm/gos/go1.15.7/src/runtime/asm_amd64.s:370 +0x66
runtime.mstart()
        /home/usr/.gvm/gos/go1.15.7/src/runtime/proc.go:1116

goroutine 1 [running]:
runtime.systemstack_switch()
        /home/usr/.gvm/gos/go1.15.7/src/runtime/asm_amd64.s:330 fp=0xc000067888 sp=0xc000067880 pc=0x46caa0
runtime.mallocgc(0x13ffff4fd4, 0x50d8e0, 0x300001, 0x0)
        /home/usr/.gvm/gos/go1.15.7/src/runtime/malloc.go:1070 +0x7e6 fp=0xc000067928 sp=0xc000067888 pc=0x40dde6
runtime.makeslice(0x50d8e0, 0xfffff731, 0xfffff731, 0x10000)
        /home/usr/.gvm/gos/go1.15.7/src/runtime/slice.go:98 +0x6f fp=0xc000067960 sp=0xc000067928 pc=0x45040f
debug/pe.readCOFFSymbols(0xc00019e000, 0x528ea0, 0xc00006a210, 0x0, 0x0, 0x0, 0x0, 0x0)
        /home/usr/.gvm/gos/go1.15.7/src/debug/pe/symbol.go:36 +0x2f8 fp=0xc000067a98 sp=0xc000067960 pc=0x4eb378
debug/pe.NewFile(0x528d20, 0xc00000e028, 0x0, 0x0, 0x0)
        /home/usr/.gvm/gos/go1.15.7/src/debug/pe/file.go:103 +0x69f fp=0xc000067e30 sp=0xc000067a98 pc=0x4e5c9f
main.main()
        /home/usr/go/src/pe-file-bug/main.go:17 +0x22d fp=0xc000067f88 sp=0xc000067e30 pc=0x4ee54d
runtime.main()
        /home/usr/.gvm/gos/go1.15.7/src/runtime/proc.go:204 +0x1cf fp=0xc000067fe0 sp=0xc000067f88 pc=0x43c1cf
runtime.goexit()
        /home/usr/.gvm/gos/go1.15.7/src/runtime/asm_amd64.s:1374 +0x1 fp=0xc000067fe8 sp=0xc000067fe0 pc=0x46e6e1

cc @vchaindz

y53ybaqx

y53ybaqx1#

感谢@tc-hib,是的,在这种情况下需要一个优雅的错误。

lztngnrs

lztngnrs2#

不确定我们能在这里做什么。PE文件的头部没有类似于魔术数字的东西,所以很难判断它是否真的是PE文件。我们唯一能做的就是检查机器是否合理,我们已经这样做了。

对我来说,不清楚MS-DOS存根是否是可选的。如果人们总是使用它,也许我们可以检查一下。

我想我们可以检查一下分配大小是否合理。这个特定的例子报告了比文件可能包含的符号更多的符号,我们尽职地为它们分配存储空间。

rur96b6h

rur96b6h3#

我不知道为什么这个函数似乎接受PE文件,这些文件会直接从文件头开始。
我认为DOS存根和PE签名都是必需的。
应该尽早读取可选头部,因为它以一个魔术数字开头。
我不知道这个函数是用来做什么的,我对系统编程一无所知,但是一个损坏的文件可能会导致几GB的无用分配,这是可以接受的吗?这个函数难道不能检测到它会读到文件末尾之外的地方吗?
或者至少使用一个临时的bytes.Bufferio.CopyN,在达到那个大小之前有机会遇到EOF。

dw1jzc5e

dw1jzc5e4#

我猜我们可以检查一下分配大小是否合理。这个特定的例子报告了比文件可能包含的符号更多的符号,我们尽职地为它们分配存储空间。
但是我想知道如何用 io.ReaderAt 获取读取字节数组的大小?
如果我们可以获取输入字节数组的大小,那么我认为这个解决方案是好的

pnwntuvh

pnwntuvh5#

@howjmay我猜你可以尝试在读取整个内容之前先读取最后一个字节。或者你也可以模仿io.CopyN,或者直接使用它:

buf := bytes.Buffer{}
_, err = io.CopyN(&buf, r, COFFSymbolSize*int64(fh.NumberOfSymbols))
if err != nil {
	return nil, fmt.Errorf("fail to read symbol table: %v", err)
}
syms := make([]COFFSymbol, fh.NumberOfSymbols)
err = binary.Read(&buf, binary.LittleEndian, syms)

(免责声明:我是一个初学者,既在学习也在帮助)

sq1bmfud

sq1bmfud6#

@tc-hib 我喜欢你的建议,测试效果很好。不过,我认为我们或许应该直接使用 LimitReader()Copy()?

zlhcx6iw

zlhcx6iw7#

https://golang.org/cl/286113提到了这个问题:debug/pe: fix OOM caused by huge NumberOfSymbols

cwtwac6a

cwtwac6a8#

@tc-hib 我喜欢你的建议,并且测试得很好。然而,我认为也许我们应该直接使用 LimitReader()Copy()?
这样做的好处是什么?
如果这个功能没有在其他地方使用,你也可以将其传递给原始的 ReaderAt 并执行类似以下操作:

size := int64(fh.NumberOfSymbols)*COFFSymbolSize
_, err := r.ReadAt([]byte{0}, int64(fh.PointerToSymbolTable) + size - 1)
if err != nil {
	return nil, fmt.Errorf("fail to read to symbol table: %v", err)
}
syms := make([]COFFSymbol, fh.NumberOfSymbols)
err = binary.Read(io.NewSectionReader(r, int64(fh.PointerToSymbolTable), size), binary.LittleEndian, syms)
kadbb459

kadbb4599#

你好,
这个文件根本不像是一个PE文件。
所以,你所能期望的最好结果是优雅地出错,而不是恐慌,比如“这不是一个有效的PE文件”。
这里有一个更短的复现器:

package main

import (
	"bytes"
	"debug/pe"
)

func main() {
	pe.NewFile(bytes.NewReader([]byte{
		0x08: 0x10, 0x00, 0x00, 0x00, 0x71, 0x1C, 0xC7, 0xF1, 0x04,
		0xFF: 0,
	}))
}

相关问题