use crate::{cargo::Lockfile, Category, Location, Severity};
use dyn_iter::{DynIter, IntoDynIterator as _};
use eyre::{Context as _, Result};
use rustsec::{Report, Vulnerability};
const AUDIT_ENGINE: &str = "audit";
pub struct Audit<'lock> {
issues: DynIter<'lock, Issue<'lock>>,
}
impl<'lock> Audit<'lock> {
/// Create a Audit parser for issues
///
/// # Errors
/// May fail reading and parsing the file (IO errors).
#[inline]
pub fn try_new<R>(json_read: R, lockfile: &'lock Lockfile) -> Result<Self>
where
R: std::io::Read + 'static,
{
let report = serde_json::from_reader::<_, Report>(json_read)
.context("failed to be parsed as a 'rustsec::report::Report'")?;
let issues = report
.vulnerabilities
.list
.into_iter()
.map(move |vulnerability| (lockfile, vulnerability))
.into_dyn_iter();
let audit = Self { issues };
Ok(audit)
}
}
impl<'lock> Iterator for Audit<'lock> {
type Item = Issue<'lock>;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
self.issues.next()
}
}
pub type Issue<'lock> = (&'lock Lockfile, Vulnerability);
impl crate::Issue for Issue<'_> {
#[inline]
fn analyzer_id(&self) -> String {
AUDIT_ENGINE.to_owned()
}
#[inline]
fn issue_id(&self) -> String {
self.1.advisory.id.to_string()
}
#[inline]
fn fingerprint(&self) -> md5::Digest {
// For each package, each issue should be unique
md5::compute(format!("{}:{}", self.1.advisory.package, self.issue_id()))
}
#[inline]
fn category(&self) -> Category {
Category::Security
}
#[inline]
fn severity(&self) -> Severity {
Severity::Major
}
#[inline]
fn location(&self) -> Option<Location> {
let location = Location {
path: self.0.lockfile_path.clone(),
range: self.0.dependency_range(self.1.advisory.package.as_str()),
message: format!(
"{} (see https://github.com/rustsec/advisory-db/blob/main/crates/{}/{}.md)",
self.1.advisory.title, self.1.advisory.package, self.1.advisory.id
),
};
Some(location)
}
}
#[cfg(test)]
mod tests {
use crate::{
Category, Issue as _, Severity, TextRange,
{audit::Audit, cargo::PackageRange, Lockfile},
};
use std::{io::Write as _, path::PathBuf};
use test_log::test;
#[test]
fn single_issue() {
let json = r####"{
"database": {
"advisory-count": 462,
"last-commit": "1736a7bd7cf0d00161721ca6abb2799b05c96fc6",
"last-updated": "2022-10-19T01:14:12Z"
},
"lockfile": {
"dependency-count": 183
},
"settings": {
"target_arch": [],
"target_os": [],
"severity": null,
"ignore": [],
"informational_warnings": [
"unmaintained"
]
},
"vulnerabilities": {
"found": true,
"count": 1,
"list": [
{
"advisory": {
"id": "RUSTSEC-2020-0071",
"package": "time",
"title": "Potential segfault in the time crate",
"description": "### Impact\n\nUnix-like operating systems may segfault due to dereferencing a dangling pointer in specific circumstances. This requires an environment variable to be set in a different thread than the affected functions. This may occur without the user's knowledge, notably in a third-party library.\n\nThe affected functions from time 0.2.7 through 0.2.22 are:\n\n- `time::UtcOffset::local_offset_at`\n- `time::UtcOffset::try_local_offset_at`\n- `time::UtcOffset::current_local_offset`\n- `time::UtcOffset::try_current_local_offset`\n- `time::OffsetDateTime::now_local`\n- `time::OffsetDateTime::try_now_local`\n\nThe affected functions in time 0.1 (all versions) are:\n\n- `at`\n- `at_utc`\n- `now`\n\nNon-Unix targets (including Windows and wasm) are unaffected.\n\n### Patches\n\nPending a proper fix, the internal method that determines the local offset has been modified to always return `None` on the affected operating systems. This has the effect of returning an `Err` on the `try_*` methods and `UTC` on the non-`try_*` methods.\n\nUsers and library authors with time in their dependency tree should perform `cargo update`, which will pull in the updated, unaffected code.\n\nUsers of time 0.1 do not have a patch and should upgrade to an unaffected version: time 0.2.23 or greater or the 0.3 series.\n\n### Workarounds\n\nNo workarounds are known.",
"date": "2020-11-18",
"aliases": [
"CVE-2020-26235"
],
"related": [],
"collection": "crates",
"categories": [
"code-execution",
"memory-corruption"
],
"keywords": [
"segfault"
],
"cvss": "CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H",
"informational": null,
"references": [],
"source": null,
"url": "https://github.com/time-rs/time/issues/293",
"withdrawn": null
},
"versions": {
"patched": [
">=0.2.23"
],
"unaffected": [
"=0.2.0",
"=0.2.1",
"=0.2.2",
"=0.2.3",
"=0.2.4",
"=0.2.5",
"=0.2.6"
]
},
"affected": {
"arch": [],
"os": [
"linux",
"redox",
"solaris",
"android",
"ios",
"macos",
"netbsd",
"openbsd",
"freebsd"
],
"functions": {
"time::OffsetDateTime::now_local": [
"<0.2.23"
],
"time::OffsetDateTime::try_now_local": [
"<0.2.23"
],
"time::UtcOffset::current_local_offset": [
"<0.2.23"
],
"time::UtcOffset::local_offset_at": [
"<0.2.23"
],
"time::UtcOffset::try_current_local_offset": [
"<0.2.23"
],
"time::UtcOffset::try_local_offset_at": [
"<0.2.23"
],
"time::at": [
"^0.1"
],
"time::at_utc": [
"^0.1"
],
"time::now": [
"^0.1"
]
}
},
"package": {
"name": "time",
"version": "0.1.44",
"source": "registry+https://github.com/rust-lang/crates.io-index",
"checksum": "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255",
"dependencies": [
{
"name": "libc",
"version": "0.2.135",
"source": "registry+https://github.com/rust-lang/crates.io-index"
},
{
"name": "wasi",
"version": "0.10.0+wasi-snapshot-preview1",
"source": "registry+https://github.com/rust-lang/crates.io-index"
},
{
"name": "winapi",
"version": "0.3.9",
"source": "registry+https://github.com/rust-lang/crates.io-index"
}
],
"replace": null
}
}
]
},
"warnings": {
"unmaintained": [
{
"kind": "unmaintained",
"package": {
"name": "ansi_term",
"version": "0.12.1",
"source": "registry+https://github.com/rust-lang/crates.io-index",
"checksum": "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2",
"dependencies": [
{
"name": "winapi",
"version": "0.3.9",
"source": "registry+https://github.com/rust-lang/crates.io-index"
}
],
"replace": null
},
"advisory": {
"id": "RUSTSEC-2021-0139",
"package": "ansi_term",
"title": "ansi_term is Unmaintained",
"description": "The maintainer has advised that this crate is deprecated and will not receive any maintenance.\n\nThe crate does not seem to have much dependencies and may or may not be ok to use as-is.\n\nLast release seems to have been three years ago.\n\n## Possible Alternative(s)\n\n The below list has not been vetted in any way and may or may not contain alternatives;\n\n - [anstyle](https://github.com/epage/anstyle)\n - [console](https://crates.io/crates/console)\n - [nu-ansi-term](https://crates.io/crates/nu-ansi-term)\n - [owo-colors](https://crates.io/crates/owo-colors)\n - [stylish](https://crates.io/crates/stylish)\n - [yansi](https://crates.io/crates/yansi)\n\n## Dependency Specific Migration(s)\n\n - [structopt, clap2](https://github.com/clap-rs/clap/discussions/4172)",
"date": "2021-08-18",
"aliases": [],
"related": [],
"collection": "crates",
"categories": [],
"keywords": [],
"cvss": null,
"informational": "unmaintained",
"references": [],
"source": null,
"url": "https://github.com/ogham/rust-ansi-term/issues/72",
"withdrawn": null
},
"versions": {
"patched": [],
"unaffected": []
}
}
]
}
}"####;
let json = json.to_owned().replace('\n', "");
let mut audit_json = tempfile::NamedTempFile::new().unwrap();
write!(audit_json, "{}", json).unwrap();
let audit_json = audit_json.reopen().unwrap();
let lockfile = Lockfile {
lockfile_path: PathBuf::from("Cargo.lock"),
dependencies: [(
"time".to_owned(),
PackageRange {
range: TextRange::new((175, 1), (184, 2)),
name_range: TextRange::new((176, 9), (176, 12)),
version_range: TextRange::new((177, 12), (177, 16)),
},
)]
.into_iter()
.collect(),
};
let mut audit = Audit::try_new(audit_json, &lockfile).unwrap();
let issue = audit.next().unwrap();
assert_eq!(issue.analyzer_id(), "audit");
assert_eq!(issue.issue_uid(), "audit::RUSTSEC-2020-0071");
assert!(matches!(issue.severity(), Severity::Major));
assert!(matches!(issue.category(), Category::Security));
let location = issue.location().unwrap();
assert_eq!(location.path, PathBuf::from("Cargo.lock"));
assert_eq!(
location.message,
"Potential segfault in the time crate (see https://github.com/rustsec/advisory-db/blob/main/crates/time/RUSTSEC-2020-0071.md)"
);
assert_eq!(location.range.start.line, 175);
assert_eq!(location.range.end.line, 184);
assert_eq!(location.range.start.column, 1);
assert_eq!(location.range.end.column, 2);
}
}