mirror of
https://github.com/golang/go.git
synced 2026-06-27 19:30:52 +00:00
cmd/link: check linkname access to assembly symbols
With the introduction of checklinkname, using linkname to reference a symbol from a different symbol is checked, and is permitted only when there is a push linkname. One exception is that linkname reference to an assembly symbol is always permitted, for now. This CL addresses this exception, and let checklinkname handle assembly symbols as well. The rule is similar to Go functions: linkname access is permitted if there is a push linkname annotation. One trickiness is that the assembly function is defined in assembly, whereas the annotation is in Go. They are different objects. To connect the two, we apply the annotation to the ABI wrapper, and let the linker to check the ABI wrapper symbol. A further complication is that on non-regABI platforms there is no ABI wrapper. In this case, we just allow the access for now. As most popular platforms are register ABI platforms, this shouldn't leave too big a hole. To allow one package pushes assembly functions to another, like, package a defines an assembly symbol b.F, it is permitted to reference directly from the target package (b in the example) based on the name. This change also makes it handle linkname references to ABI wrappers more strict. Previously it was always permitted. Now we treat the ABI wrapper the same as the underlying symbol. With this, we can migrate runtime.newcoro to linknamestd and remove it from the blocklist, and the coro_var and coro_asm test cases in cmd/link.TestCheckLinkname still pass. Change-Id: I6c03467d3eaaa536663e52ce289e3c1c23079aa6 Reviewed-on: https://go-review.googlesource.com/c/go/+/761481 Reviewed-by: David Chase <drchase@google.com> LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
This commit is contained in:
parent
ad46b4815e
commit
aee6009ba5
9 changed files with 72 additions and 14 deletions
|
|
@ -280,6 +280,10 @@ func makeABIWrapper(f *ir.Func, wrapperABI obj.ABI) {
|
|||
fn.SetABIWrapper(true)
|
||||
fn.SetDupok(true)
|
||||
|
||||
// Propagate linkname attribute.
|
||||
fn.LinksymABI(fn.ABI).Set(obj.AttrLinkname, f.Linksym().IsLinkname())
|
||||
fn.LinksymABI(fn.ABI).Set(obj.AttrLinknameStd, f.Linksym().IsLinknameStd())
|
||||
|
||||
// ABI0-to-ABIInternal wrappers will be mainly loading params from
|
||||
// stack into registers (and/or storing stack locations back to
|
||||
// registers after the wrapped call); in most cases they won't
|
||||
|
|
|
|||
|
|
@ -371,9 +371,7 @@ func (w *writer) Sym(s *LSym) {
|
|||
if s.IsPkgInit() {
|
||||
flag2 |= goobj.SymFlagPkgInit
|
||||
}
|
||||
if s.IsLinkname() || (w.ctxt.IsAsm && name != "") || name == "main.main" {
|
||||
// Assembly reference is treated the same as linkname,
|
||||
// but not for unnamed (aux) symbols.
|
||||
if s.IsLinkname() || name == "main.main" {
|
||||
// The runtime linknames main.main.
|
||||
flag2 |= goobj.SymFlagLinkname
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2265,7 +2265,16 @@ func instinit(ctxt *obj.Link) {
|
|||
|
||||
switch ctxt.Headtype {
|
||||
case objabi.Hplan9:
|
||||
// _privates is a special symbol on Plan 9 that
|
||||
// points to per–process private data (like TLS area).
|
||||
// See https://9p.io/magic/man2html/2/exec .
|
||||
// The assembler inserts a reference to this symbol
|
||||
// for accessing the G. Mark it as linkname so it is
|
||||
// allowed to access from anywhere. (Would be nice to
|
||||
// mark it external, but we don't have a mechanism for
|
||||
// that.)
|
||||
plan9privates = ctxt.Lookup("_privates")
|
||||
plan9privates.Set(obj.AttrLinkname, true)
|
||||
}
|
||||
|
||||
for i := range avxOptab {
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import (
|
|||
"debug/elf"
|
||||
"fmt"
|
||||
"internal/abi"
|
||||
"internal/buildcfg"
|
||||
"io"
|
||||
"iter"
|
||||
"log"
|
||||
|
|
@ -2368,7 +2369,7 @@ func loadObjRefs(l *Loader, r *oReader, arch *sys.Arch) {
|
|||
v := abiToVer(osym.ABI(), r.version)
|
||||
gi := l.LookupOrCreateSym(name, v)
|
||||
r.syms[ndef+i] = gi
|
||||
if osym.IsLinkname() || osym.IsLinknameStd() {
|
||||
if osym.IsLinkname() || osym.IsLinknameStd() || r.FromAssembly() {
|
||||
// Check if a linkname reference is allowed.
|
||||
// Only check references (pull), not definitions (push),
|
||||
// so push is always allowed.
|
||||
|
|
@ -2426,8 +2427,6 @@ func abiToVer(abi uint16, localSymVersion int) int {
|
|||
// If a name is in this map, it is allowed only in listed packages,
|
||||
// even if it has a linknamed definition.
|
||||
var blockedLinknames = map[string][]string{
|
||||
// coroutines
|
||||
"runtime.newcoro": {"iter"},
|
||||
// fips info
|
||||
"go:fipsinfo": {"crypto/internal/fips140/check"},
|
||||
// New internal linknames in Go 1.24
|
||||
|
|
@ -2553,6 +2552,27 @@ func (l *Loader) checkLinkname(refpkg *oReader, name string, s Sym) {
|
|||
return
|
||||
}
|
||||
osym := r.Sym(li)
|
||||
if r.FromAssembly() && !osym.IsLinknameStd() && !osym.IsLinkname() {
|
||||
if strings.HasPrefix(name, pkg) {
|
||||
// Allow if by name it is pushed to pkg, e.g. in package a,
|
||||
// an assembly function is defined as b.F, then it is allowed
|
||||
// to be used in package b.
|
||||
return
|
||||
}
|
||||
// For an assembly symbol, check if there is a linkname applied
|
||||
// to its ABI wrapper.
|
||||
if !buildcfg.Experiment.RegabiWrappers {
|
||||
// If ABI wrapper is not enabled (i.e. non-regabi platform),
|
||||
// permit for now, as there is no good way to check.
|
||||
return
|
||||
}
|
||||
otherABI := 1 - abiToVer(osym.ABI(), r.version) // for now, we only have ABI 0 and 1
|
||||
w := l.Lookup(name, otherABI) // TODO: use an aux symbol instead of name lookup?
|
||||
if w != 0 {
|
||||
r, li = l.toLocal(w)
|
||||
osym = r.Sym(li)
|
||||
}
|
||||
}
|
||||
if osym.IsLinknameStd() {
|
||||
// It is pushed with linknamestd. Allow only pulls from the
|
||||
// standard library.
|
||||
|
|
@ -2560,12 +2580,8 @@ func (l *Loader) checkLinkname(refpkg *oReader, name string, s Sym) {
|
|||
return
|
||||
}
|
||||
}
|
||||
if osym.IsLinkname() || osym.ABIWrapper() {
|
||||
if osym.IsLinkname() {
|
||||
// Allow if the def has a linkname (push).
|
||||
// ABI wrapper usually wraps an assembly symbol, a linknamed symbol,
|
||||
// or an external symbol, or provide access of a Go symbol to assembly.
|
||||
// For now, allow ABI wrappers.
|
||||
// TODO: check the wrapped symbol?
|
||||
return
|
||||
}
|
||||
error()
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import (
|
|||
"debug/pe"
|
||||
"errors"
|
||||
"internal/abi"
|
||||
"internal/buildcfg"
|
||||
"internal/platform"
|
||||
"internal/testenv"
|
||||
"internal/xcoff"
|
||||
|
|
@ -1715,6 +1716,10 @@ func TestCheckLinkname(t *testing.T) {
|
|||
{"coro2.go", false},
|
||||
// pull linkname of a builtin symbol is not ok
|
||||
{"builtin.go", false},
|
||||
// using a linkname to reference a runtime assembly
|
||||
// function is not ok (except on non-regabi platforms)
|
||||
{"systemstack.go", !buildcfg.Experiment.RegabiWrappers},
|
||||
// misc
|
||||
{"addmoduledata.go", false},
|
||||
{"freegc.go", false},
|
||||
// legacy bad linkname is ok, for now
|
||||
|
|
|
|||
|
|
@ -3,7 +3,8 @@
|
|||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Existing pull linknames in the wild are allowed _for now_,
|
||||
// for legacy reason. Test a function and a method.
|
||||
// for legacy reason. Test a function, a method, and an
|
||||
// assembly symbol.
|
||||
// NOTE: this may not be allowed in the future. Don't do this!
|
||||
|
||||
package main
|
||||
|
|
@ -19,6 +20,12 @@ func noescape(unsafe.Pointer) unsafe.Pointer
|
|||
//go:linkname rtype_String reflect.(*rtype).String
|
||||
func rtype_String(unsafe.Pointer) string
|
||||
|
||||
//go:linkname memmove runtime.memmove
|
||||
func memmove(to, from unsafe.Pointer, n uintptr)
|
||||
|
||||
var n uintptr // use a global to prevent compiler optimize out memmove call
|
||||
|
||||
func main() {
|
||||
println(rtype_String(noescape(nil)))
|
||||
memmove(nil, nil, n)
|
||||
}
|
||||
|
|
|
|||
19
src/cmd/link/testdata/linkname/systemstack.go
vendored
Normal file
19
src/cmd/link/testdata/linkname/systemstack.go
vendored
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
// Copyright 2026 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 systemstack is not allowed, even if it is
|
||||
// defined in assembly.
|
||||
|
||||
package main
|
||||
|
||||
import _ "unsafe"
|
||||
|
||||
func f() {}
|
||||
|
||||
func main() {
|
||||
systemstack(f)
|
||||
}
|
||||
|
||||
//go:linkname systemstack runtime.systemstack
|
||||
func systemstack(func())
|
||||
|
|
@ -230,7 +230,7 @@ type Seq2[K, V any] func(yield func(K, V) bool)
|
|||
|
||||
type coro struct{}
|
||||
|
||||
//go:linkname newcoro runtime.newcoro
|
||||
//go:linknamestd newcoro runtime.newcoro
|
||||
func newcoro(func(*coro)) *coro
|
||||
|
||||
//go:linknamestd coroswitch runtime.coroswitch
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ type coro struct {
|
|||
lockedInt uint32 // mp's internal lockOSThread counter at coro creation time.
|
||||
}
|
||||
|
||||
//go:linkname newcoro
|
||||
//go:linknamestd newcoro
|
||||
|
||||
// newcoro creates a new coro containing a
|
||||
// goroutine blocked waiting to run f
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue