blob: afd9bb6964cabedc1ab551d848da4bd2118a0ddc [file] [log] [blame]
//! The `[affected]` subsection of an advisory: metadata specifying the scope
//! of impacted systems/functions/usages.
use crate::{
error::{Error, ErrorKind},
Map,
};
use platforms::target::{Arch, OS};
use semver::VersionReq;
use serde::{de::Error as DeError, Deserialize, Deserializer, Serialize, Serializer};
use std::{
fmt::{self, Display},
slice,
str::FromStr,
};
/// The `[affected]` subsection of an advisory: additional metadata detailing
/// the specifics of what is impacted by this advisory (e.g. operating systems,
/// what functions in the crate)
#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
pub struct Affected {
/// CPU architectures that this vulnerability is specific to
#[serde(default)]
pub arch: Vec<Arch>,
/// Operating systems that this vulnerability is specific to
#[serde(default)]
pub os: Vec<OS>,
/// Paths to types and/or functions containing vulnerable code, enumerated
/// as canonical Rust paths (i.e. starting with the crate name), sans any
/// path parameters.
///
/// (e.g. `mycrate::path::to::VulnerableStruct::vulnerable_func`)
#[serde(default)]
pub functions: Map<FunctionPath, Vec<VersionReq>>,
}
/// Canonical Rust Paths (sans parameters) to vulnerable types and/or functions
/// affected by a particular advisory.
/// <https://doc.rust-lang.org/reference/paths.html#canonical-paths>
#[derive(Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
pub struct FunctionPath(Vec<Identifier>);
impl FunctionPath {
/// Get the crate name for this path
pub fn crate_name(&self) -> &str {
self.iter()
.next()
.expect("path must have 2 or more segments")
.as_str()
}
/// Convert this path into an owned vector of `Identifier`s
pub fn into_vec(self) -> Vec<Identifier> {
self.0
}
/// Iterate over the segments of this path
pub fn iter(&self) -> slice::Iter<'_, Identifier> {
self.0.iter()
}
/// Borrow the segments of this path
pub fn segments(&self) -> &[Identifier] {
self.0.as_slice()
}
}
impl Display for FunctionPath {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut segments = self.iter();
let crate_name = segments.next().expect("path must have 2 or more segments");
write!(f, "{}", crate_name.as_str())?;
for segment in segments {
write!(f, "::{}", segment.as_str())?;
}
Ok(())
}
}
impl FromStr for FunctionPath {
type Err = Error;
/// Parse a canonical, parameter-free path contained in an advisory
fn from_str(path: &str) -> Result<Self, Error> {
let mut segments = vec![];
for segment in path.split("::") {
segments.push(segment.parse()?);
}
if segments.len() >= 2 {
Ok(FunctionPath(segments))
} else {
fail!(
ErrorKind::Parse,
"paths must start with the crate name (i.e. minimum two segments): '{}'",
path
)
}
}
}
impl<'de> Deserialize<'de> for FunctionPath {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let string = String::deserialize(deserializer)?;
string
.parse()
.map_err(|e| D::Error::custom(format!("{}", e)))
}
}
impl Serialize for FunctionPath {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
serializer.serialize_str(&self.to_string())
}
}
/// Identifiers within paths. Note that the typical Rust path grammar supports
/// multiple types of path segments, however for the purposes of vulnerability
/// advisories we only care about identifiers.
/// <https://doc.rust-lang.org/reference/identifiers.html>
#[derive(Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
pub struct Identifier(String);
impl Identifier {
/// Borrow this identifier as a `str`
pub fn as_str(&self) -> &str {
&self.0
}
}
impl AsRef<str> for Identifier {
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl FromStr for Identifier {
type Err = Error;
/// Parse an `Identifier` within a `path`
fn from_str(identifier: &str) -> Result<Self, Error> {
validate_identifier(identifier)?;
Ok(Identifier(identifier.into()))
}
}
/// Validate an identifier within a path is valid
fn validate_identifier(identifier: &str) -> Result<(), Error> {
let mut chars = identifier.chars();
if let Some(first_char) = chars.next() {
match first_char {
'A'..='Z' | 'a'..='z' | '_' | '<' => (),
_ => fail!(
ErrorKind::Parse,
"invalid character at start of ident: '{}'",
identifier
),
}
} else {
fail!(ErrorKind::Parse, "empty identifier in affected path");
}
for c in chars {
match c {
'A'..='Z' | 'a'..='z' | '0'..='9' | '_' | '<' | '>' | ',' => (),
'(' | ')' => fail!(
ErrorKind::Parse,
"omit parameters when specifying affected paths: '{}'",
identifier
),
_ => fail!(
ErrorKind::Parse,
"invalid character in identifier: '{}'",
identifier
),
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::FunctionPath;
use std::str::FromStr;
const EXAMPLE_PATH_STR: &str = "foo::bar::baz";
#[test]
fn crate_name_test() {
let path = FunctionPath::from_str(EXAMPLE_PATH_STR).unwrap();
assert_eq!(path.crate_name(), "foo");
}
#[test]
fn display_test() {
let path = FunctionPath::from_str(EXAMPLE_PATH_STR).unwrap();
assert_eq!(path.to_string(), EXAMPLE_PATH_STR)
}
#[test]
fn from_str_test() {
// Valid paths
assert!(FunctionPath::from_str("foo::bar").is_ok());
assert!(FunctionPath::from_str("foo::bar::baz").is_ok());
assert!(FunctionPath::from_str("foo::Bar::baz").is_ok());
assert!(FunctionPath::from_str("foo::<Bar>::baz").is_ok());
assert!(FunctionPath::from_str("foo::<BarA,BarB>::baz").is_ok());
assert!(FunctionPath::from_str("foo::Bar<Baz>::quux").is_ok());
assert!(FunctionPath::from_str("foo::Bar::_baz").is_ok());
assert!(FunctionPath::from_str("foo::Bar::_baz_").is_ok());
assert!(FunctionPath::from_str("f00::B4r::_b4z_").is_ok());
// Invalid paths
assert!(FunctionPath::from_str("minimum_two_components").is_err());
assert!(FunctionPath::from_str("no-hyphens::foobar").is_err());
assert!(FunctionPath::from_str("no_leading_digits::0rly").is_err());
}
}