fix: capture ${source:Version}, the number OSV ranges are actually stated in #1
+20
-3
@@ -835,11 +835,13 @@ class LinuxDriver(OSDriver):
|
||||
def _get_packages_apt(self) -> list[PackageDict]:
|
||||
out = self._send(
|
||||
"dpkg-query -W -f='${Package}\\t${Version}\\t${Installed-Size}"
|
||||
"\\t${source:Package}\\t${binary:Summary}\\n' 2>/dev/null"
|
||||
"\\t${source:Package}\\t${source:Version}\\t${binary:Summary}\\n' 2>/dev/null"
|
||||
)
|
||||
packages: list[PackageDict] = []
|
||||
for line in out.splitlines():
|
||||
parts = line.split("\t", 4)
|
||||
# Summary stays last and keeps whatever it contains: maxsplit must
|
||||
# equal the number of tabs the format writes, not the field count.
|
||||
parts = line.split("\t", 5)
|
||||
if len(parts) < 2:
|
||||
continue
|
||||
name = parts[0].strip()
|
||||
@@ -847,7 +849,21 @@ class LinuxDriver(OSDriver):
|
||||
size = int(parts[2].strip()) * 1024 if len(parts) > 2 and parts[2].strip().isdigit() else 0
|
||||
# Debian source package (e.g. openssh-server → openssh) for OSV matching.
|
||||
source_package = parts[3].strip() if len(parts) > 3 and parts[3].strip() else name
|
||||
description = parts[4].strip() if len(parts) > 4 else ""
|
||||
# And its version, which is a different number from this package's.
|
||||
#
|
||||
# OSV states Debian ranges in *source* versions. A source package
|
||||
# that ships several binaries gives each its own upstream version:
|
||||
# libldb2 is 2:2.11.0+samba4.22.11+dfsg-… while its source, samba,
|
||||
# is 2:4.22.11+dfsg-…. A consumer matching on source_package and
|
||||
# comparing `version` compares two unrelated numbers — dpkg reads
|
||||
# ldb's 2.11.0 as older than the 2:4.17.4+dfsg-1 that fixed
|
||||
# CVE-2022-44640, and a host five releases past the fix was reported
|
||||
# vulnerable on four packages at once.
|
||||
#
|
||||
# dpkg leaves this empty when it equals `Version`; so does an older
|
||||
# dpkg that does not know the field at all.
|
||||
source_version = parts[4].strip() if len(parts) > 4 and parts[4].strip() else version
|
||||
description = parts[5].strip() if len(parts) > 5 else ""
|
||||
packages.append({
|
||||
"name": name,
|
||||
"version": version,
|
||||
@@ -856,6 +872,7 @@ class LinuxDriver(OSDriver):
|
||||
"size": size,
|
||||
"source": "apt",
|
||||
"source_package": source_package,
|
||||
"source_version": source_version,
|
||||
})
|
||||
return packages
|
||||
|
||||
|
||||
+50
-3
@@ -153,9 +153,22 @@ def test_parse_uptime_invalid(driver):
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
#: What `dpkg-query` actually returns for the format this driver asks for.
|
||||
#:
|
||||
#: The previous fixture carried four fields against a format string asking for
|
||||
#: five, so `source_package` was silently receiving the description and no
|
||||
#: assertion noticed. A fixture simpler than the data cannot fail the way the
|
||||
#: data does.
|
||||
APT_PKG_OUTPUT = (
|
||||
"openssh-server\t1:9.2p1-2+deb12u2\t512\tsecure shell server\n"
|
||||
"curl\t7.88.1-10+deb12u5\t1024\tcommand line tool for transferring data\n"
|
||||
"openssh-server\t1:9.2p1-2+deb12u2\t512\topenssh\t1:9.2p1-2+deb12u2"
|
||||
"\tsecure shell server\n"
|
||||
"curl\t7.88.1-10+deb12u5\t1024\tcurl\t7.88.1-10+deb12u5"
|
||||
"\tcommand line tool for transferring data\n"
|
||||
# The shape that matters: a binary package whose own upstream version has
|
||||
# nothing to do with its source package's. ldb 2.11.0 is built from samba
|
||||
# 4.22.11, and OSV states Debian ranges in source versions.
|
||||
"libldb2\t2:2.11.0+samba4.22.11+dfsg-0+deb13u1\t2048\tsamba"
|
||||
"\t2:4.22.11+dfsg-0+deb13u1\tLDB shared library\n"
|
||||
)
|
||||
|
||||
|
||||
@@ -163,11 +176,45 @@ def test_get_packages_apt(driver):
|
||||
driver._pkg_manager = "apt"
|
||||
with patch.object(driver, "_send", return_value=APT_PKG_OUTPUT):
|
||||
pkgs = driver.get_packages()
|
||||
assert len(pkgs) == 2
|
||||
assert len(pkgs) == 3
|
||||
assert pkgs[0]["name"] == "openssh-server"
|
||||
assert pkgs[0]["version"] == "1:9.2p1-2+deb12u2"
|
||||
assert pkgs[0]["installed"] is True
|
||||
assert pkgs[0]["source"] == "apt"
|
||||
assert pkgs[0]["description"] == "secure shell server"
|
||||
|
||||
|
||||
def test_get_packages_apt_keeps_the_source_package_and_its_version(driver):
|
||||
"""OSV states Debian ranges in *source* package versions.
|
||||
|
||||
A consumer that matches on the source package and then compares the binary
|
||||
package's version is comparing two unrelated numbers. On a Debian 13 host
|
||||
that reported four Samba libraries as vulnerable to CVE-2022-44640 while
|
||||
running samba 4.22.11 — five releases past the fix — because dpkg reads
|
||||
ldb's own `2.11.0` as older than samba's `2:4.17.4+dfsg-1`.
|
||||
|
||||
The driver cannot fix the comparison, but it is the only place that can
|
||||
supply the number to compare.
|
||||
"""
|
||||
driver._pkg_manager = "apt"
|
||||
with patch.object(driver, "_send", return_value=APT_PKG_OUTPUT):
|
||||
pkgs = {p["name"]: p for p in driver.get_packages()}
|
||||
|
||||
assert pkgs["libldb2"]["source_package"] == "samba"
|
||||
assert pkgs["libldb2"]["source_version"] == "2:4.22.11+dfsg-0+deb13u1"
|
||||
assert pkgs["libldb2"]["version"] == "2:2.11.0+samba4.22.11+dfsg-0+deb13u1"
|
||||
assert pkgs["libldb2"]["description"] == "LDB shared library"
|
||||
|
||||
|
||||
def test_get_packages_apt_falls_back_when_dpkg_gives_no_source(driver):
|
||||
"""`source:Package` is empty for a package whose source name equals its own.
|
||||
Older dpkg builds leave `source:Version` empty in that case too."""
|
||||
driver._pkg_manager = "apt"
|
||||
with patch.object(driver, "_send", return_value="curl\t7.88.1-10\t1024\t\t\ttool\n"):
|
||||
(pkg,) = driver.get_packages()
|
||||
|
||||
assert pkg["source_package"] == "curl"
|
||||
assert pkg["source_version"] == "7.88.1-10"
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user