Skip to content

Commit a548dee

Browse files
committed
cmd/link/internal/ld: fix overlapping sections in ELF relro links
This patch fixes a problem with how the .dynamic and .got sections are handled during PIE linking on ELF targets. These sections were being given addresses that overlapped with the .data.rel.ro section, which resulted in binaries that worked correctly but confused the binutils "strip" tool (which, confusingly, produced non-working stripped output when used on Go PIE binaries without returning a non-zero exit status). The new RELRO PIE code path preserves .dynamic and .got as their own independent sections, while ensuring that they make it into the RELRO segment. A new test verifies that we can successfully strip and run Go PIE binaries, and also that we don't wind up with any sections whose address ranges overlap. Fixes #67261. Updates #45681. Change-Id: If874be05285252a9b074d4a1fc6a4023b9a28b5e Reviewed-on: https://go-review.googlesource.com/c/go/+/584595 Reviewed-by: Cherry Mui <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]>
1 parent 1259a30 commit a548dee

File tree

2 files changed

+199
-2
lines changed

2 files changed

+199
-2
lines changed

src/cmd/link/internal/ld/data.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -2106,9 +2106,9 @@ func (state *dodataState) allocateDataSections(ctxt *Link) {
21062106
xcoffUpdateOuterSize(ctxt, state.datsize-symnStartValue, symn)
21072107
}
21082108
}
2109-
state.assignToSection(sect, sym.SELFRELROSECT, sym.SRODATA)
2110-
21112109
sect.Length = uint64(state.datsize) - sect.Vaddr
2110+
2111+
state.allocateSingleSymSections(segrelro, sym.SELFRELROSECT, sym.SRODATA, relroSecPerm)
21122112
}
21132113

21142114
/* typelink */

src/cmd/link/internal/ld/elf_test.go

+197
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,10 @@ import (
1111
"fmt"
1212
"internal/testenv"
1313
"os"
14+
"os/exec"
1415
"path/filepath"
1516
"runtime"
17+
"sort"
1618
"strings"
1719
"testing"
1820
)
@@ -409,3 +411,198 @@ func TestElfBindNow(t *testing.T) {
409411
})
410412
}
411413
}
414+
415+
// This program is intended to be just big/complicated enough that
416+
// we wind up with decent-sized .data.rel.ro.{typelink,itablink,gopclntab}
417+
// sections.
418+
const ifacecallsProg = `
419+
package main
420+
421+
import "reflect"
422+
423+
type A string
424+
type B int
425+
type C float64
426+
427+
type describer interface{ What() string }
428+
type timer interface{ When() int }
429+
type rationale interface{ Why() error }
430+
431+
func (a *A) What() string { return "string" }
432+
func (b *B) What() string { return "int" }
433+
func (b *B) When() int { return int(*b) }
434+
func (b *B) Why() error { return nil }
435+
func (c *C) What() string { return "float64" }
436+
437+
func i_am_dead(c C) {
438+
var d describer = &c
439+
println(d.What())
440+
}
441+
442+
func example(a A, b B) describer {
443+
if b == 1 {
444+
return &a
445+
}
446+
return &b
447+
}
448+
449+
func ouch(a any, what string) string {
450+
cv := reflect.ValueOf(a).MethodByName(what).Call(nil)
451+
return cv[0].String()
452+
}
453+
454+
func main() {
455+
println(example("", 1).What())
456+
println(ouch(example("", 1), "What"))
457+
}
458+
459+
`
460+
461+
func TestRelroSectionOverlapIssue67261(t *testing.T) {
462+
t.Parallel()
463+
testenv.MustHaveGoBuild(t)
464+
testenv.MustHaveBuildMode(t, "pie")
465+
testenv.MustInternalLinkPIE(t)
466+
467+
// This test case inspired by issue 67261, in which the linker
468+
// produces a set of sections for -buildmode=pie that confuse the
469+
// "strip" command, due to overlapping extents. The test first
470+
// verifies that we don't have any overlapping PROGBITS/DYNAMIC
471+
// sections, then runs "strip" on the resulting binary.
472+
473+
dir := t.TempDir()
474+
src := filepath.Join(dir, "e.go")
475+
binFile := filepath.Join(dir, "e.exe")
476+
477+
if err := os.WriteFile(src, []byte(ifacecallsProg), 0666); err != nil {
478+
t.Fatal(err)
479+
}
480+
481+
cmdArgs := []string{"build", "-o", binFile, "-buildmode=pie", "-ldflags=linkmode=internal", src}
482+
cmd := testenv.Command(t, testenv.GoToolPath(t), cmdArgs...)
483+
484+
if out, err := cmd.CombinedOutput(); err != nil {
485+
t.Fatalf("failed to build %v: %v:\n%s", cmd.Args, err, out)
486+
}
487+
488+
fi, err := os.Open(binFile)
489+
if err != nil {
490+
t.Fatalf("failed to open built file: %v", err)
491+
}
492+
defer fi.Close()
493+
494+
elfFile, err := elf.NewFile(fi)
495+
if err != nil {
496+
t.Skip("The system may not support ELF, skipped.")
497+
}
498+
defer elfFile.Close()
499+
500+
// List of interesting sections. Here "interesting" means progbits/dynamic
501+
// and loadable (has an address), nonzero size.
502+
secs := []*elf.Section{}
503+
for _, s := range elfFile.Sections {
504+
if s.Type != elf.SHT_PROGBITS && s.Type != elf.SHT_DYNAMIC {
505+
continue
506+
}
507+
if s.Addr == 0 || s.Size == 0 {
508+
continue
509+
}
510+
secs = append(secs, s)
511+
}
512+
513+
secOverlaps := func(s1, s2 *elf.Section) bool {
514+
st1 := s1.Addr
515+
st2 := s2.Addr
516+
en1 := s1.Addr + s1.Size
517+
en2 := s2.Addr + s2.Size
518+
return max(st1, st2) < min(en1, en2)
519+
}
520+
521+
// Sort by address
522+
sort.SliceStable(secs, func(i, j int) bool {
523+
return secs[i].Addr < secs[j].Addr
524+
})
525+
526+
// Check to make sure we don't have any overlaps.
527+
foundOverlap := false
528+
for i := 0; i < len(secs)-1; i++ {
529+
for j := i + 1; j < len(secs); j++ {
530+
s := secs[i]
531+
sn := secs[j]
532+
if secOverlaps(s, sn) {
533+
t.Errorf("unexpected: section %d:%q (addr=%x size=%x) overlaps section %d:%q (addr=%x size=%x)", i, s.Name, s.Addr, s.Size, i+1, sn.Name, sn.Addr, sn.Size)
534+
foundOverlap = true
535+
}
536+
}
537+
}
538+
if foundOverlap {
539+
// Print some additional info for human inspection.
540+
t.Logf("** section list follows\n")
541+
for i := range secs {
542+
s := secs[i]
543+
fmt.Printf(" | %2d: ad=0x%08x en=0x%08x sz=0x%08x t=%s %q\n",
544+
i, s.Addr, s.Addr+s.Size, s.Size, s.Type, s.Name)
545+
}
546+
}
547+
548+
// We need CGO / c-compiler for the next bit.
549+
testenv.MustHaveCGO(t)
550+
551+
// Make sure that the resulting binary can be put through strip.
552+
// Try both "strip" and "llvm-strip"; in each case ask out CC
553+
// command where to find the tool with "-print-prog-name" (meaning
554+
// that if CC is gcc, we typically won't be able to find llvm-strip).
555+
//
556+
// Interestingly, binutils version of strip will (unfortunately)
557+
// print error messages if there is a problem but will not return
558+
// a non-zero exit status (?why?), so we consider any output a
559+
// failure here.
560+
stripExecs := []string{}
561+
ecmd := testenv.Command(t, testenv.GoToolPath(t), "env", "CC")
562+
if out, err := ecmd.CombinedOutput(); err != nil {
563+
t.Fatalf("go env CC failed: %v:\n%s", err, out)
564+
} else {
565+
ccprog := strings.TrimSpace(string(out))
566+
tries := []string{"strip", "llvm-strip"}
567+
for _, try := range tries {
568+
cmd := testenv.Command(t, ccprog, "-print-prog-name="+try)
569+
if out, err := cmd.CombinedOutput(); err != nil {
570+
t.Fatalf("print-prog-name failed: %+v %v:\n%s",
571+
cmd.Args, err, out)
572+
} else {
573+
sprog := strings.TrimSpace(string(out))
574+
stripExecs = append(stripExecs, sprog)
575+
}
576+
}
577+
}
578+
579+
// Run strip on our Go PIE binary, making sure that the strip
580+
// succeeds and we get no output from strip, then run the resulting
581+
// stripped binary.
582+
for k, sprog := range stripExecs {
583+
if _, err := os.Stat(sprog); err != nil {
584+
sp1, err := exec.LookPath(sprog)
585+
if err != nil || sp1 == "" {
586+
continue
587+
}
588+
sprog = sp1
589+
}
590+
targ := fmt.Sprintf("p%d.exe", k)
591+
scmd := testenv.Command(t, sprog, "-o", targ, binFile)
592+
scmd.Dir = dir
593+
if sout, serr := scmd.CombinedOutput(); serr != nil {
594+
t.Fatalf("failed to strip %v: %v:\n%s", scmd.Args, serr, sout)
595+
} else {
596+
// Non-empty output indicates failure, as mentioned above.
597+
if len(string(sout)) != 0 {
598+
t.Errorf("unexpected outut from %s:\n%s\n", sprog, string(sout))
599+
}
600+
}
601+
rcmd := testenv.Command(t, filepath.Join(dir, targ))
602+
if out, err := rcmd.CombinedOutput(); err != nil {
603+
t.Errorf("binary stripped by %s failed: %v:\n%s",
604+
scmd.Args, err, string(out))
605+
}
606+
}
607+
608+
}

0 commit comments

Comments
 (0)