Skip to content

feat: capture golang binary lib symbols#4988

Open
spiffcs wants to merge 10 commits into
mainfrom
go-binary-symbols
Open

feat: capture golang binary lib symbols#4988
spiffcs wants to merge 10 commits into
mainfrom
go-binary-symbols

Conversation

@spiffcs

@spiffcs spiffcs commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds optional extraction of function symbols from Go binaries cataloging. Symbol extraction attributes them to the module that owns them which come out in the SBOM as syft packages. When enabled, syft reads the binary's pclntab and records the function symbols compiled into the binary.

Symbols are attributed to their owning module and on that package's metadata. Standard-library symbols are attached to the synthetic stdlib package. Inlined functions are also recovered from the pclntab funcname table so a vulnerable but inlined function can still be reported.

Changes

  • New symbols.go:parsing for ELF and Mach-O, symbol→module attribution, stdlib detection
  • GolangBinaryBuildinfoEntry.Symbols (syft/pkg/golang.go): new symbols JSON field (omitempty).
  • the cataloger accumulates stdlib symbols per binary location
  • Schema bump: 16.1.8 → 16.1.9.

Configuration: default off

capture-symbols is a three-value scope enum. Disabled by default, so upgrading does not change existing Go SBOM output or increase its size unless explicitly enabled.

┌──────────────────────┬──────────────────────────────────────────────────────────────────────────────┐
│       Surface        │                                   Setting                                    │
├──────────────────────┼──────────────────────────────────────────────────────────────────────────────┤
│ Environment variable │ SYFT_GOLANG_CAPTURE_SYMBOLS=stdlib                                                           │
├──────────────────────┼──────────────────────────────────────────────────────────────────────────────┤
│ Config file          │ golang.capture-symbols: none                                               │
├──────────────────────┼──────────────────────────────────────────────────────────────────────────────┤
│ Library              │ golang.DefaultCatalogerConfig().WithCaptureSymbols(golang.SymbolScopeStdlib) │
└──────────────────────┴──────────────────────────────────────────────────────────────────────────────┘
┌────────────────┬──────────────────────────────────────────────┐
│     Value      │                   Behavior                   │
├────────────────┼──────────────────────────────────────────────┤
│ none (default) │ no symbols captured                          │
├────────────────┼──────────────────────────────────────────────┤
│ stdlib         │ symbols only on the synthetic stdlib package │
├────────────────┼──────────────────────────────────────────────┤
│ all            │ symbols on all module packages plus stdlib   │
└────────────────┴──────────────────────────────────────────────┘

@oss-housekeeper

This comment was marked as outdated.

@spiffcs spiffcs marked this pull request as draft June 17, 2026 14:17
spiffcs added 4 commits June 30, 2026 22:30
Signed-off-by: Christopher Phillips <32073428+spiffcs@users.noreply.github.com>
Signed-off-by: Christopher Phillips <32073428+spiffcs@users.noreply.github.com>
Signed-off-by: Christopher Phillips <32073428+spiffcs@users.noreply.github.com>
Signed-off-by: Christopher Phillips <32073428+spiffcs@users.noreply.github.com>
@spiffcs spiffcs force-pushed the go-binary-symbols branch from 8028f3f to 7050c09 Compare July 1, 2026 02:49
@spiffcs spiffcs marked this pull request as ready for review July 1, 2026 02:50
@spiffcs spiffcs requested a review from wagoodman July 1, 2026 02:50
spiffcs added 3 commits June 30, 2026 23:15
Signed-off-by: Christopher Phillips <32073428+spiffcs@users.noreply.github.com>
Signed-off-by: Christopher Phillips <32073428+spiffcs@users.noreply.github.com>
Signed-off-by: Christopher Phillips <32073428+spiffcs@users.noreply.github.com>
@spiffcs spiffcs changed the title feat: POC capture lib symbols feat: capture golang binary lib symbols Jul 1, 2026
spiffcs and others added 2 commits July 1, 2026 02:26
Signed-off-by: Christopher Phillips <32073428+spiffcs@users.noreply.github.com>
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
Comment thread syft/pkg/vcpkg.go

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was getting warnings when regenerating the json schema, so I went ahead and added descriptions for these.

Comment thread syft/cataloging/symbols.go Outdated
case string(SymbolScopeNone), "":
return SymbolScopeNone
}
return ""

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

see the compliance parsing for an example; we tend to fail towards defaults instead of require checking and error by the caller. So when we use it in syft/pkg/cataloger/golang/parse_go_binary, we should trust the value at that point (not call Parse() again)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ahh nice call I totally missed that compliance pattern. The options layer's PostLoad just assigns the parsed value (no error path anymore). I think this is the same shape as complianceConfig.PostLoad() now.

return &goBinaryCataloger{
licenseResolver: newGoLicenseResolver(binaryCatalogerName, opts),
mainModuleVersion: opts.MainModuleVersion,
symbolScope: opts.CaptureSymbols.Parse(),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no need to call Parse() again

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I updated this so the cataloger now takes opts.CaptureSymbols as-is and trusts that normalization happened at the options layer.

defer func() {
if r := recover(); r != nil {
// the gosym package can panic on malformed pclntab data
err = fmt.Errorf("recovered from panic while reading pclntab: %v", r)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should clear syms at this point too

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 I updated this so the recover block now sets syms = nil before setting the error. A panic mid-parse can't return partial results alongside the error.

Comment thread syft/pkg/cataloger/golang/symbols.go Outdated
// belong to no package and are dropped rather than mis-attributed (e.g. bucketed under a bogus "type"
// stdlib package).
func isCompilerGeneratedName(name string) bool {
return strings.Contains(name, ":") || strings.Contains(name, "..")

@wagoodman wagoodman Jul 1, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this isn't accounting for the go.type case that I think older compiler versions have

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea I confirmed pre-1.20 toolchains used . where newer ones use :

I added prefix matches to account for the above. Real module paths like go.uber.org/zap and go.opentelemetry.io/otel also start with go. and shouldn't be dropped. I added test cases to cover this.

if isCompilerGeneratedName(name) {
return ""
}
slash := strings.LastIndex(name, "/")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this doesn't account for cases when there are type arguments like foo/bar.Do[net/url.Values] ... we'd find that the package is foo/bar.Do[net

@spiffcs spiffcs Jul 2, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, I found that the last "/" search was landing inside the type arguments

foo/bar.Do[net/url.Values] derived foo/bar.Do[net.

I fixed this by stripping the [...] portion before deriving the package path.
New test cases have been added to symbols_test

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I realize this is outside of review, but noticed it in the review locally... I think this is swapped

Suggested change
after, _, found := strings.Cut(version, "+")

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice catch! Fixed this apologies for the oversight.

Signed-off-by: Christopher Phillips <32073428+spiffcs@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants