From 3f4105a657f7d53a29fbf1ffe8010853dea08fdc Mon Sep 17 00:00:00 2001 From: somaz <112675579+somaz94@users.noreply.github.com> Date: Wed, 23 Sep 2026 23:02:12 +0900 Subject: [PATCH] fix(elf): avoid a panic on a truncated dynamic section (#5321) Signed-off-by: somaz --- syft/file/cataloger/executable/elf.go | 26 +------ syft/file/cataloger/executable/elf_test.go | 88 ++++++++++++++++++++++ 2 files changed, 91 insertions(+), 23 deletions(-) diff --git a/syft/file/cataloger/executable/elf.go b/syft/file/cataloger/executable/elf.go index e7f6d588d..b58b3eaa2 100644 --- a/syft/file/cataloger/executable/elf.go +++ b/syft/file/cataloger/executable/elf.go @@ -156,32 +156,12 @@ func hasElfDynFlag1(f *elf.File, flag elf.DynFlag1) bool { } func hasElfDynTag(f *elf.File, tag elf.DynTag) bool { - // source https://github.com/golang/go/blob/9b4b3e5acca2dabe107fa2c3ed963097d78a4562/src/cmd/cgo/internal/testshared/shared_test.go#L280 - - ds := f.SectionByType(elf.SHT_DYNAMIC) - if ds == nil { - return false - } - d, err := ds.Data() + vals, err := f.DynValue(tag) if err != nil { + log.WithFields("error", err).Trace("unable to read dynamic section from elf file") return false } - - for len(d) > 0 { - var t elf.DynTag - switch f.Class { - case elf.ELFCLASS32: - t = elf.DynTag(f.ByteOrder.Uint32(d[0:4])) - d = d[8:] - case elf.ELFCLASS64: - t = elf.DynTag(f.ByteOrder.Uint64(d[0:8])) - d = d[16:] - } - if t == tag { - return true - } - } - return false + return len(vals) > 0 } func isELFPIE(f *elf.File) bool { diff --git a/syft/file/cataloger/executable/elf_test.go b/syft/file/cataloger/executable/elf_test.go index a380a5cd6..23b19eced 100644 --- a/syft/file/cataloger/executable/elf_test.go +++ b/syft/file/cataloger/executable/elf_test.go @@ -1,7 +1,9 @@ package executable import ( + "bytes" "debug/elf" + "encoding/binary" "os" "path/filepath" "testing" @@ -226,3 +228,89 @@ func Test_elfHasExports(t *testing.T) { }) } } + +func Test_hasElfDynTag(t *testing.T) { + dynEntry := func(class elf.Class, tag elf.DynTag) []byte { + if class == elf.ELFCLASS32 { + return binary.LittleEndian.AppendUint32(binary.LittleEndian.AppendUint32(nil, uint32(tag)), 0) + } + return binary.LittleEndian.AppendUint64(binary.LittleEndian.AppendUint64(nil, uint64(tag)), 0) + } + + tests := []struct { + name string + class elf.Class + dynamic []byte + want bool + }{ + { + name: "32-bit tag present", + class: elf.ELFCLASS32, + dynamic: dynEntry(elf.ELFCLASS32, elf.DT_BIND_NOW), + want: true, + }, + { + name: "64-bit tag present", + class: elf.ELFCLASS64, + dynamic: dynEntry(elf.ELFCLASS64, elf.DT_BIND_NOW), + want: true, + }, + { + name: "64-bit tag absent", + class: elf.ELFCLASS64, + dynamic: dynEntry(elf.ELFCLASS64, elf.DT_NULL), + want: false, + }, + { + name: "32-bit truncated dynamic section", + class: elf.ELFCLASS32, + dynamic: dynEntry(elf.ELFCLASS32, elf.DT_BIND_NOW)[:4], + want: false, + }, + { + name: "64-bit truncated dynamic section", + class: elf.ELFCLASS64, + dynamic: dynEntry(elf.ELFCLASS64, elf.DT_BIND_NOW)[:12], + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + f, err := elf.NewFile(bytes.NewReader(elfWithDynamicSection(t, tt.class, tt.dynamic))) + require.NoError(t, err) + assert.Equal(t, tt.want, hasElfDynTag(f, elf.DT_BIND_NOW)) + }) + } +} + +// elfWithDynamicSection builds a little-endian ELF holding only a null section and a SHT_DYNAMIC section. +func elfWithDynamicSection(t *testing.T, class elf.Class, dynamic []byte) []byte { + t.Helper() + ident := [elf.EI_NIDENT]byte{0x7f, 'E', 'L', 'F', byte(class), byte(elf.ELFDATA2LSB), byte(elf.EV_CURRENT)} + var buf bytes.Buffer + write := func(v any) { + require.NoError(t, binary.Write(&buf, binary.LittleEndian, v)) + } + + if class == elf.ELFCLASS32 { + ehsize, shentsize := binary.Size(elf.Header32{}), binary.Size(elf.Section32{}) + dataOff := ehsize + 2*shentsize + write(elf.Header32{ + Ident: ident, Type: uint16(elf.ET_DYN), Machine: uint16(elf.EM_386), Version: uint32(elf.EV_CURRENT), + Shoff: uint32(ehsize), Ehsize: uint16(ehsize), Shentsize: uint16(shentsize), Shnum: 2, + }) + write(elf.Section32{}) + write(elf.Section32{Type: uint32(elf.SHT_DYNAMIC), Off: uint32(dataOff), Size: uint32(len(dynamic)), Addralign: 1}) + } else { + ehsize, shentsize := binary.Size(elf.Header64{}), binary.Size(elf.Section64{}) + dataOff := ehsize + 2*shentsize + write(elf.Header64{ + Ident: ident, Type: uint16(elf.ET_DYN), Machine: uint16(elf.EM_X86_64), Version: uint32(elf.EV_CURRENT), + Shoff: uint64(ehsize), Ehsize: uint16(ehsize), Shentsize: uint16(shentsize), Shnum: 2, + }) + write(elf.Section64{}) + write(elf.Section64{Type: uint32(elf.SHT_DYNAMIC), Off: uint64(dataOff), Size: uint64(len(dynamic)), Addralign: 1}) + } + buf.Write(dynamic) + return buf.Bytes() +}