From fb2ddde9bf58fd9e58577f5f93bbdbb2a188317e Mon Sep 17 00:00:00 2001 From: Tim Lee Date: Thu, 12 Feb 2026 15:21:59 -0700 Subject: [PATCH] Scan goval-dict for rhel kernel vulnerabilities(#39749) --- changes/33990-rhel-kernel-vulns | 1 + .../cards/OperatingSystems/OSTableConfig.tsx | 2 +- server/datastore/mysql/software.go | 8 +++++ server/datastore/mysql/software_test.go | 30 +++++++++++++++++++ server/fleet/software.go | 7 +++-- server/service/osquery_utils/queries.go | 7 +++++ .../goval_dictionary/analyzer.go | 10 ++++++- server/vulnerabilities/oval/oval_platform.go | 22 ++++++++++++++ .../oval/oval_platform_test.go | 23 ++++++++++++++ 9 files changed, 105 insertions(+), 5 deletions(-) create mode 100644 changes/33990-rhel-kernel-vulns diff --git a/changes/33990-rhel-kernel-vulns b/changes/33990-rhel-kernel-vulns new file mode 100644 index 0000000000..774112d478 --- /dev/null +++ b/changes/33990-rhel-kernel-vulns @@ -0,0 +1 @@ +- added ability to scan for kernel vulnerabilities on RHEL based hosts \ No newline at end of file diff --git a/frontend/pages/DashboardPage/cards/OperatingSystems/OSTableConfig.tsx b/frontend/pages/DashboardPage/cards/OperatingSystems/OSTableConfig.tsx index fd92872d10..8a6f3ef5ed 100644 --- a/frontend/pages/DashboardPage/cards/OperatingSystems/OSTableConfig.tsx +++ b/frontend/pages/DashboardPage/cards/OperatingSystems/OSTableConfig.tsx @@ -119,7 +119,7 @@ const generateDefaultTableHeaders = ( tipContent={ <> Vulnerabilities on Linux are currently supported
- for Ubuntu, Debian, and Amazon Linux. + for Ubuntu, Debian, and RHEL based systems. } > diff --git a/server/datastore/mysql/software.go b/server/datastore/mysql/software.go index 5a355a4cd3..f2b7ae5e1f 100644 --- a/server/datastore/mysql/software.go +++ b/server/datastore/mysql/software.go @@ -3060,6 +3060,10 @@ func (ds *Datastore) ListSoftwareForVulnDetection( baseSQL += "JOIN host_software hs ON s.id = hs.software_id " } + if filters.KernelsOnly { + baseSQL += "JOIN software_titles st ON s.title_id = st.id " + } + conditions := []string{} if filters.HostID != nil { @@ -3077,6 +3081,10 @@ func (ds *Datastore) ListSoftwareForVulnDetection( args = append(args, filters.Source) } + if filters.KernelsOnly { + conditions = append(conditions, "st.is_kernel = 1") + } + if len(conditions) > 0 { sqlstmt = baseSQL + "WHERE " + strings.Join(conditions, " AND ") } else { diff --git a/server/datastore/mysql/software_test.go b/server/datastore/mysql/software_test.go index dce122003a..89c395b38e 100644 --- a/server/datastore/mysql/software_test.go +++ b/server/datastore/mysql/software_test.go @@ -2804,6 +2804,36 @@ func testListSoftwareForVulnDetection(t *testing.T, ds *Datastore) { require.Equal(t, "baz", result[0].Name) require.Equal(t, "biz", result[1].Name) }) + + t.Run("KernelsOnly filter returns only kernel software", func(t *testing.T) { + ctx := context.Background() + + host := test.NewHost(t, ds, "host_kernel_test", "", "hostkernelkey", "hostkerneluuid", time.Now()) + host.Platform = "rhel" + require.NoError(t, ds.UpdateHost(ctx, host)) + + software := []fleet.Software{ + {Name: "kernel", Version: "5.14.0-503.38.1.el9_5", Release: "", Arch: "x86_64", Source: "rpm_packages", IsKernel: true}, + {Name: "kernel-modules", Version: "5.14.0", Release: "503.38.1.el9_5", Arch: "x86_64", Source: "rpm_packages", IsKernel: false}, + {Name: "bash", Version: "5.1.8", Release: "6.el9_1", Arch: "x86_64", Source: "rpm_packages", IsKernel: false}, + {Name: "openssl", Version: "3.0.7", Release: "18.el9_2", Arch: "x86_64", Source: "rpm_packages", IsKernel: false}, + } + _, err := ds.UpdateHostSoftware(ctx, host.ID, software) + require.NoError(t, err) + + // Test KernelsOnly filter + filter := fleet.VulnSoftwareFilter{HostID: &host.ID, KernelsOnly: true} + result, err := ds.ListSoftwareForVulnDetection(ctx, filter) + require.NoError(t, err) + require.Len(t, result, 1) + require.Equal(t, "kernel", result[0].Name) + + // Verify non-kernel filter returns all software + filter = fleet.VulnSoftwareFilter{HostID: &host.ID, KernelsOnly: false} + result, err = ds.ListSoftwareForVulnDetection(ctx, filter) + require.NoError(t, err) + require.Len(t, result, 4) + }) } func testSoftwareByIDNoDuplicatedVulns(t *testing.T, ds *Datastore) { diff --git a/server/fleet/software.go b/server/fleet/software.go index 6fd404b025..337290a162 100644 --- a/server/fleet/software.go +++ b/server/fleet/software.go @@ -255,9 +255,10 @@ type VulnerableSoftware struct { } type VulnSoftwareFilter struct { - HostID *uint - Name string // LIKE filter - Source string // exact match + HostID *uint + Name string // LIKE filter + Source string // exact match + KernelsOnly bool // filter to kernel packages only (for RHEL goval-dictionary scanning) } type SliceString []string diff --git a/server/service/osquery_utils/queries.go b/server/service/osquery_utils/queries.go index 7c4121b947..649a3bdb4e 100644 --- a/server/service/osquery_utils/queries.go +++ b/server/service/osquery_utils/queries.go @@ -2275,6 +2275,13 @@ func MutateSoftwareOnIngestion(s *fleet.Software, logger log.Logger) { } } } + + // For RHEL kernels, join version and release to match OVAL format. + // See server/vulnerabilities/goval_dictionary/database.Eval + if s != nil && s.Source == "rpm_packages" && s.Name == rpmKernelName && s.Release != "" { + s.Version = fmt.Sprintf("%s-%s", s.Version, s.Release) + s.Release = "" // Clear release to avoid issues with vulnerability matching + } } // shouldRemoveSoftware returns whether or not we should remove the given Software item from this diff --git a/server/vulnerabilities/goval_dictionary/analyzer.go b/server/vulnerabilities/goval_dictionary/analyzer.go index 6350c4e77f..dfd0f4b041 100644 --- a/server/vulnerabilities/goval_dictionary/analyzer.go +++ b/server/vulnerabilities/goval_dictionary/analyzer.go @@ -40,6 +40,11 @@ func Analyze( return nil, err } + // For kernel-only platforms (e.g., RHEL), we only scan kernel packages via goval-dictionary. + // Non-kernel packages are scanned via regular OVAL processing. This keeps the testing + // surface smaller. We can consider expanding scope to all packages in the future if needed. + kernelsOnly := platform.IsGovalDictionaryKernelOnly() + // Since hosts and software have a M:N relationship, the following sets are used to // avoid doing duplicated inserts/delete operations (a vulnerable software might be // present in many hosts). @@ -61,7 +66,10 @@ func Analyze( foundInBatch := make(map[uint][]fleet.SoftwareVulnerability) for _, hostID := range hostIDs { hostID := hostID - software, err := ds.ListSoftwareForVulnDetection(ctx, fleet.VulnSoftwareFilter{HostID: &hostID}) + software, err := ds.ListSoftwareForVulnDetection(ctx, fleet.VulnSoftwareFilter{ + HostID: &hostID, + KernelsOnly: kernelsOnly, + }) if err != nil { return nil, err } diff --git a/server/vulnerabilities/oval/oval_platform.go b/server/vulnerabilities/oval/oval_platform.go index 8d137eb0ab..993f0c4f03 100644 --- a/server/vulnerabilities/oval/oval_platform.go +++ b/server/vulnerabilities/oval/oval_platform.go @@ -25,6 +25,17 @@ var SupportedGovalPlatforms = []string{ "amzn_02", "amzn_2022", "amzn_2023", + "rhel_07", + "rhel_08", + "rhel_09", +} + +// GovalKernelOnlyPlatforms are platforms where goval-dictionary is used only for kernel vulnerability scanning. +// These platforms use the regular OVAL scanning for non-kernel packages. +var GovalKernelOnlyPlatforms = []string{ + "rhel_07", + "rhel_08", + "rhel_09", } // getMajorMinorVer returns the major and minor version of an 'os_version'. @@ -129,6 +140,17 @@ func (op Platform) IsGovalDictionarySupported() bool { return false } +// IsGovalDictionaryKernelOnly returns true if this platform uses goval-dictionary +// only for kernel vulnerability scanning (non-kernel packages use regular OVAL). +func (op Platform) IsGovalDictionaryKernelOnly() bool { + for _, p := range GovalKernelOnlyPlatforms { + if strings.HasPrefix(string(op), p) { + return true + } + } + return false +} + // IsUbuntu checks whether the current Platform targets Ubuntu. func (op Platform) IsUbuntu() bool { return strings.HasPrefix(string(op), "ubuntu") diff --git a/server/vulnerabilities/oval/oval_platform_test.go b/server/vulnerabilities/oval/oval_platform_test.go index d95bb6d4a3..b546641b8f 100644 --- a/server/vulnerabilities/oval/oval_platform_test.go +++ b/server/vulnerabilities/oval/oval_platform_test.go @@ -104,4 +104,27 @@ func TestOvalPlatform(t *testing.T) { require.Equal(t, c.expected, plat.ToGovalDatabaseFilename()) } }) + + t.Run("IsGovalDictionaryKernelOnly", func(t *testing.T) { + cases := []struct { + platform string + osVersion string + kernelOnly bool + }{ + // Amazon Linux is NOT kernel-only (full goval-dictionary scanning) + {"amzn", "Amazon Linux 1.0.0", false}, + {"amzn", "Amazon Linux 2.0.0", false}, + {"amzn", "Amazon Linux 2023.0.0", false}, + // RHEL is kernel-only (only kernel packages scanned via goval-dictionary) + {"rhel", "CentOS Linux 7.9.2009", true}, + {"rhel", "CentOS Linux 8.3.2011", true}, + {"rhel", "Red Hat Enterprise Linux 9.0.0", true}, + // Ubuntu is not supported by goval-dictionary at all + {"ubuntu", "Ubuntu 20.4.0", false}, + } + for _, c := range cases { + plat := NewPlatform(c.platform, c.osVersion) + require.Equal(t, c.kernelOnly, plat.IsGovalDictionaryKernelOnly(), "platform=%s, osVersion=%s", c.platform, c.osVersion) + } + }) }