From bed1c6a3187aa12ef05b22722fd7b6dc89350552 Mon Sep 17 00:00:00 2001 From: Ian Littman Date: Fri, 25 Jul 2025 08:45:39 -0500 Subject: [PATCH] Add software sanitation on ingest back, use it to fix DCV Viewer versions (#31251) We'll want to pull this into a feed so fixes don't take a Fleet release to propagate, and some fixes currently in the vulns mutations list should probably move over here (as they're also dealing with non-semver versions), but that's out of scope for this particular fix. Fixes #31123. # Checklist for submitter If some of the following don't apply, delete the relevant line. - [x] Changes file added for user-visible changes in `changes/`, `orbit/changes/` or `ee/fleetd-chrome/changes`. See [Changes files](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/guides/committing-changes.md#changes-files) for more information. - [x] Input data is properly validated, `SELECT *` is avoided, SQL injection is prevented (using placeholders for values in statements) - [x] Added/updated automated tests - [x] Manual QA for all new/changed functionality --- changes/31123-dcv-viewer-fix | 1 + server/service/osquery_utils/queries.go | 38 ++++++++++++++++++++ server/service/osquery_utils/queries_test.go | 29 +++++++++++++++ 3 files changed, 68 insertions(+) create mode 100644 changes/31123-dcv-viewer-fix diff --git a/changes/31123-dcv-viewer-fix b/changes/31123-dcv-viewer-fix new file mode 100644 index 0000000000..d63ebf1cc2 --- /dev/null +++ b/changes/31123-dcv-viewer-fix @@ -0,0 +1 @@ +* Added back software mutation on ingestion to fix non-semver-compliant software versions, starting with DCV Viewer. diff --git a/server/service/osquery_utils/queries.go b/server/service/osquery_utils/queries.go index eef2f60ec8..64330e5976 100644 --- a/server/service/osquery_utils/queries.go +++ b/server/service/osquery_utils/queries.go @@ -1756,6 +1756,8 @@ func directIngestSoftware(ctx context.Context, logger log.Logger, host *fleet.Ho continue } + MutateSoftwareOnIngestion(s, logger) + if shouldRemoveSoftware(host, s) { continue } @@ -1799,6 +1801,42 @@ func directIngestSoftware(ctx context.Context, logger log.Logger, host *fleet.Ho return nil } +var ( + dcvVersionFormat = regexp.MustCompile(`^(\d+\.\d+)\s*\(r(\d+)\)$`) + softwareSanitizers = []struct { + matches func(*fleet.Software) bool + mutate func(*fleet.Software, log.Logger) + }{ + { + matches: func(s *fleet.Software) bool { + return s.Source == "apps" && s.BundleIdentifier == "com.nicesoftware.dcvviewer" + }, + mutate: func(s *fleet.Software, logger log.Logger) { + if versionMatches := dcvVersionFormat.FindStringSubmatch(s.Version); len(versionMatches) == 3 { + s.Version = fmt.Sprintf("%s.%s", versionMatches[1], versionMatches[2]) + } + }, + }, + } +) + +// MutateSoftwareOnIngestion performs any tweaks required to the ingested software fields. +// +// Some fields are reported with known incorrect values and we need to fix them before using them. +func MutateSoftwareOnIngestion(s *fleet.Software, logger log.Logger) { + for _, softwareSanitizer := range softwareSanitizers { + if softwareSanitizer.matches(s) { + defer func() { + if r := recover(); r != nil { + level.Warn(logger).Log("msg", "panic during software mutation", "softwareName", s.Name, "softwareVersion", s.Version, "error", r) + } + }() + softwareSanitizer.mutate(s, logger) + break + } + } +} + // shouldRemoveSoftware returns whether or not we should remove the given Software item from this // host's software list. func shouldRemoveSoftware(h *fleet.Host, s *fleet.Software) bool { diff --git a/server/service/osquery_utils/queries_test.go b/server/service/osquery_utils/queries_test.go index be87f0da2e..0d4a2884df 100644 --- a/server/service/osquery_utils/queries_test.go +++ b/server/service/osquery_utils/queries_test.go @@ -33,6 +33,35 @@ import ( "golang.org/x/exp/maps" ) +func TestSoftwareIngestionMutations(t *testing.T) { + dcvViewer := &fleet.Software{ + BundleIdentifier: "com.nicesoftware.dcvviewer", + Source: "apps", + Version: "2024.0 (r8004)", + } + + MutateSoftwareOnIngestion(dcvViewer, log.NewNopLogger()) + assert.Equal(t, "2024.0.8004", dcvViewer.Version) + + noOp := &fleet.Software{ + BundleIdentifier: "com.nicesoftware.dcvviewer", + Source: "apps", + Version: "2024", + } + + MutateSoftwareOnIngestion(dcvViewer, log.NewNopLogger()) + assert.Equal(t, "2024", noOp.Version) + + noMatch := &fleet.Software{ + BundleIdentifier: "com.google.chrome", + Source: "apps", + Version: "2024.0 (r8004)", + } + + MutateSoftwareOnIngestion(noMatch, log.NewNopLogger()) + assert.Equal(t, "2024.0 (r8004)", noMatch.Version) +} + func TestDetailQueryNetworkInterfaces(t *testing.T) { var initialHost fleet.Host host := initialHost