Coverage Report

Created: 2026-06-30 07:20

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}