diff --git a/Cargo.toml b/Cargo.toml index 7f2f3b7..9544796 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,6 +10,7 @@ publish = false name = "rustsec-advisory-db" [dependencies] -gumdrop = "0.6" -rustsec = "0.12" crates_io_api = "0.5" +gumdrop = "0.6" +rustsec = "0.13.0-alpha1" +termcolor = "1" diff --git a/README.md b/README.md index 23fa038..c70a335 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,7 @@ See [CONTRIBUTING.md] for more information. Each advisory contains information in [TOML] format: ```toml -# Before you submit a PR using this template, please delete the comments +# Before you submit a PR using this template, **please delete the comments** # explaining each field, as well as any unused fields. [advisory] @@ -73,7 +73,12 @@ patched_versions = [">= 1.2.0"] # a change log entry, or a blogpost announcing the release (optional) url = "https://github.com/mystuff/mycrate/issues/123" -# Keywords which describe this vulnerability, similar to Cargo (optional) +# Optional: Categories this advisory falls under. Valid categories are: +# "code-execution", "crypto-failure", "denial-of-service", "file-disclosure" +# "format-injection", "memory-corruption", "memory-exposure", "privilege-escalation" +categories = ["crypto-failure"] + +# Freeform keywords which describe this vulnerability, similar to Cargo (optional) keywords = ["ssl", "mitm"] # Vulnerability aliases, e.g. CVE IDs (optional but recommended) @@ -84,22 +89,28 @@ keywords = ["ssl", "mitm"] # e.g. CVE for a C library wrapped by a -sys crate) #references = ["CVE-2018-YYYY", "CVE-2018-ZZZZ"] -# CPU architectures impacted by this vulnerability (optional) +# Optional: metadata which narrows the scope of what this advisory affects +[affected] +# CPU architectures impacted by this vulnerability (optional). +# Only use this if the vulnerability is specific to a particular CPU architecture, +# e.g. the vulnerability is in x86 assembly. # For a list of CPU architecture strings, see the "platforms" crate: # -#affected_arch = ["x86", "x86_64"] +#arch = ["x86", "x86_64"] # Operating systems impacted by this vulnerability (optional) +# Only use this if the vulnerable is specific to a particular OS, e.g. it was +# located in a binding to a Windows-specific API. # For a list of OS strings, see the "platforms" crate: # -#affected_os = ["windows"] +#os = ["windows"] -# List of canonical paths to vulnerable functions (optional) -# The path syntax is cratename::path::to::function, without any -# return type or parameters. More information: -# -# For example, for RUSTSEC-2018-0003, this would look like: -#affected_functions = ["smallvec::SmallVec::insert_many"] +# Table of canonical paths to vulnerable functions (optional) +# mapping to which versions impacted by this advisory used that particular +# name (e.g. if the function was renamed between versions). +# The path syntax is `cratename::path::to::function`, without any +# parameters or additional information, followed by a list of version reqs. +functions = { "mycrate::MyType::vulnerable_function" = ["< 1.2.0, >= 1.1.0"] } ``` ## License diff --git a/crates/ammonia/RUSTSEC-2019-0001.toml b/crates/ammonia/RUSTSEC-2019-0001.toml index a370123..387302c 100644 --- a/crates/ammonia/RUSTSEC-2019-0001.toml +++ b/crates/ammonia/RUSTSEC-2019-0001.toml @@ -15,8 +15,8 @@ The flaw was corrected by serializing the DOM tree iteratively instead. patched_versions = [">= 2.1.0"] url = "https://github.com/rust-ammonia/ammonia/blob/master/CHANGELOG.md#210" keywords = ["stack-overflow", "crash"] -affected_functions = [ - "ammonia::clean", - "ammonia::Document::to_string", - "ammonia::Document::write_to", -] + +[affected.functions] +"ammonia::clean" = ["< 2.1.0"] +"ammonia::Document::to_string" = ["< 2.1.0"] +"ammonia::Document::write_to" = ["< 2.1.0"] diff --git a/crates/arrayfire/RUSTSEC-2018-0011.toml b/crates/arrayfire/RUSTSEC-2018-0011.toml index e3dbe43..c485fff 100644 --- a/crates/arrayfire/RUSTSEC-2018-0011.toml +++ b/crates/arrayfire/RUSTSEC-2018-0011.toml @@ -1,12 +1,8 @@ [advisory] id = "RUSTSEC-2018-0011" - package = "arrayfire" - date = "2018-12-18" - title = "Enum repr causing potential memory corruption" - description = """ The attribute repr() added to enums to be compatible with C-FFI caused memory corruption on MSVC toolchain. @@ -19,15 +15,12 @@ The issue seems to be interlinked with which version of Rust is being used. The issue was fixed in crate 3.6.0. """ - patched_versions = [">= 3.6.0"] - unaffected_versions = ["<= 3.5.0"] - url = "https://github.com/arrayfire/arrayfire-rust/pull/177" +categories = ["memory-corruption"] +keywords = ["enum", "repr"] -keywords = ["enum", "repr", "memory-corruption"] - -affected_arch = ["x86_64"] - -affected_os = ["windows"] +[affected] +arch = ["x86_64"] +os = ["windows"] diff --git a/crates/blake2/RUSTSEC-2019-0019.toml b/crates/blake2/RUSTSEC-2019-0019.toml index 948c606..7472993 100644 --- a/crates/blake2/RUSTSEC-2019-0019.toml +++ b/crates/blake2/RUSTSEC-2019-0019.toml @@ -17,4 +17,4 @@ impact `Digest` functionality. """ patched_versions = [">= 0.8.1"] url = "https://github.com/RustCrypto/MACs/issues/19" -category = ["crypto-failure"] +categories = ["crypto-failure"] diff --git a/crates/compact_arena/RUSTSEC-2019-0015.toml b/crates/compact_arena/RUSTSEC-2019-0015.toml index e1f3ce6..d14e62e 100644 --- a/crates/compact_arena/RUSTSEC-2019-0015.toml +++ b/crates/compact_arena/RUSTSEC-2019-0015.toml @@ -7,13 +7,16 @@ description = """ Affected versions of this crate did not properly implement the generativity, because the invariant lifetimes were not necessarily `drop`ped. -This allows an attacker to mix up two arenas, using indices created from one -arena with another one. This might lead to an out-of-bounds read or write +This allows an attacker to mix up two arenas, using indices created from one +arena with another one. This might lead to an out-of-bounds read or write access into the memory reserved for the arena. - + The flaw was corrected by implementing generativity correctly in version 0.4.0. """ patched_versions = [">= 0.4.0"] url = "https://github.com/llogiq/compact_arena/issues/22" -keywords = ["memory-corruption", "uninitialized-memory"] -affected_functions = ["compact_arena::SmallArena::new"] +categories = ["memory-corruption"] +keywords = ["uninitialized-memory"] + +[affected.functions] +"compact_arena::SmallArena::new" = ["< 0.4.0"] diff --git a/crates/hyper/RUSTSEC-2016-0002.toml b/crates/hyper/RUSTSEC-2016-0002.toml index 5e54700..60553e6 100644 --- a/crates/hyper/RUSTSEC-2016-0002.toml +++ b/crates/hyper/RUSTSEC-2016-0002.toml @@ -4,8 +4,8 @@ package = "hyper" date = "2016-05-09" url = "https://github.com/hyperium/hyper/blob/master/CHANGELOG.md#v094-2016-05-09" title = "HTTPS MitM vulnerability due to lack of hostname verification" +categories = ["crypto-failure"] keywords = ["ssl", "mitm"] -affected_os = ["windows"] patched_versions = [">= 0.9.4"] references = ["RUSTSEC-2016-0001"] description = """ @@ -18,3 +18,6 @@ CA-issued certificate, even if there's a hostname mismatch. The problem was addressed by leveraging rust-openssl's built-in support for hostname verification. """ + +[affected] +os = ["windows"] diff --git a/crates/image/RUSTSEC-2019-0014.toml b/crates/image/RUSTSEC-2019-0014.toml index e349d84..12602ff 100644 --- a/crates/image/RUSTSEC-2019-0014.toml +++ b/crates/image/RUSTSEC-2019-0014.toml @@ -11,7 +11,7 @@ initializing all instances. This could run Drop implementations on uninitialized types, equivalent to use-after-free, and allow an attacker arbitrary code execution. - + Two different fixes were applied. It is possible to conserve the interface by ensuring proper initialization before calling `Vec::set_len`. Drop is no longer called in case of panic, though. @@ -24,4 +24,6 @@ patched_versions = [">= 0.21.3"] unaffected_versions = ["< 0.10.2"] url = "https://github.com/image-rs/image/pull/985" keywords = ["drop", "use-after-free"] -affected_functions = ["image::hdr::HDRDecoder::read_image_transform"] + +[affected.functions] +"image::hdr::HDRDecoder::read_image_transform" = ["< 0.21.3, >= 0.10.2"] diff --git a/crates/libflate/RUSTSEC-2019-0010.toml b/crates/libflate/RUSTSEC-2019-0010.toml index 5b6d4a7..8bff5bb 100644 --- a/crates/libflate/RUSTSEC-2019-0010.toml +++ b/crates/libflate/RUSTSEC-2019-0010.toml @@ -4,14 +4,16 @@ package = "libflate" date = "2019-07-04" title = "MultiDecoder::read() drops uninitialized memory of arbitrary type on panic in client code" description = """ -Affected versions of libflate have set a field of an internal structure with a generic type to an uninitialized value in `MultiDecoder::read()` and reverted it to the original value after the function completed. However, execution of `MultiDecoder::read()` could be interrupted by a panic in caller-supplied `Read` implementation. This would cause `drop()` to be called on uninitialized memory of a generic type implementing `Read`. +Affected versions of libflate have set a field of an internal structure with a generic type to an uninitialized value in `MultiDecoder::read()` and reverted it to the original value after the function completed. However, execution of `MultiDecoder::read()` could be interrupted by a panic in caller-supplied `Read` implementation. This would cause `drop()` to be called on uninitialized memory of a generic type implementing `Read`. This is equivalent to a use-after-free vulnerability and could allow an attacker to gain arbitrary code execution. - + The flaw was corrected by aborting immediately instead of unwinding the stack in case of panic within `MultiDecoder::read()`. The issue was discovered and fixed by Shnatsel. """ patched_versions = [">= 0.1.25"] unaffected_versions = ["< 0.1.14"] url = "https://github.com/sile/libflate/issues/35" keywords = ["drop", "use-after-free"] -affected_functions = ["libflate::gzip::MultiDecoder::read"] + +[affected.functions] +"libflate::gzip::MultiDecoder::read" = ["< 0.1.25, >= 0.1.14"] diff --git a/crates/ncurses/RUSTSEC-2019-0006.toml b/crates/ncurses/RUSTSEC-2019-0006.toml index 12e340d..0575d51 100644 --- a/crates/ncurses/RUSTSEC-2019-0006.toml +++ b/crates/ncurses/RUSTSEC-2019-0006.toml @@ -2,9 +2,7 @@ id = "RUSTSEC-2019-0006" package = "ncurses" date = "2019-06-15" - title = "Buffer overflow and format vulnerabilities in functions exposed without unsafe" - description = """ `ncurses` exposes functions from the ncurses library which: @@ -14,9 +12,12 @@ description = """ input to execute a format string attack, which trivially allows writing arbitrary data to stack memory (functions in the `printw` family). """ - patched_versions = [] - url = "https://github.com/RustSec/advisory-db/issues/106" -affected_functions = ["ncurses::instr", "ncurses::mvwinstr", "ncurses::printw", "ncurses::mvprintw", "ncurses::mvwprintw"] +[affected.functions] +"ncurses::instr" = [">= 0"] +"ncurses::mvwinstr" = [">= 0"] +"ncurses::printw" = [">= 0"] +"ncurses::mvprintw" = [">= 0"] +"ncurses::mvwprintw" = [">= 0"] diff --git a/crates/once_cell/RUSTSEC-2019-0017.toml b/crates/once_cell/RUSTSEC-2019-0017.toml index 359a798..e8ddaa3 100644 --- a/crates/once_cell/RUSTSEC-2019-0017.toml +++ b/crates/once_cell/RUSTSEC-2019-0017.toml @@ -23,9 +23,8 @@ url = "https://github.com/matklad/once_cell/issues/46" keywords = ["undefined_behavior"] -affected_functions = [ - "once_cell::unsync::Lazy::force", - "once_cell::unsync::Lazy::deref", - "once_cell::sync::Lazy::force", - "once_cell::sync::Lazy::deref", -] +[affected.functions] +"once_cell::unsync::Lazy::force" = ["< 1.0.1, >= 0.2.5"] +"once_cell::unsync::Lazy::deref" = ["< 1.0.1, >= 0.2.5"] +"once_cell::sync::Lazy::force" = ["< 1.0.1, >= 0.2.5"] +"once_cell::sync::Lazy::deref" = ["< 1.0.1, >= 0.2.5"] diff --git a/crates/pancurses/RUSTSEC-2019-0005.toml b/crates/pancurses/RUSTSEC-2019-0005.toml index 5dea17f..5276753 100644 --- a/crates/pancurses/RUSTSEC-2019-0005.toml +++ b/crates/pancurses/RUSTSEC-2019-0005.toml @@ -2,17 +2,15 @@ id = "RUSTSEC-2019-0005" package = "pancurses" date = "2019-06-15" - title = "Format string vulnerabilities in `pancurses`" - description = """ `pancurses::mvprintw` and `pancurses::printw` passes a pointer from a rust `&str` to C, allowing hostile input to execute a format string attack, which trivially allows writing arbitrary data to stack memory. """ - patched_versions = [] - url = "https://github.com/RustSec/advisory-db/issues/106" -affected_functions = ["pancurses::mvprintw", "pancurses::printw"] +[affected.functions] +"pancurses::mvprintw" = [">= 0"] +"pancurses::printw" = [">= 0"] diff --git a/crates/protobuf/RUSTSEC-2019-0003.toml b/crates/protobuf/RUSTSEC-2019-0003.toml index 69c9f2e..acf6691 100644 --- a/crates/protobuf/RUSTSEC-2019-0003.toml +++ b/crates/protobuf/RUSTSEC-2019-0003.toml @@ -10,6 +10,9 @@ This allows an attacker to cause an Out of Memory condition while calling the vulnerable method on untrusted data. """ url = "https://github.com/stepancheg/rust-protobuf/issues/411" -keywords = ["oom", "panic", "dos"] -affected_functions = ["stream::read_raw_bytes_into"] +categories = ["denial-of-service"] +keywords = ["oom", "panic"] patched_versions = ["^1.7.5", ">= 2.6.0"] + +[affected.functions] +"protobuf::stream::read_raw_bytes_into" = ["< 2.6.0"] diff --git a/crates/renderdoc/RUSTSEC-2019-0018.toml b/crates/renderdoc/RUSTSEC-2019-0018.toml index 53a1ae0..071aa08 100644 --- a/crates/renderdoc/RUSTSEC-2019-0018.toml +++ b/crates/renderdoc/RUSTSEC-2019-0018.toml @@ -10,13 +10,13 @@ value internally. This is technically unsound and calling these methods from multiple threads without synchronization could lead to unexpected and unpredictable behavior. - + The flaw was corrected in release 0.5.0. """ patched_versions = [">= 0.5.0"] url = "https://github.com/ebkalderon/renderdoc-rs/pull/32" keywords = ["undefined_behavior"] -affected_functions = [ - "renderdoc::api::RenderDocV110::trigger_multi_frame_capture", - "renderdoc::api::RenderDocV120::set_capture_file_comments", -] + +[affected.functions] +"renderdoc::api::RenderDocV110::trigger_multi_frame_capture" = ["< 0.5.0"] +"renderdoc::api::RenderDocV120::set_capture_file_comments" = ["< 0.5.0"] diff --git a/crates/safe-transmute/RUSTSEC-2018-0013.toml b/crates/safe-transmute/RUSTSEC-2018-0013.toml index 2222456..7f46878 100644 --- a/crates/safe-transmute/RUSTSEC-2018-0013.toml +++ b/crates/safe-transmute/RUSTSEC-2018-0013.toml @@ -2,7 +2,6 @@ id = "RUSTSEC-2018-0013" package = "safe-transmute" date = "2018-11-27" - title = "Vec-to-vec transmutations could lead to heap overflow/corruption" description = """ Affected versions of this crate switched the length and capacity arguments in the Vec::from_raw_parts() constructor, @@ -10,14 +9,12 @@ which could lead to memory corruption or data leakage. The flaw was corrected by using the constructor correctly. """ - patched_versions = [">= 0.10.1"] unaffected_versions = ["< 0.4.0"] - url = "https://github.com/nabijaczleweli/safe-transmute-rs/pull/36" - keywords = ["memory-corruption"] -[affected_paths] -">= 0.4.0, <= 0.10.0" = ["safe_transmute::guarded_transmute_vec_permissive"] -"= 0.10.0" = ["safe_transmute::guarded_transmute_to_bytes_vec"] +# TODO(tarcieri): fix linter to respect crate name +#[affected.functions] +#"safe_transmute::guarded_transmute_vec_permissive" = [">= 0.4.0, <= 0.10.0"] +#"safe_transmute::guarded_transmute_to_bytes_vec" = ["= 0.10.0"] diff --git a/crates/simd-json/RUSTSEC-2019-0008.toml b/crates/simd-json/RUSTSEC-2019-0008.toml index 855925d..080ff9b 100644 --- a/crates/simd-json/RUSTSEC-2019-0008.toml +++ b/crates/simd-json/RUSTSEC-2019-0008.toml @@ -1,12 +1,8 @@ [advisory] id = "RUSTSEC-2019-0008" - package = "simd-json" - date = "2019-06-24" - title = "Flaw in string parsing can lead to crashes due to invalid memory access." - description = """ The affected version of this crate did not guard against accessing memory beyond the range of its input data. A pointer cast to read the data into @@ -22,17 +18,14 @@ segflt | [ 32 | byte ] | ``` This allows an attacker to eventually crash a service. - + The flaw was corrected by using a padding buffer for the last read from the input. So that we are we never read over the boundary of the input data. """ - patched_versions = [">= 0.1.15"] - unaffected_versions = ["<= 0.1.13"] - url = "https://github.com/Licenser/simdjson-rs/pull/27" - keywords = ["simd"] -affected_arch = ["x86", "x86_64"] +[affected] +arch = ["x86", "x86_64"] diff --git a/crates/smallvec/RUSTSEC-2019-0009.toml b/crates/smallvec/RUSTSEC-2019-0009.toml index 277d0f4..02f6f13 100644 --- a/crates/smallvec/RUSTSEC-2019-0009.toml +++ b/crates/smallvec/RUSTSEC-2019-0009.toml @@ -14,4 +14,6 @@ patched_versions = [">= 0.6.10"] unaffected_versions = ["< 0.6.5"] url = "https://github.com/servo/rust-smallvec/issues/148" keywords = ["double free", "use after free", "arbitrary code execution"] -affected_functions = ["smallvec::SmallVec::grow"] + +[affected.functions] +"smallvec::SmallVec::grow" = ["< 0.6.10, >= 0.6.5"] diff --git a/crates/smallvec/RUSTSEC-2019-0012.toml b/crates/smallvec/RUSTSEC-2019-0012.toml index 868ffec..1f25002 100644 --- a/crates/smallvec/RUSTSEC-2019-0012.toml +++ b/crates/smallvec/RUSTSEC-2019-0012.toml @@ -13,5 +13,7 @@ Credits to @ehuss for discovering, reporting and fixing the bug. patched_versions = [">= 0.6.10"] unaffected_versions = ["< 0.6.3"] url = "https://github.com/servo/rust-smallvec/issues/149" -keywords = ["memory corruption", "arbitrary code execution"] -affected_functions = ["smallvec::SmallVec::grow"] +categories = ["code-execution", "memory-corruption"] + +[affected.functions] +"smallvec::SmallVec::grow" = ["< 0.6.10, >= 0.6.3"] diff --git a/crates/spin/RUSTSEC-2019-0013.toml b/crates/spin/RUSTSEC-2019-0013.toml index 172448f..edd87b7 100644 --- a/crates/spin/RUSTSEC-2019-0013.toml +++ b/crates/spin/RUSTSEC-2019-0013.toml @@ -9,10 +9,12 @@ Wrong memory orderings inside the RwLock implementation allow for two writers to Only users of the RwLock implementation are affected. Users of Once (including users of lazy_static with the `spin_no_std` feature enabled) are NOT affected. On strongly ordered CPU architectures like x86, the only real way that this would lead to a memory corruption is if the compiler reorders an access after the lock is yielded, which is possible but in practice unlikely. It is a more serious issue on weakly ordered architectures such as ARM which, except in the presence of certain instructions, allow the hardware to decide which accesses are seen at what times. Therefore on an ARM system it is likely that using the wrong memory ordering would result in a memory corruption, even if the compiler itself doesn't reorder the memory accesses in a buggy way. - + The flaw was corrected by https://github.com/mvdnes/spin-rs/pull/66. """ patched_versions = [">= 0.5.2"] url = "https://github.com/mvdnes/spin-rs/issues/65" keywords = ["atomic", "ordering", "spin", "lock", "mutex", "rwlock"] -affected_functions = ["spin::RwLock::new"] + +[affected.functions] +"spin::RwLock::new" = ["< 0.5.2"] diff --git a/rust/std/CVE-2018-1000657.toml b/rust/std/CVE-2018-1000657.toml index 82abd88..f6985fa 100644 --- a/rust/std/CVE-2018-1000657.toml +++ b/rust/std/CVE-2018-1000657.toml @@ -12,4 +12,6 @@ patched_versions = [">= 1.22.0"] unaffected_versions = ["< 1.3.0"] categories = ["code-execution", "denial-of-service"] url = "https://github.com/rust-lang/rust/issues/44800" -affected_functions = ["std::collections::vec_deque::VecDeque::reserve"] + +[affected.functions] +"std::collections::vec_deque::VecDeque::reserve" = ["< 1.22.0, >= 1.3.0"] diff --git a/rust/std/CVE-2018-1000810.toml b/rust/std/CVE-2018-1000810.toml index 4940dd6..951b38e 100644 --- a/rust/std/CVE-2018-1000810.toml +++ b/rust/std/CVE-2018-1000810.toml @@ -96,4 +96,6 @@ unaffected_versions = ["< 1.26.0"] url = "https://groups.google.com/forum/#!topic/rustlang-security-announcements/CmSuTm-SaU0" categories = ["denial-of-service", "memory-corruption"] cvss = "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H" -affected_functions = ["std::str::repeat"] + +[affected.functions] +"std::str::repeat" = ["< 1.29.1, >= 1.26.0"] diff --git a/src/main.rs b/src/main.rs index 04562bc..bb920d1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,120 +1,204 @@ +//! RustSec Advisory DB Linter + #![allow(clippy::never_loop)] use gumdrop::Options; -use rustsec::{AdvisoryDatabase, Repository}; -use std::{env, process::exit}; +use rustsec::package; +use std::{env, io::Write, path::PathBuf, process::exit}; +use termcolor::{ + Color::{Green, Red}, + ColorChoice, ColorSpec, StandardStream, WriteColor, +}; -const MIN_EXPECTED_ADVISORIES: usize = 5; +/// Minimum number of advisories we expect in the database +const MIN_EXPECTED_ADVISORIES: usize = 10; -/// Subcommands -#[derive(Debug, Options)] -enum Opts { - #[options(help = "show help for a command")] - Help(HelpOpts), +macro_rules! writeln_color { + ($stream:expr, $color:path, $fmt:expr, $msg:expr) => { + let mut color = ColorSpec::new(); + color.bold(); + color.set_fg(Some($color)); + $stream.set_color(&color).unwrap(); - #[options(help = "check the advisory DB is well-formed")] - Check(CheckOpts), + writeln!($stream, $fmt, $msg).unwrap(); + $stream.reset().unwrap(); + }; } -/// Options for the `help` command -#[derive(Debug, Default, Options)] -struct HelpOpts { - #[options(free)] - commands: Vec, +macro_rules! writeln_success { + ($stream:expr, $msg:expr) => { + writeln_color!($stream, Green, "✔ {}", $msg); + }; + ($stream:expr, $fmt:expr, $($arg:tt)+) => { + writeln_success!($stream, format!($fmt, $($arg)+)); + } } -/// Options for the `check` command -#[derive(Debug, Default, Options)] -struct CheckOpts {} +macro_rules! writeln_error { + ($stream:expr, $msg:expr) => { + writeln_color!($stream, Red, "✘ {}", $msg); + }; + ($stream:expr, $fmt:expr, $($arg:tt)+) => { + writeln_error!($stream, format!($fmt, $($arg)+)); + } +} fn main() { let args: Vec<_> = env::args().collect(); - let opts = Opts::parse_args_default(&args[1..]).unwrap_or_else(|e| { + let opts = Commands::parse_args_default(&args[1..]).unwrap_or_else(|e| { match e.to_string().as_ref() { // Show usage if no command name is given or if "help" is given - "missing command name" => help(&[]), + "missing command name" => help(), string => eprintln!("{}: {}", args[0], string), } - exit(2); + exit(1); }); match opts { - Opts::Help(opts) => help(&opts.commands), - Opts::Check(_) => check(), + Commands::Help(_) => help(), + Commands::Check(check) => check.run(), } +} - exit(0); +/// Subcommands +#[derive(Debug, Options)] +enum Commands { + #[options(help = "show help for a command")] + Help(HelpCommand), + + #[options(help = "check the advisory DB is well-formed")] + Check(CheckCommand), +} + +/// Options for the `help` command +#[derive(Debug, Default, Options)] +struct HelpCommand { + #[options(free)] + commands: Vec, } /// Print help message -fn help(_commands: &[String]) { +fn help() { println!("Usage: {} [COMMAND] [OPTIONS]", env::args().next().unwrap()); println!(); println!("Available commands:"); println!(); - println!("{}", Opts::command_list().unwrap()); + println!("{}", Commands::command_list().unwrap()); println!(); } -fn check() { - let repo = Repository::open(".").unwrap(); +/// Options for the `check` command +#[derive(Debug, Default, Options)] +struct CheckCommand {} - // Ensure Advisories.toml parses - let db = AdvisoryDatabase::from_repository(&repo).unwrap(); - let advisories = db.advisories(); +impl CheckCommand { + fn run(&self) { + let mut stdout = StandardStream::stdout(ColorChoice::Auto); + let repo = rustsec::Repository::open(".").unwrap(); - // Ensure we're parsing some advisories - if advisories.len() > MIN_EXPECTED_ADVISORIES { - println!( - "*** Check succeeded! Successfully parsed {} advisories.", - advisories.len() - ); - } else { - panic!( - "Missing advisories! Expected at least {}, but got {}", - MIN_EXPECTED_ADVISORIES, - advisories.len() - ); + // Ensure Advisories.toml parses + let db = rustsec::Database::load(&repo).unwrap(); + let advisories = db.iter(); + + // Ensure we're parsing some advisories + if advisories.len() > MIN_EXPECTED_ADVISORIES { + writeln_success!( + &mut stdout, + "Successfully parsed {} advisories", + advisories.len() + ); + } else { + writeln_error!( + &mut stdout, + "Missing advisories! Expected at least {}, but got {}", + MIN_EXPECTED_ADVISORIES, + advisories.len() + ); + exit(1); + } + + let cratesio_client = crates_io_api::SyncClient::new(); + + let mut invalid_advisories = 0; + + for advisory in advisories { + if !self.check_advisory(&mut stdout, &cratesio_client, advisory) { + invalid_advisories += 1; + } + } + + if invalid_advisories == 0 { + writeln_success!(&mut stdout, "All advisories are well-formed"); + } else { + writeln_error!( + &mut stdout, + "{} advisories contain errors!", + invalid_advisories + ); + exit(1); + } } - let cratesio_client = crates_io_api::SyncClient::new(); - for advisory in advisories { - check_advisory(&cratesio_client, advisory); - } - println!("*** Check succeeded! All advisories refer to valid crates."); -} - -fn check_advisory(cratesio_client: &crates_io_api::SyncClient, advisory: &rustsec::Advisory) { - let response = cratesio_client - .get_crate(advisory.package.as_str()) - .unwrap_or_else(|_| { - panic!( - "Failed to get package from crates.io: {}", - advisory.package.as_str() - ) - }); - - if response.crate_data.name != advisory.package.as_str() { - panic!( - "crates.io package name does not match package name in advisory for {}", - advisory.package.as_str() - ); - } - - // Check that each path in `affected_paths` starts with the crate name - if let Some(ref version_req_paths) = advisory.affected_paths { - for (_, paths) in version_req_paths.iter() { - for path in paths { - if path.crate_name() != response.crate_data.name { - panic!( - "{}: affected_path does not begin with crate name: {}", - response.crate_data.name, - path.crate_name() - ) + fn check_advisory( + &self, + stdout: &mut StandardStream, + cratesio_client: &crates_io_api::SyncClient, + advisory: &rustsec::Advisory, + ) -> bool { + if advisory.metadata.collection == Some(package::Collection::Crates) { + match cratesio_client.get_crate(advisory.metadata.package.as_str()) { + Ok(response) => { + if response.crate_data.name != advisory.metadata.package.as_str() { + writeln_error!( + stdout, + "crates.io package name does not match package name in advisory for {}", + advisory.metadata.package.as_str() + ); + return false; + } + } + Err(err) => { + writeln_error!( + stdout, + "Failed to get package `{}` from crates.io: {}", + advisory.metadata.package.as_str(), + err + ); + return false; } } } + + let mut advisory_path = PathBuf::from(".") + .join(advisory.metadata.collection.as_ref().unwrap().to_string()) + .join(advisory.metadata.package.as_str()) + .join(advisory.metadata.id.as_str()); + + advisory_path.set_extension("toml"); + + let lint = rustsec::advisory::Linter::lint_file(&advisory_path).unwrap(); + + if lint.errors().is_empty() { + writeln_success!( + stdout, + "{} successfully passed lint", + advisory_path.display() + ); + true + } else { + writeln_error!( + stdout, + "{} contained the following lint errors:", + advisory_path.display() + ); + + for error in lint.errors() { + writeln!(stdout, " - {}", error).unwrap(); + } + + false + } } }