@@ -11,8 +11,10 @@ import (
11
11
"fmt"
12
12
"internal/testenv"
13
13
"os"
14
+ "os/exec"
14
15
"path/filepath"
15
16
"runtime"
17
+ "sort"
16
18
"strings"
17
19
"testing"
18
20
)
@@ -409,3 +411,198 @@ func TestElfBindNow(t *testing.T) {
409
411
})
410
412
}
411
413
}
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