Skip to content

fix(detector/vuls2): let only an Ubuntu build's own pocket judge it - #2650

Draft
MaineK00n wants to merge 1 commit into
masterfrom
MaineK00n/ubuntu-pocket-lineage
Draft

fix(detector/vuls2): let only an Ubuntu build's own pocket judge it#2650
MaineK00n wants to merge 1 commit into
masterfrom
MaineK00n/ubuntu-pocket-lineage

Conversation

@MaineK00n

@MaineK00n MaineK00n commented Aug 26, 2026

Copy link
Copy Markdown
Collaborator

What did you implement:

Fixes #2645.

The ubuntu-cve-tracker states one release in several pockets that all land in the same ecosystem and are told apart only by the detection tag: the plain <release> tag for the archive, esm-apps/<release> and esm-infra/<release> (and their -legacy variants) for Ubuntu Pro, fips/<release> and fips-updates/<release> for FIPS.

walkVulnerabilityDetections evaluated every condition on its own, so an installed version got compared against fixed versions from build lineages it never came from. Those numbers do not order across pockets:

$ dpkg --compare-versions 3.4.0-1ubuntu0.1~esm1 lt 3.4.0-1ubuntu0.1 && echo TRUE
TRUE     # an archive build sorts ABOVE the esm-apps build that reserves the next archive version

$ dpkg --compare-versions 1.0.2g-1ubuntu4.fips.4.20.9 gt 1.0.2g-1ubuntu4.20+esm9 && echo TRUE
TRUE     # a FIPS build sorts above both

Three consequences, all reproducible against the nightly DB:

1. The reported bug (#2645). A universe package the archive never fixes stays needed in the plain pocket forever, while esm-apps publishes the only fix. vuls db search root CVE-2024-24258 --datasource ubuntu-cve-tracker, ubuntu:24.04:

tag freeglut
esm-apps/noble_medium fixed, < 3.4.0-1ubuntu0.1~esm1
noble_medium unfixed, vendor needed (no range — matches every version)

With 3.4.0-1ubuntu0.1~esm1 installed the esm-apps criterion simply does not match, so the plain pocket's needed is the only statement left and the build carrying the fix is reported as vulnerable. The reporter saw 60 such findings on one Pro-attached noble host.

2. Both pockets fixed, at different versions. ubuntu:20.04 / imagemagick / CVE-2021-20224:

focal_medium          : fixed, < 8:6.9.10.23+dfsg-2.1ubuntu11.9
esm-apps/focal_medium : fixed, < 8:6.9.10.23+dfsg-2.1ubuntu11.4+esm1

An installed …11.4+esm1 is below the archive's fixed version, so the plain pocket reported a package the ESM pocket had already fixed.

3. FIPS hosts. ignoreCriterion dropped every FIPS tag outright to keep non-FIPS hosts from being judged by FIPS statements. That left FIPS hosts judged by archive statements their builds sort above — silently clearing them — and pointed the ones that were still reported at an archive fixed version they cannot install.

The change

Model the pockets as what they are — separate build lineages — and let exactly one of them judge each installed build: the first of its pocketFallbacks that says anything about the package. A build no candidate pocket speaks about is dropped from every criterion.

tagPocket tag → archive / esm / fips / unknown. The tracker's release keys put the service on either side of the slash (esm-apps/noble, but trusty/esm), so both are looked up
installedPocket reads the pocket off Canonical's version convention (~esmN / +esmN / +esm.N, .fips. / +fips.N)
pocketStatements collects, over all the sibling conditions, which pockets have a criterion that survives ignoreCriterion
pocketFallbacks esm→[esm, archive], archive→[archive, esm], fips→[fips, esm, archive]
narrowToPocket drops the scanned packages this criterion's pocket does not get to judge

narrowToPocket is applied in filterCriterion, which already exists to narrow Accepts.Version for ecosystem-specific reasons (the running-kernel filter). ignoreCriterion's esm allowlist becomes "is this a pocket we know", so FIPS tags are kept and narrowed instead of dropped.

Two details that matter:

  • Only usable criterions count as a pocket statement. 24,735 of the 25,666 plain-unfixed / esm-fixed pairs in the DB are ignored: end of standard support, which ignoreCriterion already drops. Counting those as an archive statement would stop the ESM statement being used at all on xenial and trusty.
  • Nothing falls back INTO FIPS. That is what makes keeping the FIPS tags safe for non-FIPS hosts, and it is why the existing fips-updates/jammy_low expectation in Test_postConvert is unchanged.

models.Package.Repository now reaches the query only where the data gates on it

The authoritative pocket is the apt origin of the installed version. vuls does not report it:

  • models.Packages.MergeNewVersion overwrites Repository with the candidate version's repository for every package with a pending update, in every family that scans updatables.
  • scanner/debian.go fills that from apt-cache policy with the candidate's suite (noble-updates/main), and only outside fast / offline scans.
  • The one installed-origin query, repoquery --pkgnarrow=installed --qf='… %{UI_FROM_REPO}', runs only for Amazon Linux 2 with yum-utils present.

preConvertPkgs now carries Repository into the vuls2 query only for the ecosystems whose data actually gates on it — redhat (centos resolves to the redhat ecosystem too), amazon, alpine. An allowlist rather than a denylist, so a family added later, or a scanner that starts reporting something new, defaults to not gating; that direction only ever loses filtering, never a detection. For alma / rocky / oracle / fedora / suse / debian / ubuntu it is a no-op or the fix, since none of their data sources emit repositories.

installedPocket should consult the field once a scanner reports the installed build's own origin, keeping the version convention as the fallback for fast / offline scans and older result JSON.

This also guards against MaineK00n/vuls-data-update#953: without it, a candidate suite reaching the repository gate would filter out exactly the packages that have an update available. MaineK00n/vuls2#431 applies the same allowlist on the vuls2 CLI's own path, which never goes through preConvertPkgs.

Type of change

  • Bug fix (non-breaking change which fixes an issue)

How Has This Been Tested?

Test_postConvert gains "ubuntu: a pocket only judges the builds installed from it", with the four situations above:

installed plain esm / fips result
2.8.1-6ubuntu0.1~esm1 needed esm fixed <2.8.1-6ubuntu0.1~esm1 not reported (#2645)
1.7.15-1 (archive) needed esm fixed <1.7.15-1ubuntu0.1~esm1 reported needed (unchanged)
…22.04.1+esm1 fixed <…22.04.5 esm fixed <…22.04.1+esm1 not reported
3.0.2-0ubuntu1.10+fips.1 fixed <3.0.2-0ubuntu1.12 fips fixed <3.0.2-0ubuntu1.10+fips.2 reported with fixedIn: 3.0.2-0ubuntu1.10+fips.2

Verified to have teeth: with the narrowing disabled and the FIPS drop restored, rows 1, 3 and 4 all fail.

Also added:

  • Test_tagPocket / Test_installedPocket, which pin the version convention — including that a marker-less Ubuntu Pro build (4.15.0-1146.161~14.04.1, as esm-infra republishes for CVEs fixed before a release left standard support) reads as archive and is covered by pocketFallbacks instead. 17,117 of esm-infra's 17,605 fixed statements are marker-less, so this is the normal case, not an edge one.
  • A Test_preConvertPkgs case for the dropped candidate suite.

Every existing test passes with no expectation changed.

$ go test ./detector/... ./models/...
ok  	github.com/future-architect/vuls/detector	0.031s
ok  	github.com/future-architect/vuls/detector/vuls2	0.732s
ok  	github.com/future-architect/vuls/models	0.056s

Known gaps

Neither is a regression; both need the installed build's apt origin (a scanner change) to close.

  • Marker-less Ubuntu Pro builds — mostly trusty / xenial kernels. pocketFallbacks keeps them detected, but where the archive and ESM statements disagree the archive's is used. e.g. ubuntu:16.04 / linux / CVE-2020-12352 is 4.4.0-197.229 in the archive and 4.4.0-262.296 in esm-infra.
  • esm-infra vs esm-infra-legacy — separate pockets sharing one version convention, so they collapse into pocketESM and their statements are OR-ed. They disagree for 299 packages in the DB sample.

Reference

Checklist:

  • Write tests
  • Write documentation
  • Check that there aren't other open pull requests for the same issue/feature
  • Format your source code by make fmt
  • Pass the test by make test
  • Provide verification config / commands
  • Enable "Allow edits from maintainers" for this PR
  • Update the messages below

Is this ready for review?: NO

🤖 Generated with Claude Code

Fixes #2645.

The ubuntu-cve-tracker states one release in several pockets that all
land in the same ecosystem and are told apart only by the detection tag:
the plain <release> tag for the archive, esm-apps/<release> and
esm-infra/<release> (and their -legacy variants) for Ubuntu Pro,
fips/<release> and fips-updates/<release> for FIPS. Every condition was
evaluated on its own, so an installed version got compared against fixed
versions from build lineages it never came from -- and those numbers do
not order across pockets:

    3.4.0-1ubuntu0.1~esm1  <  3.4.0-1ubuntu0.1
      an archive build sorts ABOVE the esm-apps build that reserves the
      next archive version

    1.0.2g-1ubuntu4.fips.4.20.9  >  1.0.2g-1ubuntu4.20+esm9  >  1.0.2g-1ubuntu4.20
      a FIPS build sorts above both

Three consequences, all reproducible against the nightly DB:

  * A universe package the archive never fixes stays "needed" in the
    plain pocket forever while esm-apps publishes the only fix. The ESM
    build carrying that fix was reported as vulnerable, because the
    esm-apps criterion simply did not match it and the plain pocket's
    "needed" was the only statement left (#2645: 60 such findings on one
    Pro-attached noble host).

  * Where both pockets publish a fix at different versions -- focal
    imagemagick CVE-2021-20224 is fixed at 8:6.9.10.23+dfsg-2.1ubuntu11.9
    in the archive and at ...11.4+esm1 in esm-apps -- an installed
    ...11.4+esm1 is below the archive's version, so the plain pocket
    reported a package the ESM pocket had already fixed.

  * ignoreCriterion dropped every FIPS tag outright to keep non-FIPS
    hosts from being judged by FIPS statements. That left FIPS hosts
    judged by archive statements their builds sort above, silently
    clearing them, and pointed the ones that were reported at an archive
    fixed version they cannot install.

Model the pockets as what they are -- separate build lineages -- and let
exactly one of them judge each installed build: the first of its
pocketFallbacks that says anything about the package. A build no
candidate pocket speaks about is dropped from every criterion.

  * tagPocket resolves a tag to archive / esm / fips. The tracker's
    release keys put the service on either side of the slash
    (esm-apps/noble, but trusty/esm), so both are looked up.

  * installedPocket reads the pocket off Canonical's version convention
    (~esmN / +esmN / +esm.N, .fips. / +fips.N). The authoritative answer
    is the apt origin of the installed version, which the Ubuntu scanner
    does not collect yet -- MergeNewVersion overwrites Repository with the
    CANDIDATE version's repository whenever an update is pending, and the
    Debian scanner fills that from `apt-cache policy` with an apt suite.
    preConvertPkgs now carries Repository only for the ecosystems whose
    data gates on it (redhat, centos, amazon, alpine), an allowlist rather
    than a denylist so a family added later defaults to not gating -- that
    direction only ever loses filtering, never a detection. installedPocket
    should consult the field once a scanner reports the installed build's
    own origin.

  * pocketStatements collects, over all the sibling conditions, which
    pockets have a criterion that survives ignoreCriterion. That is what
    separates "this pocket says the build is fine" from "this pocket says
    nothing", and counting only usable criterions matters: 24,735 of the
    25,666 plain-unfixed/esm-fixed pairs in the DB are "ignored: end of
    standard support", and treating those as an archive statement would
    stop the ESM statement being used at all on xenial and trusty.

  * pocketFallbacks orders the candidates. An Ubuntu Pro build is built
    on an archive one, and the Pro pockets continue the archive's builds
    once a release leaves standard support, so the two fall back to each
    other -- which is also what covers the Pro builds installedPocket
    cannot recognise (esm-infra republishes plain archive versions,
    carrying no marker: 17,117 of its 17,605 fixed statements). Nothing
    falls back INTO FIPS, so keeping the FIPS tags is now safe.

ignoreCriterion's esm allowlist becomes "is this a pocket we know", which
keeps dropping tags naming a pocket this build cannot match against an
installed build.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@MaineK00n
MaineK00n force-pushed the MaineK00n/ubuntu-pocket-lineage branch from eab7d22 to 2a740bc Compare August 28, 2026 07:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant