1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
use super::diags;
use crate::{
diag::{self, Check, Pack},
index::Index,
Krate, Krates,
};
use anyhow::Error;
use rustsec::advisory::Metadata;
use semver::Version;
#[derive(Debug)]
pub struct Patch<'a> {
/// The advisories the patch is attempting to address
pub advisories: Vec<&'a Metadata>,
/// The crate manifest we want to patch to update versions
pub manifest_to_patch: &'a Krate,
/// The crate and version we want to update to to get the fix(es)
pub crate_to_patch: (&'a str, Version),
}
#[derive(Clone, Copy)]
pub enum Semver {
Compatible,
Latest,
}
#[derive(Clone, Copy)]
pub enum NoVersionReason {
/// No versions were available that used a required version of the crate
NoMatchingVersions,
/// Unable to find registry index entry for crate
NoIndexEntry,
}
use std::fmt;
impl fmt::Display for NoVersionReason {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::NoMatchingVersions => {
f.write_str("No versions were available that used a required version of the crate")
}
Self::NoIndexEntry => f.write_str("Unable to find registry index entry for crate"),
}
}
}
pub struct PatchSet<'a> {
pub diagnostics: Vec<Pack>,
pub patches: Vec<Patch<'a>>,
}
struct Patchable<'a> {
advisory: &'a Metadata,
patched: &'a [rustsec::VersionReq],
krate: &'a rustsec::package::Package,
}
impl super::Report {
fn iter_patchable(&self) -> impl Iterator<Item = Patchable<'_>> {
self.vulnerabilities
.iter()
.map(|vuln| Patchable {
advisory: &vuln.advisory,
patched: vuln.versions.patched(),
krate: &vuln.package,
})
.chain(self.iter_warnings().filter_map(|(kind, warning)| {
// Unmaintained crates are by definition unpatchable, so skip them entirely
if kind == rustsec::warning::Kind::Unmaintained {
return None;
}
warning.versions.as_ref().and_then(|vs| {
warning.advisory.as_ref().map(|adv| Patchable {
advisory: adv,
patched: vs.patched(),
krate: &warning.package,
})
})
}))
}
pub fn gather_patches<'a>(
&'a self,
krates: &'a Krates,
semv: Semver,
krate_spans: &'a diag::KrateSpans,
) -> Result<PatchSet<'a>, Error> {
use krates::petgraph as pg;
use pg::{visit::EdgeRef, Direction};
let graph = krates.graph();
let mut index = Index::load(krates)?;
let mut candidates: Vec<(pg::graph::EdgeIndex<u32>, Vec<PatchCandidate<'_>>)> = Vec::new();
let mut diags = Vec::new();
#[derive(Debug)]
struct PatchCandidate<'a> {
advisory: &'a Metadata,
required: Vec<Version>,
}
let mut visited = std::collections::HashSet::new();
for patchable in self.iter_patchable() {
// 1. Get the package with the vulnerability
// 2. Recursively walk up the dependency chain until we've reach all roots
// (workspace/local crates) that depend on the vulnerable crate version
// 3. For each crate in the chain, check to see if has a version
// available that ultimately includes a patched version of the vulnerable crate
let (ind, vuln_krate) = super::krate_for_pkg(krates, patchable.krate).unwrap();
let mut pack = Pack::with_kid(Check::Advisories, vuln_krate.id.clone());
// We could also see if there are unaffected versions, but easier to just say we only support
// fixing by using newer versions than older versions
if patchable.patched.is_empty() {
pack.push(diags::NoAvailablePatches {
affected_krate_coord: krate_spans.get_coord(ind.index()),
advisory: patchable.advisory,
});
diags.push(pack);
continue;
}
// Gather the versions that fix the issue. Note that there could be
// cases where the fix is in disjoint versions.
let required: Vec<_> = {
let mut req = None;
index.read_krate(vuln_krate, |ik| {
if let Some(index_krate) = ik {
let patched = &patchable.patched;
req = Some(
index_krate
.versions
.iter()
// Disregard any versions older than the current one
.skip_while(|vs| vs.vers < vuln_krate.version)
.filter_map(|vs| {
if patched.iter().any(|v| v.matches(&vs.vers)) {
Some(vs.vers.clone())
} else {
None
}
})
.collect(),
);
}
});
match req {
Some(r) => r,
None => continue,
}
};
if required.is_empty() {
pack.push(diags::NoAvailablePatchedVersions {
affected_krate_coord: krate_spans.get_coord(ind.index()),
advisory: patchable.advisory,
});
diags.push(pack);
continue;
}
let mut krate_stack = vec![(ind, vuln_krate, required)];
while let Some((nid, dep, required)) = krate_stack.pop() {
for edge in graph.edges_directed(nid, Direction::Incoming) {
// We only need to visit each unique edge once
let edge_id = edge.id();
if !visited.insert(edge_id) {
continue;
}
let parent_id = edge.source();
let parent = &graph[parent_id];
if let Some(src) = &parent.krate.source {
if src.is_registry() {
match Self::find_possible_versions(
&mut index,
&parent.krate,
dep,
&required,
) {
Ok(parent_versions) => {
krate_stack.push((parent_id, &parent.krate, parent_versions));
continue;
}
Err(e) => {
pack.push(diags::UnableToFindMatchingVersion {
parent_krate: &parent.krate,
reason: e,
dep,
});
continue;
}
}
} else if !src.is_path() {
pack.push(diags::UnpatchableSource {
parent_krate: &parent.krate,
});
continue;
}
}
match candidates.iter_mut().find(|c| c.0 == edge_id) {
Some(cand) => {
cand.1.push(PatchCandidate {
advisory: patchable.advisory,
required: required.clone(),
});
}
None => {
candidates.push((
edge_id,
vec![PatchCandidate {
advisory: patchable.advisory,
required: required.clone(),
}],
));
}
}
}
}
if !pack.is_empty() {
diags.push(pack);
}
}
// Now that we've gathered all of the patches that we can apply, try to
// find a semver compatible version to patch to, or if the user has
// allowed non-semver patching, just pick the highest version
let mut patches = Vec::new();
let mut patch_pack = Pack::new(Check::Advisories);
for (edge, candidates) in candidates {
let (local_krate, to_patch) = graph
.edge_endpoints(edge)
.map(|(src, tar)| (&krates[src], &krates[tar]))
.unwrap();
// Get the maximum version, either of the possible versions that we
// require or of the maximum from the set of semver compatible
// versions. I honestly expect the semver compatible one to never
// actually work, but provide it just because ¯\_(ツ)_/¯
let version = match semv {
Semver::Compatible => {
match local_krate.deps.iter().find(|dep| {
dep.kind != krates::cm::DependencyKind::Development
&& dep.name == to_patch.name
}) {
Some(dep) => {
let req = &dep.req;
let mut new_version = None;
let mut all_compatible = true;
for pc in &candidates {
match pc.required.iter().filter(|vs| req.matches(vs)).max() {
Some(max) => match &mut new_version {
Some(cur) => {
*cur = std::cmp::max(&*cur, &max);
}
None => {
new_version = Some(max);
}
},
None => {
patch_pack.push(diags::IncompatibleLocalKrate {
local_krate,
dep_req: req,
dep: to_patch,
required_versions: &pc.required,
});
all_compatible = false;
}
}
}
if !all_compatible {
continue;
}
new_version
}
None => {
continue;
}
}
}
Semver::Latest => candidates.iter().flat_map(|pc| pc.required.iter()).max(),
};
let version = match version {
Some(v) => {
if v == &to_patch.version {
patch_pack.push(diags::NoNewerVersionAvailable {
local_krate,
dep: to_patch,
});
continue;
}
v.clone()
}
None => {
//log::warn!("Local crate {:#?} dependency for {}, does not meet any of the required versions {:#?}", local_krate, to_patch.name, candidates);
continue;
}
};
patches.push(Patch {
advisories: candidates.into_iter().map(|pc| pc.advisory).collect(),
manifest_to_patch: local_krate,
crate_to_patch: (to_patch.name.as_ref(), version),
});
}
if !patch_pack.is_empty() {
diags.push(patch_pack);
}
Ok(PatchSet {
patches,
diagnostics: diags,
})
}
fn find_possible_versions(
index: &mut Index,
parent: &Krate,
child: &Krate,
required: &[Version],
) -> Result<Vec<Version>, NoVersionReason> {
let mut res = None;
index.read_krate(parent, |ik| {
match ik {
Some(parent_krate) => {
// Grab all of the versions of the parent crate that have a version requirement that accepts
// any of the specified required versions
let available: Vec<_> = parent_krate
.versions
.iter()
// Disregard any versions older than the current one
.skip_while(|vs| vs.vers < parent.version)
.filter_map(|kv| {
let version = &kv.vers;
if let Some(dep) = kv.deps.iter().find(|dep| {
dep.kind != Some(krates::cm::DependencyKind::Development)
&& dep.name == child.name
}) {
if !required.iter().any(|vs| dep.req.matches(vs)) {
return None;
}
}
// If the dependency has been removed from a future version, it is
// also a candidate
Some(version.clone())
})
.collect();
if available.is_empty() {
res = Some(Err(NoVersionReason::NoMatchingVersions));
} else {
res = Some(Ok(available));
}
}
None => {
res = Some(Err(NoVersionReason::NoIndexEntry));
}
}
});
res.unwrap()
}
}