Skip to content

Commit dc318c3

Browse files
authored
Merge pull request #148 from sbs2001/add-importer
Add rust security importer
2 parents 99b4191 + 6d8ed83 commit dc318c3

4 files changed

Lines changed: 133 additions & 1 deletion

File tree

requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,3 +31,4 @@ tqdm==4.41.1
3131
wcwidth==0.1.7
3232
whitenoise==5.0.1
3333
zipp==0.6.0
34+
pytoml==0.1.21

vulnerabilities/data_dump.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,3 +225,40 @@ def ruby_dump(extract_data):
225225
vulnerability=vulnerability,
226226
package=unaffected_package
227227
)
228+
229+
230+
def rust_dump(extract_data):
231+
232+
for package_data in extract_data:
233+
234+
vulnerability, _ = Vulnerability.objects.get_or_create(
235+
summary=package_data['description']
236+
)
237+
238+
VulnerabilityReference.objects.get_or_create(
239+
vulnerability=vulnerability,
240+
url=package_data['advisory'],
241+
reference_id=package_data['vuln_id']
242+
)
243+
244+
for version in package_data['affected_versions']:
245+
affected_package = Package.objects.create(
246+
name=package_data['package_name'],
247+
type='cargo',
248+
version=version
249+
)
250+
ImpactedPackage.objects.create(
251+
vulnerability=vulnerability,
252+
package=affected_package
253+
)
254+
255+
for version in package_data['fixed_versions']:
256+
unaffected_package = Package.objects.create(
257+
name=package_data['package_name'],
258+
type='cargo',
259+
version=version
260+
)
261+
ResolvedPackage.objects.create(
262+
vulnerability=vulnerability,
263+
package=unaffected_package
264+
)

vulnerabilities/management/commands/import.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,10 @@
2424
from django.core.management.base import BaseCommand, CommandError
2525

2626
from vulnerabilities import data_dump as dd
27-
from vulnerabilities.scraper import debian, ubuntu, archlinux, npm, ruby
27+
from vulnerabilities.scraper import debian, ubuntu, archlinux, npm, ruby, rust
2828

2929
IMPORTERS = {
30+
'rust': lambda: dd.rust_dump(rust.import_vulnerabilities()),
3031
'ruby': lambda: dd.ruby_dump(ruby.import_vulnerabilities()),
3132
'npm': lambda: dd.npm_dump(npm.scrape_vulnerabilities()),
3233
'debian': lambda: dd.debian_dump(debian.scrape_vulnerabilities()),

vulnerabilities/scraper/rust.py

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
from io import BytesIO
2+
import json
3+
4+
from dephell_specifier import RangeSpecifier
5+
import pytoml as toml
6+
from urllib.request import urlopen
7+
import urllib.request
8+
from urllib.error import HTTPError
9+
from zipfile import ZipFile
10+
11+
12+
RUSTSEC_DB_URL = 'https://github.com/RustSec/advisory-db/archive/master.zip'
13+
14+
15+
def rust_crate_advisories(url, prefix='advisory-db-master/crates/'):
16+
with urlopen(url) as response:
17+
with ZipFile(BytesIO(response.read())) as zf:
18+
for path in zf.namelist():
19+
if path.startswith(prefix) and path.endswith('.toml'):
20+
yield toml.load(zf.open(path))
21+
22+
23+
def all_versions_of_crate(crate):
24+
info_url = "https://crates.io/api/v1/crates/{}".format(crate)
25+
with urlopen(info_url) as info:
26+
info = json.load(info)
27+
for version_info in info['versions']:
28+
yield version_info['num']
29+
30+
31+
def import_vulnerabilities():
32+
33+
vulnerability_package_dicts = []
34+
for advisory in rust_crate_advisories(RUSTSEC_DB_URL):
35+
36+
affected_version_range_lists = list(advisory.get(
37+
'affected', {}).get('functions', {}).values())
38+
unaffected_version_range_list = advisory['advisory'].get(
39+
'unaffected_versions', [])
40+
patched_version_range_list = advisory['advisory']['patched_versions']
41+
42+
# FIXME: Make distinction between unaffected and patched packages
43+
# Check https://github.com/nexB/vulnerablecode/issues/144
44+
unaffected_version_range_list += patched_version_range_list
45+
46+
have_unaffected_version_range = len(unaffected_version_range_list) != 0
47+
have_affected_version_range = len(affected_version_range_lists) != 0
48+
49+
if not (have_affected_version_range or have_unaffected_version_range):
50+
continue
51+
52+
vuln_id = advisory['advisory']['id']
53+
crate_name = advisory['advisory']['package']
54+
description = advisory['advisory']['description']
55+
reference = advisory['advisory'].get('url', '')
56+
57+
all_versions = set(all_versions_of_crate(crate_name))
58+
affected_versions = set()
59+
unaffected_versions = set()
60+
61+
for version in all_versions:
62+
categorised = False
63+
for affected_version_range_list in affected_version_range_lists:
64+
for affected_version_range in affected_version_range_list:
65+
affected_version_range = RangeSpecifier(
66+
affected_version_range)
67+
if version in affected_version_range:
68+
affected_versions.add(version)
69+
categorised = True
70+
break
71+
72+
if have_unaffected_version_range and not categorised:
73+
for unaffected_version_range in unaffected_version_range_list:
74+
if version in RangeSpecifier(unaffected_version_range):
75+
unaffected_versions.add(version)
76+
break
77+
78+
if not have_unaffected_version_range:
79+
unaffected_versions = all_versions - affected_versions
80+
81+
elif not have_affected_version_range:
82+
affected_versions = all_versions - unaffected_versions
83+
84+
vulnerability_package_dicts.append({
85+
'package_name': crate_name,
86+
'vuln_id': vuln_id,
87+
'fixed_versions': unaffected_versions,
88+
'affected_versions': affected_versions,
89+
'advisory': reference,
90+
'description': description
91+
})
92+
93+
return vulnerability_package_dicts

0 commit comments

Comments
 (0)