dwarf: make debug_line header parser more resilient (#2456)

Check for errors, log them and return early, do not try to allocate
large chunks of memory that we can never possibly read from the file.

Fixes #2449
This commit is contained in:
Alessandro Arzilli 2021-05-04 21:36:22 +02:00 committed by GitHub
parent 6a25ee09f5
commit c6e52ecf5c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 109 additions and 15 deletions

@ -89,11 +89,19 @@ func Parse(compdir string, buf *bytes.Buffer, logfn func(string, ...interface{})
parseDebugLinePrologue(dbl, buf) parseDebugLinePrologue(dbl, buf)
if dbl.Prologue.Version >= 5 { if dbl.Prologue.Version >= 5 {
parseIncludeDirs5(dbl, buf) if !parseIncludeDirs5(dbl, buf) {
parseFileEntries5(dbl, buf) return nil
}
if !parseFileEntries5(dbl, buf) {
return nil
}
} else { } else {
parseIncludeDirs2(dbl, buf) if !parseIncludeDirs2(dbl, buf) {
parseFileEntries2(dbl, buf) return nil
}
if !parseFileEntries2(dbl, buf) {
return nil
}
} }
// Instructions size calculation breakdown: // Instructions size calculation breakdown:
@ -135,20 +143,30 @@ func parseDebugLinePrologue(dbl *DebugLineInfo, buf *bytes.Buffer) {
} }
// parseIncludeDirs2 parses the directory table for DWARF version 2 through 4. // parseIncludeDirs2 parses the directory table for DWARF version 2 through 4.
func parseIncludeDirs2(info *DebugLineInfo, buf *bytes.Buffer) { func parseIncludeDirs2(info *DebugLineInfo, buf *bytes.Buffer) bool {
for { for {
str, _ := util.ParseString(buf) str, err := util.ParseString(buf)
if err != nil {
if info.Logf != nil {
info.Logf("error reading string: %v", err)
}
return false
}
if str == "" { if str == "" {
break break
} }
info.IncludeDirs = append(info.IncludeDirs, str) info.IncludeDirs = append(info.IncludeDirs, str)
} }
return true
} }
// parseIncludeDirs5 parses the directory table for DWARF version 5. // parseIncludeDirs5 parses the directory table for DWARF version 5.
func parseIncludeDirs5(info *DebugLineInfo, buf *bytes.Buffer) { func parseIncludeDirs5(info *DebugLineInfo, buf *bytes.Buffer) bool {
dirEntryFormReader := readEntryFormat(buf, info.Logf) dirEntryFormReader := readEntryFormat(buf, info.Logf)
if dirEntryFormReader == nil {
return false
}
dirCount, _ := util.DecodeULEB128(buf) dirCount, _ := util.DecodeULEB128(buf)
info.IncludeDirs = make([]string, 0, dirCount) info.IncludeDirs = make([]string, 0, dirCount)
for i := uint64(0); i < dirCount; i++ { for i := uint64(0); i < dirCount; i++ {
@ -168,13 +186,23 @@ func parseIncludeDirs5(info *DebugLineInfo, buf *bytes.Buffer) {
case _DW_LNCT_MD5: case _DW_LNCT_MD5:
} }
} }
if dirEntryFormReader.err != nil {
if info.Logf != nil {
info.Logf("error reading directory entries table: %v", dirEntryFormReader.err)
}
return false
}
} }
return true
} }
// parseFileEntries2 parses the file table for DWARF 2 through 4 // parseFileEntries2 parses the file table for DWARF 2 through 4
func parseFileEntries2(info *DebugLineInfo, buf *bytes.Buffer) { func parseFileEntries2(info *DebugLineInfo, buf *bytes.Buffer) bool {
for { for {
entry := readFileEntry(info, buf, true) entry := readFileEntry(info, buf, true)
if entry == nil {
return false
}
if entry.Path == "" { if entry.Path == "" {
break break
} }
@ -182,12 +210,20 @@ func parseFileEntries2(info *DebugLineInfo, buf *bytes.Buffer) {
info.FileNames = append(info.FileNames, entry) info.FileNames = append(info.FileNames, entry)
info.Lookup[entry.Path] = entry info.Lookup[entry.Path] = entry
} }
return true
} }
func readFileEntry(info *DebugLineInfo, buf *bytes.Buffer, exitOnEmptyPath bool) *FileEntry { func readFileEntry(info *DebugLineInfo, buf *bytes.Buffer, exitOnEmptyPath bool) *FileEntry {
entry := new(FileEntry) entry := new(FileEntry)
entry.Path, _ = util.ParseString(buf) var err error
entry.Path, err = util.ParseString(buf)
if err != nil {
if info.Logf != nil {
info.Logf("error reading file entry: %v", err)
}
return nil
}
if entry.Path == "" && exitOnEmptyPath { if entry.Path == "" && exitOnEmptyPath {
return entry return entry
} }
@ -225,8 +261,11 @@ func pathIsAbs(s string) bool {
} }
// parseFileEntries5 parses the file table for DWARF 5 // parseFileEntries5 parses the file table for DWARF 5
func parseFileEntries5(info *DebugLineInfo, buf *bytes.Buffer) { func parseFileEntries5(info *DebugLineInfo, buf *bytes.Buffer) bool {
fileEntryFormReader := readEntryFormat(buf, info.Logf) fileEntryFormReader := readEntryFormat(buf, info.Logf)
if fileEntryFormReader == nil {
return false
}
fileCount, _ := util.DecodeULEB128(buf) fileCount, _ := util.DecodeULEB128(buf)
info.FileNames = make([]*FileEntry, 0, fileCount) info.FileNames = make([]*FileEntry, 0, fileCount)
for i := 0; i < int(fileCount); i++ { for i := 0; i < int(fileCount); i++ {
@ -265,5 +304,12 @@ func parseFileEntries5(info *DebugLineInfo, buf *bytes.Buffer) {
info.FileNames = append(info.FileNames, entry) info.FileNames = append(info.FileNames, entry)
info.Lookup[entry.Path] = entry info.Lookup[entry.Path] = entry
} }
if fileEntryFormReader.err != nil {
if info.Logf != nil {
info.Logf("error reading file entries table: %v", fileEntryFormReader.err)
}
return false
}
} }
return true
} }

@ -3,6 +3,7 @@ package line
import ( import (
"bytes" "bytes"
"encoding/binary" "encoding/binary"
"errors"
"github.com/go-delve/delve/pkg/dwarf/util" "github.com/go-delve/delve/pkg/dwarf/util"
) )
@ -39,6 +40,8 @@ const (
_DW_LNCT_MD5 _DW_LNCT_MD5
) )
var ErrBufferUnderflow = errors.New("buffer underflow")
type formReader struct { type formReader struct {
logf func(string, ...interface{}) logf func(string, ...interface{})
contentTypes []uint64 contentTypes []uint64
@ -51,11 +54,15 @@ type formReader struct {
u64 uint64 u64 uint64
i64 int64 i64 int64
str string str string
err error
nexti int nexti int
} }
func readEntryFormat(buf *bytes.Buffer, logf func(string, ...interface{})) *formReader { func readEntryFormat(buf *bytes.Buffer, logf func(string, ...interface{})) *formReader {
if buf.Len() < 1 {
return nil
}
count := buf.Next(1)[0] count := buf.Next(1)[0]
r := &formReader{ r := &formReader{
logf: logf, logf: logf,
@ -70,10 +77,14 @@ func readEntryFormat(buf *bytes.Buffer, logf func(string, ...interface{})) *form
} }
func (rdr *formReader) reset() { func (rdr *formReader) reset() {
rdr.err = nil
rdr.nexti = 0 rdr.nexti = 0
} }
func (rdr *formReader) next(buf *bytes.Buffer) bool { func (rdr *formReader) next(buf *bytes.Buffer) bool {
if rdr.err != nil {
return false
}
if rdr.nexti >= len(rdr.contentTypes) { if rdr.nexti >= len(rdr.contentTypes) {
return false return false
} }
@ -87,24 +98,52 @@ func (rdr *formReader) next(buf *bytes.Buffer) bool {
rdr.readBlock(buf, n) rdr.readBlock(buf, n)
case _DW_FORM_block1: case _DW_FORM_block1:
if buf.Len() < 1 {
rdr.err = ErrBufferUnderflow
return false
}
rdr.readBlock(buf, uint64(buf.Next(1)[0])) rdr.readBlock(buf, uint64(buf.Next(1)[0]))
case _DW_FORM_block2: case _DW_FORM_block2:
if buf.Len() < 2 {
rdr.err = ErrBufferUnderflow
return false
}
rdr.readBlock(buf, uint64(binary.LittleEndian.Uint16(buf.Next(2)))) rdr.readBlock(buf, uint64(binary.LittleEndian.Uint16(buf.Next(2))))
case _DW_FORM_block4: case _DW_FORM_block4:
if buf.Len() < 4 {
rdr.err = ErrBufferUnderflow
return false
}
rdr.readBlock(buf, uint64(binary.LittleEndian.Uint32(buf.Next(4)))) rdr.readBlock(buf, uint64(binary.LittleEndian.Uint32(buf.Next(4))))
case _DW_FORM_data1, _DW_FORM_flag, _DW_FORM_strx1: case _DW_FORM_data1, _DW_FORM_flag, _DW_FORM_strx1:
if buf.Len() < 1 {
rdr.err = ErrBufferUnderflow
return false
}
rdr.u64 = uint64(buf.Next(1)[0]) rdr.u64 = uint64(buf.Next(1)[0])
case _DW_FORM_data2, _DW_FORM_strx2: case _DW_FORM_data2, _DW_FORM_strx2:
if buf.Len() < 2 {
rdr.err = ErrBufferUnderflow
return false
}
rdr.u64 = uint64(binary.LittleEndian.Uint16(buf.Next(2))) rdr.u64 = uint64(binary.LittleEndian.Uint16(buf.Next(2)))
case _DW_FORM_data4, _DW_FORM_line_strp, _DW_FORM_sec_offset, _DW_FORM_strp, _DW_FORM_strx4: case _DW_FORM_data4, _DW_FORM_line_strp, _DW_FORM_sec_offset, _DW_FORM_strp, _DW_FORM_strx4:
if buf.Len() < 4 {
rdr.err = ErrBufferUnderflow
return false
}
rdr.u64 = uint64(binary.LittleEndian.Uint32(buf.Next(4))) rdr.u64 = uint64(binary.LittleEndian.Uint32(buf.Next(4)))
case _DW_FORM_data8: case _DW_FORM_data8:
if buf.Len() < 8 {
rdr.err = ErrBufferUnderflow
return false
}
rdr.u64 = binary.LittleEndian.Uint64(buf.Next(8)) rdr.u64 = binary.LittleEndian.Uint64(buf.Next(8))
case _DW_FORM_data16: case _DW_FORM_data16:
@ -120,6 +159,10 @@ func (rdr *formReader) next(buf *bytes.Buffer) bool {
rdr.str, _ = util.ParseString(buf) rdr.str, _ = util.ParseString(buf)
case _DW_FORM_strx3: case _DW_FORM_strx3:
if buf.Len() < 3 {
rdr.err = ErrBufferUnderflow
return false
}
rdr.u64 = uint64(binary.LittleEndian.Uint32(append(buf.Next(3), 0x0))) rdr.u64 = uint64(binary.LittleEndian.Uint32(append(buf.Next(3), 0x0)))
default: default:
@ -136,6 +179,10 @@ func (rdr *formReader) next(buf *bytes.Buffer) bool {
} }
func (rdr *formReader) readBlock(buf *bytes.Buffer, n uint64) { func (rdr *formReader) readBlock(buf *bytes.Buffer, n uint64) {
if uint64(buf.Len()) < n {
rdr.err = ErrBufferUnderflow
return
}
if cap(rdr.block) < int(n) { if cap(rdr.block) < int(n) {
rdr.block = make([]byte, 0, n) rdr.block = make([]byte, 0, n)
} }

@ -552,8 +552,9 @@ func setdiscriminator(sm *StateMachine, buf *bytes.Buffer) {
} }
func definefile(sm *StateMachine, buf *bytes.Buffer) { func definefile(sm *StateMachine, buf *bytes.Buffer) {
entry := readFileEntry(sm.dbl, sm.buf, false) if entry := readFileEntry(sm.dbl, sm.buf, false); entry != nil {
sm.definedFiles = append(sm.definedFiles, entry) sm.definedFiles = append(sm.definedFiles, entry)
}
} }
func prologueend(sm *StateMachine, buf *bytes.Buffer) { func prologueend(sm *StateMachine, buf *bytes.Buffer) {

@ -128,13 +128,13 @@ func EncodeSLEB128(out io.ByteWriter, x int64) {
} }
// ParseString reads a null-terminated string from data. // ParseString reads a null-terminated string from data.
func ParseString(data *bytes.Buffer) (string, uint32) { func ParseString(data *bytes.Buffer) (string, error) {
str, err := data.ReadString(0x0) str, err := data.ReadString(0x0)
if err != nil { if err != nil {
panic("Could not parse string") return "", err
} }
return str[:len(str)-1], uint32(len(str)) return str[:len(str)-1], nil
} }
// ReadUintRaw reads an integer of ptrSize bytes, with the specified byte order, from reader. // ReadUintRaw reads an integer of ptrSize bytes, with the specified byte order, from reader.