Skip to content

Commit d886469

Browse files
authored
Merge pull request #281 from sbs2001/rust_new_format
Adapt rust importer to new advisory format
2 parents e9d9f8a + 37b07f7 commit d886469

File tree

8 files changed

+264
-66
lines changed

8 files changed

+264
-66
lines changed

requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,3 +56,4 @@ wcwidth==0.1.7
5656
whitenoise==5.0.1
5757
zipp==0.6.0
5858
requests==2.23.0
59+
toml==0.10.2

vulnerabilities/importers/rust.py

Lines changed: 68 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -23,15 +23,16 @@
2323
import asyncio
2424
import json
2525
from itertools import chain
26+
import re
2627
from typing import Optional, Mapping
2728
from typing import Set
2829
from typing import Tuple
2930
from urllib.error import HTTPError
3031
from urllib.request import urlopen
3132

32-
import pytoml as toml
3333
from dephell_specifier import RangeSpecifier
3434
from packageurl import PackageURL
35+
import toml
3536

3637
from vulnerabilities.data_source import Advisory
3738
from vulnerabilities.data_source import GitDataSource
@@ -47,7 +48,7 @@ def __enter__(self):
4748
self._added_files, self._updated_files = self.file_changes(
4849
subdir="crates", # TODO Consider importing the advisories for cargo, etc as well.
4950
recursive=True,
50-
file_ext="toml",
51+
file_ext="md",
5152
)
5253

5354
@property
@@ -66,15 +67,15 @@ def updated_advisories(self) -> Set[Advisory]:
6667
return self._load_advisories(self._updated_files)
6768

6869
def _load_advisories(self, files) -> Set[Advisory]:
69-
files = [f for f in files if not f.endswith("-0000.toml")] # skip temporary files
70+
# per @tarcieri It will always be named RUSTSEC-0000-0000.md
71+
# https://github.com/nexB/vulnerablecode/pull/281/files#r528899864
72+
files = [f for f in files if not f.endswith("-0000.md")] # skip temporary files
7073
packages = self.collect_packages(files)
7174
self.set_api(packages)
7275

7376
while files:
7477
batch, files = files[: self.batch_size], files[self.batch_size:]
75-
7678
advisories = set()
77-
7879
for path in batch:
7980
advisory = self._load_advisory(path)
8081
if advisory:
@@ -84,13 +85,13 @@ def _load_advisories(self, files) -> Set[Advisory]:
8485
def collect_packages(self, paths):
8586
packages = set()
8687
for path in paths:
87-
record = load_toml(path)
88+
record = get_advisory_data(path)
8889
packages.add(record["advisory"]["package"])
8990

9091
return packages
9192

9293
def _load_advisory(self, path: str) -> Optional[Advisory]:
93-
record = load_toml(path)
94+
record = get_advisory_data(path)
9495
advisory = record.get("advisory", {})
9596
crate_name = advisory["package"]
9697
references = []
@@ -175,6 +176,63 @@ def categorize_versions(
175176
return unaffected, affected
176177

177178

178-
def load_toml(path):
179-
with open(path) as f:
180-
return toml.load(f)
179+
def get_toml_lines(lines):
180+
"""
181+
Yield lines of TOML extracted from an iterable of text ``lines``.
182+
The lines are expected to be from a RustSec Markdown advisory file with
183+
embedded TOML metadata.
184+
185+
For example::
186+
187+
>>> text = '''
188+
... ```toml
189+
... [advisory]
190+
... id = "RUST-001"
191+
...
192+
... [versions]
193+
... patch = [">= 1.2.1"]
194+
... ```
195+
... # Use-after-free with objects returned by `Stream`'s `get_format_info`
196+
...
197+
... Affected versions contained a pair of use-after-free issues with the objects.
198+
... '''
199+
>>> list(get_toml_lines(text.splitlines()))
200+
['', '[advisory]', 'id = "RUST-001"', '', '[versions]', 'patch = [">= 1.2.1"]']
201+
"""
202+
203+
for line in lines:
204+
line = line.strip()
205+
if line.startswith("```toml"):
206+
continue
207+
elif line.endswith("```"):
208+
break
209+
else:
210+
yield line
211+
212+
213+
def data_from_toml_lines(lines):
214+
"""
215+
Return a mapping of data from an iterable of TOML text ``lines``.
216+
217+
For example::
218+
219+
>>> lines = ['[advisory]', 'id = "RUST1"', '', '[versions]', 'patch = [">= 1"]']
220+
>>> data_from_toml_lines(lines)
221+
{'advisory': {'id': 'RUST1'}, 'versions': {'patch': ['>= 1']}}
222+
"""
223+
return toml.loads("\n".join(lines))
224+
225+
226+
def get_advisory_data(location):
227+
"""
228+
Return a mapping of vulnerability data from a RustSec advisory file at
229+
``location``.
230+
RustSec advisories documents are .md files starting with a block of TOML
231+
identified as the text inside a tripple-backtick TOML block. Per
232+
https://github.com/RustSec/advisory-db#advisory-format:
233+
Advisories are formatted in Markdown with TOML "front matter".
234+
"""
235+
236+
with open(location) as lines:
237+
toml_lines = get_toml_lines(lines)
238+
return data_from_toml_lines(toml_lines)

vulnerabilities/package_managers.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -319,5 +319,4 @@ async def fetch(self, owner_repo: str, session) -> None:
319319
endpoint = f"https://api.github.com/repos/{owner_repo}/git/refs/tags"
320320
resp = await session.request(method="GET", url=endpoint)
321321
resp = await resp.json()
322-
print(resp)
323322
self.cache[owner_repo] = [release["ref"].split("/")[-1] for release in resp]
File renamed without changes.
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
```toml
2+
[advisory]
3+
id = "CVE-2019-16760"
4+
package = "cargo"
5+
aliases = ["GHSA-phjm-8x66-qw4r"]
6+
date = "2019-09-30"
7+
url = "https://groups.google.com/forum/#!topic/rustlang-security-announcements/rVQ5e3TDnpQ"
8+
9+
[versions]
10+
patched = [">= 1.26.0"]
11+
```
12+
13+
# Cargo prior to Rust 1.26.0 may download the wrong dependency
14+
15+
The Rust team was recently notified of a security concern when using older
16+
versions of Cargo to build crates which use the package rename feature added in
17+
newer versions of Cargo. If you're using Rust 1.26.0, released on 2018-05-10,
18+
or later you're not affected.
19+
20+
The CVE for this vulnerability is [CVE-2019-16760][0].
21+
22+
## Overview
23+
24+
Cargo can be configured through `Cargo.toml` and the `[dependencies]` section
25+
to depend on different crates, such as those from crates.io. There are multiple
26+
ways to configure how you depend on crates as well, for example if you depend
27+
on `serde` and enable the `derive` feature it would look like:
28+
29+
```toml
30+
serde = { version = "1.0", features = ['derive'] }
31+
```
32+
33+
Rust 1.31.0 [introduced a new feature of Cargo][1] where one of the optional
34+
keys you can specify in this map is `package`, a way to [rename a crate
35+
locally][2]. For example if you preferred to use `serde1` locally instead of
36+
`serde`, you could write:
37+
38+
```toml
39+
serde1 = { version = "1.0", features = ['derive'], package = "serde" }
40+
```
41+
42+
It's the addition of the `package` key that causes Cargo to compile the crate
43+
differently. This feature was [first implemented][3] in Rust 1.26.0, but it was
44+
unstable at the time. For Rust 1.25.0 and prior, however, Cargo would ignore
45+
the `package` key and and interpret the dependency line as if it were:
46+
47+
```toml
48+
serde1 = { version = "1.0", features = ['derive'] }
49+
```
50+
51+
This means when compiled with Rust 1.25.0 and prior then it would attempt to
52+
download the `serde1` crate. A malicious user could squat the `serde1` name on
53+
crates.io to look like `serde 1.0.0` but instead act maliciously when built.
54+
55+
In summary, usage of the `package` key to rename dependencies in `Cargo.toml`
56+
is ignored in Rust 1.25.0 and prior. When Rust 1.25.0 and prior is used Cargo
57+
will ignore `package` and download the wrong dependency, which could be
58+
squatted on crates.io to be a malicious package. This not only affects
59+
manifests that you write locally yourself, but also manifests published to
60+
crates.io. If you published a crate, for example, that depends on `serde1` to
61+
crates.io then users who depend on you may also be vulnerable if they use Rust
62+
1.25.0 and prior.
63+
64+
## Affected Versions
65+
66+
Rust 1.0.0 through Rust 1.25.0 is affected by this advisory because Cargo will
67+
ignore the `package` key in manifests. Rust 1.26.0 through Rust 1.30.0 are not
68+
affected and typically will emit an error because the `package` key is
69+
unstable. Rust 1.31.0 and after are not affected because Cargo understands the
70+
`package` key.
71+
72+
In terms of Cargo versions, this affects Cargo up through Cargo 0.26.0. All
73+
future versions of Cargo are unaffected.
74+
75+
## Mitigations
76+
77+
We strongly recommend that users of the affected versions update their compiler
78+
to the latest available one. Preventing this issue from happening requires
79+
updating your compiler to either Rust 1.26.0 or newer.
80+
81+
We will not be issuing a patch release for Rust versions prior to 1.26.0. Users
82+
of Rust 1.19.0 to Rust 1.25.0 can instead apply [the provided patches][4] to
83+
mitigate the issue.
84+
85+
An audit of existing crates published to crates.io using the `package` key has
86+
been performed and there is no evidence that this vulnerability has been
87+
exploited in the wild. Our audit only covers the crates currently published on
88+
crates.io: if you notice crates exploiting this vulnerability in the future
89+
please don't hesitate to email secu...@rust-lang.org in accordance with [our
90+
security policy][5].
91+
92+
## Timeline of events
93+
94+
* Wed, Sep 18, 2019 at 13:54 UTC - Bug reported to secu...@rust-lang.org
95+
* Wed, Sep 18, 2019 at 15:35 UTC - Response confirming the report
96+
* Wed, Sep 18, 2019 - Cargo, Core, and crates.io teams confer on how best to
97+
handle this
98+
* Thu, Sep 19, 2019 - Confirmed with Elichai plan of action and continued to
99+
audit existing crates
100+
* Mon, Sep 23, 2019 - Advisory drafted, patches developed, audit completed
101+
* Mon, Sep 30, 2019 - Advisory published, security list informed of this issue
102+
103+
## Acknowledgments
104+
105+
Thanks to Elichai Turkel, who found this bug and reported it to us in accordance
106+
with our [security policy][5].
107+
108+
## Links
109+
110+
[0]: https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-16760
111+
[1]: https://blog.rust-lang.org/2018/12/06/Rust-1.31-and-rust-2018.html#cargo-features
112+
[2]: https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html#renaming-dependencies-in-cargotoml
113+
[3]: https://github.com/rust-lang/cargo/pull/4953
114+
[4]: https://gist.github.com/pietroalbini/0d293b24a44babbeb6187e06eebd4992
115+
[5]: https://www.rust-lang.org/policies/security
880 KB
Binary file not shown.

vulnerabilities/tests/test_data_source.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -268,7 +268,7 @@ class GitDataSourceTest(TestCase):
268268
@classmethod
269269
def setUpClass(cls) -> None:
270270
cls.tempdir = tempfile.mkdtemp()
271-
zip_path = os.path.join(TEST_DATA, 'rust-advisory-db.zip')
271+
zip_path = os.path.join(TEST_DATA, 'advisory-db.zip')
272272

273273
with zipfile.ZipFile(zip_path, "r") as zip_ref:
274274
zip_ref.extractall(cls.tempdir)

0 commit comments

Comments
 (0)