Skip to content

getNextFixVersion panics on an advisory with an unparsable fixed version, crashing result output #2936

Open

Description

@ashvinctrl

Summary

internal/output/output_result.go builds the scan result for every matched
package. For each vulnerability it calls getNextFixVersion, which parses the
fixed string of every affected range event with semantic.MustParse
(output_result.go:590). MustParse panics when the ecosystem's version parser
returns an error, so a single advisory whose fixed value is unparsable aborts
the entire scan after matching has already succeeded. Because every output
format (table, JSON, SARIF, HTML) routes through BuildResults, all of them
crash.

This is a distinct site from #2867 (match path, ecosystem-name MustParse) and
#2837 (match path, version parse). Neither touched the output/result-building
path.

Affected code

internal/output/output_result.go:590:

order, _ = semantic.MustParse(affectedEvent.GetFixed(), ecosystemPrefix).CompareStr(minFixVersion)

semantic.Parse returns ErrUnsupportedEcosystem for ecosystems with no
version parser, and ErrInvalidVersion for a malformed version in ecosystems
whose parser validates input (CRAN, Debian, Alpine, Ubuntu, Hackage, ...).
MustParse turns either error into a panic. The guard at :584 does not
prevent this: vp.CompareStr(GetFixed()) returns (0, err) on an unparsable
fixed, the error is discarded, so order == 0, the order > 0 skip does not
fire, and execution reaches the panicking MustParse.

Reproduction (public CLI, offline DB)

Reproduces end-to-end through the documented --offline-vulnerabilities / --local-db-path feature — no internal calls. mypkg@1.0.0 (CRAN) matches an
advisory by its versions list, and result building then parses the range's
malformed fixed value and panics. Real advisories routinely carry both a
versions list and ranges.

WORK="$(mktemp -d)"
mkdir -p "$WORK/target"
cat > "$WORK/target/renv.lock" <<'EOF'
{ "R": { "Version": "4.3.0" },
  "Packages": { "mypkg": { "Package": "mypkg", "Version": "1.0.0", "Repository": "CRAN" } } }
EOF

python3 - "$WORK" <<'PY'
import json,os,sys,zipfile
work=sys.argv[1]
adv={"id":"CB-CRAN-REPRO","affected":[
 {"package":{"ecosystem":"CRAN","name":"mypkg"},
  "versions":["1.0.0"],
  "ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"notaversion"}]}]}]}
zp=os.path.join(work,"db","osv-scanner","CRAN","all.zip")
os.makedirs(os.path.dirname(zp),exist_ok=True)
zipfile.ZipFile(zp,"w",zipfile.ZIP_DEFLATED).writestr("CB-CRAN-REPRO.json",json.dumps(adv))
PY

go run ./cmd/osv-scanner scan source \
  --offline-vulnerabilities --local-db-path "$WORK/db" "$WORK/target"

Actual behavior

panic: invalid version: 'notaversion' is not allowed
  semantic.MustParse            semantic/parse.go:36
  output.getNextFixVersion      internal/output/output_result.go:590
  output.updateVuln             internal/output/output_result.go:541
  output.processPackage         internal/output/output_result.go:462
  output.BuildResults           internal/output/output_result.go:202
  output.PrintTableResults      internal/output/table.go:30

Expected behavior

An unparsable fixed version is skipped (that event simply cannot be offered as
a fix); the scan completes and reports the matched vulnerability with no
available fix.

Why it is reachable without an attacker

The per-ecosystem DB zip stores the full advisory record, and the vendored
osv-schema / semantic parsers are pinned per release while osv.dev's data
evolves. Any advisory that carries a fixed string this build cannot parse —
future syntax, an ecosystem whose parser validates input, or a self-maintained
local DB — crashes result building on a package that otherwise matched. Broad
blast radius for a tool that runs in CI.

Suggested fix

Use the non-panicking semantic.Parse and skip the event on error, mirroring
the "skip, don't panic on unsupported ecosystems/versions" approach already
taken in #2837/#2867. The default (parsable) path is unchanged.

I have a fix and reproducing tests ready and would be happy to open a PR once
this is assigned.


Related, separate observation (not part of this fix): internal/output/table.go:334
uses osvecosystem.MustParse(eco.Name) on the scanned package's ecosystem in the
table renderer. Reachability depends on whether an unrecognized ecosystem can
reach output (e.g. via SBOM input); worth a separate look but not covered here.

Activity

  1. G-Rath commented on Jul 22, 2026

    @G-Rath
    Collaborator

    Do you have a reproduction using a real published advisory, rather than one you've crafted?

  2. ashvinctrl commented on Jul 22, 2026

    @ashvinctrl
    Author

    No, there's no real published advisory that triggers it. I ran every affected[].ranges[].fixed value through semantic.Parse for its own ecosystem across all the validating ecosystems (Alpine, CRAN, Debian, Ubuntu, Hackage, Wolfi, Chainguard — ~193k records). Nothing errors on the path getNextFixVersion actually takes. The only rejecting value is a git commit hash in CVE-2024-38998, and that's in a GIT-range entry with an empty ecosystem, which line 579 filters out before the parse — so it's not reachable.

    So this is a latent / forward-compatibility crash, not something current osv.dev data hits — same situation as #2867. It's deterministic with a self-hosted --local-db-path DB (repro in the issue), and it'll fire if a fixed value the pinned parser can't handle ever lands in a scannable ecosystem. I'm happy to send the one-line MustParse -> Parse-and-skip fix on that basis, but if you'd rather only track output-path panics that live data actually reaches, feel free to close.

  3. hamodywe commented on Aug 16, 2026

    @hamodywe

    Could this be assigned to me? Per CONTRIBUTING I'd rather ask before opening anything — the fix is written and verified locally, and I'm happy to drop it if you'd rather not take this.

    On the question of a real advisory: I agree with the earlier answer that none currently triggers it, and I don't think that settles it either way. Two things make it look worth hardening regardless:

    The change is small: CompareStr already returns an error on an unparsable version and it was being discarded, which is why the existing order > 0 guard doesn't stop execution reaching MustParse. Using semantic.Parse and skipping the event when it fails keeps a valid fix that appears alongside an unparsable one, so nothing that works today changes.

    I verified it the way you'd want: with the change reverted the new test fails with panic: invalid version: 'notaversion' is not allowed, and with it applied the whole internal/output package is green.

  4. ashvinctrl commented on Aug 16, 2026

    @ashvinctrl
    Author

    @hamodywe go ahead, it's yours. No point in two of us sending the same patch.

    One thing worth carrying over so you don't have to redo it. I ran every affected[].ranges[].fixed value through the real semantic.Parse for its own ecosystem across the validating ones (Alpine, CRAN, Debian, Ubuntu, Hackage, Wolfi, Chainguard), about 193k records, and nothing reaches the panic. The only value that rejects is the git commit hash in CVE-2024-38998, and that sits in a GIT-range entry with an empty ecosystem, which the check at line 579 filters out before getNextFixVersion ever sees it.

    So this is forward-compat hardening rather than a live crash, same shape as #2867. Worth saying that plainly in the PR so @G-Rath can weigh it for what it actually is.

  5. github-actions commented on Oct 15, 2026

    @github-actions

    This issue has not had any activity for 60 days and will be automatically closed in two weeks

    See https://github.com/google/osv-scanner/blob/main/CONTRIBUTING.md for how to contribute a PR if you're interested in helping out.

  6. added
    staleThe issue or PR is stale and pending automated closure
    on Oct 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    staleThe issue or PR is stale and pending automated closure

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions