skip to content

Why can a distro-rebuilt package version like 1.1.1n-0+deb11u3 produce a false vulnerability match?

level: middleimportance: should knowfreq 52%

answer

  1. the fix moved, the upstream number did not
  2. two halves in one version string
  3. the suffix is the evidence
  4. namespace names who rebuilt it
  5. keep epoch and revision, never truncate

basics

~10 s

Distributions backport security fixes into an older upstream version and mark the rebuild with their own revision suffix. A matcher reading only the upstream part flags a package whose fix is already in.

solid answer

~50 s

Distributions rarely bump to a new upstream release to fix a defect; they apply the patch to the version they already ship and append their own revision, so `1.1.1n-0+deb11u3` is upstream `1.1.1n` **plus** the fix. Anything that parses the version as upstream `1.1.1n` and tests it against a range like `< 1.1.1o` flags it, even though the fix is in — a classic false positive that burns triage time. The identity fix is to record the component as what it actually is: `pkg:deb/debian/[email protected]%2Bdeb11u3?arch=amd64&distro=debian-11`, keeping the full version including epoch and revision, so it joins to the distribution that builds, patches and versions it rather than to the upstream project. Truncating the revision to look tidy destroys the only evidence that the backport happened, and it also loses the ability to see defects the distro's own patches introduced.

code

json · 8 lines
json
{
  "type": "library",
  "name": "openssl",
  "version": "1.1.1n-0+deb11u3",
  "purl": "pkg:deb/debian/[email protected]%2Bdeb11u3?arch=amd64&distro=debian-11",
  "cpe": "cpe:2.3:a:openssl:openssl:1.1.1n:*:*:*:*:*:*:*",
  "hashes": [ { "alg": "SHA-256", "content": "..." } ]
}

go deeper

for a junior

Be ready to read a version like 1.1.1n-0+deb11u3 out loud and say which part is upstream and which part the distribution added, and why the second part matters.

for a middle

Explain backporting and both failure directions: a patched package flagged as vulnerable, and a distribution-introduced defect that becomes invisible once you record the component as upstream.

for a senior

Show how you stop the false-positive flood without turning off the check: route by the distro coordinate, preserve full version strings end to end, and treat repeated noise as an identity defect rather than a triage cost.

for a principal

Own the policy that whoever rebuilds a component owns its patch responsibility, and make sure the inventory schema, the base-image strategy and the vendor questions all encode that same rule.

## What a distro rebuild actually is When a distribution ships a library, it does not ship the upstream tarball untouched. It ships a **rebuild**: upstream source, plus a stack of distribution patches, built with the distribution's toolchain and flags, packaged under the distribution's own release policy. Stable distributions freeze upstream versions for the life of a release, so when a defect is found the maintainers do not upgrade to a new upstream release — they cherry-pick the fix onto the frozen version and publish a new *package* revision. That is why the version string has two halves. In `1.1.1n-0+deb11u3`: - `1.1.1n` is the **upstream version**, and it does not move. - `-0` is the distribution's own package revision. - `+deb11u3` marks the third update built for that stable release. The component is patched. Nothing in the upstream half of the string says so. ## The two failure directions **False positive.** A matcher normalises the version to its upstream part and compares it to a range expressed in upstream terms — say, fixed in `1.1.1o`. `1.1.1n` is less than `1.1.1o`, so it flags. An engineer then spends an afternoon proving that a package which was already fixed is already fixed. At estate scale this is not an annoyance; it is the reason people stop reading the report, which is how real findings get lost. **False negative.** The mirror image is just as real. If you record the component as the upstream project and throw away the distribution namespace, you can no longer see problems that belong to the *rebuild*: a patch the distribution itself introduced, a build-flag choice that disabled a hardening feature, a packaging defect in the pre- or post-install scripting. Those exist only in the distribution's identity space, and you have just deleted the key to it. ## Identity is the fix, not cleverness in the matcher The durable answer is to name the component after the entity that actually produced it. purl expresses this directly: ``` pkg:deb/debian/[email protected]%2Bdeb11u3?arch=amd64&distro=debian-11 ``` Read the parts: the type `deb` says this is a distribution package rather than a source release; the namespace `debian` names the distribution; the version keeps the **entire** string including the revision; `arch` and `distro` pin which build of it you hold. An `rpm` package is expressed the same way with its own namespace, and there the epoch matters too — an epoch prefix changes ordering in a way naive string comparison never reproduces. Three rules follow from this and they are what an interviewer wants to hear: 1. **Never truncate the version.** The revision suffix is the evidence that a backport happened. Normalising it away to make the inventory look tidy is deleting data you will need. 2. **Never silently promote a distro package to its upstream project.** Recording an upstream coordinate *as well* — clearly labelled as the derived-from relationship rather than as the component's identity — is useful for humans and for reachability work. Substituting it is not. 3. **Route by identity.** A `deb` component's authority for whether it is fixed is the distribution that patched it; an upstream range is a statement about upstream releases and was never a statement about somebody's rebuild. ## Why this generalises The distro case is the friendliest instance of a wider pattern: **whoever rebuilds a component owns its identity from that point forward.** A container base image maintainer, a language runtime vendor shipping its own build of a compression library, an internal platform team that recompiles a dependency with different flags — each produces an artifact that shares an upstream lineage but is not the upstream artifact. The version string is the only place most inventories record that, so the version string has to survive intact, and the namespace has to say who did the rebuilding. Get that right and the same inventory entry answers two different questions correctly: what upstream code is in here, and who is responsible for patching it.

  • Is recording the upstream coordinate alongside the distro purl ever useful?
    Yes, as a derived-from relationship rather than as the component's identity. Knowing which upstream project and release a rebuild came from helps a human reason about whether vulnerable code is even present, and it supports reachability work that operates on source. What it must not do is replace the distro coordinate, because the distribution is the party that patches and versions the thing you actually run.
  • How does an epoch in an RPM version change comparison?
    An epoch is a leading counter that takes priority over everything after it, so a package with a higher epoch is newer regardless of how the rest of the version string compares. Naive lexical or SemVer-style comparison ignores it and orders the versions backwards. If your inventory stores versions as plain strings and compares them itself, epochs are one of the places it silently gets the answer wrong.
  • The same library is present both as a distro package and as a language-ecosystem package in one image. What do you record?
    Two components, with two purls, because they are two artifacts with two suppliers and two patch paths. Deduplicating them by name produces an inventory that is wrong in both directions: you lose one of the two patch owners, and you attribute a fix in one to the other. Only a hash can tell you whether they are literally the same bytes, and usually they are not.

saying these in an interview costs you the question

  • Says the package must be vulnerable because the version is old
  • Normalises the version to its upstream part for tidiness
  • Treats a distro version string as SemVer
  • Replaces the distro coordinate with the upstream project
  • Assumes an epoch is decoration

context