Version parsing errors not recovered during advisory scans
Summary
During advisory scans performed by GlobalAdvisoryScanWorker
,
SemverDialects
might fail to parse the version of a SBOM component matching the advisory, and raise an exception.
That exception is NOT recovered, and it makes the entire scanning job fail, preventing other SBOM components from being scanned.
NOTE: Unfortunately Sentry has grouped this with unrelated errors.
Further details
Error:
NoMethodError: undefined method `[]' for nil:NilClass
version_string = matches[:version]
^^^^^^^^^^
Overview of the stack trace:
- https://gitlab.com/gitlab-org/vulnerability-research/foss/semver_dialects/-/blob/ae047eafa7ce496628a4d472cedb9f6092ac2221/lib/semver_dialects/semantic_version/version_parser.rb#L15-16
- https://gitlab.com/gitlab-org/vulnerability-research/foss/semver_dialects/-/blob/ae047eafa7ce496628a4d472cedb9f6092ac2221/lib/semver_dialects.rb#L49
- https://gitlab.com/gitlab-org/gitlab/-/blob/9ea8f57526964d1b3daae3a5e1a717a262cf6ade/ee/lib/gitlab/vulnerability_scanning/advisory_scanner.rb#L63
- https://gitlab.com/gitlab-org/gitlab/-/blob/9ea8f57526964d1b3daae3a5e1a717a262cf6ade/ee/lib/gitlab/vulnerability_scanning/advisory_scanner.rb#L83
Steps to reproduce
Example Project
This happened on gitlab.com.
What is the current bug behavior?
The scanning job raises an exception.
What is the expected correct behavior?
The scanning job logs an error for the project, and continues on to the next projects of the same batch.
Relevant logs and/or screenshots
Raw stack trace
NoMethodError: undefined method `[]' for nil:NilClass
version_string = matches[:version]
^^^^^^^^^^
from semver_dialects (1.5.0) lib/semver_dialects/semantic_version/version_parser.rb:16:in `block in parse'
from semver_dialects (1.5.0) lib/semver_dialects/semantic_version/version_parser.rb:13:in `each'
from semver_dialects (1.5.0) lib/semver_dialects/semantic_version/version_parser.rb:13:in `parse'
from semver_dialects (1.5.0) lib/semver_dialects.rb:49:in `version_sat?'
from ee/lib/gitlab/vulnerability_scanning/dependency_scanning/affected_version_range_matcher.rb:26:in `affected?'
from ee/lib/gitlab/vulnerability_scanning/advisory_scanner.rb:63:in `occurrence_is_affected?'
from ee/lib/gitlab/vulnerability_scanning/advisory_scanner.rb:83:in `block in bulk_vulnerability_ingestion'
from activerecord (7.0.8) lib/active_record/relation/delegation.rb:88:in `each'
from activerecord (7.0.8) lib/active_record/relation/delegation.rb:88:in `each'
from ee/lib/gitlab/vulnerability_scanning/advisory_scanner.rb:80:in `filter_map'
from ee/lib/gitlab/vulnerability_scanning/advisory_scanner.rb:80:in `bulk_vulnerability_ingestion'
from ee/lib/gitlab/vulnerability_scanning/advisory_scanner.rb:40:in `block (2 levels) in execute'
from ee/app/finders/sbom/possibly_affected_occurrences_finder.rb:22:in `block in execute_in_batches'
from app/models/concerns/each_batch.rb:99:in `block (2 levels) in each_batch'
from activerecord (7.0.8) lib/active_record/relation.rb:881:in `_scoping'
from activerecord (7.0.8) lib/active_record/relation.rb:428:in `scoping'
from activerecord (7.0.8) lib/active_record/scoping/default.rb:43:in `unscoped'
from app/models/concerns/each_batch.rb:99:in `block in each_batch'
from app/models/concerns/each_batch.rb:69:in `step'
from app/models/concerns/each_batch.rb:69:in `each_batch'
from activerecord (7.0.8) lib/active_record/relation/delegation.rb:108:in `public_send'
from activerecord (7.0.8) lib/active_record/relation/delegation.rb:108:in `block in method_missing'
from activerecord (7.0.8) lib/active_record/relation.rb:881:in `_scoping'
from activerecord (7.0.8) lib/active_record/relation.rb:428:in `scoping'
from activerecord (7.0.8) lib/active_record/relation/delegation.rb:108:in `method_missing'
from ee/app/finders/sbom/possibly_affected_occurrences_finder.rb:21:in `execute_in_batches'
from ee/lib/gitlab/vulnerability_scanning/advisory_scanner.rb:39:in `block in execute'
from activerecord (7.0.8) lib/active_record/relation/delegation.rb:88:in `each'
from activerecord (7.0.8) lib/active_record/relation/delegation.rb:88:in `each'
from ee/lib/gitlab/vulnerability_scanning/advisory_scanner.rb:31:in `execute'
from ee/lib/gitlab/vulnerability_scanning/advisory_scanner.rb:12:in `scan_projects_for'
from ee/app/services/package_metadata/advisory_scan_service.rb:6:in `execute'
from ee/app/workers/package_metadata/global_advisory_scan_worker.rb:20:in `handle_event'
Output of checks
Results of GitLab environment info
Expand for output related to GitLab environment info
(For installations with omnibus-gitlab package run and paste the output of: `sudo gitlab-rake gitlab:env:info`) (For installations from source run and paste the output of: `sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production`)
Results of GitLab application Check
Expand for output related to the GitLab application check
(For installations with omnibus-gitlab package run and paste the output of:
sudo gitlab-rake gitlab:check SANITIZE=true
)(For installations from source run and paste the output of:
sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production SANITIZE=true
)(we will only investigate if the tests are passing)
Proposal
Recover from parsing errors of SBOM component version, log that error with project ID, and continue.
Parsing errors for the version range of the advisory should NOT be recovered. These should make the job fail entirely.
Implementation plan
- Update SemverDialects.
-
Raise distinct exceptions for the following cases: - Range can't be parsed.
- Version can't be parsed.
-
Release new version.
-
- Update gitlab.
-
Upgrade to the new version of the semver_dialects
gem. -
Update the AdvisoryScanner. - Catch and log version parsing error with version string and project ID.
- Let range parsing error fail. This one should NOT be recovered.
-
We might reuse CreateVulnerabilityService::process_recoverable_error
, or move it to a Rails concern class.