diff options
| author | Cherry Mui <cherryyz@google.com> | 2025-11-25 13:54:18 -0500 |
|---|---|---|
| committer | Cherry Mui <cherryyz@google.com> | 2026-04-01 11:00:13 -0700 |
| commit | 46755cb4dea406093296ead4250ff5f7091c04a2 (patch) | |
| tree | 4558617545a2c0b2dc924d2f95c15655ccb74ee3 /src | |
| parent | 8ea160279ff6700282f254d6b74fb02f8a316abe (diff) | |
| download | go-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')
| -rw-r--r-- | src/cmd/compile/internal/noder/linker.go | 1 | ||||
| -rw-r--r-- | src/cmd/compile/internal/noder/noder.go | 9 | ||||
| -rw-r--r-- | src/cmd/compile/internal/noder/reader.go | 7 | ||||
| -rw-r--r-- | src/cmd/compile/internal/noder/writer.go | 33 | ||||
| -rw-r--r-- | src/cmd/compile/internal/ssagen/abi.go | 3 | ||||
| -rw-r--r-- | src/cmd/internal/goobj/objfile.go | 2 | ||||
| -rw-r--r-- | src/cmd/internal/obj/link.go | 5 | ||||
| -rw-r--r-- | src/cmd/internal/obj/objfile.go | 3 | ||||
| -rw-r--r-- | src/cmd/link/internal/loader/loader.go | 25 | ||||
| -rw-r--r-- | src/iter/iter.go | 2 | ||||
| -rw-r--r-- | src/runtime/coro.go | 2 |
11 files changed, 67 insertions, 25 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, diff --git a/src/iter/iter.go b/src/iter/iter.go index f119eae995..57d19d047b 100644 --- a/src/iter/iter.go +++ b/src/iter/iter.go @@ -233,7 +233,7 @@ type coro struct{} //go:linkname newcoro runtime.newcoro func newcoro(func(*coro)) *coro -//go:linkname coroswitch runtime.coroswitch +//go:linknamestd coroswitch runtime.coroswitch func coroswitch(*coro) // Pull converts the “push-style” iterator sequence seq diff --git a/src/runtime/coro.go b/src/runtime/coro.go index 40d4e47fbe..72c58b7964 100644 --- a/src/runtime/coro.go +++ b/src/runtime/coro.go @@ -84,7 +84,7 @@ func coroexit(c *coro) { mcall(coroswitch_m) } -//go:linkname coroswitch +//go:linknamestd coroswitch // coroswitch switches to the goroutine blocked on c // and then blocks the current goroutine on c. |
