Coverage Report

Created: 2025-11-16 07:09

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/suricata/rust/src/detect/byte_extract.rs
Line
Count
Source
1
/* Copyright (C) 2024 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
// Author: Jeff Lucovsky <jlucovsky@oisf.net>
19
20
use crate::detect::error::RuleParseError;
21
use crate::detect::parser::{parse_token, take_until_whitespace};
22
use crate::detect::*;
23
use std::ffi::{CStr, CString};
24
use std::os::raw::c_char;
25
26
use nom8::bytes::complete::tag;
27
use nom8::character::complete::multispace0;
28
use nom8::sequence::preceded;
29
use nom8::{Err, IResult, Parser};
30
use std::str;
31
32
pub const DETECT_BYTE_EXTRACT_FLAG_RELATIVE: u16 = 0x01;
33
pub const DETECT_BYTE_EXTRACT_FLAG_STRING: u16 = 0x02;
34
pub const DETECT_BYTE_EXTRACT_FLAG_ALIGN: u16 = 0x04;
35
pub const DETECT_BYTE_EXTRACT_FLAG_ENDIAN: u16 = 0x08;
36
pub const DETECT_BYTE_EXTRACT_FLAG_SLICE: u16 = 0x10;
37
pub const DETECT_BYTE_EXTRACT_FLAG_MULTIPLIER: u16 = 0x20;
38
pub const DETECT_BYTE_EXTRACT_FLAG_NBYTES: u16 = 0x40;
39
pub const DETECT_BYTE_EXTRACT_FLAG_OFFSET: u16 = 0x80;
40
pub const DETECT_BYTE_EXTRACT_FLAG_BASE: u16 = 0x100;
41
42
pub const DETECT_BYTE_EXTRACT_MULTIPLIER_DEFAULT: u16 = 1;
43
44
const BASE_DEFAULT: ByteBase = ByteBase::BaseDec;
45
46
// Fixed position parameter count: bytes, offset, variable
47
const DETECT_BYTE_EXTRACT_FIXED_PARAM_COUNT: usize = 3;
48
// Optional parameters: endian, relative, string, dce, slice, align, multiplier
49
const DETECT_BYTE_EXTRACT_MAX_PARAM_COUNT: usize = 10;
50
51
#[repr(C)]
52
#[derive(Debug)]
53
pub struct SCDetectByteExtractData {
54
    local_id: u8,
55
    nbytes: u8,
56
    offset: i16,
57
    name: *const c_char,
58
    flags: u16,
59
    endian: ByteEndian, // big, little, dce
60
    base: ByteBase,     // From string or dce
61
    align_value: u8,
62
    multiplier_value: u16,
63
    id: u16,
64
}
65
66
impl Drop for SCDetectByteExtractData {
67
188k
    fn drop(&mut self) {
68
        unsafe {
69
188k
            if !self.name.is_null() {
70
89.6k
                let _ = CString::from_raw(self.name as *mut c_char);
71
99.1k
            }
72
        }
73
188k
    }
74
}
75
76
impl Default for SCDetectByteExtractData {
77
94.3k
    fn default() -> Self {
78
94.3k
        SCDetectByteExtractData {
79
94.3k
            local_id: 0,
80
94.3k
            nbytes: 0,
81
94.3k
            offset: 0,
82
94.3k
            name: std::ptr::null_mut(),
83
94.3k
            flags: 0,
84
94.3k
            endian: ByteEndian::BigEndian, // big, little, dce
85
94.3k
            base: BASE_DEFAULT,            // From string or dce
86
94.3k
            align_value: 0,
87
94.3k
            multiplier_value: DETECT_BYTE_EXTRACT_MULTIPLIER_DEFAULT,
88
94.3k
            id: 0,
89
94.3k
        }
90
94.3k
    }
91
}
92
93
104k
fn parse_byteextract(input: &str) -> IResult<&str, SCDetectByteExtractData, RuleParseError<&str>> {
94
    // Inner utility function for easy error creation.
95
22.4k
    fn make_error(reason: String) -> nom8::Err<RuleParseError<&'static str>> {
96
22.4k
        Err::Error(RuleParseError::InvalidByteExtract(reason))
97
22.4k
    }
98
104k
    let (_, values) = nom8::multi::separated_list1(
99
104k
        tag(","),
100
104k
        preceded(multispace0, nom8::bytes::complete::is_not(",")),
101
104k
    ).parse(input)?;
102
103
104k
    if values.len() < DETECT_BYTE_EXTRACT_FIXED_PARAM_COUNT
104
102k
        || values.len() > DETECT_BYTE_EXTRACT_MAX_PARAM_COUNT
105
    {
106
1.56k
        return Err(make_error(format!("Incorrect argument string; at least {} values must be specified but no more than {}: {:?}",
107
1.56k
            DETECT_BYTE_EXTRACT_FIXED_PARAM_COUNT, DETECT_BYTE_EXTRACT_MAX_PARAM_COUNT, input)));
108
102k
    }
109
110
94.3k
    let mut byte_extract = {
111
        SCDetectByteExtractData {
112
102k
            nbytes: values[0]
113
102k
                .parse::<u8>()
114
102k
                .map_err(|_| make_error(format!("invalid nbytes value: {}", values[0])))?,
115
94.3k
            ..Default::default()
116
        }
117
    };
118
119
94.3k
    let value = values[1]
120
94.3k
        .parse::<i16>()
121
94.3k
        .map_err(|_| make_error(format!("invalid offset value: {}", values[1])))?;
122
89.6k
    byte_extract.offset = value;
123
124
89.6k
    let (_, value) = parse_token(values[2])?;
125
89.6k
    if let Ok(newval) = CString::new(value) {
126
89.6k
        byte_extract.name = newval.into_raw();
127
89.6k
    } else {
128
0
        return Err(make_error(
129
0
            "parse string not safely convertible to C".to_string(),
130
0
        ));
131
    }
132
133
138k
    for value in values.iter().skip(DETECT_BYTE_EXTRACT_FIXED_PARAM_COUNT) {
134
138k
        let (mut val, mut name) = take_until_whitespace(value)?;
135
138k
        val = val.trim();
136
138k
        name = name.trim();
137
138k
        match name {
138
138k
            "align" => {
139
133
                if 0 != (byte_extract.flags & DETECT_BYTE_EXTRACT_FLAG_ALIGN) {
140
1
                    return Err(make_error("align already set".to_string()));
141
132
                }
142
132
                byte_extract.align_value = val
143
132
                    .parse::<u8>()
144
132
                    .map_err(|_| make_error(format!("invalid align value: {}", val)))?;
145
123
                if !(byte_extract.align_value == 2 || byte_extract.align_value == 4) {
146
1
                    return Err(make_error(format!(
147
1
                        "invalid align value: must be 2 or 4: {}",
148
1
                        val
149
1
                    )));
150
122
                }
151
122
                byte_extract.flags |= DETECT_BYTE_EXTRACT_FLAG_ALIGN;
152
            }
153
138k
            "slice" => {
154
4
                if 0 != (byte_extract.flags & DETECT_BYTE_EXTRACT_FLAG_SLICE) {
155
1
                    return Err(make_error("slice already set".to_string()));
156
3
                }
157
3
                byte_extract.flags |= DETECT_BYTE_EXTRACT_FLAG_SLICE;
158
            }
159
138k
            "dce" | "big" | "little" => {
160
3.56k
                if 0 != (byte_extract.flags & DETECT_BYTE_EXTRACT_FLAG_ENDIAN) {
161
43
                    return Err(make_error("endianess already set".to_string()));
162
3.52k
                }
163
3.52k
                if let Some(endian) = get_endian_value(name) {
164
3.52k
                    byte_extract.endian = endian;
165
3.52k
                } else {
166
0
                    return Err(make_error(format!("invalid endian value: {}", val)));
167
                };
168
3.52k
                byte_extract.flags |= DETECT_BYTE_EXTRACT_FLAG_ENDIAN;
169
            }
170
135k
            "string" => {
171
52.2k
                if 0 != (byte_extract.flags & DETECT_BYTE_EXTRACT_FLAG_STRING) {
172
240
                    return Err(make_error("string already set".to_string()));
173
52.0k
                }
174
52.0k
                if 0 != (byte_extract.flags & DETECT_BYTE_EXTRACT_FLAG_BASE) {
175
0
                    return Err(make_error(
176
0
                        "base specified before string; use \"string, base\"".to_string(),
177
0
                    ));
178
52.0k
                }
179
52.0k
                byte_extract.flags |= DETECT_BYTE_EXTRACT_FLAG_STRING;
180
            }
181
82.7k
            "oct" | "dec" | "hex" => {
182
50.6k
                if 0 == (byte_extract.flags & DETECT_BYTE_EXTRACT_FLAG_STRING) {
183
60
                    return Err(make_error("string must be set first".to_string()));
184
50.5k
                }
185
50.5k
                if 0 != (byte_extract.flags & DETECT_BYTE_EXTRACT_FLAG_BASE) {
186
605
                    return Err(make_error("base already set".to_string()));
187
49.9k
                }
188
49.9k
                if let Some(base) = get_string_value(name) {
189
49.9k
                    byte_extract.base = base;
190
49.9k
                } else {
191
0
                    return Err(make_error(format!("invalid string value: {}", val)));
192
                };
193
49.9k
                byte_extract.flags |= DETECT_BYTE_EXTRACT_FLAG_BASE;
194
            }
195
32.1k
            "relative" => {
196
26.0k
                if 0 != (byte_extract.flags & DETECT_BYTE_EXTRACT_FLAG_RELATIVE) {
197
604
                    return Err(make_error("relative already set".to_string()));
198
25.4k
                }
199
25.4k
                byte_extract.flags |= DETECT_BYTE_EXTRACT_FLAG_RELATIVE;
200
            }
201
6.07k
            "multiplier" => {
202
68
                if 0 != (byte_extract.flags & DETECT_BYTE_EXTRACT_FLAG_MULTIPLIER) {
203
1
                    return Err(make_error("multiplier already set".to_string()));
204
67
                }
205
67
                let mult = val
206
67
                    .parse::<u32>()
207
67
                    .map_err(|_| make_error(format!("invalid multiplier value: {}", val)))?;
208
48
                if mult == 0 || mult > u32::from(u16::MAX) {
209
1
                    return Err(make_error(format!(
210
1
                        "invalid multiplier value: must be between 0 and {}: {}",
211
1
                        u16::MAX,
212
1
                        val
213
1
                    )));
214
47
                }
215
47
                byte_extract.multiplier_value = mult as u16;
216
47
                byte_extract.flags |= DETECT_BYTE_EXTRACT_FLAG_MULTIPLIER;
217
            }
218
            _ => {
219
6.00k
                return Err(make_error(format!("unknown byte_extract option: {}", name)));
220
            }
221
        };
222
    }
223
224
    // string w/out base: default is set to decimal so no error
225
226
    // base w/out string
227
82.0k
    if 0 != byte_extract.flags & DETECT_BYTE_EXTRACT_FLAG_BASE
228
49.3k
        && (0 == byte_extract.flags & DETECT_BYTE_EXTRACT_FLAG_STRING)
229
    {
230
0
        return Err(make_error("must specify string with base".to_string()));
231
82.0k
    }
232
233
82.0k
    if 0 != byte_extract.flags & DETECT_BYTE_EXTRACT_FLAG_STRING
234
49.8k
        && 0 != byte_extract.flags & DETECT_BYTE_EXTRACT_FLAG_ENDIAN
235
    {
236
23
        return Err(make_error(
237
23
            "can't specify string and an endian value".to_string(),
238
23
        ));
239
82.0k
    }
240
241
82.0k
    if (DETECT_BYTE_EXTRACT_FLAG_STRING | DETECT_BYTE_EXTRACT_FLAG_SLICE)
242
82.0k
        == (byte_extract.flags & (DETECT_BYTE_EXTRACT_FLAG_STRING | DETECT_BYTE_EXTRACT_FLAG_SLICE))
243
    {
244
1
        return Err(make_error(
245
1
            "string and slice are mutually exclusive".to_string(),
246
1
        ));
247
82.0k
    }
248
249
82.0k
    Ok((input, byte_extract))
250
104k
}
251
252
/// Intermediary function between the C code and the parsing functions.
253
#[no_mangle]
254
104k
pub unsafe extern "C" fn SCByteExtractParse(c_arg: *const c_char) -> *mut SCDetectByteExtractData {
255
104k
    if c_arg.is_null() {
256
0
        return std::ptr::null_mut();
257
104k
    }
258
259
104k
    if let Ok(arg) = CStr::from_ptr(c_arg).to_str() {
260
104k
        match parse_byteextract(arg) {
261
82.0k
            Ok((_, detect)) => return Box::into_raw(Box::new(detect)),
262
22.4k
            Err(_) => return std::ptr::null_mut(),
263
        };
264
0
    };
265
266
0
    return std::ptr::null_mut();
267
104k
}
268
269
#[no_mangle]
270
107k
pub unsafe extern "C" fn SCByteExtractFree(ptr: *mut SCDetectByteExtractData) {
271
107k
    if !ptr.is_null() {
272
82.0k
        let _ = Box::from_raw(ptr);
273
82.0k
    }
274
107k
}
275
276
#[cfg(test)]
277
mod tests {
278
    use super::*;
279
    // structure equality only used by test cases
280
    impl PartialEq for SCDetectByteExtractData {
281
        fn eq(&self, other: &Self) -> bool {
282
            let mut res: bool = false;
283
284
            if !self.name.is_null() && !other.name.is_null() {
285
                let s_val = unsafe { CStr::from_ptr(self.name) };
286
                let o_val = unsafe { CStr::from_ptr(other.name) };
287
                res = s_val == o_val;
288
            } else if !self.name.is_null() || !other.name.is_null() {
289
                return false;
290
            }
291
292
            res && self.local_id == other.local_id
293
                && self.nbytes == other.nbytes
294
                && self.offset == other.offset
295
                && self.flags == other.flags
296
                && self.endian == other.endian
297
                && self.base == other.base
298
                && self.align_value == other.align_value
299
                && self.multiplier_value == other.multiplier_value
300
                && self.id == other.id
301
        }
302
    }
303
304
    fn valid_test(
305
        args: &str, nbytes: u8, offset: i16, var_name_str: &str, base: ByteBase,
306
        endian: ByteEndian, align_value: u8, multiplier_value: u16, flags: u16,
307
    ) {
308
        let bed = SCDetectByteExtractData {
309
            nbytes,
310
            offset,
311
            name: if !var_name_str.is_empty() {
312
                CString::new(var_name_str).unwrap().into_raw()
313
            } else {
314
                std::ptr::null_mut()
315
            },
316
            base,
317
            endian,
318
            align_value,
319
            multiplier_value,
320
            flags,
321
            ..Default::default()
322
        };
323
324
        let (_, val) = parse_byteextract(args).unwrap();
325
        assert_eq!(val, bed);
326
    }
327
328
    #[test]
329
    fn parser_valid() {
330
        assert!(parse_byteextract("4, 2, one").is_ok());
331
        assert!(parse_byteextract("4, 2, one, relative").is_ok());
332
        assert!(parse_byteextract("4, 2, one, relative, multiplier 10").is_ok());
333
        assert!(parse_byteextract("4, 2, one, big").is_ok());
334
        assert!(parse_byteextract("4, 2, one, little").is_ok());
335
        assert!(parse_byteextract("4, 2, one, dce").is_ok());
336
        assert!(parse_byteextract("4, 2, one, string").is_ok());
337
        assert!(parse_byteextract("4, 2, one, string, hex").is_ok());
338
        assert!(parse_byteextract("4, 2, one, string, dec").is_ok());
339
        assert!(parse_byteextract("4, 2, one, string, oct").is_ok());
340
        assert!(parse_byteextract("4, 2, one, align 4").is_ok());
341
        assert!(parse_byteextract("4, 2, one, align 4, relative").is_ok());
342
        assert!(parse_byteextract("4, 2, one, align 2, relative").is_ok());
343
        assert!(parse_byteextract("4, 2, one, align 4, relative, big").is_ok());
344
        assert!(parse_byteextract("4, 2, one, align 4, relative, dce").is_ok());
345
        assert!(parse_byteextract("4, 2, one, align 4, relative, little").is_ok());
346
        assert!(parse_byteextract("4, 2, one, align 4, relative, little, multiplier 2").is_ok());
347
        assert!(
348
            parse_byteextract("4, 2, one, align 4, relative, little, multiplier 2, slice").is_ok()
349
        );
350
    }
351
    #[test]
352
    // Invalid token combinations
353
    fn parser_invalid() {
354
        assert!(parse_byteextract("4").is_err());
355
        assert!(parse_byteextract("4, 2").is_err());
356
        assert!(parse_byteextract("4, 605536").is_err());
357
        assert!(parse_byteextract("4, -605536").is_err());
358
        assert!(parse_byteextract("4, 65536").is_err());
359
        assert!(parse_byteextract("4, -65536").is_err());
360
        assert!(parse_byteextract("4, 2, one, align 4, align 4").is_err());
361
        assert!(parse_byteextract("4, 2, one, relative, relative").is_err());
362
        assert!(parse_byteextract("4, 2, one, hex").is_err());
363
        assert!(parse_byteextract("4, 2, one, dec").is_err());
364
        assert!(parse_byteextract("4, 2, one, oct").is_err());
365
        assert!(parse_byteextract("4, 2, one, little, little").is_err());
366
        assert!(parse_byteextract("4, 2, one, slice, slice").is_err());
367
        assert!(parse_byteextract("4, 2, one, multiplier").is_err());
368
        assert!(parse_byteextract("4, 2, one, multiplier 0").is_err());
369
        assert!(parse_byteextract("4, 2, one, multiplier 65536").is_err());
370
        assert!(parse_byteextract("4, 2, one, multiplier -1").is_err());
371
        assert!(parse_byteextract("4, 2, one, multiplier 2, multiplier 2").is_err());
372
        assert!(parse_byteextract(
373
            "4, 2, one, align 4, relative, little, multiplier 2, string hex"
374
        )
375
        .is_err());
376
    }
377
378
    #[test]
379
    fn test_parser_valid() {
380
        valid_test(
381
            "4, 2, one",
382
            4,
383
            2,
384
            "one",
385
            BASE_DEFAULT,
386
            ByteEndian::BigEndian,
387
            0,
388
            DETECT_BYTE_EXTRACT_MULTIPLIER_DEFAULT,
389
            0,
390
        );
391
        valid_test(
392
            "4, 2, one, relative",
393
            4,
394
            2,
395
            "one",
396
            BASE_DEFAULT,
397
            ByteEndian::BigEndian,
398
            0,
399
            DETECT_BYTE_EXTRACT_MULTIPLIER_DEFAULT,
400
            DETECT_BYTE_EXTRACT_FLAG_RELATIVE,
401
        );
402
        valid_test(
403
            "4, 2, one, string",
404
            4,
405
            2,
406
            "one",
407
            BASE_DEFAULT,
408
            ByteEndian::BigEndian,
409
            0,
410
            DETECT_BYTE_EXTRACT_MULTIPLIER_DEFAULT,
411
            DETECT_BYTE_EXTRACT_FLAG_STRING,
412
        );
413
        valid_test(
414
            "4, 2, one, string, hex",
415
            4,
416
            2,
417
            "one",
418
            ByteBase::BaseHex,
419
            ByteEndian::BigEndian,
420
            0,
421
            DETECT_BYTE_EXTRACT_MULTIPLIER_DEFAULT,
422
            DETECT_BYTE_EXTRACT_FLAG_BASE | DETECT_BYTE_EXTRACT_FLAG_STRING,
423
        );
424
        valid_test(
425
            "4, 2, one, dce",
426
            4,
427
            2,
428
            "one",
429
            BASE_DEFAULT,
430
            ByteEndian::EndianDCE,
431
            0,
432
            DETECT_BYTE_EXTRACT_MULTIPLIER_DEFAULT,
433
            DETECT_BYTE_EXTRACT_FLAG_ENDIAN,
434
        );
435
        valid_test(
436
            "4, 2, one, align 4, relative, little, multiplier 2, slice",
437
            4,
438
            2,
439
            "one",
440
            ByteBase::BaseDec,
441
            ByteEndian::LittleEndian,
442
            4,
443
            2,
444
            DETECT_BYTE_EXTRACT_FLAG_ENDIAN
445
                | DETECT_BYTE_EXTRACT_FLAG_RELATIVE
446
                | DETECT_BYTE_EXTRACT_FLAG_MULTIPLIER
447
                | DETECT_BYTE_EXTRACT_FLAG_ALIGN
448
                | DETECT_BYTE_EXTRACT_FLAG_SLICE,
449
        );
450
    }
451
}