aboutsummaryrefslogtreecommitdiff
path: root/src/cmd
diff options
context:
space:
mode:
authorCherry Mui <cherryyz@google.com>2025-11-25 13:54:18 -0500
committerCherry Mui <cherryyz@google.com>2026-04-01 11:00:13 -0700
commit46755cb4dea406093296ead4250ff5f7091c04a2 (patch)
tree4558617545a2c0b2dc924d2f95c15655ccb74ee3 /src/cmd
parent8ea160279ff6700282f254d6b74fb02f8a316abe (diff)
downloadgo-46755cb4dea406093296ead4250ff5f7091c04a2.tar.xz
cmd/compile, cmd/link: add linknamestd directive for std-only linknames
In the standard library, there are a number of linknames, for sharing symbols within the standard library. They are not supposed to be accessed externally. But currently there is no good mechanism to prevent that. In the linker we have a blocklist of linknames, which forbids linkname references other than explicitly allowed packages. The blocklist is manually maintained, requiring periodic manual update. To move away from that manually maintained blocklist, this CL introduces a new directive, linknamestd, that marks a linkname for use within the standard library only. The linker will allow references within the standard library and forbid others. For a proof of concept, runtime.coroswitch is removed from the blocklist, and replaced with linknamestd. An external reference to it is still disallowed by the linker, as tested with cmd/link.TestCheckLinkname with testdata/linkname/coro.go. Change-Id: I0d0f8746b8835d8cdcfc3ff835d22a551da5f038 Reviewed-on: https://go-review.googlesource.com/c/go/+/749942 LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Reviewed-by: David Chase <drchase@google.com>
Diffstat (limited to 'src/cmd')
-rw-r--r--src/cmd/compile/internal/noder/linker.go1
-rw-r--r--src/cmd/compile/internal/noder/noder.go9
-rw-r--r--src/cmd/compile/internal/noder/reader.go7
-rw-r--r--src/cmd/compile/internal/noder/writer.go33
-rw-r--r--src/cmd/compile/internal/ssagen/abi.go3
-rw-r--r--src/cmd/internal/goobj/objfile.go2
-rw-r--r--src/cmd/internal/obj/link.go5
-rw-r--r--src/cmd/internal/obj/objfile.go3
-rw-r--r--src/cmd/link/internal/loader/loader.go25
9 files changed, 65 insertions, 23 deletions
diff --git a/src/cmd/compile/internal/noder/linker.go b/src/cmd/compile/internal/noder/linker.go
index 51b03a1897..df1e248398 100644
--- a/src/cmd/compile/internal/noder/linker.go
+++ b/src/cmd/compile/internal/noder/linker.go
@@ -338,6 +338,7 @@ func (l *linker) linkname(w *pkgbits.Encoder, name *ir.Name) {
linkname := name.Sym().Linkname
if !l.lsymIdx(w, linkname, name.Linksym()) {
w.String(linkname)
+ w.Bool(name.Linksym().IsLinknameStd())
}
}
diff --git a/src/cmd/compile/internal/noder/noder.go b/src/cmd/compile/internal/noder/noder.go
index 973d917784..4e48126e82 100644
--- a/src/cmd/compile/internal/noder/noder.go
+++ b/src/cmd/compile/internal/noder/noder.go
@@ -103,9 +103,10 @@ type noder struct {
err chan syntax.Error
}
-// linkname records a //go:linkname directive.
+// linkname records a //go:linkname or //go:linknamestd directive.
type linkname struct {
pos syntax.Pos
+ std bool
local string
remote string
}
@@ -273,10 +274,10 @@ func (p *noder) pragma(pos syntax.Pos, blankLine bool, text string, old syntax.P
}
}
- case strings.HasPrefix(text, "go:linkname "):
+ case strings.HasPrefix(text, "go:linkname "), strings.HasPrefix(text, "go:linknamestd "):
f := strings.Fields(text)
if !(2 <= len(f) && len(f) <= 3) {
- p.error(syntax.Error{Pos: pos, Msg: "usage: //go:linkname localname [linkname]"})
+ p.error(syntax.Error{Pos: pos, Msg: fmt.Sprintf("usage: //%s localname [linkname]", f[0])})
break
}
// The second argument is optional. If omitted, we use
@@ -294,7 +295,7 @@ func (p *noder) pragma(pos syntax.Pos, blankLine bool, text string, old syntax.P
} else {
panic("missing pkgpath")
}
- p.linknames = append(p.linknames, linkname{pos, f[1], target})
+ p.linknames = append(p.linknames, linkname{pos, f[0] == "go:linknamestd", f[1], target})
case text == "go:embed", strings.HasPrefix(text, "go:embed "):
args, err := parseGoEmbed(text[len("go:embed"):])
diff --git a/src/cmd/compile/internal/noder/reader.go b/src/cmd/compile/internal/noder/reader.go
index 00710775d6..ebb5043a05 100644
--- a/src/cmd/compile/internal/noder/reader.go
+++ b/src/cmd/compile/internal/noder/reader.go
@@ -1277,6 +1277,7 @@ func (r *reader) linkname(name *ir.Name) {
lsym.Set(obj.AttrIndexed, true)
} else {
linkname := r.String()
+ std := r.Bool()
sym := name.Sym()
sym.Linkname = linkname
if sym.Pkg == types.LocalPkg && linkname != "" {
@@ -1286,7 +1287,11 @@ func (r *reader) linkname(name *ir.Name) {
// 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)
+ if std {
+ sym.Linksym().Set(obj.AttrLinknameStd, true)
+ } else {
+ sym.Linksym().Set(obj.AttrLinkname, true)
+ }
}
}
}
diff --git a/src/cmd/compile/internal/noder/writer.go b/src/cmd/compile/internal/noder/writer.go
index 9e0f4fc9c8..027d7e0636 100644
--- a/src/cmd/compile/internal/noder/writer.go
+++ b/src/cmd/compile/internal/noder/writer.go
@@ -86,8 +86,11 @@ type pkgWriter struct {
typDecls map[*types2.TypeName]typeDeclGen
// linknames maps package-scope objects to their linker symbol name,
- // if specified by a //go:linkname directive.
- linknames map[types2.Object]string
+ // if specified by a //go:linkname or //go:linknamestd directive.
+ linknames map[types2.Object]struct {
+ remote string
+ std bool
+ }
// cgoPragmas accumulates any //go:cgo_* pragmas that need to be
// passed through to cmd/link.
@@ -114,7 +117,10 @@ func newPkgWriter(m posMap, pkg *types2.Package, info *types2.Info, otherInfo ma
funDecls: make(map[*types2.Func]*syntax.FuncDecl),
typDecls: make(map[*types2.TypeName]typeDeclGen),
- linknames: make(map[types2.Object]string),
+ linknames: make(map[types2.Object]struct {
+ remote string
+ std bool
+ }),
}
}
@@ -1175,7 +1181,9 @@ func (w *writer) varExt(obj *types2.Var) {
func (w *writer) linkname(obj types2.Object) {
w.Sync(pkgbits.SyncLinkname)
w.Int64(-1)
- w.String(w.p.linknames[obj])
+ info := w.p.linknames[obj]
+ w.String(info.remote)
+ w.Bool(info.std)
}
func (w *writer) pragmaFlag(p ir.PragmaFlag) {
@@ -2799,26 +2807,33 @@ func (pw *pkgWriter) collectDecls(noders []*noder) {
pw.cgoPragmas = append(pw.cgoPragmas, p.pragcgobuf...)
for _, l := range p.linknames {
+ directive := "go:linkname"
+ if l.std {
+ directive = "go:linknamestd"
+ }
if !file.importedUnsafe {
- pw.errorf(l.pos, "//go:linkname only allowed in Go files that import \"unsafe\"")
+ pw.errorf(l.pos, "//%s only allowed in Go files that import \"unsafe\"", directive)
continue
}
if strings.Contains(l.remote, "[") && strings.Contains(l.remote, "]") {
- pw.errorf(l.pos, "//go:linkname reference of an instantiation is not allowed")
+ pw.errorf(l.pos, "//%s reference of an instantiation is not allowed", directive)
continue
}
switch obj := pw.curpkg.Scope().Lookup(l.local).(type) {
case *types2.Func, *types2.Var:
if _, ok := pw.linknames[obj]; !ok {
- pw.linknames[obj] = l.remote
+ pw.linknames[obj] = struct {
+ remote string
+ std bool
+ }{l.remote, l.std}
} else {
- pw.errorf(l.pos, "duplicate //go:linkname for %s", l.local)
+ pw.errorf(l.pos, "duplicate //%s for %s", directive, l.local)
}
default:
if types.AllowsGoVersion(1, 18) {
- pw.errorf(l.pos, "//go:linkname must refer to declared function or variable")
+ pw.errorf(l.pos, "//%s must refer to declared function or variable", directive)
}
}
}
diff --git a/src/cmd/compile/internal/ssagen/abi.go b/src/cmd/compile/internal/ssagen/abi.go
index 0e8dbd9445..107e48c8c9 100644
--- a/src/cmd/compile/internal/ssagen/abi.go
+++ b/src/cmd/compile/internal/ssagen/abi.go
@@ -168,6 +168,9 @@ func (s *SymABIs) GenABIWrappers() {
if sym.Linksym().IsLinkname() {
sym.LinksymABI(fn.ABI).Set(obj.AttrLinkname, true)
}
+ if sym.Linksym().IsLinknameStd() {
+ sym.LinksymABI(fn.ABI).Set(obj.AttrLinknameStd, true)
+ }
}
// If cgo-exported, add the definition ABI to the cgo
diff --git a/src/cmd/internal/goobj/objfile.go b/src/cmd/internal/goobj/objfile.go
index cca3c840e0..679f4357e2 100644
--- a/src/cmd/internal/goobj/objfile.go
+++ b/src/cmd/internal/goobj/objfile.go
@@ -306,6 +306,7 @@ const (
SymFlagDict
SymFlagPkgInit
SymFlagLinkname
+ SymFlagLinknameStd
SymFlagABIWrapper
SymFlagWasmExport
)
@@ -340,6 +341,7 @@ 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) IsLinknameStd() bool { return s.Flag2()&SymFlagLinknameStd != 0 }
func (s *Sym) ABIWrapper() bool { return s.Flag2()&SymFlagABIWrapper != 0 }
func (s *Sym) WasmExport() bool { return s.Flag2()&SymFlagWasmExport != 0 }
diff --git a/src/cmd/internal/obj/link.go b/src/cmd/internal/obj/link.go
index 088a7c57aa..69c9e7583b 100644
--- a/src/cmd/internal/obj/link.go
+++ b/src/cmd/internal/obj/link.go
@@ -977,6 +977,9 @@ const (
// Linkname indicates this is a go:linkname'd symbol.
AttrLinkname
+ // LinknameStd indicates this is a go:linknamestd'd symbol.
+ AttrLinknameStd
+
// 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.
@@ -1007,6 +1010,7 @@ func (a *Attribute) ABIWrapper() bool { return a.load()&AttrABIWrapper !
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) IsLinknameStd() bool { return a.load()&AttrLinknameStd != 0 }
func (a *Attribute) Set(flag Attribute, value bool) {
for {
@@ -1057,6 +1061,7 @@ var textAttrStrings = [...]struct {
{bit: AttrABIWrapper, s: "ABIWRAPPER"},
{bit: AttrPkgInit, s: "PKGINIT"},
{bit: AttrLinkname, s: "LINKNAME"},
+ {bit: AttrLinknameStd, s: "LINKNAMESTD"},
}
// String formats a for printing in as part of a TEXT prog.
diff --git a/src/cmd/internal/obj/objfile.go b/src/cmd/internal/obj/objfile.go
index ea1483de01..de5f172b11 100644
--- a/src/cmd/internal/obj/objfile.go
+++ b/src/cmd/internal/obj/objfile.go
@@ -360,6 +360,9 @@ func (w *writer) Sym(s *LSym) {
// The runtime linknames main.main.
flag2 |= goobj.SymFlagLinkname
}
+ if s.IsLinknameStd() {
+ flag2 |= goobj.SymFlagLinknameStd
+ }
if s.ABIWrapper() {
flag2 |= goobj.SymFlagABIWrapper
}
diff --git a/src/cmd/link/internal/loader/loader.go b/src/cmd/link/internal/loader/loader.go
index 9ac2b9201d..100ebd4dce 100644
--- a/src/cmd/link/internal/loader/loader.go
+++ b/src/cmd/link/internal/loader/loader.go
@@ -2220,7 +2220,7 @@ type loadState struct {
}
type linknameVarRef struct {
- pkg string // package of reference (not definition)
+ pkg *oReader // package of reference (not definition)
name string
sym Sym
}
@@ -2257,7 +2257,7 @@ func (st *loadState) preloadSyms(r *oReader, kind int) {
}
gi := st.addSym(name, v, r, i, kind, osym)
r.syms[i] = gi
- if kind == nonPkgDef && osym.IsLinkname() && r.DataSize(i) == 0 && strings.Contains(name, ".") {
+ if kind == nonPkgDef && (osym.IsLinkname() || osym.IsLinknameStd()) && r.DataSize(i) == 0 && strings.Contains(name, ".") {
// This is a linknamed "var" "reference" (var x T with no data and //go:linkname x).
// We want to check if a linkname reference is allowed. Here we haven't loaded all
// symbol definitions, so we don't yet know all the push linknames. So we add to a
@@ -2268,7 +2268,7 @@ func (st *loadState) preloadSyms(r *oReader, kind int) {
// This use of linkname is usually for referencing C symbols, so allow symbols
// with no "." in its name (not a regular Go symbol).
// Linkname is always a non-package reference.
- st.linknameVarRefs = append(st.linknameVarRefs, linknameVarRef{r.unit.Lib.Pkg, name, gi})
+ st.linknameVarRefs = append(st.linknameVarRefs, linknameVarRef{r, name, gi})
}
if osym.Local() {
l.SetAttrLocal(gi, true)
@@ -2346,12 +2346,12 @@ 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() {
+ if osym.IsLinkname() || osym.IsLinknameStd() {
// 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.
- l.checkLinkname(r.unit.Lib.Pkg, name, gi)
+ l.checkLinkname(r, name, gi)
}
if osym.Local() {
l.SetAttrLocal(gi, true)
@@ -2405,8 +2405,7 @@ func abiToVer(abi uint16, localSymVersion int) int {
// even if it has a linknamed definition.
var blockedLinknames = map[string][]string{
// coroutines
- "runtime.coroswitch": {"iter"},
- "runtime.newcoro": {"iter"},
+ "runtime.newcoro": {"iter"},
// fips info
"go:fipsinfo": {"crypto/internal/fips140/check"},
// New internal linknames in Go 1.24
@@ -2492,12 +2491,13 @@ var blockedLinknames = map[string][]string{
"runtime.addmoduledata": {}, // assembly symbol, disallow all packages
}
-// check if a linkname reference to symbol s from pkg is allowed
-func (l *Loader) checkLinkname(pkg, name string, s Sym) {
+// check if a linkname reference to symbol s from refpkg is allowed
+func (l *Loader) checkLinkname(refpkg *oReader, name string, s Sym) {
if l.flags&FlagCheckLinkname == 0 {
return
}
+ pkg := refpkg.unit.Lib.Pkg
error := func() {
log.Fatalf("%s: invalid reference to %s", pkg, name)
}
@@ -2531,6 +2531,13 @@ func (l *Loader) checkLinkname(pkg, name string, s Sym) {
return
}
osym := r.Sym(li)
+ if osym.IsLinknameStd() {
+ // It is pushed with linknamestd. Allow only pulls from the
+ // standard library.
+ if refpkg.Std() {
+ return
+ }
+ }
if osym.IsLinkname() || osym.ABIWrapper() {
// Allow if the def has a linkname (push).
// ABI wrapper usually wraps an assembly symbol, a linknamed symbol,