diff --git a/doc/userguide/rules/transforms.rst b/doc/userguide/rules/transforms.rst index eb5117ba7dee..37d8d1af9732 100644 --- a/doc/userguide/rules/transforms.rst +++ b/doc/userguide/rules/transforms.rst @@ -394,3 +394,89 @@ the buffer. local sub = string.sub(input, offset + 1, offset + bytes) return string.upper(sub), bytes end + +subslice +-------- + +This transform creates a slice of the input buffer. + +The subslice transform requires parameters: + + * `offset` Specifies the starting offset at which to create the + subslice. When negative, expresses how far from the end of the + input buffer to begin. [REQUIRED] + * `nbytes` Specifies the size of the subslice. When negative, + specifies that the subslice will end that many bytes from + the end of the input buffer. The default value is the + size of the input buffer minus the value of ``offset``. [OPTIONAL] + * `truncate` Specifies behavior when ``offset + nbytes`` is larger + than the input buffer size. When specified, the result will + be trimmed as though ``offset + nbyfes == buffer_length``. When + not specified [DEFAULT], an empty buffer will be produced on + which ``bsize:0`` will match. [OPTIONAL] + +Specify the subslice desired -- `nbytes` and `truncate` are optional: + +Format:: + + subslice: offset <, nbytes>, <, truncate>;; + +When `nbytes` is not specified, the size of the subslice will be the size +of the input buffer minus the `offset` value. + +When ``truncate`` is not specified and the value of ``offset + nbytes`` exceeds +the buffer length, and empty buffer will be produced such that ``bsize: 0`` will +match. + +The following examples use an input buffer of ``This is Suricata``. + +Examples + +The subslice will be a copy of the input buffer but omit the input buffer's first byte. +The subslice is ``his is Suricata``:: + + subslice: 1; + +This example creates the subslice ``This is Suric``:: + + subslice: 0, 13; + +This example starts at offset ``10`` and ends at 5 bytes from the end +of the buffer which creates a subslice from offset ``10`` to offset ``12``. +The length of the input buffer is ``17`` bytes; ``5`` bytes from the end +is ``12``:: + + subslice: 10, -5; + +This example will create a subslice from the last 3 bytes of the input +buffer and create ``ata``:: + + subslice: -3; + +When the buffer has less bytes than ``offset + nbytes``, the transform +will either trim the resulting buffer as though ``offset + nbytes == buffer_length`` +or produce an empty buffer on which `bsize:0` would match. The behavior +is determined by the inclusion of ``truncate`` with the keyword. + +This example receives an input buffer with the value ``curl/7.64.1`` and +produces ``curl/7.64.1``:: + + subslice: 0, 30; + +With truncation off, the default, the buffer produced by the transform +with the same input buffer would be the empty string: ``""`` and +``bsize:0`` would match:: + + subslice: 0, 30; + +When ``truncate`` is specified, ``nbytes + offset`` is reduced +such that they equal the input buffer length. In the following example, +the transform produces ``curl/7.64.1``:: + + subslice: 0, 30, truncate; + +Specifying ``truncate`` does not require ``nbytes`` to be specified: +such that they equal the input buffer length. In the following example, +the transform produces ``curl/7.64.1``:: + + subslice: 0, truncate; diff --git a/rust/src/detect/transforms/mod.rs b/rust/src/detect/transforms/mod.rs index 939fbfa1954d..7f437117c8a0 100644 --- a/rust/src/detect/transforms/mod.rs +++ b/rust/src/detect/transforms/mod.rs @@ -27,3 +27,4 @@ pub mod http_headers; pub mod strip_whitespace; pub mod urldecode; pub mod xor; +pub mod subslice; diff --git a/rust/src/detect/transforms/subslice.rs b/rust/src/detect/transforms/subslice.rs new file mode 100644 index 000000000000..3d5fd5cff766 --- /dev/null +++ b/rust/src/detect/transforms/subslice.rs @@ -0,0 +1,654 @@ +/* Copyright (C) 2025 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +use suricata_sys::sys::{ + DetectEngineCtx, DetectEngineThreadCtx, InspectionBuffer, SCDetectHelperTransformRegister, + SCDetectSignatureAddTransform, SCInspectionBufferCheckAndExpand, SCInspectionBufferTruncate, + SCTransformTableElmt, Signature, +}; + +use std::os::raw::{c_int, c_void}; + +use std::ffi::CStr; + +static mut G_TRANSFORM_SUBSLICE_ID: c_int = 0; + +#[derive(Debug, PartialEq)] +#[repr(C)] +struct DetectTransformSubsliceData { + pub offset: isize, + pub nbytes: Option, + pub truncate: bool, +} + +impl Default for DetectTransformSubsliceData { + fn default() -> Self { + DetectTransformSubsliceData { + offset: 0, + nbytes: None, + truncate: false, + } + } +} + +fn subslice_do_parse(i: &str) -> Option { + let parts: Vec<_> = i.trim().split(',').map(str::trim).collect(); + + match parts.as_slice() { + [offset] => { + let offset = offset.parse::().ok()?; + Some(DetectTransformSubsliceData { + offset, + nbytes: None, + truncate: false, + }) + } + // offset, truncate OR offset, nbytes + [first, second] => { + let offset = first.parse::().ok()?; + + if second.eq_ignore_ascii_case("truncate") { + // offset, truncate + Some(DetectTransformSubsliceData { + offset, + nbytes: None, + truncate: true, + }) + } else { + // offset, nbytes + let nbytes = second.parse::().ok()?; + if nbytes == 0 { + return None; + } + Some(DetectTransformSubsliceData { + offset, + nbytes: Some(nbytes), + truncate: false, + }) + } + } + // offset, nbytes, truncate + [first, second, third] => { + let offset = first.parse::().ok()?; + let nbytes = second.parse::().ok()?; + + if !third.eq_ignore_ascii_case("truncate") { + return None; + } + + if nbytes == 0 { + return None; + } + + Some(DetectTransformSubsliceData { + offset, + nbytes: Some(nbytes), + truncate: true, + }) + } + _ => None, + } +} + +unsafe fn subslice_parse(raw: *const std::os::raw::c_char) -> *mut c_void { + let raw: &CStr = CStr::from_ptr(raw); + if let Ok(s) = raw.to_str() { + if let Some(ctx) = subslice_do_parse(s) { + let boxed = Box::new(ctx); + return Box::into_raw(boxed) as *mut _; + } + } + return std::ptr::null_mut(); +} + +unsafe extern "C" fn subslice_free(_de: *mut DetectEngineCtx, ctx: *mut c_void) { + std::mem::drop(Box::from_raw(ctx as *mut DetectTransformSubsliceData)); +} + +unsafe extern "C" fn subslice_setup( + de: *mut DetectEngineCtx, s: *mut Signature, opt_str: *const std::os::raw::c_char, +) -> c_int { + if opt_str.is_null() { + return -1; + } + + let ctx = subslice_parse(opt_str); + if ctx.is_null() { + return -1; + } + + let r = SCDetectSignatureAddTransform(s, G_TRANSFORM_SUBSLICE_ID, ctx); + if r != 0 { + subslice_free(de, ctx); + } + + return r; +} + +fn subslice_apply<'a>(data: &'a [u8], ctx: &DetectTransformSubsliceData) -> Option<&'a [u8]> { + let len = data.len() as isize; + + // Reject impossible offsets + if ctx.offset.abs() > len { + return None; + } + + // Compute start index + let start = if ctx.offset >= 0 { + ctx.offset + } else { + len + ctx.offset // start is < 0 + }; + + // Compute end index + let end = match ctx.nbytes { + None => len, + Some(0) => return None, + Some(e) if e < 0 => { + let end = len + e; + if end < 0 { + return None; + } + end + } + Some(e) if e > len && !ctx.truncate => return None, + Some(e) => e + start, + }; + + // Normalize if indices reversed + let (start, end) = if end < start { + (end, start) + } else { + (start, end) + }; + + // Safety: both start and end are within range + let (start, mut end) = (start as usize, end as usize); + if start >= data.len() || end > data.len() && !ctx.truncate { + return None; + } + + if end > data.len() { + end = data.len(); + } + + Some(&data[start..end]) +} + +fn subslice_transform_do( + input: &[u8], output: &mut [u8], ctx: &DetectTransformSubsliceData, +) -> u32 { + let Some(slice) = subslice_apply(input, ctx) else { + return 0; + }; + + // copy result into output + let len = slice.len(); + output[..len].copy_from_slice(slice); + + len as u32 +} + +unsafe extern "C" fn subslice_transform( + _det: *mut DetectEngineThreadCtx, buffer: *mut InspectionBuffer, ctx: *mut c_void, +) { + let input = (*buffer).inspect; + let input_len = (*buffer).inspect_len; + if input.is_null() || input_len == 0 { + return; + } + + let output = SCInspectionBufferCheckAndExpand(buffer, input_len); + if output.is_null() { + return; + } + let output = std::slice::from_raw_parts_mut(output, input_len as usize); + let ctx = cast_pointer!(ctx, DetectTransformSubsliceData); + let input = build_slice!(input, input_len as usize); + let out_length = subslice_transform_do(input, output, ctx); + SCInspectionBufferTruncate(buffer, out_length); +} + +unsafe extern "C" fn subslice_id(data: *mut *const u8, length: *mut u32, ctx: *mut c_void) { + if data.is_null() || length.is_null() || ctx.is_null() { + return; + } + + // This works because the structure is flat + // Once variables are really implemented, we should investigate if the structure should own + // its serialization or just borrow it to a caller + *data = ctx as *const u8; + *length = std::mem::size_of::() as u32; +} + +#[no_mangle] +pub unsafe extern "C" fn DetectTransformSubsliceRegister() { + let kw = SCTransformTableElmt { + name: b"subslice\0".as_ptr() as *const libc::c_char, + desc: b"create a subslice from the current buffer\0".as_ptr() as *const libc::c_char, + url: b"/rules/transforms.html#subslice\0".as_ptr() as *const libc::c_char, + Setup: Some(subslice_setup), + flags: 0, + Transform: Some(subslice_transform), + Free: Some(subslice_free), + TransformValidate: None, + TransformId: Some(subslice_id), + }; + unsafe { + G_TRANSFORM_SUBSLICE_ID = SCDetectHelperTransformRegister(&kw); + if G_TRANSFORM_SUBSLICE_ID < 0 { + SCLogWarning!("Failed registering transform subslice"); + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + static TEST_STRING: &[u8] = b"this is suricata"; + + #[test] + fn test_subslice_parse_invalid() { + assert!(subslice_do_parse("f, y").is_none()); + assert!(subslice_do_parse("0:-10").is_none()); + assert!(subslice_do_parse("1 1").is_none()); + assert!(subslice_do_parse("1,0").is_none()); + assert!(subslice_do_parse("").is_none()); + assert!(subslice_do_parse("1, 2, nottruncate").is_none()); + } + + #[test] + fn test_subslice_parse_valid() { + assert_eq!( + subslice_do_parse(" 0 , 9 ").unwrap(), + DetectTransformSubsliceData { + offset: 0, + nbytes: Some(9), + truncate: false + } + ); + assert_eq!( + subslice_do_parse("0, 9").unwrap(), + DetectTransformSubsliceData { + offset: 0, + nbytes: Some(9), + truncate: false + } + ); + assert_eq!( + subslice_do_parse("1, 1").unwrap(), + DetectTransformSubsliceData { + offset: 1, + nbytes: Some(1), + truncate: false + } + ); + assert_eq!( + subslice_do_parse("1").unwrap(), + DetectTransformSubsliceData { + offset: 1, + nbytes: None, + truncate: false + } + ); + assert_eq!( + subslice_do_parse("-1").unwrap(), + DetectTransformSubsliceData { + offset: -1, + nbytes: None, + truncate: false + } + ); + assert_eq!( + subslice_do_parse("0, -3").unwrap(), + DetectTransformSubsliceData { + offset: 0, + nbytes: Some(-3), + truncate: false + } + ); + assert_eq!( + subslice_do_parse("-10, -2").unwrap(), + DetectTransformSubsliceData { + offset: -10, + nbytes: Some(-2), + truncate: false + } + ); + assert_eq!( + subslice_do_parse("1, truncate").unwrap(), + DetectTransformSubsliceData { + offset: 1, + nbytes: None, + truncate: true + } + ); + assert_eq!( + subslice_do_parse("1, TRUNCATE").unwrap(), + DetectTransformSubsliceData { + offset: 1, + nbytes: None, + truncate: true + } + ); + assert_eq!( + subslice_do_parse("2, 10, truncate").unwrap(), + DetectTransformSubsliceData { + offset: 2, + nbytes: Some(10), + truncate: true + } + ); + } + + #[test] + fn test_subslice_transform_offset() { + let expected_output = b"his is suricata"; + let expected_output_len = expected_output.len(); + + let mut buf = Vec::new(); + buf.extend_from_slice(TEST_STRING); + + let mut out = vec![0u8; expected_output_len]; + let ctx = subslice_do_parse("1").unwrap(); + let cnt = subslice_transform_do(&buf, &mut out, &ctx); + + assert_eq!(cnt, expected_output_len as u32); + assert_eq!(&out[..cnt as usize], expected_output); + } + + #[test] + fn test_subslice_transform_nbytes() { + let expected_output = b"this is s"; + let expected_output_len = expected_output.len(); + + let mut buf = Vec::new(); + buf.extend_from_slice(TEST_STRING); + let mut out = vec![0u8; expected_output_len]; + + let ctx = subslice_do_parse("00,9").unwrap(); + let cnt = subslice_transform_do(&buf, &mut out, &ctx); + + assert_eq!(cnt, expected_output_len as u32); + assert_eq!(&out[..cnt as usize], expected_output); + } + + #[test] + fn test_subslice_transform_nbytes_2() { + let mut buf = Vec::new(); + buf.extend_from_slice(b"OISF provides architecture and infrastructure to open source security software communities and to projects like Suricata, the world-class threat-detection engine. As the need for robust and relevant security technologies grows, OISF serves to protect and maintain the authenticity of open source space. We welcome participation from diverse groups to generate networks and build active communities. We strengthen our communal space by offering user and developer training sessions around the world. OISF hosts SuriCon, the dynamic annual OISF/Suricata User Conference which gives our entire community a unique opportunity to collaborate together. + +OISF is funded by donations from world-class security organizations committed to our mission. A list of these organizations is available on our Consortium Members page."); + let mut out = vec![0u8; 24]; + if let Some(ctx) = subslice_do_parse("15, 24") { + subslice_transform_do(&buf, &mut out, &ctx); + } + assert_eq!(&out[..24], b"rchitecture and infrastr"); + } + + #[test] + fn test_subslice_transform_nbytes_3() { + let expected_output = b"s is suricata"; + let expected_output_len = expected_output.len(); + + let mut buf = Vec::new(); + buf.extend_from_slice(TEST_STRING); + let mut out = vec![0u8; expected_output_len]; + + let ctx = subslice_do_parse("3").unwrap(); + let cnt = subslice_transform_do(&buf, &mut out, &ctx); + + assert_eq!(cnt, expected_output_len as u32); + assert_eq!(&out[..cnt as usize], expected_output); + } + + #[test] + fn test_subslice_transform_offset_nbytes() { + let expected_output = b"his is suric"; + let expected_output_len = expected_output.len(); + + let mut buf = Vec::new(); + buf.extend_from_slice(TEST_STRING); + let mut out = vec![0u8; expected_output_len]; + + let ctx = subslice_do_parse("1,12").unwrap(); + let cnt = subslice_transform_do(&buf, &mut out, &ctx); + + assert_eq!(cnt, expected_output_len as u32); + assert_eq!(&out[..cnt as usize], expected_output); + } + + #[test] + fn test_subslice_transform_offset_neg_nbytes() { + let expected_output = b" is suric"; + let expected_output_len = expected_output.len(); + + let mut buf = Vec::new(); + buf.extend_from_slice(TEST_STRING); + let mut out = vec![0u8; expected_output_len]; + + let ctx = subslice_do_parse("13,-12").unwrap(); + assert!(!ctx.truncate); + let cnt = subslice_transform_do(&buf, &mut out, &ctx); + + assert_eq!(cnt, expected_output_len as u32); + assert_eq!(&out[..cnt as usize], expected_output); + } + + // offset + nbytes > data_len() [NO TRUNCATE] + #[test] + fn test_subslice_transform_offset_and_nbytes_exceeds_len_00() { + let expected_output = b""; + let expected_output_len = expected_output.len(); + + let mut buf = Vec::new(); + buf.extend_from_slice(TEST_STRING); + let mut out = vec![0u8; expected_output_len]; + + let ctx = subslice_do_parse("2, 30").unwrap(); + assert!(!ctx.truncate); + let cnt = subslice_transform_do(&buf, &mut out, &ctx); + + assert_eq!(cnt, expected_output_len as u32); + assert_eq!(&out[..cnt as usize], expected_output); + } + + // offset + nbytes > data_len() [TRUNCATE] + #[test] + fn test_subslice_transform_offset_and_nbytes_exceeds_len_01() { + let expected_output = b"is is suricata"; + let expected_output_len = expected_output.len(); + + let mut buf = Vec::new(); + buf.extend_from_slice(TEST_STRING); + let mut out = vec![0u8; expected_output_len]; + + let ctx = subslice_do_parse("2, 30, truncate").unwrap(); + assert!(ctx.truncate); + let cnt = subslice_transform_do(&buf, &mut out, &ctx); + + assert_eq!(cnt, expected_output_len as u32); + assert_eq!(&out[..cnt as usize], expected_output); + } + + // Positive offset with nbytes out of range + #[test] + fn test_subslice_transform_offset_neg_nbytes_2() { + let expected_output = b"his is suric"; + let expected_output_len = expected_output.len(); + + let mut buf = Vec::new(); + buf.extend_from_slice(TEST_STRING); + let mut out = vec![0u8; expected_output_len]; + + let ctx = subslice_do_parse("13,-15").unwrap(); + let cnt = subslice_transform_do(&buf, &mut out, &ctx); + + assert_eq!(cnt, expected_output_len as u32); + assert_eq!(&out[..cnt as usize], expected_output); + } + + // Positive offset with nbytes out of range + #[test] + fn test_subslice_transform_offset_neg_nbytes_3() { + let expected_output = b"r"; + let expected_output_len = expected_output.len(); + + let mut buf = Vec::new(); + buf.extend_from_slice(TEST_STRING); + let mut out = vec![0u8; expected_output_len]; + + let ctx = subslice_do_parse("10,-5").unwrap(); + assert!(!ctx.truncate); + let cnt = subslice_transform_do(&buf, &mut out, &ctx); + + assert_eq!(cnt, expected_output_len as u32); + assert_eq!(&out[..cnt as usize], expected_output); + } + + #[test] + fn test_subslice_transform_offset_neg_offset() { + let expected_output = b"ata"; + let expected_output_len = expected_output.len(); + + let mut buf = Vec::new(); + buf.extend_from_slice(TEST_STRING); + let mut out = vec![0u8; expected_output_len]; + + let ctx = subslice_do_parse("-3").unwrap(); + let cnt = subslice_transform_do(&buf, &mut out, &ctx); + + assert_eq!(cnt, expected_output_len as u32); + assert_eq!(&out[..cnt as usize], expected_output); + } + + // abs(offset) exceeds length + #[test] + fn test_subslice_transform_offset_neg_offset_2() { + let mut buf = Vec::new(); + buf.extend_from_slice(TEST_STRING); + let mut out = vec![0u8; 3]; + + let ctx = subslice_do_parse("-17").unwrap(); + assert!(!ctx.truncate); + let cnt = subslice_transform_do(&buf, &mut out, &ctx); + + assert_eq!(cnt, 0); + } + + #[test] + fn test_subslice_with_truncate_literal() { + let expected_output = b"is is suricata"; + let expected_output_len = expected_output.len(); + + let mut buf = Vec::new(); + buf.extend_from_slice(TEST_STRING); + let mut out = vec![0u8; expected_output_len]; + + let ctx = subslice_do_parse("2, truncate").unwrap(); + assert!(ctx.truncate); + assert_eq!(ctx.offset, 2); + assert_eq!(ctx.nbytes, None); + + let cnt = subslice_transform_do(&buf, &mut out, &ctx); + assert_eq!(cnt, expected_output_len as u32); + assert_eq!(&out[..cnt as usize], expected_output); + } + + #[test] + fn test_subslice_with_nbytes_and_truncate_01() { + let expected_output = b"is is suricata"; + let expected_output_len = expected_output.len(); + + let mut buf = Vec::new(); + buf.extend_from_slice(TEST_STRING); + let mut out = vec![0u8; expected_output_len]; + + let ctx = subslice_do_parse("2, 20, truncate").unwrap(); + assert!(ctx.truncate); + assert_eq!(ctx.offset, 2); + assert_eq!(ctx.nbytes, Some(20)); + + let cnt = subslice_transform_do(&buf, &mut out, &ctx); + assert_eq!(cnt, expected_output_len as u32); + assert_eq!(&out[..cnt as usize], expected_output); + } + + #[test] + fn test_subslice_with_nbytes_and_truncate_02() { + let expected_output = b"curl/7.64.1"; + let expected_output_len = expected_output.len(); + + let mut buf = Vec::new(); + let test_string: &[u8] = b"curl/7.64.1"; + buf.extend_from_slice(test_string); + let mut out = vec![0u8; expected_output_len]; + + let ctx = subslice_do_parse("0, 30, truncate").unwrap(); + assert!(ctx.truncate); + assert_eq!(ctx.offset, 0); + assert_eq!(ctx.nbytes, Some(30)); + assert!(ctx.truncate); + + let cnt = subslice_transform_do(&buf, &mut out, &ctx); + assert_eq!(cnt, expected_output_len as u32); + assert_eq!(&out[..cnt as usize], expected_output); + } + + #[test] + fn test_subslice_with_offset_and_truncate_01() { + let expected_output = b"url/7.64.1"; + let expected_output_len = expected_output.len(); + + let mut buf = Vec::new(); + let test_string: &[u8] = b"curl/7.64.1"; + buf.extend_from_slice(test_string); + let mut out = vec![0u8; expected_output_len]; + + let ctx = subslice_do_parse("1, truncate").unwrap(); + assert!(ctx.truncate); + assert_eq!(ctx.offset, 1); + assert_eq!(ctx.nbytes, None); + assert!(ctx.truncate); + + let cnt = subslice_transform_do(&buf, &mut out, &ctx); + assert_eq!(cnt, expected_output_len as u32); + assert_eq!(&out[..cnt as usize], expected_output); + } + + #[test] + fn test_subslice_with_offset_and_truncate_02() { + let expected_output = b"curl/7.64.1"; + let expected_output_len = expected_output.len(); + + let mut buf = Vec::new(); + let test_string: &[u8] = b"curl/7.64.1"; + buf.extend_from_slice(test_string); + let mut out = vec![0u8; expected_output_len]; + + let ctx = subslice_do_parse("0, truncate").unwrap(); + assert!(ctx.truncate); + assert_eq!(ctx.offset, 0); + assert_eq!(ctx.nbytes, None); + assert!(ctx.truncate); + + let cnt = subslice_transform_do(&buf, &mut out, &ctx); + assert_eq!(cnt, expected_output_len as u32); + assert_eq!(&out[..cnt as usize], expected_output); + } +} diff --git a/src/detect-engine-register.c b/src/detect-engine-register.c index 59c3ce42a3f2..9bc27909222e 100644 --- a/src/detect-engine-register.c +++ b/src/detect-engine-register.c @@ -775,6 +775,7 @@ void SigTableSetup(void) DetectTransformFromBase64DecodeRegister(); SCDetectTransformDomainRegister(); DetectTransformLuaxformRegister(); + DetectTransformSubsliceRegister(); DetectFileHandlerRegister(); diff --git a/suricata.yaml.in b/suricata.yaml.in index 15339656f955..c3086c19c1ef 100644 --- a/suricata.yaml.in +++ b/suricata.yaml.in @@ -1273,6 +1273,17 @@ app-layer: mdns: enabled: yes +# Indicate subslice transform behavior if the buffer is smaller +# than offset + nbytes. +# The subslice transform accepts 2 parameters -- offset and nbytes. +# When nbytes + offset exceeds the buffer, the transform can +# - Do nothing +# - Truncate the subslice by adjusting nbytes so nbytes + offset = input buffer len +# The default is to truncate (on). Set to off to not truncate and produce an +# empty buffer (bsize:0 will match). +subslice: + truncate: on + # Limit for the maximum number of asn1 frames to decode (default 256) asn1-max-frames: 256