1
0
mirror of https://github.com/golang/go synced 2024-11-15 02:20:32 -07:00

cmd/compile, cmd/link: disallow linkname of some newly added internal functions

Go API is defined through exported symbols. When a package is
imported, the compiler ensures that only exported symbols can be
accessed, and the go command ensures that internal packages cannot
be imported. This ensures API integrity. But there is a hole:
using linkname, one can access internal or non-exported symbols.
Linkname is a mechanism to give access of a symbol to a package
without adding it to the public API. It is intended for coupled
packages to share some implementation details, or to break
circular dependencies, and both "push" (definition) and "pull"
(reference) sides are controlled, so they can be updated in sync.
Nevertheless, it is abused as a mechanism to reach into internal
details of other packages uncontrolled by the user, notably the
runtime. As the other package evolves, the code often breaks,
because the linknamed symbol may no longer exist, or change its
signature or semantics.

This CL adds a mechanism to enforce the integrity of linknames.
Generally, "push" linkname is allowed, as the package defining
the symbol explicitly opt in for access outside of the package.
"Pull" linkname is checked and only allowed in some circumstances.
Given that there are existing code that use "pull"-only linkname
to access other package's internals, disallowing it completely is
too much a change at this point in the release cycle. For a start,
implement a hard-coded blocklist, which contains some newly added
internal functions that, if used inappropriately, may break memory
safety or runtime integrity. All blocked symbols are newly added
in Go 1.23. So existing code that builds with Go 1.22 will
continue to build.

For the implementation, when compiling a package, we mark
linknamed symbols in the current package with an attribute. At
link time, marked linknamed symbols are checked against the
blocklist. Care is taken so it distinguishes a linkname reference
in the current package vs. a reference of a linkname from another
package and propagated to the current package (e.g. through
inlining or instantiation).

Symbol references in assembly code are similar to linknames, and
are treated similarly.

Change-Id: I8067efe29c122740cd4f1effd2dec2d839147d5d
Reviewed-on: https://go-review.googlesource.com/c/go/+/584598
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Than McIntosh <thanm@google.com>
Reviewed-by: Cuong Manh Le <cuong.manhle.vn@gmail.com>
This commit is contained in:
Cherry Mui 2024-05-09 17:07:43 -04:00
parent df4f40b9e0
commit 4721f95058
14 changed files with 284 additions and 2 deletions

View File

@ -1176,7 +1176,18 @@ func (r *reader) linkname(name *ir.Name) {
lsym.SymIdx = int32(idx)
lsym.Set(obj.AttrIndexed, true)
} else {
name.Sym().Linkname = r.String()
linkname := r.String()
sym := name.Sym()
sym.Linkname = linkname
if sym.Pkg == types.LocalPkg && linkname != "" {
// Mark linkname in the current package. We don't mark the
// ones that are imported and propagated (e.g. through
// inlining or instantiation, which are marked in their
// corresponding packages). So we can tell in which package
// the linkname is used (pulled), and the linker can
// make a decision for allowing or disallowing it.
sym.Linksym().Set(obj.AttrLinkname, true)
}
}
}

View File

@ -303,6 +303,7 @@ const (
SymFlagItab
SymFlagDict
SymFlagPkgInit
SymFlagLinkname
)
// Returns the length of the name of the symbol.
@ -334,6 +335,7 @@ func (s *Sym) UsedInIface() bool { return s.Flag2()&SymFlagUsedInIface != 0 }
func (s *Sym) IsItab() bool { return s.Flag2()&SymFlagItab != 0 }
func (s *Sym) IsDict() bool { return s.Flag2()&SymFlagDict != 0 }
func (s *Sym) IsPkgInit() bool { return s.Flag2()&SymFlagPkgInit != 0 }
func (s *Sym) IsLinkname() bool { return s.Flag2()&SymFlagLinkname != 0 }
func (s *Sym) SetName(x string, w *Writer) {
binary.LittleEndian.PutUint32(s[:], uint32(len(x)))

View File

@ -836,6 +836,9 @@ const (
// PkgInit indicates this is a compiler-generated package init func.
AttrPkgInit
// Linkname indicates this is a go:linkname'd symbol.
AttrLinkname
// attrABIBase is the value at which the ABI is encoded in
// Attribute. This must be last; all bits after this are
// assumed to be an ABI value.
@ -865,6 +868,7 @@ func (a *Attribute) ContentAddressable() bool { return a.load()&AttrContentAddre
func (a *Attribute) ABIWrapper() bool { return a.load()&AttrABIWrapper != 0 }
func (a *Attribute) IsPcdata() bool { return a.load()&AttrPcdata != 0 }
func (a *Attribute) IsPkgInit() bool { return a.load()&AttrPkgInit != 0 }
func (a *Attribute) IsLinkname() bool { return a.load()&AttrLinkname != 0 }
func (a *Attribute) Set(flag Attribute, value bool) {
for {
@ -914,6 +918,7 @@ var textAttrStrings = [...]struct {
{bit: AttrContentAddressable, s: ""},
{bit: AttrABIWrapper, s: "ABIWRAPPER"},
{bit: AttrPkgInit, s: "PKGINIT"},
{bit: AttrLinkname, s: "LINKNAME"},
}
// String formats a for printing in as part of a TEXT prog.

View File

@ -348,6 +348,9 @@ func (w *writer) Sym(s *LSym) {
if s.IsPkgInit() {
flag2 |= goobj.SymFlagPkgInit
}
if s.IsLinkname() || w.ctxt.IsAsm { // assembly reference is treated the same as linkname
flag2 |= goobj.SymFlagLinkname
}
name := s.Name
if strings.HasPrefix(name, "gofile..") {
name = filepath.ToSlash(name)

View File

@ -420,7 +420,16 @@ func (st *loadState) addSym(name string, ver int, r *oReader, li uint32, kind in
return i
}
// Non-package (named) symbol. Check if it already exists.
// Non-package (named) symbol.
if osym.IsLinkname() && r.DataSize(li) == 0 {
// This is a linknamed "var" "reference" (var x T with no data and //go:linkname x).
// Check if a linkname reference is allowed.
// Only check references (pull), not definitions (push, with non-zero size),
// so push is always allowed.
// Linkname is always a non-package reference.
checkLinkname(r.unit.Lib.Pkg, name)
}
// Check if it already exists.
oldi, existed := l.symsByName[ver][name]
if !existed {
l.symsByName[ver][name] = i
@ -2243,6 +2252,13 @@ func loadObjRefs(l *Loader, r *oReader, arch *sys.Arch) {
osym := r.Sym(ndef + i)
name := osym.Name(r.Reader)
v := abiToVer(osym.ABI(), r.version)
if osym.IsLinkname() {
// Check if a linkname reference is allowed.
// Only check references (pull), not definitions (push),
// so push is always allowed.
// Linkname is always a non-package reference.
checkLinkname(r.unit.Lib.Pkg, name)
}
r.syms[ndef+i] = l.LookupOrCreateSym(name, v)
gi := r.syms[ndef+i]
if osym.Local() {
@ -2289,6 +2305,49 @@ func abiToVer(abi uint16, localSymVersion int) int {
return v
}
// A list of blocked linknames. Some linknames are allowed only
// in specific packages. This maps symbol names to allowed packages.
// If a name is not in this map, and not with a blocked prefix (see
// blockedLinknamePrefixes), it is allowed everywhere.
// If a name is in this map, it is allowed only in listed packages.
var blockedLinknames = map[string][]string{
// coroutines
"runtime.coroexit": nil,
"runtime.corostart": nil,
"runtime.coroswitch": {"iter"},
"runtime.newcoro": {"iter"},
// weak references
"internal/weak.runtime_registerWeakPointer": {"internal/weak"},
"internal/weak.runtime_makeStrongFromWeak": {"internal/weak"},
"runtime.getOrAddWeakHandle": nil,
}
// A list of blocked linkname prefixes (packages).
var blockedLinknamePrefixes = []string{
"internal/weak.",
"internal/concurrent.",
}
func checkLinkname(pkg, name string) {
error := func() {
log.Fatalf("linkname or assembly reference of %s is not allowed in package %s", name, pkg)
}
pkgs, ok := blockedLinknames[name]
if ok {
for _, p := range pkgs {
if pkg == p {
return // pkg is allowed
}
}
error()
}
for _, p := range blockedLinknamePrefixes {
if strings.HasPrefix(name, p) {
error()
}
}
}
// TopLevelSym tests a symbol (by name and kind) to determine whether
// the symbol first class sym (participating in the link) or is an
// anonymous aux or sub-symbol containing some sub-part or payload of

View File

@ -1415,3 +1415,43 @@ func TestRandLayout(t *testing.T) {
t.Errorf("randlayout with different seeds produced same layout:\n%s\n===\n\n%s", syms[0], syms[1])
}
}
func TestBlockedLinkname(t *testing.T) {
// Test that code containing blocked linknames does not build.
testenv.MustHaveGoBuild(t)
t.Parallel()
tmpdir := t.TempDir()
tests := []struct {
src string
ok bool
}{
// use (instantiation) of public API is ok
{"ok.go", true},
// push linkname is ok
{"push.go", true},
// pull linkname of blocked symbol is not ok
{"coro.go", false},
{"weak.go", false},
{"coro_var.go", false},
// assembly reference is not ok
{"coro_asm", false},
}
for _, test := range tests {
test := test
t.Run(test.src, func(t *testing.T) {
t.Parallel()
src := filepath.Join("testdata", "linkname", test.src)
exe := filepath.Join(tmpdir, test.src+".exe")
cmd := testenv.Command(t, testenv.GoToolPath(t), "build", "-o", exe, src)
out, err := cmd.CombinedOutput()
if test.ok && err != nil {
t.Errorf("build failed unexpectedly: %v:\n%s", err, out)
}
if !test.ok && err == nil {
t.Errorf("build succeeded unexpectedly: %v:\n%s", err, out)
}
})
}
}

27
src/cmd/link/testdata/linkname/coro.go vendored Normal file
View File

@ -0,0 +1,27 @@
// Copyright 2024 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.
// Linkname coroswitch is not allowed, even if iter.Pull
// is instantiated in the same package.
package main
import (
"iter"
"unsafe"
)
func seq(yield func(int) bool) {
yield(123)
}
func main() {
next, stop := iter.Pull(seq)
next()
stop()
coroswitch(nil)
}
//go:linkname coroswitch runtime.coroswitch
func coroswitch(unsafe.Pointer)

View File

@ -0,0 +1,7 @@
// Copyright 2024 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.
TEXT ·newcoro(SB),0,$0-0
CALL runtime·newcoro(SB)
RET

View File

@ -0,0 +1,13 @@
// Copyright 2024 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.
// Assembly reference of newcoro is not allowed.
package main
func main() {
newcoro()
}
func newcoro()

View File

@ -0,0 +1,21 @@
// Copyright 2024 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.
// Linkname "var" to reference newcoro is not allowed.
package main
import "unsafe"
func main() {
call(&newcoro)
}
//go:linkname newcoro runtime.newcoro
var newcoro unsafe.Pointer
//go:noinline
func call(*unsafe.Pointer) {
// not implemented
}

27
src/cmd/link/testdata/linkname/ok.go vendored Normal file
View File

@ -0,0 +1,27 @@
// Copyright 2024 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.
// Use of public API is ok.
package main
import (
"iter"
"unique"
)
func seq(yield func(int) bool) {
yield(123)
}
var s = "hello"
func main() {
h := unique.Make(s)
next, stop := iter.Pull(seq)
defer stop()
println(h.Value())
println(next())
println(next())
}

19
src/cmd/link/testdata/linkname/p/p.go vendored Normal file
View File

@ -0,0 +1,19 @@
// Copyright 2024 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 p
import _ "unsafe"
// f1 is pushed from main.
//
//go:linkname f1
func f1()
// Push f2 to main.
//
//go:linkname f2 main.f2
func f2() {}
func F() { f1() }

26
src/cmd/link/testdata/linkname/push.go vendored Normal file
View File

@ -0,0 +1,26 @@
// Copyright 2024 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.
// "Push" linknames are ok.
package main
import (
"cmd/link/testdata/linkname/p"
_ "unsafe"
)
// Push f1 to p.
//
//go:linkname f1 cmd/link/testdata/linkname/p.f1
func f1() { f2() }
// f2 is pushed from p.
//
//go:linkname f2
func f2()
func main() {
p.F()
}

22
src/cmd/link/testdata/linkname/weak.go vendored Normal file
View File

@ -0,0 +1,22 @@
// Copyright 2024 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.
// Linkname generic functions in internal/weak is not
// allowed; legitimate instantiation is ok.
package main
import (
"unique"
"unsafe"
)
//go:linkname weakMake internal/weak.Make[string]
func weakMake(string) unsafe.Pointer
func main() {
h := unique.Make("xxx")
println(h.Value())
weakMake("xxx")
}