cmd/internal/ld, runtime: abort on shared library ABI mismatch
This:
1) Defines the ABI hash of a package (as the SHA1 of the __.PKGDEF)
2) Defines the ABI hash of a shared library (sort the packages by import
path, concatenate the hashes of the packages and SHA1 that)
3) When building a shared library, compute the above value and define a
global symbol that points to a go string that has the hash as its value.
4) When linking against a shared library, read the abi hash from the
library and put both the value seen at link time and a reference
to the global symbol into the moduledata.
5) During runtime initialization, check that the hash seen at link time
still matches the hash the global symbol points to.
Change-Id: Iaa54c783790e6dde3057a2feadc35473d49614a5
Reviewed-on: https://go-review.googlesource.com/8773
Reviewed-by: Ian Lance Taylor <iant@golang.org>
Run-TryBot: Michael Hudson-Doyle <michael.hudson@canonical.com>
diff --git a/src/cmd/internal/ld/data.go b/src/cmd/internal/ld/data.go
index 37d4588..b015754 100644
--- a/src/cmd/internal/ld/data.go
+++ b/src/cmd/internal/ld/data.go
@@ -963,6 +963,22 @@
return int64(r)
}
+// addgostring adds str, as a Go string value, to s. symname is the name of the
+// symbol used to define the string data and must be unique per linked object.
+func addgostring(s *LSym, symname, str string) {
+ sym := Linklookup(Ctxt, symname, 0)
+ if sym.Type != obj.Sxxx {
+ Diag("duplicate symname in addgostring: %s", symname)
+ }
+ sym.Reachable = true
+ sym.Local = true
+ sym.Type = obj.SRODATA
+ sym.Size = int64(len(str))
+ sym.P = []byte(str)
+ Addaddr(Ctxt, s, sym)
+ adduint(Ctxt, s, uint64(len(str)))
+}
+
func addinitarrdata(s *LSym) {
p := s.Name + ".ptr"
sp := Linklookup(Ctxt, p, 0)
diff --git a/src/cmd/internal/ld/ld.go b/src/cmd/internal/ld/ld.go
index 7242301..1068bdd 100644
--- a/src/cmd/internal/ld/ld.go
+++ b/src/cmd/internal/ld/ld.go
@@ -109,8 +109,8 @@
fmt.Fprintf(ctxt.Bso, "%5.2f addlibpath: srcref: %s objref: %s file: %s pkg: %s shlibnamefile: %s\n", obj.Cputime(), srcref, objref, file, pkg, shlibnamefile)
}
- ctxt.Library = append(ctxt.Library, Library{})
- l := &ctxt.Library[len(ctxt.Library)-1]
+ ctxt.Library = append(ctxt.Library, &Library{})
+ l := ctxt.Library[len(ctxt.Library)-1]
l.Objref = objref
l.Srcref = srcref
l.File = file
diff --git a/src/cmd/internal/ld/lib.go b/src/cmd/internal/ld/lib.go
index e4e68ea..d4e6780 100644
--- a/src/cmd/internal/ld/lib.go
+++ b/src/cmd/internal/ld/lib.go
@@ -34,6 +34,7 @@
"bufio"
"bytes"
"cmd/internal/obj"
+ "crypto/sha1"
"debug/elf"
"fmt"
"io"
@@ -474,7 +475,7 @@
if Ctxt.Library[i].Shlib != "" {
ldshlibsyms(Ctxt.Library[i].Shlib)
} else {
- objfile(Ctxt.Library[i].File, Ctxt.Library[i].Pkg)
+ objfile(Ctxt.Library[i])
}
}
@@ -520,7 +521,7 @@
if DynlinkingGo() {
Exitf("cannot implicitly include runtime/cgo in a shared library")
}
- objfile(Ctxt.Library[i].File, Ctxt.Library[i].Pkg)
+ objfile(Ctxt.Library[i])
}
}
}
@@ -631,18 +632,18 @@
return int64(arsize) + SAR_HDR
}
-func objfile(file string, pkg string) {
- pkg = pathtoprefix(pkg)
+func objfile(lib *Library) {
+ pkg := pathtoprefix(lib.Pkg)
if Debug['v'] > 1 {
- fmt.Fprintf(&Bso, "%5.2f ldobj: %s (%s)\n", obj.Cputime(), file, pkg)
+ fmt.Fprintf(&Bso, "%5.2f ldobj: %s (%s)\n", obj.Cputime(), lib.File, pkg)
}
Bso.Flush()
var err error
var f *obj.Biobuf
- f, err = obj.Bopenr(file)
+ f, err = obj.Bopenr(lib.File)
if err != nil {
- Exitf("cannot open file %s: %v", file, err)
+ Exitf("cannot open file %s: %v", lib.File, err)
}
magbuf := make([]byte, len(ARMAG))
@@ -651,7 +652,7 @@
l := obj.Bseek(f, 0, 2)
obj.Bseek(f, 0, 0)
- ldobj(f, pkg, l, file, file, FileObj)
+ ldobj(f, pkg, l, lib.File, lib.File, FileObj)
obj.Bterm(f)
return
@@ -664,7 +665,7 @@
l := nextar(f, off, &arhdr)
var pname string
if l <= 0 {
- Diag("%s: short read on archive file symbol header", file)
+ Diag("%s: short read on archive file symbol header", lib.File)
goto out
}
@@ -672,20 +673,29 @@
off += l
l = nextar(f, off, &arhdr)
if l <= 0 {
- Diag("%s: short read on archive file symbol header", file)
+ Diag("%s: short read on archive file symbol header", lib.File)
goto out
}
}
if !strings.HasPrefix(arhdr.name, pkgname) {
- Diag("%s: cannot find package header", file)
+ Diag("%s: cannot find package header", lib.File)
goto out
}
+ if Buildmode == BuildmodeShared {
+ before := obj.Boffset(f)
+ pkgdefBytes := make([]byte, atolwhex(arhdr.size))
+ obj.Bread(f, pkgdefBytes)
+ hash := sha1.Sum(pkgdefBytes)
+ lib.hash = hash[:]
+ obj.Bseek(f, before, 0)
+ }
+
off += l
if Debug['u'] != 0 {
- ldpkg(f, pkg, atolwhex(arhdr.size), file, Pkgdef)
+ ldpkg(f, pkg, atolwhex(arhdr.size), lib.File, Pkgdef)
}
/*
@@ -706,14 +716,14 @@
break
}
if l < 0 {
- Exitf("%s: malformed archive", file)
+ Exitf("%s: malformed archive", lib.File)
}
off += l
- pname = fmt.Sprintf("%s(%s)", file, arhdr.name)
+ pname = fmt.Sprintf("%s(%s)", lib.File, arhdr.name)
l = atolwhex(arhdr.size)
- ldobj(f, pkg, l, pname, file, ArchiveObj)
+ ldobj(f, pkg, l, pname, lib.File, ArchiveObj)
}
out:
@@ -974,7 +984,7 @@
if Linkshared {
for _, shlib := range Ctxt.Shlibs {
- dir, base := filepath.Split(shlib)
+ dir, base := filepath.Split(shlib.Path)
argv = append(argv, "-L"+dir)
if !rpath.set {
argv = append(argv, "-Wl,-rpath="+dir)
@@ -1120,6 +1130,19 @@
ldobjfile(Ctxt, f, pkg, eof-obj.Boffset(f), pn)
}
+func readelfsymboldata(f *elf.File, sym *elf.Symbol) []byte {
+ data := make([]byte, sym.Size)
+ sect := f.Sections[sym.Section]
+ if sect.Type != elf.SHT_PROGBITS {
+ Diag("reading %s from non-PROGBITS section", sym.Name)
+ }
+ n, err := sect.ReadAt(data, int64(sym.Value-sect.Offset))
+ if uint64(n) != sym.Size {
+ Diag("reading contents of %s: %v", sym.Name, err)
+ }
+ return data
+}
+
func ldshlibsyms(shlib string) {
found := false
libpath := ""
@@ -1134,8 +1157,8 @@
Diag("cannot find shared library: %s", shlib)
return
}
- for _, processedname := range Ctxt.Shlibs {
- if processedname == libpath {
+ for _, processedlib := range Ctxt.Shlibs {
+ if processedlib.Path == libpath {
return
}
}
@@ -1167,6 +1190,7 @@
// table removed.
gcmasks := make(map[uint64][]byte)
types := []*LSym{}
+ var hash []byte
for _, s := range syms {
if elf.ST_TYPE(s.Info) == elf.STT_NOTYPE || elf.ST_TYPE(s.Info) == elf.STT_SECTION {
continue
@@ -1178,15 +1202,10 @@
continue
}
if strings.HasPrefix(s.Name, "runtime.gcbits.0x") {
- data := make([]byte, s.Size)
- sect := f.Sections[s.Section]
- if sect.Type == elf.SHT_PROGBITS {
- n, err := sect.ReadAt(data, int64(s.Value-sect.Offset))
- if uint64(n) != s.Size {
- Diag("Error reading contents of %s: %v", s.Name, err)
- }
- }
- gcmasks[s.Value] = data
+ gcmasks[s.Value] = readelfsymboldata(f, &s)
+ }
+ if s.Name == "go.link.abihashbytes" {
+ hash = readelfsymboldata(f, &s)
}
if elf.ST_BIND(s.Info) != elf.STB_GLOBAL {
continue
@@ -1201,14 +1220,8 @@
lsym.ElfType = elf.ST_TYPE(s.Info)
lsym.File = libpath
if strings.HasPrefix(lsym.Name, "type.") {
- data := make([]byte, s.Size)
- sect := f.Sections[s.Section]
- if sect.Type == elf.SHT_PROGBITS {
- n, err := sect.ReadAt(data, int64(s.Value-sect.Offset))
- if uint64(n) != s.Size {
- Diag("Error reading contents of %s: %v", s.Name, err)
- }
- lsym.P = data
+ if f.Sections[s.Section].Type == elf.SHT_PROGBITS {
+ lsym.P = readelfsymboldata(f, &s)
}
if !strings.HasPrefix(lsym.Name, "type..") {
types = append(types, lsym)
@@ -1255,7 +1268,7 @@
Ctxt.Etextp = last
}
- Ctxt.Shlibs = append(Ctxt.Shlibs, libpath)
+ Ctxt.Shlibs = append(Ctxt.Shlibs, Shlib{Path: libpath, Hash: hash})
}
func mywhatsys() {
diff --git a/src/cmd/internal/ld/link.go b/src/cmd/internal/ld/link.go
index 03da52a..a314ca13 100644
--- a/src/cmd/internal/ld/link.go
+++ b/src/cmd/internal/ld/link.go
@@ -106,6 +106,11 @@
Gotype *LSym
}
+type Shlib struct {
+ Path string
+ Hash []byte
+}
+
type Link struct {
Thechar int32
Thestring string
@@ -122,8 +127,8 @@
Nsymbol int32
Tlsg *LSym
Libdir []string
- Library []Library
- Shlibs []string
+ Library []*Library
+ Shlibs []Shlib
Tlsoffset int
Diag func(string, ...interface{})
Cursym *LSym
@@ -149,6 +154,7 @@
File string
Pkg string
Shlib string
+ hash []byte
}
type Pcln struct {
diff --git a/src/cmd/internal/ld/symtab.go b/src/cmd/internal/ld/symtab.go
index d6e79dc..ca66541 100644
--- a/src/cmd/internal/ld/symtab.go
+++ b/src/cmd/internal/ld/symtab.go
@@ -32,6 +32,10 @@
import (
"cmd/internal/obj"
+ "crypto/sha1"
+ "fmt"
+ "path/filepath"
+ "sort"
"strings"
)
@@ -294,6 +298,20 @@
Lputl(uint32(v >> 32))
}
+type byPkg []*Library
+
+func (libs byPkg) Len() int {
+ return len(libs)
+}
+
+func (libs byPkg) Less(a, b int) bool {
+ return libs[a].Pkg < libs[b].Pkg
+}
+
+func (libs byPkg) Swap(a, b int) {
+ libs[a], libs[b] = libs[b], libs[a]
+}
+
func symtab() {
dosymtype()
@@ -410,6 +428,19 @@
}
}
+ if Buildmode == BuildmodeShared {
+ sort.Sort(byPkg(Ctxt.Library))
+ h := sha1.New()
+ for _, l := range Ctxt.Library {
+ h.Write(l.hash)
+ }
+ abihashgostr := Linklookup(Ctxt, "go.link.abihash."+filepath.Base(outfile), 0)
+ abihashgostr.Reachable = true
+ abihashgostr.Type = obj.SRODATA
+ var hashbytes []byte
+ addgostring(abihashgostr, "go.link.abihashbytes", string(h.Sum(hashbytes)))
+ }
+
// Information about the layout of the executable image for the
// runtime to use. Any changes here must be matched by changes to
// the definition of moduledata in runtime/symtab.go.
@@ -454,6 +485,38 @@
Addaddr(Ctxt, moduledata, Linklookup(Ctxt, "runtime.typelink", 0))
adduint(Ctxt, moduledata, uint64(ntypelinks))
adduint(Ctxt, moduledata, uint64(ntypelinks))
+ if len(Ctxt.Shlibs) > 0 {
+ thismodulename := filepath.Base(outfile)
+ if Buildmode == BuildmodeExe {
+ // When linking an executable, outfile is just "a.out". Make
+ // it something slightly more comprehensible.
+ thismodulename = "the executable"
+ }
+ addgostring(moduledata, "go.link.thismodulename", thismodulename)
+
+ modulehashes := Linklookup(Ctxt, "go.link.abihashes", 0)
+ modulehashes.Reachable = true
+ modulehashes.Local = true
+ modulehashes.Type = obj.SRODATA
+
+ for i, shlib := range Ctxt.Shlibs {
+ // modulehashes[i].modulename
+ modulename := filepath.Base(shlib.Path)
+ addgostring(modulehashes, fmt.Sprintf("go.link.libname.%d", i), modulename)
+
+ // modulehashes[i].linktimehash
+ addgostring(modulehashes, fmt.Sprintf("go.link.linkhash.%d", i), string(shlib.Hash))
+
+ // modulehashes[i].runtimehash
+ abihash := Linklookup(Ctxt, "go.link.abihash."+modulename, 0)
+ abihash.Reachable = true
+ Addaddr(Ctxt, modulehashes, abihash)
+ }
+
+ Addaddr(Ctxt, moduledata, modulehashes)
+ adduint(Ctxt, moduledata, uint64(len(Ctxt.Shlibs)))
+ adduint(Ctxt, moduledata, uint64(len(Ctxt.Shlibs)))
+ }
// The rest of moduledata is zero initialized.
// When linking an object that does not contain the runtime we are
// creating the moduledata from scratch and it does not have a