Skip to content

fix: match Red Hat OS distro in OSV vulnerability analysis#6325

Open
mvanhorn wants to merge 1 commit into
DependencyTrack:mainfrom
mvanhorn:fix/6156-redhat-osv-distro-matching
Open

fix: match Red Hat OS distro in OSV vulnerability analysis#6325
mvanhorn wants to merge 1 commit into
DependencyTrack:mainfrom
mvanhorn:fix/6156-redhat-osv-distro-matching

Conversation

@mvanhorn

@mvanhorn mvanhorn commented Jun 9, 2026

Copy link
Copy Markdown

Description

Adds Red Hat to the set of OS distributions consulted during OSV vulnerability matching, so Red Hat / UBI RPM components are only matched against advisories scoped to the same RHEL major version. This is the Red Hat equivalent of the Alpine/Debian/Ubuntu distro-aware matching introduced in #5783.

SBOMs from Red Hat / UBI9 images (e.g. pkg:rpm/redhat/libsolv@0.7.24-3.el9?distro=redhat-9.7) currently produce false positives, because an OSV Red Hat advisory is compared against the component without respecting the Red Hat product-stream / RHEL scope. In #6156 the reporter's RHEL 9 component (0.7.24-3.el9) is matched against an unrelated RHEL 8 Satellite advisory (1:0.7.20-6.el8sat).

OsDistribution already gates version comparison in InternalVulnAnalyzer.matchesDistro, but only Alpine/Debian/Ubuntu were modeled, so Red Hat fell through and the false positives stood. As @nscuro noted on the issue, Red Hat needs the same treatment, and OSV encodes the Red Hat product stream in the ecosystem string via a :<CPE> suffix.

Changes:

  • RedhatDistribution (new sealed OsDistribution record) modeling the RHEL major version, with matches that only matches like RHEL majors.
  • OsDistribution: added RedhatDistribution to the permits clause and extended of(PackageURL) to resolve rpm / redhat PURLs carrying a distro qualifier.
  • OsvEcosystems.toOsDistribution: added a red hat / redhat ecosystem branch that parses the :<CPE> suffix.

Addressed Issue

Fixes #6156

Additional Details

Red Hat OSV ecosystem CPEs do not all carry the RHEL major in the same field, so the parser handles them explicitly:

  • An explicit elN OS target is authoritative (e.g. Red Hat:openshift:4.18::el8 -> RHEL 8, not OpenShift 4), since layered-product versions are not the RHEL major.
  • Otherwise the major is taken from a base RHEL product (rhel, rhel_aus, rhel_eus, rhel_els, rhel_tus, rhel_e4s, enterprise_linux).
  • Layered products without an elN target (e.g. rhel_application_stack:2, rhel_atomic:7, satellite:6.16) return null rather than mis-scoping to a wrong RHEL major, which would create new false negatives.

Verified against real OSV feed entries while iterating on the CPE parsing.

Testing: mvn -pl support/os-distro-metadata test (115 tests, including 31 new in RedhatDistributionTest) and mvn -pl vuln-data-source/osv test -Dtest=OsvEcosystemsTest (40 tests) both pass. Tests cover the #6156 regression (RHEL 9 component not matched against a RHEL 8 stream advisory), same-major matches, base-RHEL and layered-product CPE forms, and PURL-qualifier resolution.

AI was used for assistance.

Checklist

  • I have read and understand the contributing guidelines
  • This PR fixes a defect, and I have provided tests to verify that the fix is effective
  • This PR implements an enhancement, and I have provided tests to verify that it works as intended
  • This PR introduces changes to the database model, and I have updated the migration changelog accordingly
  • This PR introduces new or alters existing behavior, and I have updated the documentation accordingly
  • This PR is a substantial change (per the ADR criteria), and I have added an ADR under docs/adr/

…yTrack#6156)

Signed-off-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com>
@owasp-dt-bot

Copy link
Copy Markdown

Snyk checks have passed. No issues have been found so far.

Status Scan Engine Critical High Medium Low Total (0)
Open Source Security 0 0 0 0 0 issues

💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse.

@codacy-production

codacy-production Bot commented Jun 9, 2026

Copy link
Copy Markdown

Up to standards ✅

🟢 Issues 0 issues

Results:
0 new issues

View in Codacy

🟢 Metrics 9 complexity · 1 duplication

Metric Results
Complexity 9
Duplication 1

View in Codacy

🟢 Coverage 96.97% diff coverage · +0.09% coverage variation

Metric Results
Coverage variation +0.09% coverage variation (-1.00%)
Diff coverage 96.97% diff coverage (70.00%)

View coverage diff in Codacy

Coverage variation details
Coverable lines Covered lines Coverage
Common ancestor commit (1b32636) 41682 36066 86.53%
Head commit (866b619) 41715 (+33) 36133 (+67) 86.62% (+0.09%)

Coverage variation is the difference between the coverage for the head and common ancestor commits of the pull request branch: <coverage of head commit> - <coverage of common ancestor commit>

Diff coverage details
Coverable lines Covered lines Diff coverage
Pull request (#6325) 33 32 96.97%

Diff coverage is the percentage of lines that are covered by tests out of the coverable lines that the pull request added or modified: <covered lines added or modified>/<coverable lines added or modified> * 100%

NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.

@nscuro nscuro added the enhancement New feature or request label Jun 9, 2026
@nscuro nscuro added this to the 5.1 milestone Jun 9, 2026

@nscuro nscuro left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks! A few changes to extend the coverage and accuracy.

// authoritative OS scope and takes precedence over a product version that
// happens to differ from the RHEL major (OpenShift 4.18 runs on RHEL 8).
private static final Pattern EL_TARGET_PATTERN =
Pattern.compile(".*\\bel(\\d+)\\b.*", Pattern.CASE_INSENSITIVE);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

To also handle minor versions:

.*\\bel(\\d+)(?:\\.(\\d+))?\\b.*

Comment on lines +88 to +90
final String value = qualifierValue.toLowerCase().startsWith("redhat-")
? qualifierValue.substring("redhat-".length())
: qualifierValue;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Some SBOM generators like Syft use rhel- instead of redhat- for the distro qualifier. We should handle both here.

* carry a RHEL major version, which is the smallest reliably comparable scope:
* an advisory for RHEL 8 must not be matched against a RHEL 9 component.
*
* @since 4.14.0

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
* @since 4.14.0
* @since 5.1.0

Comment on lines +55 to +58
private static final Pattern RHEL_PRODUCT_PATTERN =
Pattern.compile("^(?:rhel|rhel_aus|rhel_eus|rhel_els|rhel_tus|rhel_e4s|enterprise_linux):(\\d+)(?:[.:].*)?$",
Pattern.CASE_INSENSITIVE);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Based on the OSV data set, there are a few more product names we should capture:

Suggested change
private static final Pattern RHEL_PRODUCT_PATTERN =
Pattern.compile("^(?:rhel|rhel_aus|rhel_eus|rhel_els|rhel_tus|rhel_e4s|enterprise_linux):(\\d+)(?:[.:].*)?$",
Pattern.CASE_INSENSITIVE);
private static final Pattern RHEL_PRODUCT_PATTERN = Pattern.compile("""
^(?:\
enterprise_linux\\w*\
|enterprise_ipa\
|rhel_application_server\
|rhel_aus\
|rhel_cluster\
|rhel_cluster_storage\
|rhel_developer_suite\
|rhel_e4s\
|rhel_els\
|rhel_eus\
|rhel_eus_long_life\
|rhel_extras\\w*\
|rhel_global_file_system\
|rhel_mission_critical\
|rhel_productivity\
|rhel_rhn_tools\
|rhel_sjis\
|rhel_stronghold\
|rhel_tus\
|rhel_virtualization\
):(\\d+)(?:\\.(\\d+))?(?:[.:].*)?$\
""", Pattern.CASE_INSENSITIVE);

That would also capture minor versions.

*
* @since 4.14.0
*/
public record RedhatDistribution(String majorVersion) implements OsDistribution {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We should also track the (optional) minor version. A large chunk of the OSV data provides Red Hat versions in the major.minor format. So if OSV claims a vuln to affect redhat-8.6, it shouldn't match against a component with redhat-8.7. But it should match a component with redhat-8 and vice-versa. matches would need to be updated accordingly.

// The leading major version of a PURL "distro" qualifier value, e.g. "9" in
// "redhat-9.7" or "9.7". PURL qualifiers carry a bare RHEL version, not a CPE.
private static final Pattern PURL_VERSION_PATTERN =
Pattern.compile("^(\\d+)(?:\\..*)?$");

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

To also handle minor versions:

Suggested change
Pattern.compile("^(\\d+)(?:\\..*)?$");
Pattern.compile("^(\\d+)(?:\\.(\\d+))?(?:\\..*)?$");

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

False positives: RedHat scanner result are compared with wrong version format

3 participants