Skip to content

Commit

Permalink
cmd/link, runtime, plugin: versioning
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
crawshaw committed Nov 15, 2016
1 parent a145890 commit 03da269
Show file tree
Hide file tree
Showing 9 changed files with 98 additions and 6 deletions.
11 changes: 11 additions & 0 deletions misc/cgo/testplugin/altpath/src/common/common.go
@@ -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
}
17 changes: 17 additions & 0 deletions misc/cgo/testplugin/altpath/src/plugin-mismatch/main.go
@@ -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
}
9 changes: 9 additions & 0 deletions misc/cgo/testplugin/src/host/host.go
Expand Up @@ -9,6 +9,7 @@ import (
"log"
"path/filepath"
"plugin"
"strings"

"common"
)
Expand Down Expand Up @@ -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")
}
1 change: 1 addition & 0 deletions misc/cgo/testplugin/test.bash
Expand Up @@ -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

Expand Down
2 changes: 1 addition & 1 deletion src/cmd/link/internal/ld/lib.go
Expand Up @@ -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 {
Expand Down
35 changes: 35 additions & 0 deletions src/cmd/link/internal/ld/symtab.go
Expand Up @@ -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)

Expand Down Expand Up @@ -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)
}
Expand Down
8 changes: 6 additions & 2 deletions src/plugin/plugin_dlopen.go
Expand Up @@ -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)
}
Expand Down Expand Up @@ -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)
9 changes: 7 additions & 2 deletions src/runtime/plugin.go
Expand Up @@ -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")
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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].
Expand Down
12 changes: 11 additions & 1 deletion src/runtime/symtab.go
Expand Up @@ -202,7 +202,9 @@ type moduledata struct {

ptab []ptabEntry

pluginpath string
pluginpath string
pkghashes []modulehash

modulename string
modulehashes []modulehash

Expand All @@ -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
Expand Down

0 comments on commit 03da269

Please sign in to comment.