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
//! 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, 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());
    }
}