From 61914997f91c0dcb4f4863f4de326792f5953516 Mon Sep 17 00:00:00 2001 From: Nick Revin Date: Sat, 16 Dec 2023 14:41:25 +0400 Subject: [PATCH 1/9] cmd/link: add option to enable full RELRO for ELF -bindnow linker option enables full RELRO on ELF targets. This options defaults to false and preserves current behavior - partial relro for buildmode=pie. Also, the following changes were made to align internal linker's behavior with external ELF linkers: - GNU_RELRO segment is marked Read-only - .dynamic is a relro section for partial and full RELRO - .got is a relro section for partial and full RELRO - .got.plt is a relro section for full RELRO only Supersedes #45681 (golang.org/cl/312509) --- src/cmd/go/internal/work/security.go | 3 +- src/cmd/go/internal/work/security_test.go | 4 +++ src/cmd/link/doc.go | 2 ++ src/cmd/link/internal/ld/elf.go | 34 +++++++++++++++++------ src/cmd/link/internal/ld/lib.go | 4 ++- src/cmd/link/internal/ld/main.go | 1 + 6 files changed, 37 insertions(+), 11 deletions(-) diff --git a/src/cmd/go/internal/work/security.go b/src/cmd/go/internal/work/security.go index 88504be6cd6b41..3289276e7749cb 100644 --- a/src/cmd/go/internal/work/security.go +++ b/src/cmd/go/internal/work/security.go @@ -210,8 +210,7 @@ var validLinkerFlags = []*lazyregexp.Regexp{ re(`-Wl,-?-unresolved-symbols=[^,]+`), re(`-Wl,--(no-)?warn-([^,]+)`), re(`-Wl,-?-wrap[=,][^,@\-][^,]*`), - re(`-Wl,-z,(no)?execstack`), - re(`-Wl,-z,relro`), + re(`-Wl(,-z,(relro|now|(no)?execstack))+`), re(`[a-zA-Z0-9_/].*\.(a|o|obj|dll|dylib|so|tbd)`), // direct linker inputs: x.o or libfoo.so (but not -foo.o or @foo.o) re(`\./.*\.(a|o|obj|dll|dylib|so|tbd)`), diff --git a/src/cmd/go/internal/work/security_test.go b/src/cmd/go/internal/work/security_test.go index c05ba7b9a472e3..a4c055670ae380 100644 --- a/src/cmd/go/internal/work/security_test.go +++ b/src/cmd/go/internal/work/security_test.go @@ -167,6 +167,10 @@ var goodLinkerFlags = [][]string{ {"-Wl,-framework", "-Wl,Chocolate"}, {"-Wl,-framework,Chocolate"}, {"-Wl,-unresolved-symbols=ignore-all"}, + {"-Wl,-z,relro"}, + {"-Wl,-z,relro,-z,now"}, + {"-Wl,-z,now"}, + {"-Wl,-z,noexecstack"}, {"libcgotbdtest.tbd"}, {"./libcgotbdtest.tbd"}, } diff --git a/src/cmd/link/doc.go b/src/cmd/link/doc.go index b0f2700ac1d253..aeb51212a8f6c2 100644 --- a/src/cmd/link/doc.go +++ b/src/cmd/link/doc.go @@ -47,6 +47,8 @@ Flags: Link with C/C++ address sanitizer support. -aslr Enable ASLR for buildmode=c-shared on windows (default true). + -bindnow + Mark ELF object for immediate function binding (default false). -buildid id Record id as Go toolchain build id. -buildmode mode diff --git a/src/cmd/link/internal/ld/elf.go b/src/cmd/link/internal/ld/elf.go index be9e22946a3868..3b0b22152dd7de 100644 --- a/src/cmd/link/internal/ld/elf.go +++ b/src/cmd/link/internal/ld/elf.go @@ -1056,10 +1056,16 @@ func elfdynhash(ctxt *Link) { } s = ldr.CreateSymForUpdate(".dynamic", 0) - if ctxt.BuildMode == BuildModePIE { - // https://github.com/bminor/glibc/blob/895ef79e04a953cac1493863bcae29ad85657ee1/elf/elf.h#L986 - const DTFLAGS_1_PIE = 0x08000000 - Elfwritedynent(ctxt.Arch, s, elf.DT_FLAGS_1, uint64(DTFLAGS_1_PIE)) + if *flagBindNow { + Elfwritedynent(ctxt.Arch, s, elf.DT_FLAGS, uint64(elf.DF_BIND_NOW)) + } + switch { + case ctxt.BuildMode == BuildModePIE && *flagBindNow: + Elfwritedynent(ctxt.Arch, s, elf.DT_FLAGS_1, uint64(elf.DF_1_PIE)|uint64(elf.DF_1_NOW)) + case ctxt.BuildMode == BuildModePIE && !*flagBindNow: + Elfwritedynent(ctxt.Arch, s, elf.DT_FLAGS_1, uint64(elf.DF_1_PIE)) + case ctxt.BuildMode != BuildModePIE && *flagBindNow: + Elfwritedynent(ctxt.Arch, s, elf.DT_FLAGS_1, uint64(elf.DF_1_NOW)) } elfverneed = nfile if elfverneed != 0 { @@ -1107,6 +1113,7 @@ func elfphload(seg *sym.Segment) *ElfPhdr { func elfphrelro(seg *sym.Segment) { ph := newElfPhdr() ph.Type = elf.PT_GNU_RELRO + ph.Flags = elf.PF_R ph.Vaddr = seg.Vaddr ph.Paddr = seg.Vaddr ph.Memsz = seg.Length @@ -1556,7 +1563,11 @@ func (ctxt *Link) doelf() { /* global offset table */ got := ldr.CreateSymForUpdate(".got", 0) - got.SetType(sym.SELFGOT) // writable + if ctxt.UseRelro() { + got.SetType(sym.SRODATARELRO) + } else { + got.SetType(sym.SELFGOT) // writable + } /* ppc64 glink resolver */ if ctxt.IsPPC64() { @@ -1569,7 +1580,11 @@ func (ctxt *Link) doelf() { hash.SetType(sym.SELFROSECT) gotplt := ldr.CreateSymForUpdate(".got.plt", 0) - gotplt.SetType(sym.SELFSECT) // writable + if ctxt.UseRelro() && *flagBindNow { + gotplt.SetType(sym.SRODATARELRO) + } else { + gotplt.SetType(sym.SELFSECT) // writable + } plt := ldr.CreateSymForUpdate(".plt", 0) if ctxt.IsPPC64() { @@ -1591,9 +1606,12 @@ func (ctxt *Link) doelf() { /* define dynamic elf table */ dynamic := ldr.CreateSymForUpdate(".dynamic", 0) - if thearch.ELF.DynamicReadOnly { + switch { + case thearch.ELF.DynamicReadOnly: dynamic.SetType(sym.SELFROSECT) - } else { + case ctxt.UseRelro(): + dynamic.SetType(sym.SRODATARELRO) + default: dynamic.SetType(sym.SELFSECT) } diff --git a/src/cmd/link/internal/ld/lib.go b/src/cmd/link/internal/ld/lib.go index df83896100872f..50ca3ac13fda86 100644 --- a/src/cmd/link/internal/ld/lib.go +++ b/src/cmd/link/internal/ld/lib.go @@ -1599,12 +1599,14 @@ func (ctxt *Link) hostlink() { } var altLinker string - if ctxt.IsELF && ctxt.DynlinkingGo() { + if ctxt.IsELF && (ctxt.DynlinkingGo() || *flagBindNow) { // We force all symbol resolution to be done at program startup // because lazy PLT resolution can use large amounts of stack at // times we cannot allow it to do so. argv = append(argv, "-Wl,-z,now") + } + if ctxt.IsELF && ctxt.DynlinkingGo() { // Do not let the host linker generate COPY relocations. These // can move symbols out of sections that rely on stable offsets // from the beginning of the section (like sym.STYPE). diff --git a/src/cmd/link/internal/ld/main.go b/src/cmd/link/internal/ld/main.go index a0cc52a0292264..6028f2a1b22505 100644 --- a/src/cmd/link/internal/ld/main.go +++ b/src/cmd/link/internal/ld/main.go @@ -63,6 +63,7 @@ func init() { // Flags used by the linker. The exported flags are used by the architecture-specific packages. var ( flagBuildid = flag.String("buildid", "", "record `id` as Go toolchain build id") + flagBindNow = flag.Bool("bindnow", false, "mark ELF object for immediate function binding") flagOutfile = flag.String("o", "", "write output to `file`") flagPluginPath = flag.String("pluginpath", "", "full path name for plugin") From bf1fa878110fba7f18cdb54302cb27854a4a8a2a Mon Sep 17 00:00:00 2001 From: Nick Revin Date: Wed, 10 Jan 2024 01:02:18 +0300 Subject: [PATCH 2/9] add tests --- src/cmd/link/doc.go | 2 +- src/cmd/link/internal/ld/elf.go | 14 ++-- src/cmd/link/internal/ld/elf_test.go | 106 +++++++++++++++++++++++++++ src/cmd/link/internal/ld/lib.go | 4 +- src/cmd/link/internal/ld/main.go | 2 +- 5 files changed, 118 insertions(+), 10 deletions(-) diff --git a/src/cmd/link/doc.go b/src/cmd/link/doc.go index aeb51212a8f6c2..bd620f987831f7 100644 --- a/src/cmd/link/doc.go +++ b/src/cmd/link/doc.go @@ -48,7 +48,7 @@ Flags: -aslr Enable ASLR for buildmode=c-shared on windows (default true). -bindnow - Mark ELF object for immediate function binding (default false). + Mark a dynamically linked ELF object for immediate function binding (default false). -buildid id Record id as Go toolchain build id. -buildmode mode diff --git a/src/cmd/link/internal/ld/elf.go b/src/cmd/link/internal/ld/elf.go index 3b0b22152dd7de..7c035df97e5ca9 100644 --- a/src/cmd/link/internal/ld/elf.go +++ b/src/cmd/link/internal/ld/elf.go @@ -1056,17 +1056,17 @@ func elfdynhash(ctxt *Link) { } s = ldr.CreateSymForUpdate(".dynamic", 0) + + var dtFlags1 elf.DynFlag1 if *flagBindNow { + dtFlags1 |= elf.DF_1_NOW Elfwritedynent(ctxt.Arch, s, elf.DT_FLAGS, uint64(elf.DF_BIND_NOW)) } - switch { - case ctxt.BuildMode == BuildModePIE && *flagBindNow: - Elfwritedynent(ctxt.Arch, s, elf.DT_FLAGS_1, uint64(elf.DF_1_PIE)|uint64(elf.DF_1_NOW)) - case ctxt.BuildMode == BuildModePIE && !*flagBindNow: - Elfwritedynent(ctxt.Arch, s, elf.DT_FLAGS_1, uint64(elf.DF_1_PIE)) - case ctxt.BuildMode != BuildModePIE && *flagBindNow: - Elfwritedynent(ctxt.Arch, s, elf.DT_FLAGS_1, uint64(elf.DF_1_NOW)) + if ctxt.BuildMode == BuildModePIE { + dtFlags1 |= elf.DF_1_PIE } + Elfwritedynent(ctxt.Arch, s, elf.DT_FLAGS_1, uint64(dtFlags1)) + elfverneed = nfile if elfverneed != 0 { elfWriteDynEntSym(ctxt, s, elf.DT_VERNEED, gnuVersionR.Sym()) diff --git a/src/cmd/link/internal/ld/elf_test.go b/src/cmd/link/internal/ld/elf_test.go index e535af6a1c2e6d..0b1a3fbe973cba 100644 --- a/src/cmd/link/internal/ld/elf_test.go +++ b/src/cmd/link/internal/ld/elf_test.go @@ -8,6 +8,7 @@ package ld import ( "debug/elf" + "fmt" "internal/testenv" "os" "path/filepath" @@ -182,3 +183,108 @@ func main() { } } } + +func TestElfBindNow(t *testing.T) { + t.Parallel() + testenv.MustHaveGoBuild(t) + testenv.MustInternalLink(t, false) + + const prog = `package main; import _ "net"; func main() {}` + + tests := []struct { + name string + args []string + wantDf1Now bool + wantDf1Pie bool + wantDfBindNow bool + }{ + {name: "default"}, + { + name: "pie", + args: []string{"-buildmode=pie"}, + wantDf1Pie: true, + }, + { + name: "bindnow", + args: []string{"-ldflags", "-bindnow"}, + wantDf1Now: true, + wantDfBindNow: true, + }, + { + name: "bindnow-pie", + args: []string{"-buildmode=pie", "-ldflags", "-bindnow"}, + wantDf1Now: true, + wantDf1Pie: true, + wantDfBindNow: true, + }, + } + + gotDynFlag := func(flags []uint64, dynFlag uint64) bool { + for _, flag := range flags { + if gotFlag := dynFlag&flag != 0; gotFlag { + return true + } + } + + return false + } + + for _, test := range tests { + t.Run( + test.name, func(t *testing.T) { + var ( + gotDfBindNow, gotDf1Now, gotDf1Pie bool + + dir = t.TempDir() + src = filepath.Join(dir, fmt.Sprintf("elf_%s.go", test.name)) + binFile = filepath.Join(dir, test.name) + ) + + if err := os.WriteFile(src, []byte(prog), 0666); err != nil { + t.Fatal(err) + } + + cmdArgs := append([]string{"build", "-o", binFile}, append(test.args, src)...) + cmd := testenv.Command(t, testenv.GoToolPath(t), cmdArgs...) + + if out, err := cmd.CombinedOutput(); err != nil { + t.Fatalf("%v: %v:\n%s", cmd.Args, err, out) + } + + fi, err := os.Open(binFile) + if err != nil { + t.Fatalf("failed to open built file: %v", err) + } + defer fi.Close() + + elfFile, err := elf.NewFile(fi) + if err != nil { + t.Skip("The system may not support ELF, skipped.") + } + defer elfFile.Close() + + flags, err := elfFile.DynValue(elf.DT_FLAGS) + if err != nil { + t.Fatalf("failed to get DT_FLAGS: %v", err) + } + + flags1, err := elfFile.DynValue(elf.DT_FLAGS_1) + if err != nil { + t.Fatalf("failed to get DT_FLAGS_1: %v", err) + } + + if gotDfBindNow = gotDynFlag(flags, uint64(elf.DF_BIND_NOW)); gotDfBindNow != test.wantDfBindNow { + t.Fatalf("DT_FLAGS BIND_NOW flag is %v, want: %v", gotDfBindNow, test.wantDfBindNow) + } + + if gotDf1Now = gotDynFlag(flags1, uint64(elf.DF_1_NOW)); gotDf1Now != test.wantDf1Now { + t.Fatalf("DT_FLAGS_1 DF_1_NOW flag is %v, want: %v", gotDf1Now, test.wantDf1Now) + } + + if gotDf1Pie = gotDynFlag(flags1, uint64(elf.DF_1_PIE)); gotDf1Pie != test.wantDf1Pie { + t.Fatalf("DT_FLAGS_1 DF_1_PIE flag is %v, want: %v", gotDf1Pie, test.wantDf1Pie) + } + }, + ) + } +} diff --git a/src/cmd/link/internal/ld/lib.go b/src/cmd/link/internal/ld/lib.go index 50ca3ac13fda86..97f3ed37e3aa9b 100644 --- a/src/cmd/link/internal/ld/lib.go +++ b/src/cmd/link/internal/ld/lib.go @@ -1600,7 +1600,9 @@ func (ctxt *Link) hostlink() { var altLinker string if ctxt.IsELF && (ctxt.DynlinkingGo() || *flagBindNow) { - // We force all symbol resolution to be done at program startup + // For ELF targets, when producing dynamically linked Go code + // or when immediate binding is explicitly requested, + // we force all symbol resolution to be done at program startup // because lazy PLT resolution can use large amounts of stack at // times we cannot allow it to do so. argv = append(argv, "-Wl,-z,now") diff --git a/src/cmd/link/internal/ld/main.go b/src/cmd/link/internal/ld/main.go index 6028f2a1b22505..13077668e7be5a 100644 --- a/src/cmd/link/internal/ld/main.go +++ b/src/cmd/link/internal/ld/main.go @@ -63,7 +63,7 @@ func init() { // Flags used by the linker. The exported flags are used by the architecture-specific packages. var ( flagBuildid = flag.String("buildid", "", "record `id` as Go toolchain build id") - flagBindNow = flag.Bool("bindnow", false, "mark ELF object for immediate function binding") + flagBindNow = flag.Bool("bindnow", false, "mark a dynamically linked ELF object for immediate function binding") flagOutfile = flag.String("o", "", "write output to `file`") flagPluginPath = flag.String("pluginpath", "", "full path name for plugin") From 917d654a4330e24b9d8a50464d24a0d3ce4f22a0 Mon Sep 17 00:00:00 2001 From: Nick Revin Date: Mon, 5 Feb 2024 23:31:21 +0400 Subject: [PATCH 3/9] use internal linker explicitly --- src/cmd/link/internal/ld/elf_test.go | 127 ++++++++++++++------------- 1 file changed, 66 insertions(+), 61 deletions(-) diff --git a/src/cmd/link/internal/ld/elf_test.go b/src/cmd/link/internal/ld/elf_test.go index 0b1a3fbe973cba..937e5163f03c61 100644 --- a/src/cmd/link/internal/ld/elf_test.go +++ b/src/cmd/link/internal/ld/elf_test.go @@ -189,30 +189,37 @@ func TestElfBindNow(t *testing.T) { testenv.MustHaveGoBuild(t) testenv.MustInternalLink(t, false) - const prog = `package main; import _ "net"; func main() {}` + const ( + prog = `package main; func main() {}` + prog_C = `package main; import "C"; func main() {}` + ) tests := []struct { name string args []string + prog string wantDf1Now bool wantDf1Pie bool wantDfBindNow bool }{ - {name: "default"}, + {name: "default", prog: prog}, { name: "pie", - args: []string{"-buildmode=pie"}, + args: []string{"-buildmode=pie", "-ldflags", "-linkmode=internal"}, + prog: prog, wantDf1Pie: true, }, { name: "bindnow", - args: []string{"-ldflags", "-bindnow"}, + args: []string{"-ldflags", "-bindnow -linkmode=internal"}, + prog: prog_C, wantDf1Now: true, wantDfBindNow: true, }, { name: "bindnow-pie", - args: []string{"-buildmode=pie", "-ldflags", "-bindnow"}, + args: []string{"-buildmode=pie", "-ldflags", "-bindnow -linkmode=internal"}, + prog: prog, wantDf1Now: true, wantDf1Pie: true, wantDfBindNow: true, @@ -230,61 +237,59 @@ func TestElfBindNow(t *testing.T) { } for _, test := range tests { - t.Run( - test.name, func(t *testing.T) { - var ( - gotDfBindNow, gotDf1Now, gotDf1Pie bool - - dir = t.TempDir() - src = filepath.Join(dir, fmt.Sprintf("elf_%s.go", test.name)) - binFile = filepath.Join(dir, test.name) - ) - - if err := os.WriteFile(src, []byte(prog), 0666); err != nil { - t.Fatal(err) - } - - cmdArgs := append([]string{"build", "-o", binFile}, append(test.args, src)...) - cmd := testenv.Command(t, testenv.GoToolPath(t), cmdArgs...) - - if out, err := cmd.CombinedOutput(); err != nil { - t.Fatalf("%v: %v:\n%s", cmd.Args, err, out) - } - - fi, err := os.Open(binFile) - if err != nil { - t.Fatalf("failed to open built file: %v", err) - } - defer fi.Close() - - elfFile, err := elf.NewFile(fi) - if err != nil { - t.Skip("The system may not support ELF, skipped.") - } - defer elfFile.Close() - - flags, err := elfFile.DynValue(elf.DT_FLAGS) - if err != nil { - t.Fatalf("failed to get DT_FLAGS: %v", err) - } - - flags1, err := elfFile.DynValue(elf.DT_FLAGS_1) - if err != nil { - t.Fatalf("failed to get DT_FLAGS_1: %v", err) - } - - if gotDfBindNow = gotDynFlag(flags, uint64(elf.DF_BIND_NOW)); gotDfBindNow != test.wantDfBindNow { - t.Fatalf("DT_FLAGS BIND_NOW flag is %v, want: %v", gotDfBindNow, test.wantDfBindNow) - } - - if gotDf1Now = gotDynFlag(flags1, uint64(elf.DF_1_NOW)); gotDf1Now != test.wantDf1Now { - t.Fatalf("DT_FLAGS_1 DF_1_NOW flag is %v, want: %v", gotDf1Now, test.wantDf1Now) - } - - if gotDf1Pie = gotDynFlag(flags1, uint64(elf.DF_1_PIE)); gotDf1Pie != test.wantDf1Pie { - t.Fatalf("DT_FLAGS_1 DF_1_PIE flag is %v, want: %v", gotDf1Pie, test.wantDf1Pie) - } - }, - ) + t.Run(test.name, func(t *testing.T) { + var ( + gotDfBindNow, gotDf1Now, gotDf1Pie bool + + dir = t.TempDir() + src = filepath.Join(dir, fmt.Sprintf("elf_%s.go", test.name)) + binFile = filepath.Join(dir, test.name) + ) + + if err := os.WriteFile(src, []byte(test.prog), 0666); err != nil { + t.Fatal(err) + } + + cmdArgs := append([]string{"build", "-o", binFile}, append(test.args, src)...) + cmd := testenv.Command(t, testenv.GoToolPath(t), cmdArgs...) + + if out, err := cmd.CombinedOutput(); err != nil { + t.Fatalf("failed to build %v: %v:\n%s", cmd.Args, err, out) + } + + fi, err := os.Open(binFile) + if err != nil { + t.Fatalf("failed to open built file: %v", err) + } + defer fi.Close() + + elfFile, err := elf.NewFile(fi) + if err != nil { + t.Skip("The system may not support ELF, skipped.") + } + defer elfFile.Close() + + flags, err := elfFile.DynValue(elf.DT_FLAGS) + if err != nil { + t.Fatalf("failed to get DT_FLAGS: %v", err) + } + + flags1, err := elfFile.DynValue(elf.DT_FLAGS_1) + if err != nil { + t.Fatalf("failed to get DT_FLAGS_1: %v", err) + } + + if gotDfBindNow = gotDynFlag(flags, uint64(elf.DF_BIND_NOW)); gotDfBindNow != test.wantDfBindNow { + t.Fatalf("DT_FLAGS BIND_NOW flag is %v, want: %v", gotDfBindNow, test.wantDfBindNow) + } + + if gotDf1Now = gotDynFlag(flags1, uint64(elf.DF_1_NOW)); gotDf1Now != test.wantDf1Now { + t.Fatalf("DT_FLAGS_1 DF_1_NOW flag is %v, want: %v", gotDf1Now, test.wantDf1Now) + } + + if gotDf1Pie = gotDynFlag(flags1, uint64(elf.DF_1_PIE)); gotDf1Pie != test.wantDf1Pie { + t.Fatalf("DT_FLAGS_1 DF_1_PIE flag is %v, want: %v", gotDf1Pie, test.wantDf1Pie) + } + }) } } From f239244e1fd865ecadac691b8a111101e1ee7691 Mon Sep 17 00:00:00 2001 From: Nick Revin Date: Tue, 6 Feb 2024 19:44:16 +0400 Subject: [PATCH 4/9] tests: use proper testenv checks --- src/cmd/link/internal/ld/elf_test.go | 52 +++++++++++++++++----------- 1 file changed, 32 insertions(+), 20 deletions(-) diff --git a/src/cmd/link/internal/ld/elf_test.go b/src/cmd/link/internal/ld/elf_test.go index 937e5163f03c61..39d4eb0364eba0 100644 --- a/src/cmd/link/internal/ld/elf_test.go +++ b/src/cmd/link/internal/ld/elf_test.go @@ -187,42 +187,46 @@ func main() { func TestElfBindNow(t *testing.T) { t.Parallel() testenv.MustHaveGoBuild(t) - testenv.MustInternalLink(t, false) const ( - prog = `package main; func main() {}` - prog_C = `package main; import "C"; func main() {}` + prog = `package main; func main() {}` + progC = `package main; import "C"; func main() {}` ) tests := []struct { - name string - args []string - prog string - wantDf1Now bool - wantDf1Pie bool - wantDfBindNow bool + name string + args []string + prog string + mustHaveCGO bool + mustHaveBuildModePIE bool + wantDf1Now bool + wantDf1Pie bool + wantDfBindNow bool }{ {name: "default", prog: prog}, { - name: "pie", - args: []string{"-buildmode=pie", "-ldflags", "-linkmode=internal"}, - prog: prog, - wantDf1Pie: true, + name: "pie", + args: []string{"-buildmode=pie", "-ldflags", "-linkmode=internal"}, + mustHaveBuildModePIE: true, + prog: prog, + wantDf1Pie: true, }, { name: "bindnow", args: []string{"-ldflags", "-bindnow -linkmode=internal"}, - prog: prog_C, + prog: progC, + mustHaveCGO: true, wantDf1Now: true, wantDfBindNow: true, }, { - name: "bindnow-pie", - args: []string{"-buildmode=pie", "-ldflags", "-bindnow -linkmode=internal"}, - prog: prog, - wantDf1Now: true, - wantDf1Pie: true, - wantDfBindNow: true, + name: "bindnow-pie", + args: []string{"-buildmode=pie", "-ldflags", "-bindnow -linkmode=internal"}, + prog: prog, + mustHaveBuildModePIE: true, + wantDf1Now: true, + wantDf1Pie: true, + wantDfBindNow: true, }, } @@ -238,6 +242,14 @@ func TestElfBindNow(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { + testenv.MustInternalLink(t, test.mustHaveCGO) + if test.mustHaveCGO { + testenv.MustHaveCGO(t) + } + if test.mustHaveBuildModePIE { + testenv.MustHaveBuildMode(t, "pie") + } + var ( gotDfBindNow, gotDf1Now, gotDf1Pie bool From 782f7980bd0e19c22866e72350d6d037a510a28d Mon Sep 17 00:00:00 2001 From: Nick Revin Date: Wed, 7 Feb 2024 01:10:40 +0400 Subject: [PATCH 5/9] add tests for external linkmode --- src/cmd/link/internal/ld/elf_test.go | 56 ++++++++++++++++++++++------ 1 file changed, 44 insertions(+), 12 deletions(-) diff --git a/src/cmd/link/internal/ld/elf_test.go b/src/cmd/link/internal/ld/elf_test.go index 39d4eb0364eba0..44ab29b6cf2e77 100644 --- a/src/cmd/link/internal/ld/elf_test.go +++ b/src/cmd/link/internal/ld/elf_test.go @@ -189,7 +189,8 @@ func TestElfBindNow(t *testing.T) { testenv.MustHaveGoBuild(t) const ( - prog = `package main; func main() {}` + prog = `package main; func main() {}` + // with default buildmode code compiles in a statically linked binary, hence CGO progC = `package main; import "C"; func main() {}` ) @@ -197,36 +198,65 @@ func TestElfBindNow(t *testing.T) { name string args []string prog string - mustHaveCGO bool mustHaveBuildModePIE bool + mustHaveCGO bool + mustInternalLink bool + wantDfBindNow bool wantDf1Now bool wantDf1Pie bool - wantDfBindNow bool }{ {name: "default", prog: prog}, { - name: "pie", + name: "pie-linkmode-internal", args: []string{"-buildmode=pie", "-ldflags", "-linkmode=internal"}, + prog: prog, mustHaveBuildModePIE: true, + mustInternalLink: true, + wantDf1Pie: true, + }, + { + name: "pie-linkmode-external", + args: []string{"-buildmode=pie", "-ldflags", "-linkmode=external"}, prog: prog, + mustHaveBuildModePIE: true, wantDf1Pie: true, }, { - name: "bindnow", - args: []string{"-ldflags", "-bindnow -linkmode=internal"}, - prog: progC, - mustHaveCGO: true, - wantDf1Now: true, - wantDfBindNow: true, + name: "bindnow-linkmode-internal", + args: []string{"-ldflags", "-bindnow -linkmode=internal"}, + prog: progC, + mustHaveCGO: true, + mustInternalLink: true, + wantDfBindNow: true, + wantDf1Now: true, }, { - name: "bindnow-pie", + name: "bindnow-linkmode-external", + args: []string{"-ldflags", "-bindnow -linkmode=external"}, + prog: progC, + mustHaveCGO: true, + mustInternalLink: true, + wantDfBindNow: true, + wantDf1Now: true, + }, + { + name: "bindnow-pie-linkmode-internal", args: []string{"-buildmode=pie", "-ldflags", "-bindnow -linkmode=internal"}, prog: prog, mustHaveBuildModePIE: true, + mustInternalLink: true, + wantDfBindNow: true, wantDf1Now: true, wantDf1Pie: true, + }, + { + name: "bindnow-pie-linkmode-external", + args: []string{"-buildmode=pie", "-ldflags", "-bindnow -linkmode=external"}, + prog: prog, + mustHaveBuildModePIE: true, wantDfBindNow: true, + wantDf1Now: true, + wantDf1Pie: true, }, } @@ -242,7 +272,9 @@ func TestElfBindNow(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { - testenv.MustInternalLink(t, test.mustHaveCGO) + if test.mustInternalLink { + testenv.MustInternalLink(t, test.mustHaveCGO) + } if test.mustHaveCGO { testenv.MustHaveCGO(t) } From bfb4af125136a6d4c0eb0f5de5d468b52ffae019 Mon Sep 17 00:00:00 2001 From: Nick Revin Date: Wed, 7 Feb 2024 01:57:37 +0400 Subject: [PATCH 6/9] fix freebsd tests --- src/cmd/link/internal/ld/elf_test.go | 5 +++++ src/internal/testenv/testenv.go | 9 +++++++++ 2 files changed, 14 insertions(+) diff --git a/src/cmd/link/internal/ld/elf_test.go b/src/cmd/link/internal/ld/elf_test.go index 44ab29b6cf2e77..828b66210c4ce5 100644 --- a/src/cmd/link/internal/ld/elf_test.go +++ b/src/cmd/link/internal/ld/elf_test.go @@ -219,6 +219,7 @@ func TestElfBindNow(t *testing.T) { args: []string{"-buildmode=pie", "-ldflags", "-linkmode=external"}, prog: prog, mustHaveBuildModePIE: true, + mustHaveCGO: true, wantDf1Pie: true, }, { @@ -254,6 +255,7 @@ func TestElfBindNow(t *testing.T) { args: []string{"-buildmode=pie", "-ldflags", "-bindnow -linkmode=external"}, prog: prog, mustHaveBuildModePIE: true, + mustHaveCGO: true, wantDfBindNow: true, wantDf1Now: true, wantDf1Pie: true, @@ -281,6 +283,9 @@ func TestElfBindNow(t *testing.T) { if test.mustHaveBuildModePIE { testenv.MustHaveBuildMode(t, "pie") } + if test.mustHaveBuildModePIE && test.mustInternalLink { + testenv.MustInternalLinkPIE(t) + } var ( gotDfBindNow, gotDf1Now, gotDf1Pie bool diff --git a/src/internal/testenv/testenv.go b/src/internal/testenv/testenv.go index f767ac590ca53c..3b9d2fd1e9194a 100644 --- a/src/internal/testenv/testenv.go +++ b/src/internal/testenv/testenv.go @@ -369,6 +369,15 @@ func MustInternalLink(t testing.TB, withCgo bool) { } } +// MustInternalLinkPIE checks whether the current system can link PIE binary using +// internal linking. +// If not, MustInternalLinkPIE calls t.Skip with an explanation. +func MustInternalLinkPIE(t testing.TB) { + if !platform.InternalLinkPIESupported(runtime.GOOS, runtime.GOARCH) { + t.Skipf("skipping test: internal linking for buildmode=pie on %s/%s is not supported", runtime.GOOS, runtime.GOARCH) + } +} + // MustHaveBuildMode reports whether the current system can build programs in // the given build mode. // If not, MustHaveBuildMode calls t.Skip with an explanation. From 8c85e98dd285b0311a7358a778de2be26eeb4adf Mon Sep 17 00:00:00 2001 From: Nick Revin Date: Wed, 7 Feb 2024 02:32:16 +0400 Subject: [PATCH 7/9] fix one sub-test --- src/cmd/link/internal/ld/elf_test.go | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/cmd/link/internal/ld/elf_test.go b/src/cmd/link/internal/ld/elf_test.go index 828b66210c4ce5..819de02c72acf1 100644 --- a/src/cmd/link/internal/ld/elf_test.go +++ b/src/cmd/link/internal/ld/elf_test.go @@ -232,13 +232,12 @@ func TestElfBindNow(t *testing.T) { wantDf1Now: true, }, { - name: "bindnow-linkmode-external", - args: []string{"-ldflags", "-bindnow -linkmode=external"}, - prog: progC, - mustHaveCGO: true, - mustInternalLink: true, - wantDfBindNow: true, - wantDf1Now: true, + name: "bindnow-linkmode-external", + args: []string{"-ldflags", "-bindnow -linkmode=external"}, + prog: progC, + mustHaveCGO: true, + wantDfBindNow: true, + wantDf1Now: true, }, { name: "bindnow-pie-linkmode-internal", From a5d4ea7e1859f59c8c2c11959b3b5058181aaded Mon Sep 17 00:00:00 2001 From: Nick Revin Date: Wed, 7 Feb 2024 21:58:04 +0400 Subject: [PATCH 8/9] leave only one test for external linker --- src/cmd/link/internal/ld/elf_test.go | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/src/cmd/link/internal/ld/elf_test.go b/src/cmd/link/internal/ld/elf_test.go index 819de02c72acf1..b6991997241188 100644 --- a/src/cmd/link/internal/ld/elf_test.go +++ b/src/cmd/link/internal/ld/elf_test.go @@ -214,14 +214,6 @@ func TestElfBindNow(t *testing.T) { mustInternalLink: true, wantDf1Pie: true, }, - { - name: "pie-linkmode-external", - args: []string{"-buildmode=pie", "-ldflags", "-linkmode=external"}, - prog: prog, - mustHaveBuildModePIE: true, - mustHaveCGO: true, - wantDf1Pie: true, - }, { name: "bindnow-linkmode-internal", args: []string{"-ldflags", "-bindnow -linkmode=internal"}, @@ -231,14 +223,6 @@ func TestElfBindNow(t *testing.T) { wantDfBindNow: true, wantDf1Now: true, }, - { - name: "bindnow-linkmode-external", - args: []string{"-ldflags", "-bindnow -linkmode=external"}, - prog: progC, - mustHaveCGO: true, - wantDfBindNow: true, - wantDf1Now: true, - }, { name: "bindnow-pie-linkmode-internal", args: []string{"-buildmode=pie", "-ldflags", "-bindnow -linkmode=internal"}, From bc6826441065395b80a2b66cde67466c4d9bce2e Mon Sep 17 00:00:00 2001 From: Nick Revin Date: Thu, 15 Feb 2024 23:08:32 +0400 Subject: [PATCH 9/9] check dynamic bind now flags for external linkers less strictly --- src/cmd/link/internal/ld/elf_test.go | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/src/cmd/link/internal/ld/elf_test.go b/src/cmd/link/internal/ld/elf_test.go index b6991997241188..16bf4039b1f354 100644 --- a/src/cmd/link/internal/ld/elf_test.go +++ b/src/cmd/link/internal/ld/elf_test.go @@ -271,8 +271,6 @@ func TestElfBindNow(t *testing.T) { } var ( - gotDfBindNow, gotDf1Now, gotDf1Pie bool - dir = t.TempDir() src = filepath.Join(dir, fmt.Sprintf("elf_%s.go", test.name)) binFile = filepath.Join(dir, test.name) @@ -311,16 +309,25 @@ func TestElfBindNow(t *testing.T) { t.Fatalf("failed to get DT_FLAGS_1: %v", err) } - if gotDfBindNow = gotDynFlag(flags, uint64(elf.DF_BIND_NOW)); gotDfBindNow != test.wantDfBindNow { - t.Fatalf("DT_FLAGS BIND_NOW flag is %v, want: %v", gotDfBindNow, test.wantDfBindNow) + gotDfBindNow := gotDynFlag(flags, uint64(elf.DF_BIND_NOW)) + gotDf1Now := gotDynFlag(flags1, uint64(elf.DF_1_NOW)) + + bindNowFlagsMatch := gotDfBindNow == test.wantDfBindNow && gotDf1Now == test.wantDf1Now + + // some external linkers may set one of the two flags but not both. + if !test.mustInternalLink { + bindNowFlagsMatch = gotDfBindNow == test.wantDfBindNow || gotDf1Now == test.wantDf1Now } - if gotDf1Now = gotDynFlag(flags1, uint64(elf.DF_1_NOW)); gotDf1Now != test.wantDf1Now { - t.Fatalf("DT_FLAGS_1 DF_1_NOW flag is %v, want: %v", gotDf1Now, test.wantDf1Now) + if !bindNowFlagsMatch { + t.Fatalf("Dynamic flags mismatch:\n"+ + "DT_FLAGS BIND_NOW got: %v, want: %v\n"+ + "DT_FLAGS_1 DF_1_NOW got: %v, want: %v", + gotDfBindNow, test.wantDfBindNow, gotDf1Now, test.wantDf1Now) } - if gotDf1Pie = gotDynFlag(flags1, uint64(elf.DF_1_PIE)); gotDf1Pie != test.wantDf1Pie { - t.Fatalf("DT_FLAGS_1 DF_1_PIE flag is %v, want: %v", gotDf1Pie, test.wantDf1Pie) + if gotDf1Pie := gotDynFlag(flags1, uint64(elf.DF_1_PIE)); gotDf1Pie != test.wantDf1Pie { + t.Fatalf("DT_FLAGS_1 DF_1_PIE got: %v, want: %v", gotDf1Pie, test.wantDf1Pie) } }) }