]> Cypherpunks repositories - gostls13.git/commitdiff
cmd/link, runtime, plugin: versioning
authorDavid Crawshaw <crawshaw@golang.org>
Sat, 12 Nov 2016 11:24:36 +0000 (06:24 -0500)
committerDavid Crawshaw <crawshaw@golang.org>
Tue, 15 Nov 2016 16:14:27 +0000 (16:14 +0000)
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 <iant@golang.org>
misc/cgo/testplugin/altpath/src/common/common.go [new file with mode: 0644]
misc/cgo/testplugin/altpath/src/plugin-mismatch/main.go [new file with mode: 0644]
misc/cgo/testplugin/src/host/host.go
misc/cgo/testplugin/test.bash
src/cmd/link/internal/ld/lib.go
src/cmd/link/internal/ld/symtab.go
src/plugin/plugin_dlopen.go
src/runtime/plugin.go
src/runtime/symtab.go

diff --git a/misc/cgo/testplugin/altpath/src/common/common.go b/misc/cgo/testplugin/altpath/src/common/common.go
new file mode 100644 (file)
index 0000000..505ba02
--- /dev/null
@@ -0,0 +1,11 @@
+// Copyright 2016 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package common
+
+var X int
+
+func init() {
+       X = 4
+}
diff --git a/misc/cgo/testplugin/altpath/src/plugin-mismatch/main.go b/misc/cgo/testplugin/altpath/src/plugin-mismatch/main.go
new file mode 100644 (file)
index 0000000..8aacafc
--- /dev/null
@@ -0,0 +1,17 @@
+// Copyright 2016 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package main
+
+// // No C code required.
+import "C"
+
+// The common package imported here does not match the common package
+// imported by plugin1. A program that attempts to load plugin1 and
+// plugin-mismatch should produce an error.
+import "common"
+
+func ReadCommonX() int {
+       return common.X
+}
index 477a9e63a284445ceee176547512baaf78c5da68..b3b4df3d5840a7d734e04ab09d06b74d4f1684af 100644 (file)
@@ -9,6 +9,7 @@ import (
        "log"
        "path/filepath"
        "plugin"
+       "strings"
 
        "common"
 )
@@ -104,5 +105,13 @@ func main() {
                log.Fatalf("after loading plugin2, common.X=%d, want %d", got, want)
        }
 
+       _, err = plugin.Open("plugin-mismatch.so")
+       if err == nil {
+               log.Fatal(`plugin.Open("plugin-mismatch.so"): should have failed`)
+       }
+       if s := err.Error(); !strings.Contains(s, "different version") {
+               log.Fatalf(`plugin.Open("plugin-mismatch.so"): error does not mention "different version": %v`, s)
+       }
+
        fmt.Println("PASS")
 }
index 322d43901da6b178123d9577292a9c474074cdfa..bba46b51c66bbb47f901243442c5414b4f94061b 100755 (executable)
@@ -24,6 +24,7 @@ mkdir sub
 
 GOPATH=$(pwd) go build -buildmode=plugin plugin1
 GOPATH=$(pwd) go build -buildmode=plugin plugin2
+GOPATH=$(pwd)/altpath go build -buildmode=plugin plugin-mismatch
 GOPATH=$(pwd) go build -buildmode=plugin -o=sub/plugin1.so sub/plugin1
 GOPATH=$(pwd) go build host
 
index 8c2d31c84126c67f25c988f5b1dbe96201a20e4a..e4c34750c771aa16782a0786779f7249e65f6d1d 100644 (file)
@@ -720,7 +720,7 @@ func objfile(ctxt *Link, lib *Library) {
                goto out
        }
 
-       if Buildmode == BuildmodeShared {
+       if Buildmode == BuildmodeShared || Buildmode == BuildmodePlugin || ctxt.Syms.ROLookup("plugin.Open", 0) != nil {
                before := f.Offset()
                pkgdefBytes := make([]byte, atolwhex(arhdr.size))
                if _, err := io.ReadFull(f, pkgdefBytes); err != nil {
index 323136c6f918d14a4f6059851590866253134981..98ce3ad79b8f4f9467e0cc4dd7e32fd0a96062bc 100644 (file)
@@ -530,6 +530,20 @@ func (ctxt *Link) symtab() {
                Addaddr(ctxt, abihashgostr, hashsym)
                adduint(ctxt, abihashgostr, uint64(hashsym.Size))
        }
+       if Buildmode == BuildmodePlugin || ctxt.Syms.ROLookup("plugin.Open", 0) != nil {
+               for _, l := range ctxt.Library {
+                       s := ctxt.Syms.Lookup("go.link.pkghashbytes."+l.Pkg, 0)
+                       s.Attr |= AttrReachable
+                       s.Type = obj.SRODATA
+                       s.Size = int64(len(l.hash))
+                       s.P = []byte(l.hash)
+                       str := ctxt.Syms.Lookup("go.link.pkghash."+l.Pkg, 0)
+                       str.Attr |= AttrReachable
+                       str.Type = obj.SRODATA
+                       Addaddr(ctxt, str, s)
+                       adduint(ctxt, str, uint64(len(l.hash)))
+               }
+       }
 
        nsections := textsectionmap(ctxt)
 
@@ -604,7 +618,28 @@ func (ctxt *Link) symtab() {
        }
        if Buildmode == BuildmodePlugin {
                addgostring(ctxt, moduledata, "go.link.thispluginpath", *flagPluginPath)
+
+               pkghashes := ctxt.Syms.Lookup("go.link.pkghashes", 0)
+               pkghashes.Attr |= AttrReachable
+               pkghashes.Attr |= AttrLocal
+               pkghashes.Type = obj.SRODATA
+
+               for i, l := range ctxt.Library {
+                       // pkghashes[i].name
+                       addgostring(ctxt, pkghashes, fmt.Sprintf("go.link.pkgname.%d", i), l.Pkg)
+                       // pkghashes[i].linktimehash
+                       addgostring(ctxt, pkghashes, fmt.Sprintf("go.link.pkglinkhash.%d", i), string(l.hash))
+                       // pkghashes[i].runtimehash
+                       hash := ctxt.Syms.ROLookup("go.link.pkghash."+l.Pkg, 0)
+                       Addaddr(ctxt, pkghashes, hash)
+               }
+               Addaddr(ctxt, moduledata, pkghashes)
+               adduint(ctxt, moduledata, uint64(len(ctxt.Library)))
+               adduint(ctxt, moduledata, uint64(len(ctxt.Library)))
        } else {
+               adduint(ctxt, moduledata, 0) // pluginpath
+               adduint(ctxt, moduledata, 0)
+               adduint(ctxt, moduledata, 0) // pkghashes slice
                adduint(ctxt, moduledata, 0)
                adduint(ctxt, moduledata, 0)
        }
index f4addde74ce453107b3c47ee8b812c7ba4d579a5..c5b0a4721c5a02a08fbf6aa99d7cb2973846ff4d 100644 (file)
@@ -69,7 +69,11 @@ func open(name string) (*Plugin, error) {
                name = name[:len(name)-3]
        }
 
-       pluginpath, syms := lastmoduleinit()
+       pluginpath, syms, mismatchpkg := lastmoduleinit()
+       if mismatchpkg != "" {
+               pluginsMu.Unlock()
+               return nil, errors.New("plugin.Open: plugin was built with a different version of package " + mismatchpkg)
+       }
        if plugins == nil {
                plugins = make(map[string]*Plugin)
        }
@@ -131,4 +135,4 @@ var (
 )
 
 // lastmoduleinit is defined in package runtime
-func lastmoduleinit() (pluginpath string, syms map[string]interface{})
+func lastmoduleinit() (pluginpath string, syms map[string]interface{}, mismatchpkg string)
index 7907936e1447cf8dae5dc80516141df95fe232b5..845bf76e92c7fb448952f449fb9e97a35ebe16b0 100644 (file)
@@ -7,7 +7,7 @@ package runtime
 import "unsafe"
 
 //go:linkname plugin_lastmoduleinit plugin.lastmoduleinit
-func plugin_lastmoduleinit() (path string, syms map[string]interface{}) {
+func plugin_lastmoduleinit() (path string, syms map[string]interface{}, mismatchpkg string) {
        md := firstmoduledata.next
        if md == nil {
                throw("runtime: no plugin module data")
@@ -41,6 +41,11 @@ func plugin_lastmoduleinit() (path string, syms map[string]interface{}) {
                        throw("plugin: new module data overlaps with previous moduledata")
                }
        }
+       for _, pkghash := range md.pkghashes {
+               if pkghash.linktimehash != *pkghash.runtimehash {
+                       return "", nil, pkghash.modulename
+               }
+       }
 
        // Initialize the freshly loaded module.
        modulesinit()
@@ -74,7 +79,7 @@ func plugin_lastmoduleinit() (path string, syms map[string]interface{}) {
                }
                syms[name] = val
        }
-       return md.pluginpath, syms
+       return md.pluginpath, syms, ""
 }
 
 // inRange reports whether v0 or v1 are in the range [r0, r1].
index bba3ccfc2069c7ca2f23b0870fae7157aadb8230..686af08ef0384f87737639ca6e61b848295f7a1f 100644 (file)
@@ -202,7 +202,9 @@ type moduledata struct {
 
        ptab []ptabEntry
 
-       pluginpath   string
+       pluginpath string
+       pkghashes  []modulehash
+
        modulename   string
        modulehashes []modulehash
 
@@ -213,10 +215,18 @@ type moduledata struct {
        next *moduledata
 }
 
+// A modulehash is used to compare the ABI of a new module or a
+// package in a new module with the loaded program.
+//
 // For each shared library a module links against, the linker creates an entry in the
 // moduledata.modulehashes slice containing the name of the module, the abi hash seen
 // at link time and a pointer to the runtime abi hash. These are checked in
 // moduledataverify1 below.
+//
+// For each loaded plugin, the the pkghashes slice has a modulehash of the
+// newly loaded package that can be used to check the plugin's version of
+// a package against any previously loaded version of the package.
+// This is done in plugin.lastmoduleinit.
 type modulehash struct {
        modulename   string
        linktimehash string