Skip to content

Commit 03da269

Browse files
committed
cmd/link, runtime, plugin: versioning
In plugins and every program that opens a plugin, include a hash of every imported package. There are two versions of each hash: one local and one exported. As the program starts and plugins are loaded, the first exported symbol for each package becomes the canonical version. Any subsequent plugin's local package hash symbol has to match the canonical version. Fixes #17832 Change-Id: I4e62c8e1729d322e14b1673bada40fa7a74ea8bc Reviewed-on: https://go-review.googlesource.com/33161 Reviewed-by: Ian Lance Taylor <[email protected]>
1 parent a145890 commit 03da269

File tree

9 files changed

+98
-6
lines changed

9 files changed

+98
-6
lines changed
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// Copyright 2016 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package common
6+
7+
var X int
8+
9+
func init() {
10+
X = 4
11+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// Copyright 2016 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package main
6+
7+
// // No C code required.
8+
import "C"
9+
10+
// The common package imported here does not match the common package
11+
// imported by plugin1. A program that attempts to load plugin1 and
12+
// plugin-mismatch should produce an error.
13+
import "common"
14+
15+
func ReadCommonX() int {
16+
return common.X
17+
}

misc/cgo/testplugin/src/host/host.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"log"
1010
"path/filepath"
1111
"plugin"
12+
"strings"
1213

1314
"common"
1415
)
@@ -104,5 +105,13 @@ func main() {
104105
log.Fatalf("after loading plugin2, common.X=%d, want %d", got, want)
105106
}
106107

108+
_, err = plugin.Open("plugin-mismatch.so")
109+
if err == nil {
110+
log.Fatal(`plugin.Open("plugin-mismatch.so"): should have failed`)
111+
}
112+
if s := err.Error(); !strings.Contains(s, "different version") {
113+
log.Fatalf(`plugin.Open("plugin-mismatch.so"): error does not mention "different version": %v`, s)
114+
}
115+
107116
fmt.Println("PASS")
108117
}

misc/cgo/testplugin/test.bash

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ mkdir sub
2424

2525
GOPATH=$(pwd) go build -buildmode=plugin plugin1
2626
GOPATH=$(pwd) go build -buildmode=plugin plugin2
27+
GOPATH=$(pwd)/altpath go build -buildmode=plugin plugin-mismatch
2728
GOPATH=$(pwd) go build -buildmode=plugin -o=sub/plugin1.so sub/plugin1
2829
GOPATH=$(pwd) go build host
2930

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -720,7 +720,7 @@ func objfile(ctxt *Link, lib *Library) {
720720
goto out
721721
}
722722

723-
if Buildmode == BuildmodeShared {
723+
if Buildmode == BuildmodeShared || Buildmode == BuildmodePlugin || ctxt.Syms.ROLookup("plugin.Open", 0) != nil {
724724
before := f.Offset()
725725
pkgdefBytes := make([]byte, atolwhex(arhdr.size))
726726
if _, err := io.ReadFull(f, pkgdefBytes); err != nil {

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

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -530,6 +530,20 @@ func (ctxt *Link) symtab() {
530530
Addaddr(ctxt, abihashgostr, hashsym)
531531
adduint(ctxt, abihashgostr, uint64(hashsym.Size))
532532
}
533+
if Buildmode == BuildmodePlugin || ctxt.Syms.ROLookup("plugin.Open", 0) != nil {
534+
for _, l := range ctxt.Library {
535+
s := ctxt.Syms.Lookup("go.link.pkghashbytes."+l.Pkg, 0)
536+
s.Attr |= AttrReachable
537+
s.Type = obj.SRODATA
538+
s.Size = int64(len(l.hash))
539+
s.P = []byte(l.hash)
540+
str := ctxt.Syms.Lookup("go.link.pkghash."+l.Pkg, 0)
541+
str.Attr |= AttrReachable
542+
str.Type = obj.SRODATA
543+
Addaddr(ctxt, str, s)
544+
adduint(ctxt, str, uint64(len(l.hash)))
545+
}
546+
}
533547

534548
nsections := textsectionmap(ctxt)
535549

@@ -604,7 +618,28 @@ func (ctxt *Link) symtab() {
604618
}
605619
if Buildmode == BuildmodePlugin {
606620
addgostring(ctxt, moduledata, "go.link.thispluginpath", *flagPluginPath)
621+
622+
pkghashes := ctxt.Syms.Lookup("go.link.pkghashes", 0)
623+
pkghashes.Attr |= AttrReachable
624+
pkghashes.Attr |= AttrLocal
625+
pkghashes.Type = obj.SRODATA
626+
627+
for i, l := range ctxt.Library {
628+
// pkghashes[i].name
629+
addgostring(ctxt, pkghashes, fmt.Sprintf("go.link.pkgname.%d", i), l.Pkg)
630+
// pkghashes[i].linktimehash
631+
addgostring(ctxt, pkghashes, fmt.Sprintf("go.link.pkglinkhash.%d", i), string(l.hash))
632+
// pkghashes[i].runtimehash
633+
hash := ctxt.Syms.ROLookup("go.link.pkghash."+l.Pkg, 0)
634+
Addaddr(ctxt, pkghashes, hash)
635+
}
636+
Addaddr(ctxt, moduledata, pkghashes)
637+
adduint(ctxt, moduledata, uint64(len(ctxt.Library)))
638+
adduint(ctxt, moduledata, uint64(len(ctxt.Library)))
607639
} else {
640+
adduint(ctxt, moduledata, 0) // pluginpath
641+
adduint(ctxt, moduledata, 0)
642+
adduint(ctxt, moduledata, 0) // pkghashes slice
608643
adduint(ctxt, moduledata, 0)
609644
adduint(ctxt, moduledata, 0)
610645
}

src/plugin/plugin_dlopen.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,11 @@ func open(name string) (*Plugin, error) {
6969
name = name[:len(name)-3]
7070
}
7171

72-
pluginpath, syms := lastmoduleinit()
72+
pluginpath, syms, mismatchpkg := lastmoduleinit()
73+
if mismatchpkg != "" {
74+
pluginsMu.Unlock()
75+
return nil, errors.New("plugin.Open: plugin was built with a different version of package " + mismatchpkg)
76+
}
7377
if plugins == nil {
7478
plugins = make(map[string]*Plugin)
7579
}
@@ -131,4 +135,4 @@ var (
131135
)
132136

133137
// lastmoduleinit is defined in package runtime
134-
func lastmoduleinit() (pluginpath string, syms map[string]interface{})
138+
func lastmoduleinit() (pluginpath string, syms map[string]interface{}, mismatchpkg string)

src/runtime/plugin.go

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ package runtime
77
import "unsafe"
88

99
//go:linkname plugin_lastmoduleinit plugin.lastmoduleinit
10-
func plugin_lastmoduleinit() (path string, syms map[string]interface{}) {
10+
func plugin_lastmoduleinit() (path string, syms map[string]interface{}, mismatchpkg string) {
1111
md := firstmoduledata.next
1212
if md == nil {
1313
throw("runtime: no plugin module data")
@@ -41,6 +41,11 @@ func plugin_lastmoduleinit() (path string, syms map[string]interface{}) {
4141
throw("plugin: new module data overlaps with previous moduledata")
4242
}
4343
}
44+
for _, pkghash := range md.pkghashes {
45+
if pkghash.linktimehash != *pkghash.runtimehash {
46+
return "", nil, pkghash.modulename
47+
}
48+
}
4449

4550
// Initialize the freshly loaded module.
4651
modulesinit()
@@ -74,7 +79,7 @@ func plugin_lastmoduleinit() (path string, syms map[string]interface{}) {
7479
}
7580
syms[name] = val
7681
}
77-
return md.pluginpath, syms
82+
return md.pluginpath, syms, ""
7883
}
7984

8085
// inRange reports whether v0 or v1 are in the range [r0, r1].

src/runtime/symtab.go

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,9 @@ type moduledata struct {
202202

203203
ptab []ptabEntry
204204

205-
pluginpath string
205+
pluginpath string
206+
pkghashes []modulehash
207+
206208
modulename string
207209
modulehashes []modulehash
208210

@@ -213,10 +215,18 @@ type moduledata struct {
213215
next *moduledata
214216
}
215217

218+
// A modulehash is used to compare the ABI of a new module or a
219+
// package in a new module with the loaded program.
220+
//
216221
// For each shared library a module links against, the linker creates an entry in the
217222
// moduledata.modulehashes slice containing the name of the module, the abi hash seen
218223
// at link time and a pointer to the runtime abi hash. These are checked in
219224
// moduledataverify1 below.
225+
//
226+
// For each loaded plugin, the the pkghashes slice has a modulehash of the
227+
// newly loaded package that can be used to check the plugin's version of
228+
// a package against any previously loaded version of the package.
229+
// This is done in plugin.lastmoduleinit.
220230
type modulehash struct {
221231
modulename string
222232
linktimehash string

0 commit comments

Comments
 (0)