Coverage Report

Created: 2026-06-07 07:05

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/suricata7/rust/src/asn1/parse_rules.rs
Line
Count
Source
1
/* Copyright (C) 2020 Open Information Security Foundation
2
 *
3
 * You can copy, redistribute or modify this Program under the terms of
4
 * the GNU General Public License version 2 as published by the Free
5
 * Software Foundation.
6
 *
7
 * This program is distributed in the hope that it will be useful,
8
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10
 * GNU General Public License for more details.
11
 *
12
 * You should have received a copy of the GNU General Public License
13
 * version 2 along with this program; if not, write to the Free Software
14
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
15
 * 02110-1301, USA.
16
 */
17
18
use nom7::branch::alt;
19
use nom7::bytes::complete::tag;
20
use nom7::character::complete::{digit1, multispace0, multispace1};
21
use nom7::combinator::{map_res, opt, verify};
22
use nom7::error::{make_error, ErrorKind};
23
use nom7::sequence::{separated_pair, tuple};
24
use nom7::{Err, IResult};
25
use std::ffi::CStr;
26
use std::os::raw::c_char;
27
28
const ASN1_DEFAULT_MAX_FRAMES: u16 = 30;
29
30
/// Parse the asn1 keyword and return a pointer to a `DetectAsn1Data`
31
/// containing the parsed options, returns null on failure
32
///
33
/// # Safety
34
///
35
/// pointer must be free'd using `rs_detect_asn1_free`
36
#[no_mangle]
37
2.97k
pub unsafe extern "C" fn rs_detect_asn1_parse(input: *const c_char) -> *mut DetectAsn1Data {
38
2.97k
    if input.is_null() {
39
0
        return std::ptr::null_mut();
40
2.97k
    }
41
42
2.97k
    let arg = match CStr::from_ptr(input).to_str() {
43
2.97k
        Ok(arg) => arg,
44
        _ => {
45
0
            return std::ptr::null_mut();
46
        }
47
    };
48
49
2.97k
    match asn1_parse_rule(arg) {
50
1.29k
        Ok((_rest, data)) => {
51
1.29k
            let mut data = data;
52
53
            // Get configuration value
54
1.29k
            if let Some(max_frames) = crate::conf::conf_get("asn1-max-frames") {
55
0
                if let Ok(v) = max_frames.parse::<u16>() {
56
0
                    data.max_frames = v;
57
0
                } else {
58
0
                    SCLogError!("Could not parse asn1-max-frames: {}", max_frames);
59
0
                    return std::ptr::null_mut();
60
                };
61
1.29k
            }
62
63
1.29k
            Box::into_raw(Box::new(data))
64
        }
65
1.67k
        Err(e) => {
66
1.67k
            SCLogError!("Malformed asn1 argument: {}", e.to_string());
67
1.67k
            std::ptr::null_mut()
68
        }
69
    }
70
2.97k
}
71
72
/// Free a `DetectAsn1Data` object allocated by Rust
73
///
74
/// # Safety
75
///
76
/// ptr must be a valid object obtained using `rs_detect_asn1_parse`
77
#[no_mangle]
78
1.29k
pub unsafe extern "C" fn rs_detect_asn1_free(ptr: *mut DetectAsn1Data) {
79
1.29k
    if ptr.is_null() {
80
0
        return;
81
1.29k
    }
82
1.29k
    drop(Box::from_raw(ptr));
83
1.29k
}
84
85
/// Struct to hold parsed asn1 keyword options
86
#[derive(Debug, PartialEq, Eq)]
87
pub struct DetectAsn1Data {
88
    pub bitstring_overflow: bool,
89
    pub double_overflow: bool,
90
    pub oversize_length: Option<u32>,
91
    pub absolute_offset: Option<u16>,
92
    pub relative_offset: Option<i32>,
93
    pub max_frames: u16,
94
}
95
96
impl Default for DetectAsn1Data {
97
2.97k
    fn default() -> DetectAsn1Data {
98
2.97k
        DetectAsn1Data {
99
2.97k
            bitstring_overflow: false,
100
2.97k
            double_overflow: false,
101
2.97k
            oversize_length: None,
102
2.97k
            absolute_offset: None,
103
2.97k
            relative_offset: None,
104
2.97k
            max_frames: ASN1_DEFAULT_MAX_FRAMES,
105
2.97k
        }
106
2.97k
    }
107
}
108
109
983
fn parse_u32_number(input: &str) -> IResult<&str, u32> {
110
983
    map_res(digit1, |digits: &str| digits.parse::<u32>())(input)
111
983
}
112
113
1.91k
fn parse_u16_number(input: &str) -> IResult<&str, u16> {
114
1.91k
    map_res(digit1, |digits: &str| digits.parse::<u16>())(input)
115
1.91k
}
116
117
769
fn parse_i32_number(input: &str) -> IResult<&str, i32> {
118
769
    let (rest, negate) = opt(tag("-"))(input)?;
119
769
    let (rest, d) = map_res(digit1, |s: &str| s.parse::<i32>())(rest)?;
120
752
    let n = if negate.is_some() { -1 } else { 1 };
121
752
    Ok((rest, d * n))
122
769
}
123
124
/// Parse asn1 keyword options
125
2.97k
pub(super) fn asn1_parse_rule(input: &str) -> IResult<&str, DetectAsn1Data> {
126
    // If nothing to parse, return
127
2.97k
    if input.is_empty() {
128
0
        return Err(Err::Error(make_error(
129
0
            input,
130
0
            ErrorKind::Eof,
131
0
        )));
132
2.97k
    }
133
134
    // Rule parsing functions
135
6.85k
    fn bitstring_overflow(i: &str) -> IResult<&str, &str> {
136
6.85k
        tag("bitstring_overflow")(i)
137
6.85k
    }
138
139
6.85k
    fn double_overflow(i: &str) -> IResult<&str, &str> {
140
6.85k
        tag("double_overflow")(i)
141
6.85k
    }
142
143
6.85k
    fn oversize_length(i: &str) -> IResult<&str, (&str, u32)> {
144
6.85k
        separated_pair(tag("oversize_length"), multispace1, parse_u32_number)(i)
145
6.85k
    }
146
147
6.85k
    fn absolute_offset(i: &str) -> IResult<&str, (&str, u16)> {
148
6.85k
        separated_pair(tag("absolute_offset"), multispace1, parse_u16_number)(i)
149
6.85k
    }
150
151
6.85k
    fn relative_offset(i: &str) -> IResult<&str, (&str, i32)> {
152
6.85k
        separated_pair(
153
6.85k
            tag("relative_offset"),
154
            multispace1,
155
6.85k
            verify(parse_i32_number, |v| {
156
752
                *v >= -i32::from(u16::MAX) && *v <= i32::from(u16::MAX)
157
752
            }),
158
6.85k
        )(i)
159
6.85k
    }
160
161
2.97k
    let mut data = DetectAsn1Data::default();
162
163
2.97k
    let mut rest = input;
164
165
    // Parse the input and set data
166
8.14k
    while !rest.is_empty() {
167
        let (
168
6.85k
            new_rest,
169
            (
170
                _,
171
6.85k
                bitstring_overflow,
172
6.85k
                double_overflow,
173
6.85k
                oversize_length,
174
6.85k
                absolute_offset,
175
6.85k
                relative_offset,
176
                _,
177
            ),
178
6.85k
        ) = tuple((
179
6.85k
            opt(multispace0),
180
6.85k
            opt(bitstring_overflow),
181
6.85k
            opt(double_overflow),
182
6.85k
            opt(oversize_length),
183
6.85k
            opt(absolute_offset),
184
6.85k
            opt(relative_offset),
185
6.85k
            opt(alt((multispace1, tag(",")))),
186
6.85k
        ))(rest)?;
187
188
6.85k
        if bitstring_overflow.is_some() {
189
841
            data.bitstring_overflow = true;
190
6.00k
        } else if double_overflow.is_some() {
191
741
            data.double_overflow = true;
192
5.26k
        } else if let Some((_, v)) = oversize_length {
193
970
            data.oversize_length = Some(v);
194
4.29k
        } else if let Some((_, v)) = absolute_offset {
195
1.90k
            data.absolute_offset = Some(v);
196
2.39k
        } else if let Some((_, v)) = relative_offset {
197
718
            data.relative_offset = Some(v);
198
718
        } else {
199
1.67k
            return Err(Err::Error(make_error(
200
1.67k
                rest,
201
1.67k
                ErrorKind::Verify,
202
1.67k
            )));
203
        }
204
205
5.17k
        rest = new_rest;
206
    }
207
208
1.29k
    Ok((rest, data))
209
2.97k
}
210
211
#[cfg(test)]
212
#[allow(clippy::unused_unit)]
213
mod tests {
214
    use super::*;
215
    use test_case::test_case;
216
217
    // Test oversize_length
218
    #[test_case("oversize_length 1024",
219
        DetectAsn1Data { oversize_length: Some(1024), ..Default::default()};
220
        "check that we parse oversize_length correctly")]
221
    #[test_case("oversize_length 0",
222
        DetectAsn1Data { oversize_length: Some(0), ..Default::default()};
223
        "check lower bound on oversize_length")]
224
    #[test_case("oversize_length -1",
225
        DetectAsn1Data::default() => panics r#"Error { input: "oversize_length -1", code: Verify }"#;
226
        "check under lower bound on oversize_length")]
227
    #[test_case("oversize_length 4294967295",
228
        DetectAsn1Data { oversize_length: Some(4294967295), ..Default::default()};
229
        "check upper bound on oversize_length")]
230
    #[test_case("oversize_length 4294967296",
231
        DetectAsn1Data::default() => panics r#"Error { input: "oversize_length 4294967296", code: Verify }"#;
232
        "check over upper bound on oversize_length")]
233
    #[test_case("oversize_length",
234
        DetectAsn1Data::default() => panics r#"Error { input: "oversize_length", code: Verify }"#;
235
        "check that we fail if the needed arg oversize_length is not given")]
236
    // Test absolute_offset
237
    #[test_case("absolute_offset 1024",
238
        DetectAsn1Data { absolute_offset: Some(1024), ..Default::default()};
239
        "check that we parse absolute_offset correctly")]
240
    #[test_case("absolute_offset 0",
241
        DetectAsn1Data { absolute_offset: Some(0), ..Default::default()};
242
        "check lower bound on absolute_offset")]
243
    #[test_case("absolute_offset -1",
244
        DetectAsn1Data::default() => panics r#"Error { input: "absolute_offset -1", code: Verify }"#;
245
        "check under lower bound on absolute_offset")]
246
    #[test_case("absolute_offset 65535",
247
        DetectAsn1Data { absolute_offset: Some(65535), ..Default::default()};
248
        "check upper bound on absolute_offset")]
249
    #[test_case("absolute_offset 65536",
250
        DetectAsn1Data::default() => panics r#"Error { input: "absolute_offset 65536", code: Verify }"#;
251
        "check over upper bound on absolute_offset")]
252
    #[test_case("absolute_offset",
253
        DetectAsn1Data::default() => panics r#"Error { input: "absolute_offset", code: Verify }"#;
254
        "check that we fail if the needed arg absolute_offset is not given")]
255
    // Test relative_offset
256
    #[test_case("relative_offset 1024",
257
        DetectAsn1Data { relative_offset: Some(1024), ..Default::default()};
258
        "check that we parse relative_offset correctly")]
259
    #[test_case("relative_offset -65535",
260
        DetectAsn1Data { relative_offset: Some(-65535), ..Default::default()};
261
        "check lower bound on relative_offset")]
262
    #[test_case("relative_offset -65536",
263
        DetectAsn1Data::default() => panics r#"Error { input: "relative_offset -65536", code: Verify }"#;
264
        "check under lower bound on relative_offset")]
265
    #[test_case("relative_offset 65535",
266
        DetectAsn1Data { relative_offset: Some(65535), ..Default::default()};
267
        "check upper bound on relative_offset")]
268
    #[test_case("relative_offset 65536",
269
        DetectAsn1Data::default() => panics r#"Error { input: "relative_offset 65536", code: Verify }"#;
270
        "check over upper bound on relative_offset")]
271
    #[test_case("relative_offset",
272
        DetectAsn1Data::default() => panics r#"Error { input: "relative_offset", code: Verify }"#;
273
        "check that we fail if the needed arg relative_offset is not given")]
274
    // Test bitstring_overflow
275
    #[test_case("bitstring_overflow",
276
        DetectAsn1Data { bitstring_overflow: true, ..Default::default()};
277
        "check that we parse bitstring_overflow correctly")]
278
    // Test double_overflow
279
    #[test_case("double_overflow",
280
        DetectAsn1Data { double_overflow: true, ..Default::default()};
281
        "check that we parse double_overflow correctly")]
282
    // Test combination of params
283
    #[test_case("oversize_length 1024, relative_offset 10",
284
        DetectAsn1Data { oversize_length: Some(1024), relative_offset: Some(10),
285
            ..Default::default()};
286
        "check for combinations of keywords (comma seperated)")]
287
    #[test_case("oversize_length 1024 absolute_offset 10",
288
        DetectAsn1Data { oversize_length: Some(1024), absolute_offset: Some(10),
289
            ..Default::default()};
290
        "check for combinations of keywords (space seperated)")]
291
    #[test_case("oversize_length 1024 absolute_offset 10, bitstring_overflow",
292
        DetectAsn1Data { bitstring_overflow: true, oversize_length: Some(1024),
293
            absolute_offset: Some(10), ..Default::default()};
294
        "check for combinations of keywords (space/comma seperated)")]
295
    #[test_case(
296
        "double_overflow, oversize_length 1024 absolute_offset 10,\n bitstring_overflow",
297
        DetectAsn1Data { double_overflow: true, bitstring_overflow: true,
298
            oversize_length: Some(1024), absolute_offset: Some(10),
299
            ..Default::default()};
300
        "1. check for combinations of keywords (space/comma/newline seperated)")]
301
    #[test_case(
302
        "\n\t double_overflow, oversize_length 1024 relative_offset 10,\n bitstring_overflow",
303
        DetectAsn1Data { double_overflow: true, bitstring_overflow: true,
304
            oversize_length: Some(1024), relative_offset: Some(10),
305
            ..Default::default()};
306
        "2. check for combinations of keywords (space/comma/newline seperated)")]
307
    // Test empty
308
    #[test_case("",
309
        DetectAsn1Data::default() => panics r#"Error { input: "", code: Eof }"#;
310
        "test that we break with a empty string")]
311
    // Test invalid rules
312
    #[test_case("oversize_length 1024, some_other_param 360",
313
        DetectAsn1Data::default() => panics r#"Error { input: " some_other_param 360", code: Verify }"#;
314
        "test that we break on invalid options")]
315
    #[test_case("oversize_length 1024,,",
316
        DetectAsn1Data::default() => panics r#"Error { input: ",", code: Verify }"#;
317
        "test that we break on invalid format (missing option)")]
318
    #[test_case("bitstring_overflowabsolute_offset",
319
        DetectAsn1Data::default() => panics r#"Error { input: "absolute_offset", code: Verify }"#;
320
        "test that we break on invalid format (missing separator)")]
321
    fn test_asn1_parse_rule(input: &str, expected: DetectAsn1Data) {
322
        let (rest, res) = asn1_parse_rule(input).unwrap();
323
324
        assert_eq!(0, rest.len());
325
        assert_eq!(expected, res);
326
    }
327
}