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