Skip to content

Commit 2f15868

Browse files
committed
Add affected_version_range for unfixed Alpine packages
Signed-off-by: Anmol Vats <anmolvats2003@gmail.com>
1 parent a5e8b61 commit 2f15868

File tree

3 files changed

+156
-17
lines changed

3 files changed

+156
-17
lines changed

vulnerabilities/pipelines/v2_importers/alpine_security_importer.py

Lines changed: 36 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,9 @@
1313

1414
import requests
1515
from packageurl import PackageURL
16+
from univers.version_constraint import VersionConstraint
1617
from univers.version_range import AlpineLinuxVersionRange
18+
from univers.versions import AlpineLinuxVersion
1719
from univers.versions import InvalidVersion
1820

1921
from vulnerabilities.importer import AdvisoryDataV2
@@ -140,12 +142,16 @@ def parse_advisory(data: dict):
140142
)
141143
)
142144

145+
states = data.get("state") or []
146+
fixed_repos = {state.get("repo") or "" for state in states if state.get("fixed")}
147+
143148
affected_packages = []
144-
for state in data.get("state") or []:
145-
if not state.get("fixed"):
149+
for state in states:
150+
is_fixed = state.get("fixed")
151+
repo = state.get("repo") or ""
152+
if not is_fixed and repo in fixed_repos:
146153
continue
147154
pkg_version_url = state.get("packageVersion") or ""
148-
repo = state.get("repo") or ""
149155
parts = pkg_version_url.rstrip("/").split("/")
150156
if len(parts) < 2:
151157
continue
@@ -164,17 +170,34 @@ def parse_advisory(data: dict):
164170
name=pkg_name,
165171
qualifiers={"distroversion": distroversion, "reponame": reponame},
166172
)
167-
try:
168-
fixed_version_range = AlpineLinuxVersionRange.from_versions([version])
169-
except InvalidVersion:
170-
logger.warning("Cannot parse Alpine version %r in %s", version, cve_id)
171-
continue
172-
affected_packages.append(
173-
AffectedPackageV2(
174-
package=purl,
175-
fixed_version_range=fixed_version_range,
173+
if is_fixed:
174+
try:
175+
fixed_version_range = AlpineLinuxVersionRange.from_versions([version])
176+
except InvalidVersion:
177+
logger.warning("Cannot parse Alpine version %r in %s", version, cve_id)
178+
continue
179+
affected_packages.append(
180+
AffectedPackageV2(
181+
package=purl,
182+
fixed_version_range=fixed_version_range,
183+
)
184+
)
185+
else:
186+
try:
187+
constraint = VersionConstraint(
188+
comparator="<=",
189+
version=AlpineLinuxVersion(version),
190+
)
191+
affected_version_range = AlpineLinuxVersionRange(constraints=(constraint,))
192+
except InvalidVersion:
193+
logger.warning("Cannot parse Alpine version %r in %s", version, cve_id)
194+
continue
195+
affected_packages.append(
196+
AffectedPackageV2(
197+
package=purl,
198+
affected_version_range=affected_version_range,
199+
)
176200
)
177-
)
178201

179202
return AdvisoryDataV2(
180203
advisory_id=cve_id,

vulnerabilities/tests/test_alpine_security_importer.py

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -72,8 +72,8 @@ def test_parse_advisory_skips_malformed_package_url(self):
7272
self.assertIsNotNone(result)
7373
self.assertEqual(result.affected_packages, [])
7474

75-
def test_parse_advisory_skips_unfixed_states(self):
76-
"""State entries with fixed=False must not produce affected_packages."""
75+
def test_parse_advisory_unfixed_state_produces_affected_version_range(self):
76+
"""Unfixed state with no fix in the same repo produces an affected_version_range."""
7777
data = {
7878
"id": "https://security.alpinelinux.org/vuln/CVE-2099-00002",
7979
"description": "test",
@@ -89,7 +89,38 @@ def test_parse_advisory_skips_unfixed_states(self):
8989
}
9090
result = parse_advisory(data)
9191
self.assertIsNotNone(result)
92-
self.assertEqual(result.affected_packages, [])
92+
self.assertEqual(len(result.affected_packages), 1)
93+
pkg = result.affected_packages[0]
94+
self.assertIsNotNone(pkg.affected_version_range)
95+
self.assertIsNone(pkg.fixed_version_range)
96+
self.assertIn("<=8.0.0-r0", str(pkg.affected_version_range))
97+
98+
def test_parse_advisory_skips_unfixed_state_when_fixed_exists_in_same_repo(self):
99+
"""fixed=False states are skipped when a fixed=True state exists in the same repo."""
100+
data = {
101+
"id": "https://security.alpinelinux.org/vuln/CVE-2099-00003",
102+
"description": "test",
103+
"cvss3": {"score": 0.0, "vector": None},
104+
"ref": [],
105+
"state": [
106+
{
107+
"fixed": True,
108+
"packageVersion": "https://security.alpinelinux.org/srcpkg/curl/8.1.0-r0",
109+
"repo": "edge-main",
110+
},
111+
{
112+
"fixed": False,
113+
"packageVersion": "https://security.alpinelinux.org/srcpkg/curl/8.0.0-r0",
114+
"repo": "edge-main",
115+
},
116+
],
117+
}
118+
result = parse_advisory(data)
119+
self.assertIsNotNone(result)
120+
self.assertEqual(len(result.affected_packages), 1)
121+
pkg = result.affected_packages[0]
122+
self.assertIsNone(pkg.affected_version_range)
123+
self.assertIsNotNone(pkg.fixed_version_range)
93124

94125

95126
class TestAlpineSecurityImporterPipeline(TestCase):

vulnerabilities/tests/test_data/alpine_security/expected_alpine_security_output1.json

Lines changed: 86 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,92 @@
22
"advisory_id": "CVE-2025-46836",
33
"aliases": [],
44
"summary": "net-tools is a collection of programs that form the base set of the NET-3 networking distribution for the Linux operating system. Inn versions up to and including 2.10, the Linux network utilities (like ifconfig) from the net-tools package do not properly validate the structure of /proc files when showing interfaces. `get_name()` in `interface.c` copies interface labels from `/proc/net/dev` into a fixed 16-byte stack buffer without bounds checking, leading to possible arbitrary code execution or crash. The known attack path does not require privilege but also does not provide privilege escalation in this scenario. A patch is available and expected to be part of version 2.20.",
5-
"affected_packages": [],
5+
"affected_packages": [
6+
{
7+
"package": {
8+
"type": "apk",
9+
"namespace": "alpine",
10+
"name": "net-tools",
11+
"version": "",
12+
"qualifiers": "distroversion=edge&reponame=main",
13+
"subpath": ""
14+
},
15+
"affected_version_range": "vers:alpine/<=2.10-r3",
16+
"fixed_version_range": null,
17+
"introduced_by_commit_patches": [],
18+
"fixed_by_commit_patches": []
19+
},
20+
{
21+
"package": {
22+
"type": "apk",
23+
"namespace": "alpine",
24+
"name": "net-tools",
25+
"version": "",
26+
"qualifiers": "distroversion=v3.23&reponame=main",
27+
"subpath": ""
28+
},
29+
"affected_version_range": "vers:alpine/<=2.10-r3",
30+
"fixed_version_range": null,
31+
"introduced_by_commit_patches": [],
32+
"fixed_by_commit_patches": []
33+
},
34+
{
35+
"package": {
36+
"type": "apk",
37+
"namespace": "alpine",
38+
"name": "net-tools",
39+
"version": "",
40+
"qualifiers": "distroversion=v3.22&reponame=main",
41+
"subpath": ""
42+
},
43+
"affected_version_range": "vers:alpine/<=2.10-r3",
44+
"fixed_version_range": null,
45+
"introduced_by_commit_patches": [],
46+
"fixed_by_commit_patches": []
47+
},
48+
{
49+
"package": {
50+
"type": "apk",
51+
"namespace": "alpine",
52+
"name": "net-tools",
53+
"version": "",
54+
"qualifiers": "distroversion=v3.21&reponame=main",
55+
"subpath": ""
56+
},
57+
"affected_version_range": "vers:alpine/<=2.10-r3",
58+
"fixed_version_range": null,
59+
"introduced_by_commit_patches": [],
60+
"fixed_by_commit_patches": []
61+
},
62+
{
63+
"package": {
64+
"type": "apk",
65+
"namespace": "alpine",
66+
"name": "net-tools",
67+
"version": "",
68+
"qualifiers": "distroversion=v3.20&reponame=main",
69+
"subpath": ""
70+
},
71+
"affected_version_range": "vers:alpine/<=2.10-r3",
72+
"fixed_version_range": null,
73+
"introduced_by_commit_patches": [],
74+
"fixed_by_commit_patches": []
75+
},
76+
{
77+
"package": {
78+
"type": "apk",
79+
"namespace": "alpine",
80+
"name": "net-tools",
81+
"version": "",
82+
"qualifiers": "distroversion=v3.19&reponame=main",
83+
"subpath": ""
84+
},
85+
"affected_version_range": "vers:alpine/<=2.10-r3",
86+
"fixed_version_range": null,
87+
"introduced_by_commit_patches": [],
88+
"fixed_by_commit_patches": []
89+
}
90+
],
691
"references": [
792
{
893
"reference_id": "",

0 commit comments

Comments
 (0)