Skip to content

Commit 45b641c

Browse files
nrvnrvncherrymui
authored andcommitted
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) Change-Id: I51c4ef07b14beceb7cd6fd989f323e45f89a63ca GitHub-Last-Rev: bc68264 GitHub-Pull-Request: #58869 Reviewed-on: https://go-review.googlesource.com/c/go/+/473495 TryBot-Result: Gopher Robot <[email protected]> Reviewed-by: Cherry Mui <[email protected]> Run-TryBot: Cherry Mui <[email protected]> Reviewed-by: Than McIntosh <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]>
1 parent 0784fd1 commit 45b641c

File tree

8 files changed

+198
-11
lines changed

8 files changed

+198
-11
lines changed

src/cmd/go/internal/work/security.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -210,8 +210,7 @@ var validLinkerFlags = []*lazyregexp.Regexp{
210210
re(`-Wl,-?-unresolved-symbols=[^,]+`),
211211
re(`-Wl,--(no-)?warn-([^,]+)`),
212212
re(`-Wl,-?-wrap[=,][^,@\-][^,]*`),
213-
re(`-Wl,-z,(no)?execstack`),
214-
re(`-Wl,-z,relro`),
213+
re(`-Wl(,-z,(relro|now|(no)?execstack))+`),
215214

216215
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)
217216
re(`\./.*\.(a|o|obj|dll|dylib|so|tbd)`),

src/cmd/go/internal/work/security_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,10 @@ var goodLinkerFlags = [][]string{
167167
{"-Wl,-framework", "-Wl,Chocolate"},
168168
{"-Wl,-framework,Chocolate"},
169169
{"-Wl,-unresolved-symbols=ignore-all"},
170+
{"-Wl,-z,relro"},
171+
{"-Wl,-z,relro,-z,now"},
172+
{"-Wl,-z,now"},
173+
{"-Wl,-z,noexecstack"},
170174
{"libcgotbdtest.tbd"},
171175
{"./libcgotbdtest.tbd"},
172176
}

src/cmd/link/doc.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ Flags:
4747
Link with C/C++ address sanitizer support.
4848
-aslr
4949
Enable ASLR for buildmode=c-shared on windows (default true).
50+
-bindnow
51+
Mark a dynamically linked ELF object for immediate function binding (default false).
5052
-buildid id
5153
Record id as Go toolchain build id.
5254
-buildmode mode

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

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1056,11 +1056,17 @@ func elfdynhash(ctxt *Link) {
10561056
}
10571057

10581058
s = ldr.CreateSymForUpdate(".dynamic", 0)
1059+
1060+
var dtFlags1 elf.DynFlag1
1061+
if *flagBindNow {
1062+
dtFlags1 |= elf.DF_1_NOW
1063+
Elfwritedynent(ctxt.Arch, s, elf.DT_FLAGS, uint64(elf.DF_BIND_NOW))
1064+
}
10591065
if ctxt.BuildMode == BuildModePIE {
1060-
// https://github.com/bminor/glibc/blob/895ef79e04a953cac1493863bcae29ad85657ee1/elf/elf.h#L986
1061-
const DTFLAGS_1_PIE = 0x08000000
1062-
Elfwritedynent(ctxt.Arch, s, elf.DT_FLAGS_1, uint64(DTFLAGS_1_PIE))
1066+
dtFlags1 |= elf.DF_1_PIE
10631067
}
1068+
Elfwritedynent(ctxt.Arch, s, elf.DT_FLAGS_1, uint64(dtFlags1))
1069+
10641070
elfverneed = nfile
10651071
if elfverneed != 0 {
10661072
elfWriteDynEntSym(ctxt, s, elf.DT_VERNEED, gnuVersionR.Sym())
@@ -1107,6 +1113,7 @@ func elfphload(seg *sym.Segment) *ElfPhdr {
11071113
func elfphrelro(seg *sym.Segment) {
11081114
ph := newElfPhdr()
11091115
ph.Type = elf.PT_GNU_RELRO
1116+
ph.Flags = elf.PF_R
11101117
ph.Vaddr = seg.Vaddr
11111118
ph.Paddr = seg.Vaddr
11121119
ph.Memsz = seg.Length
@@ -1556,7 +1563,11 @@ func (ctxt *Link) doelf() {
15561563

15571564
/* global offset table */
15581565
got := ldr.CreateSymForUpdate(".got", 0)
1559-
got.SetType(sym.SELFGOT) // writable
1566+
if ctxt.UseRelro() {
1567+
got.SetType(sym.SRODATARELRO)
1568+
} else {
1569+
got.SetType(sym.SELFGOT) // writable
1570+
}
15601571

15611572
/* ppc64 glink resolver */
15621573
if ctxt.IsPPC64() {
@@ -1569,7 +1580,11 @@ func (ctxt *Link) doelf() {
15691580
hash.SetType(sym.SELFROSECT)
15701581

15711582
gotplt := ldr.CreateSymForUpdate(".got.plt", 0)
1572-
gotplt.SetType(sym.SELFSECT) // writable
1583+
if ctxt.UseRelro() && *flagBindNow {
1584+
gotplt.SetType(sym.SRODATARELRO)
1585+
} else {
1586+
gotplt.SetType(sym.SELFSECT) // writable
1587+
}
15731588

15741589
plt := ldr.CreateSymForUpdate(".plt", 0)
15751590
if ctxt.IsPPC64() {
@@ -1591,9 +1606,12 @@ func (ctxt *Link) doelf() {
15911606

15921607
/* define dynamic elf table */
15931608
dynamic := ldr.CreateSymForUpdate(".dynamic", 0)
1594-
if thearch.ELF.DynamicReadOnly {
1609+
switch {
1610+
case thearch.ELF.DynamicReadOnly:
15951611
dynamic.SetType(sym.SELFROSECT)
1596-
} else {
1612+
case ctxt.UseRelro():
1613+
dynamic.SetType(sym.SRODATARELRO)
1614+
default:
15971615
dynamic.SetType(sym.SELFSECT)
15981616
}
15991617

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

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ package ld
88

99
import (
1010
"debug/elf"
11+
"fmt"
1112
"internal/testenv"
1213
"os"
1314
"path/filepath"
@@ -182,3 +183,152 @@ func main() {
182183
}
183184
}
184185
}
186+
187+
func TestElfBindNow(t *testing.T) {
188+
t.Parallel()
189+
testenv.MustHaveGoBuild(t)
190+
191+
const (
192+
prog = `package main; func main() {}`
193+
// with default buildmode code compiles in a statically linked binary, hence CGO
194+
progC = `package main; import "C"; func main() {}`
195+
)
196+
197+
tests := []struct {
198+
name string
199+
args []string
200+
prog string
201+
mustHaveBuildModePIE bool
202+
mustHaveCGO bool
203+
mustInternalLink bool
204+
wantDfBindNow bool
205+
wantDf1Now bool
206+
wantDf1Pie bool
207+
}{
208+
{name: "default", prog: prog},
209+
{
210+
name: "pie-linkmode-internal",
211+
args: []string{"-buildmode=pie", "-ldflags", "-linkmode=internal"},
212+
prog: prog,
213+
mustHaveBuildModePIE: true,
214+
mustInternalLink: true,
215+
wantDf1Pie: true,
216+
},
217+
{
218+
name: "bindnow-linkmode-internal",
219+
args: []string{"-ldflags", "-bindnow -linkmode=internal"},
220+
prog: progC,
221+
mustHaveCGO: true,
222+
mustInternalLink: true,
223+
wantDfBindNow: true,
224+
wantDf1Now: true,
225+
},
226+
{
227+
name: "bindnow-pie-linkmode-internal",
228+
args: []string{"-buildmode=pie", "-ldflags", "-bindnow -linkmode=internal"},
229+
prog: prog,
230+
mustHaveBuildModePIE: true,
231+
mustInternalLink: true,
232+
wantDfBindNow: true,
233+
wantDf1Now: true,
234+
wantDf1Pie: true,
235+
},
236+
{
237+
name: "bindnow-pie-linkmode-external",
238+
args: []string{"-buildmode=pie", "-ldflags", "-bindnow -linkmode=external"},
239+
prog: prog,
240+
mustHaveBuildModePIE: true,
241+
mustHaveCGO: true,
242+
wantDfBindNow: true,
243+
wantDf1Now: true,
244+
wantDf1Pie: true,
245+
},
246+
}
247+
248+
gotDynFlag := func(flags []uint64, dynFlag uint64) bool {
249+
for _, flag := range flags {
250+
if gotFlag := dynFlag&flag != 0; gotFlag {
251+
return true
252+
}
253+
}
254+
255+
return false
256+
}
257+
258+
for _, test := range tests {
259+
t.Run(test.name, func(t *testing.T) {
260+
if test.mustInternalLink {
261+
testenv.MustInternalLink(t, test.mustHaveCGO)
262+
}
263+
if test.mustHaveCGO {
264+
testenv.MustHaveCGO(t)
265+
}
266+
if test.mustHaveBuildModePIE {
267+
testenv.MustHaveBuildMode(t, "pie")
268+
}
269+
if test.mustHaveBuildModePIE && test.mustInternalLink {
270+
testenv.MustInternalLinkPIE(t)
271+
}
272+
273+
var (
274+
dir = t.TempDir()
275+
src = filepath.Join(dir, fmt.Sprintf("elf_%s.go", test.name))
276+
binFile = filepath.Join(dir, test.name)
277+
)
278+
279+
if err := os.WriteFile(src, []byte(test.prog), 0666); err != nil {
280+
t.Fatal(err)
281+
}
282+
283+
cmdArgs := append([]string{"build", "-o", binFile}, append(test.args, src)...)
284+
cmd := testenv.Command(t, testenv.GoToolPath(t), cmdArgs...)
285+
286+
if out, err := cmd.CombinedOutput(); err != nil {
287+
t.Fatalf("failed to build %v: %v:\n%s", cmd.Args, err, out)
288+
}
289+
290+
fi, err := os.Open(binFile)
291+
if err != nil {
292+
t.Fatalf("failed to open built file: %v", err)
293+
}
294+
defer fi.Close()
295+
296+
elfFile, err := elf.NewFile(fi)
297+
if err != nil {
298+
t.Skip("The system may not support ELF, skipped.")
299+
}
300+
defer elfFile.Close()
301+
302+
flags, err := elfFile.DynValue(elf.DT_FLAGS)
303+
if err != nil {
304+
t.Fatalf("failed to get DT_FLAGS: %v", err)
305+
}
306+
307+
flags1, err := elfFile.DynValue(elf.DT_FLAGS_1)
308+
if err != nil {
309+
t.Fatalf("failed to get DT_FLAGS_1: %v", err)
310+
}
311+
312+
gotDfBindNow := gotDynFlag(flags, uint64(elf.DF_BIND_NOW))
313+
gotDf1Now := gotDynFlag(flags1, uint64(elf.DF_1_NOW))
314+
315+
bindNowFlagsMatch := gotDfBindNow == test.wantDfBindNow && gotDf1Now == test.wantDf1Now
316+
317+
// some external linkers may set one of the two flags but not both.
318+
if !test.mustInternalLink {
319+
bindNowFlagsMatch = gotDfBindNow == test.wantDfBindNow || gotDf1Now == test.wantDf1Now
320+
}
321+
322+
if !bindNowFlagsMatch {
323+
t.Fatalf("Dynamic flags mismatch:\n"+
324+
"DT_FLAGS BIND_NOW got: %v, want: %v\n"+
325+
"DT_FLAGS_1 DF_1_NOW got: %v, want: %v",
326+
gotDfBindNow, test.wantDfBindNow, gotDf1Now, test.wantDf1Now)
327+
}
328+
329+
if gotDf1Pie := gotDynFlag(flags1, uint64(elf.DF_1_PIE)); gotDf1Pie != test.wantDf1Pie {
330+
t.Fatalf("DT_FLAGS_1 DF_1_PIE got: %v, want: %v", gotDf1Pie, test.wantDf1Pie)
331+
}
332+
})
333+
}
334+
}

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

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1599,12 +1599,16 @@ func (ctxt *Link) hostlink() {
15991599
}
16001600

16011601
var altLinker string
1602-
if ctxt.IsELF && ctxt.DynlinkingGo() {
1603-
// We force all symbol resolution to be done at program startup
1602+
if ctxt.IsELF && (ctxt.DynlinkingGo() || *flagBindNow) {
1603+
// For ELF targets, when producing dynamically linked Go code
1604+
// or when immediate binding is explicitly requested,
1605+
// we force all symbol resolution to be done at program startup
16041606
// because lazy PLT resolution can use large amounts of stack at
16051607
// times we cannot allow it to do so.
16061608
argv = append(argv, "-Wl,-z,now")
1609+
}
16071610

1611+
if ctxt.IsELF && ctxt.DynlinkingGo() {
16081612
// Do not let the host linker generate COPY relocations. These
16091613
// can move symbols out of sections that rely on stable offsets
16101614
// from the beginning of the section (like sym.STYPE).

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ func init() {
6363
// Flags used by the linker. The exported flags are used by the architecture-specific packages.
6464
var (
6565
flagBuildid = flag.String("buildid", "", "record `id` as Go toolchain build id")
66+
flagBindNow = flag.Bool("bindnow", false, "mark a dynamically linked ELF object for immediate function binding")
6667

6768
flagOutfile = flag.String("o", "", "write output to `file`")
6869
flagPluginPath = flag.String("pluginpath", "", "full path name for plugin")

src/internal/testenv/testenv.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -369,6 +369,15 @@ func MustInternalLink(t testing.TB, withCgo bool) {
369369
}
370370
}
371371

372+
// MustInternalLinkPIE checks whether the current system can link PIE binary using
373+
// internal linking.
374+
// If not, MustInternalLinkPIE calls t.Skip with an explanation.
375+
func MustInternalLinkPIE(t testing.TB) {
376+
if !platform.InternalLinkPIESupported(runtime.GOOS, runtime.GOARCH) {
377+
t.Skipf("skipping test: internal linking for buildmode=pie on %s/%s is not supported", runtime.GOOS, runtime.GOARCH)
378+
}
379+
}
380+
372381
// MustHaveBuildMode reports whether the current system can build programs in
373382
// the given build mode.
374383
// If not, MustHaveBuildMode calls t.Skip with an explanation.

0 commit comments

Comments
 (0)