From c6e52ecf5cff2caca242c0a1860eed68f3d6dec6 Mon Sep 17 00:00:00 2001 From: Alessandro Arzilli Date: Tue, 4 May 2021 21:36:22 +0200 Subject: [PATCH] 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 --- pkg/dwarf/line/line_parser.go | 66 ++++++++++++++++++++++++++++----- pkg/dwarf/line/parse_util.go | 47 +++++++++++++++++++++++ pkg/dwarf/line/state_machine.go | 5 ++- pkg/dwarf/util/util.go | 6 +-- 4 files changed, 109 insertions(+), 15 deletions(-) diff --git a/pkg/dwarf/line/line_parser.go b/pkg/dwarf/line/line_parser.go index a6babe16..09dd6de7 100644 --- a/pkg/dwarf/line/line_parser.go +++ b/pkg/dwarf/line/line_parser.go @@ -89,11 +89,19 @@ func Parse(compdir string, buf *bytes.Buffer, logfn func(string, ...interface{}) parseDebugLinePrologue(dbl, buf) if dbl.Prologue.Version >= 5 { - parseIncludeDirs5(dbl, buf) - parseFileEntries5(dbl, buf) + if !parseIncludeDirs5(dbl, buf) { + return nil + } + if !parseFileEntries5(dbl, buf) { + return nil + } } else { - parseIncludeDirs2(dbl, buf) - parseFileEntries2(dbl, buf) + if !parseIncludeDirs2(dbl, buf) { + return nil + } + if !parseFileEntries2(dbl, buf) { + return nil + } } // 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. -func parseIncludeDirs2(info *DebugLineInfo, buf *bytes.Buffer) { +func parseIncludeDirs2(info *DebugLineInfo, buf *bytes.Buffer) bool { 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 == "" { break } info.IncludeDirs = append(info.IncludeDirs, str) } + return true } // 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) + if dirEntryFormReader == nil { + return false + } dirCount, _ := util.DecodeULEB128(buf) info.IncludeDirs = make([]string, 0, dirCount) for i := uint64(0); i < dirCount; i++ { @@ -168,13 +186,23 @@ func parseIncludeDirs5(info *DebugLineInfo, buf *bytes.Buffer) { 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 -func parseFileEntries2(info *DebugLineInfo, buf *bytes.Buffer) { +func parseFileEntries2(info *DebugLineInfo, buf *bytes.Buffer) bool { for { entry := readFileEntry(info, buf, true) + if entry == nil { + return false + } if entry.Path == "" { break } @@ -182,12 +210,20 @@ func parseFileEntries2(info *DebugLineInfo, buf *bytes.Buffer) { info.FileNames = append(info.FileNames, entry) info.Lookup[entry.Path] = entry } + return true } func readFileEntry(info *DebugLineInfo, buf *bytes.Buffer, exitOnEmptyPath bool) *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 { return entry } @@ -225,8 +261,11 @@ func pathIsAbs(s string) bool { } // 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) + if fileEntryFormReader == nil { + return false + } fileCount, _ := util.DecodeULEB128(buf) info.FileNames = make([]*FileEntry, 0, fileCount) 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.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 } diff --git a/pkg/dwarf/line/parse_util.go b/pkg/dwarf/line/parse_util.go index 10dd9633..6f96569e 100644 --- a/pkg/dwarf/line/parse_util.go +++ b/pkg/dwarf/line/parse_util.go @@ -3,6 +3,7 @@ package line import ( "bytes" "encoding/binary" + "errors" "github.com/go-delve/delve/pkg/dwarf/util" ) @@ -39,6 +40,8 @@ const ( _DW_LNCT_MD5 ) +var ErrBufferUnderflow = errors.New("buffer underflow") + type formReader struct { logf func(string, ...interface{}) contentTypes []uint64 @@ -51,11 +54,15 @@ type formReader struct { u64 uint64 i64 int64 str string + err error nexti int } func readEntryFormat(buf *bytes.Buffer, logf func(string, ...interface{})) *formReader { + if buf.Len() < 1 { + return nil + } count := buf.Next(1)[0] r := &formReader{ logf: logf, @@ -70,10 +77,14 @@ func readEntryFormat(buf *bytes.Buffer, logf func(string, ...interface{})) *form } func (rdr *formReader) reset() { + rdr.err = nil rdr.nexti = 0 } func (rdr *formReader) next(buf *bytes.Buffer) bool { + if rdr.err != nil { + return false + } if rdr.nexti >= len(rdr.contentTypes) { return false } @@ -87,24 +98,52 @@ func (rdr *formReader) next(buf *bytes.Buffer) bool { rdr.readBlock(buf, n) case _DW_FORM_block1: + if buf.Len() < 1 { + rdr.err = ErrBufferUnderflow + return false + } rdr.readBlock(buf, uint64(buf.Next(1)[0])) case _DW_FORM_block2: + if buf.Len() < 2 { + rdr.err = ErrBufferUnderflow + return false + } rdr.readBlock(buf, uint64(binary.LittleEndian.Uint16(buf.Next(2)))) case _DW_FORM_block4: + if buf.Len() < 4 { + rdr.err = ErrBufferUnderflow + return false + } rdr.readBlock(buf, uint64(binary.LittleEndian.Uint32(buf.Next(4)))) 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]) 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))) 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))) case _DW_FORM_data8: + if buf.Len() < 8 { + rdr.err = ErrBufferUnderflow + return false + } rdr.u64 = binary.LittleEndian.Uint64(buf.Next(8)) case _DW_FORM_data16: @@ -120,6 +159,10 @@ func (rdr *formReader) next(buf *bytes.Buffer) bool { rdr.str, _ = util.ParseString(buf) case _DW_FORM_strx3: + if buf.Len() < 3 { + rdr.err = ErrBufferUnderflow + return false + } rdr.u64 = uint64(binary.LittleEndian.Uint32(append(buf.Next(3), 0x0))) default: @@ -136,6 +179,10 @@ func (rdr *formReader) next(buf *bytes.Buffer) bool { } func (rdr *formReader) readBlock(buf *bytes.Buffer, n uint64) { + if uint64(buf.Len()) < n { + rdr.err = ErrBufferUnderflow + return + } if cap(rdr.block) < int(n) { rdr.block = make([]byte, 0, n) } diff --git a/pkg/dwarf/line/state_machine.go b/pkg/dwarf/line/state_machine.go index ddc83410..d7e11c26 100644 --- a/pkg/dwarf/line/state_machine.go +++ b/pkg/dwarf/line/state_machine.go @@ -552,8 +552,9 @@ func setdiscriminator(sm *StateMachine, buf *bytes.Buffer) { } func definefile(sm *StateMachine, buf *bytes.Buffer) { - entry := readFileEntry(sm.dbl, sm.buf, false) - sm.definedFiles = append(sm.definedFiles, entry) + if entry := readFileEntry(sm.dbl, sm.buf, false); entry != nil { + sm.definedFiles = append(sm.definedFiles, entry) + } } func prologueend(sm *StateMachine, buf *bytes.Buffer) { diff --git a/pkg/dwarf/util/util.go b/pkg/dwarf/util/util.go index 0fcf64f7..6ca75e1c 100644 --- a/pkg/dwarf/util/util.go +++ b/pkg/dwarf/util/util.go @@ -128,13 +128,13 @@ func EncodeSLEB128(out io.ByteWriter, x int64) { } // 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) 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.