/src/suricata7/rust/src/asn1/mod.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 | | //! ASN.1 parser module. |
19 | | |
20 | | use der_parser::ber::{parse_ber_recursive, BerObject, BerObjectContent, Tag}; |
21 | | use nom7::Err; |
22 | | use std::convert::TryFrom; |
23 | | |
24 | | mod parse_rules; |
25 | | use parse_rules::DetectAsn1Data; |
26 | | |
27 | | /// Container for parsed Asn1 objects |
28 | | #[derive(Debug)] |
29 | | pub struct Asn1<'a>(Vec<BerObject<'a>>); |
30 | | |
31 | | /// Errors possible during decoding of Asn1 |
32 | | #[derive(Debug)] |
33 | | enum Asn1DecodeError { |
34 | | InvalidKeywordParameter, |
35 | | MaxFrames, |
36 | | BerError, |
37 | | } |
38 | | |
39 | | /// Enumeration of Asn1 checks |
40 | | #[derive(Debug, PartialEq)] |
41 | | enum Asn1Check { |
42 | | OversizeLength, |
43 | | BitstringOverflow, |
44 | | DoubleOverflow, |
45 | | MaxDepth, |
46 | | } |
47 | | |
48 | | impl<'a> Asn1<'a> { |
49 | | /// Checks each BerObject contained in self with the provided detection |
50 | | /// data, returns the first successful match if one occurs |
51 | 0 | fn check(&self, ad: &DetectAsn1Data) -> Option<Asn1Check> { |
52 | 0 | for obj in &self.0 { |
53 | 0 | let res = Asn1::check_object_recursive(obj, ad, ad.max_frames as usize); |
54 | 0 | if res.is_some() { |
55 | 0 | return res; |
56 | 0 | } |
57 | | } |
58 | | |
59 | 0 | None |
60 | 0 | } |
61 | | |
62 | 0 | fn check_object_recursive( |
63 | 0 | obj: &BerObject, ad: &DetectAsn1Data, max_depth: usize, |
64 | 0 | ) -> Option<Asn1Check> { |
65 | | // Check stack depth |
66 | 0 | if max_depth == 0 { |
67 | 0 | return Some(Asn1Check::MaxDepth); |
68 | 0 | } |
69 | | |
70 | | // Check current object |
71 | 0 | let res = Asn1::check_object(obj, ad); |
72 | 0 | if res.is_some() { |
73 | 0 | return res; |
74 | 0 | } |
75 | | |
76 | | // Check sub-nodes |
77 | 0 | for node in obj.ref_iter() { |
78 | 0 | let res = Asn1::check_object_recursive(node, ad, max_depth - 1); |
79 | 0 | if res.is_some() { |
80 | 0 | return res; |
81 | 0 | } |
82 | | } |
83 | | |
84 | 0 | None |
85 | 0 | } |
86 | | |
87 | | /// Checks a BerObject and subnodes against the Asn1 checks |
88 | 0 | fn check_object(obj: &BerObject, ad: &DetectAsn1Data) -> Option<Asn1Check> { |
89 | | // get length |
90 | | // Note that if length is indefinite (BER), this will return None |
91 | 0 | let len = obj.header.length().definite().ok()?; |
92 | | // oversize_length will check if a node has a length greater than |
93 | | // the user supplied length |
94 | 0 | if let Some(oversize_length) = ad.oversize_length { |
95 | 0 | if len > oversize_length as usize |
96 | 0 | || obj.content.as_slice().unwrap_or(&[]).len() > oversize_length as usize |
97 | | { |
98 | 0 | return Some(Asn1Check::OversizeLength); |
99 | 0 | } |
100 | 0 | } |
101 | | |
102 | | // bitstring_overflow check a malformed option where the number of bits |
103 | | // to ignore is greater than the length decoded (in bits) |
104 | 0 | if ad.bitstring_overflow |
105 | 0 | && (obj.header.is_universal() |
106 | 0 | && obj.header.tag() == Tag::BitString |
107 | 0 | && obj.header.is_primitive()) |
108 | | { |
109 | 0 | if let BerObjectContent::BitString(bits, _v) = &obj.content { |
110 | 0 | if len > 0 |
111 | 0 | && *bits as usize > len.saturating_mul(8) |
112 | | { |
113 | 0 | return Some(Asn1Check::BitstringOverflow); |
114 | 0 | } |
115 | 0 | } |
116 | 0 | } |
117 | | |
118 | | // double_overflow checks a known issue that affects the MSASN1 library |
119 | | // when decoding double/real types. If the encoding is ASCII, |
120 | | // and the buffer is greater than 256, the array is overflown |
121 | 0 | if ad.double_overflow |
122 | 0 | && (obj.header.is_universal() |
123 | 0 | && obj.header.tag() == Tag::RealType |
124 | 0 | && obj.header.is_primitive()) |
125 | | { |
126 | 0 | if let Ok(data) = obj.content.as_slice() { |
127 | 0 | if len > 0 |
128 | 0 | && !data.is_empty() |
129 | 0 | && data[0] & 0xC0 == 0 |
130 | 0 | && (len > 256 || data.len() > 256) |
131 | | { |
132 | 0 | return Some(Asn1Check::DoubleOverflow); |
133 | 0 | } |
134 | 0 | } |
135 | 0 | } |
136 | | |
137 | 0 | None |
138 | 0 | } |
139 | | |
140 | 0 | fn from_slice(input: &'a [u8], ad: &DetectAsn1Data) -> Result<Asn1<'a>, Asn1DecodeError> { |
141 | 0 | let mut results = Vec::new(); |
142 | 0 | let mut rest = input; |
143 | | |
144 | | // while there's data to process |
145 | 0 | while !rest.is_empty() { |
146 | 0 | let max_depth = ad.max_frames as usize; |
147 | | |
148 | 0 | if results.len() >= max_depth { |
149 | 0 | return Err(Asn1DecodeError::MaxFrames); |
150 | 0 | } |
151 | | |
152 | 0 | let res = parse_ber_recursive(rest, max_depth); |
153 | | |
154 | 0 | match res { |
155 | 0 | Ok((new_rest, obj)) => { |
156 | 0 | results.push(obj); |
157 | 0 |
|
158 | 0 | rest = new_rest; |
159 | 0 | } |
160 | | // If there's an error, bail |
161 | | Err(_) => { |
162 | | // silent error as this could fail |
163 | | // on non-asn1 or fragmented packets |
164 | 0 | break; |
165 | | } |
166 | | } |
167 | | } |
168 | | |
169 | 0 | Ok(Asn1(results)) |
170 | 0 | } |
171 | | } |
172 | | |
173 | | /// Decodes Asn1 objects from an input + length while applying the offset |
174 | | /// defined in the asn1 keyword options |
175 | 0 | fn asn1_decode<'a>( |
176 | 0 | buffer: &'a [u8], buffer_offset: u32, ad: &DetectAsn1Data, |
177 | 0 | ) -> Result<Asn1<'a>, Asn1DecodeError> { |
178 | | // Get offset |
179 | 0 | let offset = if let Some(absolute_offset) = ad.absolute_offset { |
180 | 0 | absolute_offset |
181 | 0 | } else if let Some(relative_offset) = ad.relative_offset { |
182 | | // relative offset in regards to the last content match |
183 | | |
184 | | // buffer_offset (u32) + relative_offset (i32) => offset (u16) |
185 | 0 | u16::try_from({ |
186 | 0 | if relative_offset > 0 { |
187 | 0 | buffer_offset |
188 | 0 | .checked_add(u32::try_from(relative_offset)?) |
189 | 0 | .ok_or(Asn1DecodeError::InvalidKeywordParameter)? |
190 | | } else { |
191 | 0 | buffer_offset |
192 | 0 | .checked_sub(u32::try_from(-relative_offset)?) |
193 | 0 | .ok_or(Asn1DecodeError::InvalidKeywordParameter)? |
194 | | } |
195 | | }) |
196 | 0 | .or(Err(Asn1DecodeError::InvalidKeywordParameter))? |
197 | | } else { |
198 | 0 | 0 |
199 | | }; |
200 | | |
201 | | // Make sure we won't read past the end or front of the buffer |
202 | 0 | if offset as usize >= buffer.len() { |
203 | 0 | return Err(Asn1DecodeError::InvalidKeywordParameter); |
204 | 0 | } |
205 | | |
206 | | // Get slice from buffer at offset |
207 | 0 | let slice = &buffer[offset as usize..]; |
208 | | |
209 | 0 | Asn1::from_slice(slice, ad) |
210 | 0 | } |
211 | | |
212 | | /// Attempt to parse a Asn1 object from input, and return a pointer |
213 | | /// to the parsed object if successful, null on failure |
214 | | /// |
215 | | /// # Safety |
216 | | /// |
217 | | /// input must be a valid buffer of at least input_len bytes |
218 | | /// pointer must be freed using `rs_asn1_free` |
219 | | #[no_mangle] |
220 | 0 | pub unsafe extern "C" fn rs_asn1_decode( |
221 | 0 | input: *const u8, input_len: u16, buffer_offset: u32, ad_ptr: *const DetectAsn1Data, |
222 | 0 | ) -> *mut Asn1<'static> { |
223 | 0 | if input.is_null() || input_len == 0 || ad_ptr.is_null() { |
224 | 0 | return std::ptr::null_mut(); |
225 | 0 | } |
226 | | |
227 | 0 | let slice = build_slice!(input, input_len as usize); |
228 | | |
229 | 0 | let ad = &*ad_ptr ; |
230 | | |
231 | 0 | let res = asn1_decode(slice, buffer_offset, ad); |
232 | | |
233 | 0 | match res { |
234 | 0 | Ok(asn1) => Box::into_raw(Box::new(asn1)), |
235 | 0 | Err(_e) => std::ptr::null_mut(), |
236 | | } |
237 | 0 | } |
238 | | |
239 | | /// Free a Asn1 object allocated by Rust |
240 | | /// |
241 | | /// # Safety |
242 | | /// |
243 | | /// ptr must be a valid object obtained using `rs_asn1_decode` |
244 | | #[no_mangle] |
245 | 0 | pub unsafe extern "C" fn rs_asn1_free(ptr: *mut Asn1) { |
246 | 0 | if ptr.is_null() { |
247 | 0 | return; |
248 | 0 | } |
249 | 0 | drop(Box::from_raw(ptr)); |
250 | 0 | } |
251 | | |
252 | | /// This function implements the detection of the following options: |
253 | | /// - oversize_length |
254 | | /// - bitstring_overflow |
255 | | /// - double_overflow |
256 | | /// |
257 | | /// # Safety |
258 | | /// |
259 | | /// ptr must be a valid object obtained using `rs_asn1_decode` |
260 | | /// ad_ptr must be a valid object obtained using `rs_detect_asn1_parse` |
261 | | /// |
262 | | /// Returns 1 if any of the options match, 0 if not |
263 | | #[no_mangle] |
264 | 0 | pub unsafe extern "C" fn rs_asn1_checks(ptr: *const Asn1, ad_ptr: *const DetectAsn1Data) -> u8 { |
265 | 0 | if ptr.is_null() || ad_ptr.is_null() { |
266 | 0 | return 0; |
267 | 0 | } |
268 | | |
269 | 0 | let asn1 = &*ptr; |
270 | 0 | let ad = &*ad_ptr; |
271 | | |
272 | 0 | match asn1.check(ad) { |
273 | 0 | Some(_check) => 1, |
274 | 0 | None => 0, |
275 | | } |
276 | 0 | } |
277 | | |
278 | | impl From<std::num::TryFromIntError> for Asn1DecodeError { |
279 | 0 | fn from(_e: std::num::TryFromIntError) -> Asn1DecodeError { |
280 | 0 | Asn1DecodeError::InvalidKeywordParameter |
281 | 0 | } |
282 | | } |
283 | | |
284 | | impl From<Err<der_parser::error::BerError>> for Asn1DecodeError { |
285 | 0 | fn from(_e: Err<der_parser::error::BerError>) -> Asn1DecodeError { |
286 | 0 | Asn1DecodeError::BerError |
287 | 0 | } |
288 | | } |
289 | | |
290 | | #[cfg(test)] |
291 | | #[allow(clippy::unused_unit)] |
292 | | mod tests { |
293 | | use super::*; |
294 | | use test_case::test_case; |
295 | | |
296 | | // Example from the specification X.690-0207 Appendix A.3 |
297 | | static ASN1_A3: &[u8] = b"\x60\x81\x85\x61\x10\x1A\x04John\x1A\x01 \ |
298 | | P\x1A\x05Smith\xA0\x0A\x1A\x08Director \ |
299 | | \x42\x01\x33\xA1\x0A\x43\x0819710917 \ |
300 | | \xA2\x12\x61\x10\x1A\x04Mary\x1A\x01T\x1A\x05 \ |
301 | | Smith\xA3\x42\x31\x1F\x61\x11\x1A\x05Ralph\x1A\x01 \ |
302 | | T\x1A\x05Smith\xA0\x0A\x43\x0819571111 \ |
303 | | \x31\x1F\x61\x11\x1A\x05Susan\x1A\x01B\x1A\x05 \ |
304 | | Jones\xA0\x0A\x43\x0819590717"; |
305 | | |
306 | | /// Ensure that the checks work when they should |
307 | | #[test_case("oversize_length 132 absolute_offset 0", ASN1_A3, DetectAsn1Data { |
308 | | oversize_length: Some(132), |
309 | | absolute_offset: Some(0), |
310 | | ..Default::default() |
311 | | }, Some(Asn1Check::OversizeLength); "Test oversize_length rule (match)" )] |
312 | | #[test_case("oversize_length 133 absolute_offset 0", ASN1_A3, DetectAsn1Data { |
313 | | oversize_length: Some(133), |
314 | | absolute_offset: Some(0), |
315 | | ..Default::default() |
316 | | }, None; "Test oversize_length rule (non-match)" )] |
317 | | #[test_case("bitstring_overflow, absolute_offset 0", |
318 | | /* tagnum bitstring, primitive, and as universal tag, |
319 | | length = 1 octet, but the next octet specify to ignore the last 256 bits */ |
320 | | b"\x03\x01\xFF", |
321 | | DetectAsn1Data { |
322 | | bitstring_overflow: true, |
323 | | absolute_offset: Some(0), |
324 | | ..Default::default() |
325 | | }, Some(Asn1Check::BitstringOverflow); "Test bitstring_overflow rule (match)" )] |
326 | | #[test_case("bitstring_overflow, absolute_offset 0", |
327 | | /* tagnum bitstring, primitive, and as universal tag, |
328 | | length = 1 octet, but the next octet specify to ignore the last 7 bits */ |
329 | | b"\x03\x01\x07", |
330 | | DetectAsn1Data { |
331 | | bitstring_overflow: true, |
332 | | absolute_offset: Some(0), |
333 | | ..Default::default() |
334 | | }, None; "Test bitstring_overflow rule (non-match)" )] |
335 | | #[test_case("double_overflow, absolute_offset 0", |
336 | | { |
337 | | static TEST_BUF: [u8; 261] = { |
338 | | let mut b = [0x05; 261]; |
339 | | /* universal class, primitive type, tag_num = 9 (Data type Real) */ |
340 | | b[0] = 0x09; |
341 | | /* length, definite form, 2 octets */ |
342 | | b[1] = 0x82; |
343 | | /* length is the sum of the following octets (257): */ |
344 | | b[2] = 0x01; |
345 | | b[3] = 0x01; |
346 | | |
347 | | b |
348 | | }; |
349 | | |
350 | | &TEST_BUF |
351 | | }, |
352 | | DetectAsn1Data { |
353 | | double_overflow: true, |
354 | | absolute_offset: Some(0), |
355 | | ..Default::default() |
356 | | }, Some(Asn1Check::DoubleOverflow); "Test double_overflow rule (match)" )] |
357 | | #[test_case("double_overflow, absolute_offset 0", |
358 | | { |
359 | | static TEST_BUF: [u8; 261] = { |
360 | | let mut b = [0x05; 261]; |
361 | | /* universal class, primitive type, tag_num = 9 (Data type Real) */ |
362 | | b[0] = 0x09; |
363 | | /* length, definite form, 2 octets */ |
364 | | b[1] = 0x82; |
365 | | /* length is the sum of the following octets (256): */ |
366 | | b[2] = 0x01; |
367 | | b[3] = 0x00; |
368 | | |
369 | | b |
370 | | }; |
371 | | |
372 | | &TEST_BUF |
373 | | }, |
374 | | DetectAsn1Data { |
375 | | double_overflow: true, |
376 | | absolute_offset: Some(0), |
377 | | ..Default::default() |
378 | | }, None; "Test double_overflow rule (non-match)" )] |
379 | | fn test_checks( |
380 | | rule: &str, asn1_buf: &'static [u8], expected_data: DetectAsn1Data, |
381 | | expected_check: Option<Asn1Check>, |
382 | | ) { |
383 | | // Parse rule |
384 | | let (_rest, ad) = parse_rules::asn1_parse_rule(rule).unwrap(); |
385 | | assert_eq!(expected_data, ad); |
386 | | |
387 | | // Decode |
388 | | let asn1 = Asn1::from_slice(asn1_buf, &ad).unwrap(); |
389 | | |
390 | | // Run checks |
391 | | let result = asn1.check(&ad); |
392 | | assert_eq!(expected_check, result); |
393 | | } |
394 | | } |