From 7d157fd0eb637a4c58f629421dd8d028022391d2 Mon Sep 17 00:00:00 2001 From: Zeke Lu Date: Mon, 26 Sep 2022 20:46:24 +0000 Subject: [PATCH] debug/elf: suport files with >= 65280 (0xff00) sections The spec https://refspecs.linuxfoundation.org/elf/gabi4+/ch4.eheader.html states: 1. e_shnum: If the number of sections is greater than or equal to SHN_LORESERVE (0xff00), this member has the value zero and the actual number of section header table entries is contained in the sh_size field of the section header at index 0. 2. e_shstrndx: If the section name string table section index is greater than or equal to SHN_LORESERVE (0xff00), this member has the value SHN_XINDEX (0xffff) and the actual index of the section name string table section is contained in the sh_link field of the section header at index 0. This CL makes these changes to support files with >= 0xff00 sections: 1. if shoff > 0 && shnum == 0, read sh_size from the initial section header entry as shnum. 2. if shstrndx == SHN_XINDEX, read sh_link from the initial section header entry as shstrndx. It returns an error if the type of the initial section is not SHT_NULL. A file with >= 0xff00 sections is too big to include in the repository, so the test case constructs one on the fly, with some of the sections zeroed out. While here, remove the unnecessary use of reflect.DeepEqual in the test. Fixes #55294. Change-Id: I15ec43612c7cce6e8decfe4e81da3a5b16de47f7 GitHub-Last-Rev: 797c16480bc76524ff1a7c01ed09b759fcd7d45b GitHub-Pull-Request: golang/go#55295 Reviewed-on: https://go-review.googlesource.com/c/go/+/432255 Run-TryBot: Ian Lance Taylor Reviewed-by: Dmitri Shuralyov TryBot-Result: Gopher Robot Reviewed-by: Ian Lance Taylor Auto-Submit: Ian Lance Taylor Run-TryBot: Ian Lance Taylor --- src/cmd/internal/buildid/note.go | 5 +- src/debug/elf/file.go | 46 ++++++ src/debug/elf/file_test.go | 260 ++++++++++++++++++++++++++++++- 3 files changed, 303 insertions(+), 8 deletions(-) diff --git a/src/cmd/internal/buildid/note.go b/src/cmd/internal/buildid/note.go index f5b6fc565f..ab98701fb5 100644 --- a/src/cmd/internal/buildid/note.go +++ b/src/cmd/internal/buildid/note.go @@ -78,16 +78,19 @@ var elfGNUNote = []byte("GNU\x00") // at least 4 kB out, in data. func readELF(name string, f *os.File, data []byte) (buildid string, err error) { // Assume the note content is in the data, already read. - // Rewrite the ELF header to set shnum to 0, so that we can pass + // Rewrite the ELF header to set shoff and shnum to 0, so that we can pass // the data to elf.NewFile and it will decode the Prog list but not // try to read the section headers and the string table from disk. // That's a waste of I/O when all we care about is the Prog list // and the one ELF note. switch elf.Class(data[elf.EI_CLASS]) { case elf.ELFCLASS32: + data[32], data[33], data[34], data[35] = 0, 0, 0, 0 data[48] = 0 data[49] = 0 case elf.ELFCLASS64: + data[40], data[41], data[42], data[43] = 0, 0, 0, 0 + data[44], data[45], data[46], data[47] = 0, 0, 0, 0 data[60] = 0 data[61] = 0 } diff --git a/src/debug/elf/file.go b/src/debug/elf/file.go index aff2b00aae..db07a2daff 100644 --- a/src/debug/elf/file.go +++ b/src/debug/elf/file.go @@ -388,6 +388,52 @@ func NewFile(r io.ReaderAt) (*File, error) { f.Progs[i] = p } + // If the number of sections is greater than or equal to SHN_LORESERVE + // (0xff00), shnum has the value zero and the actual number of section + // header table entries is contained in the sh_size field of the section + // header at index 0. + if shoff > 0 && shnum == 0 { + var typ, link uint32 + sr.Seek(shoff, seekStart) + switch f.Class { + case ELFCLASS32: + sh := new(Section32) + if err := binary.Read(sr, f.ByteOrder, sh); err != nil { + return nil, err + } + shnum = int(sh.Size) + typ = sh.Type + link = sh.Link + case ELFCLASS64: + sh := new(Section64) + if err := binary.Read(sr, f.ByteOrder, sh); err != nil { + return nil, err + } + shnum = int(sh.Size) + typ = sh.Type + link = sh.Link + } + if SectionType(typ) != SHT_NULL { + return nil, &FormatError{shoff, "invalid type of the initial section", SectionType(typ)} + } + + if shnum < int(SHN_LORESERVE) { + return nil, &FormatError{shoff, "invalid ELF shnum contained in sh_size", shnum} + } + + // If the section name string table section index is greater than or + // equal to SHN_LORESERVE (0xff00), this member has the value + // SHN_XINDEX (0xffff) and the actual index of the section name + // string table section is contained in the sh_link field of the + // section header at index 0. + if shstrndx == int(SHN_XINDEX) { + shstrndx = int(link) + if shstrndx < int(SHN_LORESERVE) { + return nil, &FormatError{shoff, "invalid ELF shstrndx contained in sh_link", shstrndx} + } + } + } + // Read section headers f.Sections = make([]*Section, shnum) names := make([]uint32, shnum) diff --git a/src/debug/elf/file_test.go b/src/debug/elf/file_test.go index c0decdd66e..fe72a1908f 100644 --- a/src/debug/elf/file_test.go +++ b/src/debug/elf/file_test.go @@ -9,6 +9,7 @@ import ( "compress/gzip" "debug/dwarf" "encoding/binary" + "fmt" "io" "math/rand" "net" @@ -230,7 +231,7 @@ func TestOpen(t *testing.T) { continue } defer f.Close() - if !reflect.DeepEqual(f.FileHeader, tt.hdr) { + if f.FileHeader != tt.hdr { t.Errorf("open %s:\n\thave %#v\n\twant %#v\n", tt.file, f.FileHeader, tt.hdr) continue } @@ -238,18 +239,18 @@ func TestOpen(t *testing.T) { if i >= len(tt.sections) { break } - sh := &tt.sections[i] - if !reflect.DeepEqual(&s.SectionHeader, sh) { - t.Errorf("open %s, section %d:\n\thave %#v\n\twant %#v\n", tt.file, i, &s.SectionHeader, sh) + sh := tt.sections[i] + if s.SectionHeader != sh { + t.Errorf("open %s, section %d:\n\thave %#v\n\twant %#v\n", tt.file, i, s.SectionHeader, sh) } } for i, p := range f.Progs { if i >= len(tt.progs) { break } - ph := &tt.progs[i] - if !reflect.DeepEqual(&p.ProgHeader, ph) { - t.Errorf("open %s, program %d:\n\thave %#v\n\twant %#v\n", tt.file, i, &p.ProgHeader, ph) + ph := tt.progs[i] + if p.ProgHeader != ph { + t.Errorf("open %s, program %d:\n\thave %#v\n\twant %#v\n", tt.file, i, p.ProgHeader, ph) } } tn := len(tt.sections) @@ -944,6 +945,251 @@ func TestNoSectionOverlaps(t *testing.T) { } } +// TestLargeNumberOfSections tests the case that a file has greater than or +// equal to 65280 (0xff00) sections. +func TestLargeNumberOfSections(t *testing.T) { + // A file with >= 0xff00 sections is too big, so we will construct it on the + // fly. The original file "y.o" is generated by these commands: + // 1. generate "y.c": + // for i in `seq 1 65288`; do + // printf -v x "%04x" i; + // echo "int var_$x __attribute__((section(\"section_$x\"))) = $i;" + // done > y.c + // 2. compile: gcc -c y.c -m32 + // + // $readelf -h y.o + // ELF Header: + // Magic: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 + // Class: ELF32 + // Data: 2's complement, little endian + // Version: 1 (current) + // OS/ABI: UNIX - System V + // ABI Version: 0 + // Type: REL (Relocatable file) + // Machine: Intel 80386 + // Version: 0x1 + // Entry point address: 0x0 + // Start of program headers: 0 (bytes into file) + // Start of section headers: 3003468 (bytes into file) + // Flags: 0x0 + // Size of this header: 52 (bytes) + // Size of program headers: 0 (bytes) + // Number of program headers: 0 + // Size of section headers: 40 (bytes) + // Number of section headers: 0 (65298) + // Section header string table index: 65535 (65297) + // + // $readelf -S y.o + // There are 65298 section headers, starting at offset 0x2dd44c: + // Section Headers: + // [Nr] Name Type Addr Off Size ES Flg Lk Inf Al + // [ 0] NULL 00000000 000000 00ff12 00 65297 0 0 + // [ 1] .text PROGBITS 00000000 000034 000000 00 AX 0 0 1 + // [ 2] .data PROGBITS 00000000 000034 000000 00 WA 0 0 1 + // [ 3] .bss NOBITS 00000000 000034 000000 00 WA 0 0 1 + // [ 4] section_0001 PROGBITS 00000000 000034 000004 00 WA 0 0 4 + // [ 5] section_0002 PROGBITS 00000000 000038 000004 00 WA 0 0 4 + // [ section_0003 ~ section_ff06 truncated ] + // [65290] section_ff07 PROGBITS 00000000 03fc4c 000004 00 WA 0 0 4 + // [65291] section_ff08 PROGBITS 00000000 03fc50 000004 00 WA 0 0 4 + // [65292] .comment PROGBITS 00000000 03fc54 000027 01 MS 0 0 1 + // [65293] .note.GNU-stack PROGBITS 00000000 03fc7b 000000 00 0 0 1 + // [65294] .symtab SYMTAB 00000000 03fc7c 0ff0a0 10 65296 2 4 + // [65295] .symtab_shndx SYMTAB SECTION 00000000 13ed1c 03fc28 04 65294 0 4 + // [65296] .strtab STRTAB 00000000 17e944 08f74d 00 0 0 1 + // [65297] .shstrtab STRTAB 00000000 20e091 0cf3bb 00 0 0 1 + + var buf bytes.Buffer + + { + buf.Grow(0x55AF1C) // 3003468 + 40 * 65298 + + h := Header32{ + Ident: [16]byte{0x7F, 'E', 'L', 'F', 0x01, 0x01, 0x01}, + Type: 1, + Machine: 3, + Version: 1, + Shoff: 0x2DD44C, + Ehsize: 0x34, + Shentsize: 0x28, + Shnum: 0, + Shstrndx: 0xFFFF, + } + binary.Write(&buf, binary.LittleEndian, h) + + // Zero out sections [1]~[65294]. + buf.Write(bytes.Repeat([]byte{0}, 0x13ED1C-binary.Size(h))) + + // Write section [65295]. Section [65295] are all zeros except for the + // last 48 bytes. + buf.Write(bytes.Repeat([]byte{0}, 0x03FC28-12*4)) + for i := 0; i < 12; i++ { + binary.Write(&buf, binary.LittleEndian, uint32(0xFF00|i)) + } + + // Write section [65296]. + buf.Write([]byte{0}) + buf.Write([]byte("y.c\x00")) + for i := 1; i <= 65288; i++ { + // var_0001 ~ var_ff08 + name := fmt.Sprintf("var_%04x", i) + buf.Write([]byte(name)) + buf.Write([]byte{0}) + } + + // Write section [65297]. + buf.Write([]byte{0}) + buf.Write([]byte(".symtab\x00")) + buf.Write([]byte(".strtab\x00")) + buf.Write([]byte(".shstrtab\x00")) + buf.Write([]byte(".text\x00")) + buf.Write([]byte(".data\x00")) + buf.Write([]byte(".bss\x00")) + for i := 1; i <= 65288; i++ { + // s_0001 ~ s_ff08 + name := fmt.Sprintf("section_%04x", i) + buf.Write([]byte(name)) + buf.Write([]byte{0}) + } + buf.Write([]byte(".comment\x00")) + buf.Write([]byte(".note.GNU-stack\x00")) + buf.Write([]byte(".symtab_shndx\x00")) + + // Write section header table. + // NULL + binary.Write(&buf, binary.LittleEndian, Section32{Name: 0, Size: 0xFF12, Link: 0xFF11}) + // .text + binary.Write(&buf, binary.LittleEndian, Section32{ + Name: 0x1B, + Type: uint32(SHT_PROGBITS), + Flags: uint32(uint32(SHF_ALLOC | SHF_EXECINSTR)), + Off: 0x34, + Addralign: 0x01, + }) + // .data + binary.Write(&buf, binary.LittleEndian, Section32{ + Name: 0x21, + Type: uint32(SHT_PROGBITS), + Flags: uint32(SHF_WRITE | SHF_ALLOC), + Off: 0x34, + Addralign: 0x01, + }) + // .bss + binary.Write(&buf, binary.LittleEndian, Section32{ + Name: 0x27, + Type: uint32(SHT_NOBITS), + Flags: uint32(SHF_WRITE | SHF_ALLOC), + Off: 0x34, + Addralign: 0x01, + }) + // s_1 ~ s_65537 + for i := 0; i < 65288; i++ { + s := Section32{ + Name: uint32(0x2C + i*13), + Type: uint32(SHT_PROGBITS), + Flags: uint32(SHF_WRITE | SHF_ALLOC), + Off: uint32(0x34 + i*4), + Size: 0x04, + Addralign: 0x04, + } + binary.Write(&buf, binary.LittleEndian, s) + } + // .comment + binary.Write(&buf, binary.LittleEndian, Section32{ + Name: 0x0CF394, + Type: uint32(SHT_PROGBITS), + Flags: uint32(SHF_MERGE | SHF_STRINGS), + Off: 0x03FC54, + Size: 0x27, + Addralign: 0x01, + Entsize: 0x01, + }) + // .note.GNU-stack + binary.Write(&buf, binary.LittleEndian, Section32{ + Name: 0x0CF39D, + Type: uint32(SHT_PROGBITS), + Off: 0x03FC7B, + Addralign: 0x01, + }) + // .symtab + binary.Write(&buf, binary.LittleEndian, Section32{ + Name: 0x01, + Type: uint32(SHT_SYMTAB), + Off: 0x03FC7C, + Size: 0x0FF0A0, + Link: 0xFF10, + Info: 0x02, + Addralign: 0x04, + Entsize: 0x10, + }) + // .symtab_shndx + binary.Write(&buf, binary.LittleEndian, Section32{ + Name: 0x0CF3AD, + Type: uint32(SHT_SYMTAB_SHNDX), + Off: 0x13ED1C, + Size: 0x03FC28, + Link: 0xFF0E, + Addralign: 0x04, + Entsize: 0x04, + }) + // .strtab + binary.Write(&buf, binary.LittleEndian, Section32{ + Name: 0x09, + Type: uint32(SHT_STRTAB), + Off: 0x17E944, + Size: 0x08F74D, + Addralign: 0x01, + }) + // .shstrtab + binary.Write(&buf, binary.LittleEndian, Section32{ + Name: 0x11, + Type: uint32(SHT_STRTAB), + Off: 0x20E091, + Size: 0x0CF3BB, + Addralign: 0x01, + }) + } + + data := buf.Bytes() + + f, err := NewFile(bytes.NewReader(data)) + if err != nil { + t.Errorf("cannot create file from data: %v", err) + } + defer f.Close() + + wantFileHeader := FileHeader{ + Class: ELFCLASS32, + Data: ELFDATA2LSB, + Version: EV_CURRENT, + OSABI: ELFOSABI_NONE, + ByteOrder: binary.LittleEndian, + Type: ET_REL, + Machine: EM_386, + } + if f.FileHeader != wantFileHeader { + t.Errorf("\nhave %#v\nwant %#v\n", f.FileHeader, wantFileHeader) + } + + wantSectionNum := 65298 + if len(f.Sections) != wantSectionNum { + t.Errorf("len(Sections) = %d, want %d", len(f.Sections), wantSectionNum) + } + + wantSectionHeader := SectionHeader{ + Name: "section_0007", + Type: SHT_PROGBITS, + Flags: SHF_WRITE + SHF_ALLOC, + Offset: 0x4c, + Size: 0x4, + Addralign: 0x4, + FileSize: 0x4, + } + if f.Sections[10].SectionHeader != wantSectionHeader { + t.Errorf("\nhave %#v\nwant %#v\n", f.Sections[10].SectionHeader, wantSectionHeader) + } +} + func TestIssue10996(t *testing.T) { data := []byte("\u007fELF\x02\x01\x010000000000000" + "\x010000000000000000000" + -- 2.48.1