/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 | | } |