aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIan Lance Taylor <iant@golang.org>2026-03-23 04:38:52 -0700
committerGopher Robot <gobot@golang.org>2026-03-25 06:14:20 -0700
commite3bda445164c764aa95c7df271dff2170fa1a7ce (patch)
tree0a3b39380692cacc59ea0bf3400072af03b846e1
parentcbc2d06c9ba8285bb60f1055b44d4d1e742ca734 (diff)
downloadgo-e3bda445164c764aa95c7df271dff2170fa1a7ce.tar.xz
debug/elf: handle program header count overflow
ELF files only have a 16-bit count for program headers. To handle the very rare case of larger files, a large program header count is stored as 0xffff and the first section header info field holds the actual count. Fixes #78217 Change-Id: I35c7e15025a9677473cb43d09a41f17f75443731 Reviewed-on: https://go-review.googlesource.com/c/go/+/758040 Reviewed-by: David Chase <drchase@google.com> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Auto-Submit: Ian Lance Taylor <iant@golang.org> Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
-rw-r--r--src/debug/elf/file.go112
-rw-r--r--src/debug/elf/file_test.go85
2 files changed, 151 insertions, 46 deletions
diff --git a/src/debug/elf/file.go b/src/debug/elf/file.go
index cc40b22f39..faba6fa4e9 100644
--- a/src/debug/elf/file.go
+++ b/src/debug/elf/file.go
@@ -400,6 +400,72 @@ func NewFile(r io.ReaderAt) (*File, error) {
return nil, &FormatError{0, "invalid ELF phentsize", phentsize}
}
+ // 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 the number of segments is greater than or equal to 0xffff,
+ // phnum has the value 0xffff, and the actual number of segments
+ // is contained in the sh_info field of the section header at
+ // index 0.
+ const pnXnum = 0xffff
+ if shoff > 0 && (shnum == 0 || phnum == pnXnum) {
+ var typ, link, info uint32
+ var size uint64
+ sr.Seek(shoff, io.SeekStart)
+ switch f.Class {
+ case ELFCLASS32:
+ sh := new(Section32)
+ if err := binary.Read(sr, bo, sh); err != nil {
+ return nil, err
+ }
+ size = uint64(sh.Size)
+ typ = sh.Type
+ link = sh.Link
+ info = sh.Info
+ case ELFCLASS64:
+ sh := new(Section64)
+ if err := binary.Read(sr, bo, sh); err != nil {
+ return nil, err
+ }
+ size = sh.Size
+ typ = sh.Type
+ link = sh.Link
+ info = sh.Info
+ }
+
+ if SectionType(typ) != SHT_NULL {
+ return nil, &FormatError{shoff, "invalid type of the initial section", SectionType(typ)}
+ }
+
+ if shnum == 0 {
+ if size < uint64(SHN_LORESERVE) {
+ return nil, &FormatError{shoff, "invalid ELF shnum contained in sh_size", shnum}
+ }
+ shnum = int(size)
+ }
+
+ if phnum == pnXnum {
+ if info < 0xffff {
+ return nil, &FormatError{shoff, "invalid ELF phnum contained in sh_info", info}
+ }
+ phnum = int(info)
+ }
+
+ // 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 program headers
f.Progs = make([]*Prog, phnum)
phdata, err := saferio.ReadDataAt(sr, uint64(phnum)*uint64(phentsize), phoff)
@@ -446,52 +512,6 @@ 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, io.SeekStart)
- switch f.Class {
- case ELFCLASS32:
- sh := new(Section32)
- if err := binary.Read(sr, bo, 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, bo, 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}
- }
- }
- }
-
if shnum > 0 && shentsize < wantShentsize {
return nil, &FormatError{0, "invalid ELF shentsize", shentsize}
}
diff --git a/src/debug/elf/file_test.go b/src/debug/elf/file_test.go
index aef9967c78..e99844639a 100644
--- a/src/debug/elf/file_test.go
+++ b/src/debug/elf/file_test.go
@@ -5,6 +5,7 @@
package elf
import (
+ "bufio"
"bytes"
"compress/gzip"
"compress/zlib"
@@ -16,6 +17,7 @@ import (
"math/rand"
"net"
"os"
+ "os/exec"
"path"
"path/filepath"
"reflect"
@@ -1666,3 +1668,86 @@ func TestNewFileShortReader(t *testing.T) {
})
}
}
+
+func TestLargeNumberOfSegments(t *testing.T) {
+ switch runtime.GOOS {
+ case "dragonfly", "freebsd", "linux", "netbsd", "openbsd", "solaris":
+ default:
+ t.Skipf("ELF binaries not generated on %s", runtime.GOOS)
+ }
+
+ if testing.Short() {
+ // The GNU linker takes a long time to run this test.
+ t.Skip("skipping slow test in short mode")
+ }
+
+ t.Parallel()
+
+ tempDir := t.TempDir()
+ scriptFile := filepath.Join(tempDir, "script")
+ f, err := os.Create(scriptFile)
+ if err != nil {
+ t.Fatal(err)
+ }
+ b := bufio.NewWriter(f)
+
+ // These script segments work for GNU ld.
+ const scriptStart = `
+PHDRS {
+ headers PT_PHDR PHDRS;
+ text PT_LOAD FILEHDR PHDRS;
+ dynamic PT_DYNAMIC;
+`
+ const scriptEnd = `
+}
+SECTIONS {
+ .sec1 : { *(.sec1) } :hdr1
+}
+`
+
+ fmt.Fprintf(b, scriptStart)
+ const phdrCount = 70000
+ for i := range phdrCount {
+ fmt.Fprintf(b, "\thdr%d PT_LOAD;", i)
+ }
+ fmt.Fprintf(b, scriptEnd)
+
+ if err := b.Flush(); err != nil {
+ t.Fatal(err)
+ }
+ if err := f.Close(); err != nil {
+ t.Fatal(err)
+ }
+
+ const cCode = `
+int main(int argc, char **argv) { return 0; }
+`
+ cFile := filepath.Join(tempDir, "x.c")
+ if err := os.WriteFile(cFile, []byte(cCode), 0o644); err != nil {
+ t.Fatal(err)
+ }
+
+ cc := os.Getenv("CC")
+ if cc == "" {
+ cc = "gcc"
+ }
+ oFile := filepath.Join(tempDir, "x.exe")
+
+ out, err := exec.Command(cc, "-o", oFile, "-T", scriptFile, cFile).CombinedOutput()
+ if len(out) > 0 {
+ t.Logf("%s", out)
+ }
+ if err != nil {
+ t.Skipf("skipping test because generating test case failed: %v", err)
+ }
+
+ ef, err := Open(oFile)
+ if err != nil {
+ t.Fatalf("failed to open test file: %v", err)
+ }
+ got := len(ef.Progs)
+ if got < phdrCount {
+ t.Errorf("got %d program headers, expected at least %d", got, phdrCount)
+ }
+ t.Logf("output file has %d segments", got)
+}