|
| 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