Skip to content

test: add Go fuzz targets and fix the panics they uncovered#2541

Open
kotakanbe wants to merge 6 commits intomasterfrom
feature/add-fuzz-tests
Open

test: add Go fuzz targets and fix the panics they uncovered#2541
kotakanbe wants to merge 6 commits intomasterfrom
feature/add-fuzz-tests

Conversation

@kotakanbe
Copy link
Copy Markdown
Member

Summary

Introduces native Go fuzz tests (go test -fuzz) for 11 parser functions across the codebase, and ships fixes for the six panics / logic bugs the fuzzer found while exercising them. This also satisfies the OpenSSF Scorecard Fuzzing check, which currently scores 0 because no Fuzz*(*testing.F) functions exist in the repo (https://scorecard.dev/viewer/?uri=github.com%2Ffuture-architect%2Fvuls).

New fuzz targets

Package Function Target
detector canonicalMavenPURL FuzzCanonicalMavenPURL
scanner (redhatbase) splitFileName, parseInstalledPackagesLine FuzzSplitFileName, FuzzParseInstalledPackagesLine
scanner (debian) parseScannedPackagesLine FuzzParseScannedPackagesLine
scanner (alpine) parseApkIndex FuzzParseApkIndex
scanner (freebsd) parsePkgVersion, parseBlock FuzzParsePkgVersion, FuzzParseBlock
scanner (suse) parseOSRelease FuzzParseOSRelease
config toCpeURI FuzzToCpeURI
gost parseCwe FuzzParseCwe
contrib/snmp2cpe/pkg/cpe Convert FuzzConvert

Each target seeds the fuzzer with the existing unit-test inputs and asserts: never panic; if err == nil, hold a small set of output invariants (e.g. map key matches Name, no character invention, non-empty mandatory fields).

A 15-second smoke run per target totals ~1.5M+ executions and is enough to surface the six bugs below.

Bugs found and fixed

  1. scanner/redhatbase.splitFileName — slice bounds panic. Input like NetworkManager-1:1.40.16-1.el8.x86_64.rpm (epoch colon embedded in the version field, as some dnf builds emit) inverted the basename[epochIndex+1:verIndex] slice and crashed. Treat the colon as an epoch separator only when it sits before the detected version index.
  2. scanner/redhatbase.splitFileName — silent empty Name. Inputs like --.0 returned nil error with Name="". Surface this as the same unexpected file name error the early guards already emit.
  3. scanner/freebsd.parseBlockFields(l)[1] panic. A bare CVE: line (no value) crashed; skip the line instead.
  4. scanner/freebsd.parsePkgVersionfields[6] panic. A line like name <\n (the < status with fewer than the 7 expected whitespace-separated fields) crashed; guard with len(fields) < 7.
  5. config.toCpeURI — third-party panic. github.com/knqyf263/go-cpe's BindToURI panics on inputs like cpe:/a:vendor:product:1\:2. Recover and surface the panic as a normal error so a bad CPE in user config can never bring the process down.
  6. contrib/snmp2cpe/pkg/cpe.ConvertFields(v)[0] panic. A SysDescr0 ending in version (no value) crashed the non-Inc. Juniper branch; skip screenos CPE generation when no version token is present.

Build environment

The first commit is a build prerequisite: go test ./... currently fails on master with an ambiguous import error because google.golang.org/grpc/stats/opentelemetry is pulled in twice (once absorbed into google.golang.org/grpc v1.79.3, once as a standalone module brought in transitively by helm.sh/helm/v3 → distribution/distribution/v3@v3.0.0). An exclude directive in go.mod resolves the conflict without a dependency upgrade.

Test plan

  • go build ./cmd/... (with GOEXPERIMENT=jsonv2)
  • go build -tags scanner ./cmd/scanner/ (with GOEXPERIMENT=jsonv2)
  • go test ./detector/ ./scanner/ ./gost/ ./config/ ./contrib/snmp2cpe/pkg/cpe/ — all PASS
  • golangci-lint run — only one pre-existing prealloc finding, unrelated to this PR
  • Each new Fuzz* target re-run for 15s post-fix — all PASS, ~1.5M+ executions total
  • Reviewer confirms the splitFileName epoch-position rule is acceptable for real-world dnf / yum outputs (the changed behavior only triggers when the colon sits past the version boundary)

🤖 Generated with Claude Code

claude added 6 commits May 3, 2026 20:26
google.golang.org/grpc v1.79.3 absorbed the stats/opentelemetry
sub-package, but it is still pulled in transitively (via helm.sh/helm/v3
-> distribution/distribution/v3@v3.0.0) as a separate module. This
double-import makes `go test ./...` fail with an "ambiguous import"
error.

Adding an `exclude` directive for the standalone module forces all
imports of google.golang.org/grpc/stats/opentelemetry to resolve to the
absorbed sub-package.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…cking

splitFileName parses "(epoch:)name-version-release.arch" RPM file names
by walking strings.LastIndex calls. Two edge cases fed by `dnf` /
`yum`-style outputs caused incorrect behavior:

1. When the colon separator appears past the version boundary
   (e.g. "NetworkManager-1:1.40.16-1.el8.x86_64", emitted by some dnf
   builds where the epoch is baked into the version field instead of
   being a leading prefix), `basename[epochIndex+1:verIndex]` inverted
   and panicked with `slice bounds out of range`. Treat the colon as
   an epoch separator only when it sits before the detected version
   index; otherwise fall back to no-epoch parsing.

2. Inputs like "--.0" satisfied the existing `archIndex` / `relIndex` /
   `verIndex` guards but produced an empty Name silently. Surface this
   as the same "unexpected file name" error the early guards already
   return so callers do not insert a Package with an empty Name.

Both were found by the new FuzzSplitFileName target.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two pkg-audit / pkg-version output parsers indexed strings.Fields
results without checking length, panicking on truncated lines:

- parseBlock at "CVE:" (no value): `strings.Fields(l)[1]` panicked with
  index out of range. Skip the line when the CVE id is missing instead.
- parsePkgVersion at lines like "name <" with the "<" status but
  fewer than 7 whitespace-separated fields: `fields[6]` panicked. The
  expected format is `<name>-<ver> < needs updating (index has <new>)`,
  so guard with `len(fields) < 7`.

Both were found by the new FuzzParseBlock and FuzzParsePkgVersion
targets.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
github.com/knqyf263/go-cpe panics on some malformed CPE strings
(e.g. "cpe:/a:vendor:product:1\:2" with embedded backslash-escapes)
rather than returning an error. A single bad CPE in user config
should never bring the whole process down; recover the panic and
surface it as a normal error so the caller's xerrors-wrapping path
handles it.

Found by the new FuzzToCpeURI target.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The non-Inc. Juniper branch in Convert calls
`strings.Fields(v)[0]` on the substring after " version ". When
SysDescr0 ends with "version " (no value follows), Fields returns an
empty slice and the indexing panicked.

Skip the screenos CPE generation when no version token is present.

Found by the new FuzzConvert target.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds 11 fuzz functions exercising parsers most exposed to externally
controlled input. This raises the OpenSSF Scorecard "Fuzzing" check
(it requires only one Fuzz* function in the repo) and gives a regression
gate for the panic / logic-bug fixes that motivated this PR.

Targets and seeds (existing unit-test inputs are reused as seed corpus
where applicable):

  detector/library         FuzzCanonicalMavenPURL
  scanner/redhatbase       FuzzSplitFileName, FuzzParseInstalledPackagesLine
  scanner/debian           FuzzParseScannedPackagesLine
  scanner/alpine           FuzzParseApkIndex
  scanner/freebsd          FuzzParsePkgVersion, FuzzParseBlock
  scanner/suse             FuzzParseOSRelease
  config                   FuzzToCpeURI
  gost                     FuzzParseCwe
  contrib/snmp2cpe/pkg/cpe  FuzzConvert

Each target asserts: never panic; if err == nil, hold a small set of
output invariants (e.g. map key matches Name, no character invention,
non-empty mandatory fields).

Combined ~15s smoke run per target gives ~1.5M+ executions and surfaced
the six bugs fixed in the preceding commits.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR adds native Go fuzz targets across several parser-heavy packages and hardens the corresponding parsing code paths against malformed input. It fits into the codebase as a reliability-focused change set: improving parser robustness, preventing panics in config/package parsing, and enabling repository fuzzing coverage for scorecard/testing goals.

Changes:

  • Add new Fuzz* targets for parser helpers in scanner, detector, gost, config, and contrib/snmp2cpe.
  • Fix panic/guard conditions in Red Hat RPM parsing, FreeBSD parsing, CPE normalization, and SNMP-to-CPE conversion.
  • Add a go.mod exclude directive to avoid the grpc/stats/opentelemetry ambiguous import during builds.

Reviewed changes

Copilot reviewed 14 out of 14 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
scanner/suse_fuzz_test.go Adds fuzz coverage for SUSE /etc/os-release parsing invariants.
scanner/redhatbase_fuzz_test.go Adds fuzz targets for Red Hat installed-package parsing and RPM filename splitting.
scanner/redhatbase.go Hardens splitFileName against panic cases and empty-name outputs.
scanner/freebsd_fuzz_test.go Adds fuzz targets for FreeBSD pkg version and pkg-audit block parsing.
scanner/freebsd.go Adds bounds checks to avoid panics on truncated FreeBSD parser input.
scanner/debian_fuzz_test.go Adds fuzz coverage for Debian scanned-package line parsing.
scanner/alpine_fuzz_test.go Adds fuzz coverage for Alpine APKINDEX parsing.
gost/redhat_fuzz_test.go Adds fuzz coverage for Red Hat CWE string parsing.
go.mod Excludes the standalone gRPC OpenTelemetry module to resolve ambiguous imports.
detector/library_fuzz_test.go Adds fuzz coverage for Maven PURL canonicalization.
contrib/snmp2cpe/pkg/cpe/cpe_fuzz_test.go Adds fuzz coverage for SNMP result to CPE conversion.
contrib/snmp2cpe/pkg/cpe/cpe.go Guards Juniper ScreenOS parsing against missing version tokens.
config/tomlloader_fuzz_test.go Adds fuzz coverage for CPE URI normalization.
config/tomlloader.go Recovers from third-party CPE parsing panics and returns errors instead.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread scanner/redhatbase.go
Comment on lines +744 to 750
if epochIndex != -1 && epochIndex < verIndex {
epoch = basename[:epochIndex]
} else {
epochIndex = -1
}

name = basename[epochIndex+1 : verIndex]
Comment on lines +71 to +74
vfields := strings.Fields(v)
if len(vfields) == 0 {
break
}
Comment thread gost/redhat_fuzz_test.go
@@ -0,0 +1,44 @@
package gost
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.

3 participants