Skip to content

Commit

Permalink
plugin: add in-tree app-layer template plugin for testing
Browse files Browse the repository at this point in the history
Ticket: 7151
Ticket: 7152
Ticket: 7154
  • Loading branch information
catenacyber committed Jan 23, 2025
1 parent 355b687 commit 956a028
Show file tree
Hide file tree
Showing 12 changed files with 853 additions and 0 deletions.
10 changes: 10 additions & 0 deletions .github/workflows/builds.yml
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,16 @@ jobs:
cat eve.json | jq -c 'select(.dns)'
test $(cat eve.json | jq -c 'select(.dns)' | wc -l) = "1"
- name: Test app-layer plugin
working-directory: examples/plugins/altemplate
# test with RUSTFLAGS different than suricata build to test runtime compatibility
run: |
RUSTFLAGS="--cfg fuzzing -Cdebuginfo=1 -Cforce-frame-pointers -Cpasses=sancov-module -Cllvm-args=-sanitizer-coverage-level=4 -Cllvm-args=-sanitizer-coverage-trace-compares -Cllvm-args=-sanitizer-coverage-inline-8bit-counters -Cllvm-args=-sanitizer-coverage-trace-geps -Cllvm-args=-sanitizer-coverage-prune-blocks=0 -Cllvm-args=-sanitizer-coverage-pc-table -Clink-dead-code -Cllvm-args=-sanitizer-coverage-stack-depth" cargo build
../../../src/suricata -S altemplate.rules --set plugins.0=./target/debug/libsuricata_altemplate.so --runmode=single -l . -c altemplate.yaml -k none -r ../../../rust/src/applayertemplate/template.pcap
cat eve.json | jq -c 'select(.altemplate)'
test $(cat eve.json | jq -c 'select(.altemplate)' | wc -l) = "3"
# we get 2 alerts and 1 altemplate events

- name: Test library build in tree
working-directory: examples/lib/simple
run: make clean all
Expand Down
5 changes: 5 additions & 0 deletions examples/plugins/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,8 @@ is useful if you want to send EVE output to custom destinations.

A minimal capture plugin that can be used as a template, but also used
for testing capture plugin loading and registration in CI.

## altemplate

An app-layer template plugin with logging and detection.
Most code copied from rust/src/applayertemplate
3 changes: 3 additions & 0 deletions examples/plugins/altemplate/.cargo/config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[build]
# custom flags to pass to all compiler invocations
rustflags = ["-Clink-args=-Wl,-undefined,dynamic_lookup"]
16 changes: 16 additions & 0 deletions examples/plugins/altemplate/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
[package]
name = "suricata-altemplate"
version = "0.1.0"
edition = "2021"

[lib]
crate-type = ["cdylib"]

[dependencies]
nom7 = { version="7.0", package="nom" }
libc = "~0.2.82"
suricata = { path = "../../../rust/" }

[features]
default = ["suricata8"]
suricata8 = []
2 changes: 2 additions & 0 deletions examples/plugins/altemplate/altemplate.rules
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
alert altemplate any any -> any any (msg:"TEST"; altemplate.buffer; content:"Hello"; flow:established,to_server; sid:1; rev:1;)
alert altemplate any any -> any any (msg:"TEST"; altemplate.buffer; content:"Bye"; flow:established,to_client; sid:2; rev:1;)
17 changes: 17 additions & 0 deletions examples/plugins/altemplate/altemplate.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
%YAML 1.1
---

outputs:
- eve-log:
enabled: yes
types:
- altemplate
- alert
- flow

app-layer:
protocols:
altemplate:
enabled: yes
detection-ports:
dp: 7000
105 changes: 105 additions & 0 deletions examples/plugins/altemplate/src/detect.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
/* Copyright (C) 2024 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.
*/

// same file as rust/src/applayertemplate/detect.rs except
// TEMPLATE_START_REMOVE removed
// different paths for use statements
// keywords prefixed with altemplate instead of just template

use super::template::{TemplateTransaction, ALPROTO_TEMPLATE};
use std::os::raw::{c_int, c_void};
use suricata::cast_pointer;
use suricata::detect::{
DetectBufferSetActiveList, DetectHelperBufferMpmRegister, DetectHelperGetData,
DetectHelperKeywordRegister, DetectSignatureSetAppProto, SCSigTableElmt,
SIGMATCH_INFO_STICKY_BUFFER, SIGMATCH_NOOPT,
};
use suricata::direction::Direction;

static mut G_TEMPLATE_BUFFER_BUFFER_ID: c_int = 0;

unsafe extern "C" fn template_buffer_setup(
de: *mut c_void, s: *mut c_void, _raw: *const std::os::raw::c_char,
) -> c_int {
if DetectSignatureSetAppProto(s, ALPROTO_TEMPLATE) != 0 {
return -1;
}
if DetectBufferSetActiveList(de, s, G_TEMPLATE_BUFFER_BUFFER_ID) < 0 {
return -1;
}
return 0;
}

/// Get the request/response buffer for a transaction from C.
unsafe extern "C" fn template_buffer_get_data(
tx: *const c_void, flags: u8, buf: *mut *const u8, len: *mut u32,
) -> bool {
let tx = cast_pointer!(tx, TemplateTransaction);
if flags & Direction::ToClient as u8 != 0 {
if let Some(ref response) = tx.response {
*len = response.len() as u32;
*buf = response.as_ptr();
return true;
}
} else if let Some(ref request) = tx.request {
*len = request.len() as u32;
*buf = request.as_ptr();
return true;
}
return false;
}

unsafe extern "C" fn template_buffer_get(
de: *mut c_void, transforms: *const c_void, flow: *const c_void, flow_flags: u8,
tx: *const c_void, list_id: c_int,
) -> *mut c_void {
return DetectHelperGetData(
de,
transforms,
flow,
flow_flags,
tx,
list_id,
template_buffer_get_data,
);
}

#[no_mangle]
pub unsafe extern "C" fn ScDetectTemplateRegister() {
// TODO create a suricata-verify test
// Setup a keyword structure and register it
let kw = SCSigTableElmt {
name: b"altemplate.buffer\0".as_ptr() as *const libc::c_char,
desc: b"Template content modifier to match on the template buffer\0".as_ptr()
as *const libc::c_char,
// TODO use the right anchor for url and write doc
url: b"/rules/template-keywords.html#buffer\0".as_ptr() as *const libc::c_char,
Setup: template_buffer_setup,
flags: SIGMATCH_NOOPT | SIGMATCH_INFO_STICKY_BUFFER,
AppLayerTxMatch: None,
Free: None,
};
let _g_template_buffer_kw_id = DetectHelperKeywordRegister(&kw);
G_TEMPLATE_BUFFER_BUFFER_ID = DetectHelperBufferMpmRegister(
b"altemplate.buffer\0".as_ptr() as *const libc::c_char,
b"template.buffer intern description\0".as_ptr() as *const libc::c_char,
ALPROTO_TEMPLATE,
true, //toclient
true, //toserver
template_buffer_get,
);
}
5 changes: 5 additions & 0 deletions examples/plugins/altemplate/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
mod detect;
mod log;
mod parser;
pub mod plugin;
mod template;
85 changes: 85 additions & 0 deletions examples/plugins/altemplate/src/log.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
/* Copyright (C) 2018 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.
*/

// same file as rust/src/applayertemplate/logger.rs except
// different paths for use statements
// open_object using altemplate instead of just template
// Jsonbuilder using C API due to opaque implementation

use super::template::TemplateTransaction;
use std::ffi::{c_char, CString};
use suricata::cast_pointer;
use suricata::jsonbuilder::JsonError;

use std;

// Jsonbuilder opaque with implementation using C API to feel like usual
#[repr(C)]
pub struct JsonBuilder {
_data: [u8; 0],
}

extern "C" {
pub fn jb_set_string(jb: &mut JsonBuilder, key: *const c_char, val: *const c_char) -> bool;
pub fn jb_close(jb: &mut JsonBuilder) -> bool;
pub fn jb_open_object(jb: &mut JsonBuilder, key: *const c_char) -> bool;
}

impl JsonBuilder {
pub fn close(&mut self) -> Result<(), JsonError> {
if unsafe { !jb_close(self) } {
return Err(JsonError::Memory);
}
Ok(())
}
pub fn open_object(&mut self, key: &str) -> Result<(), JsonError> {
let keyc = CString::new(key).unwrap();
if unsafe { !jb_open_object(self, keyc.as_ptr()) } {
return Err(JsonError::Memory);
}
Ok(())
}
pub fn set_string(&mut self, key: &str, val: &str) -> Result<(), JsonError> {
let keyc = CString::new(key).unwrap();
let valc = CString::new(val.escape_default().to_string()).unwrap();
if unsafe { !jb_set_string(self, keyc.as_ptr(), valc.as_ptr()) } {
return Err(JsonError::Memory);
}
Ok(())
}
}

fn log_template(tx: &TemplateTransaction, js: &mut JsonBuilder) -> Result<(), JsonError> {
js.open_object("altemplate")?;
if let Some(ref request) = tx.request {
js.set_string("request", request)?;
}
if let Some(ref response) = tx.response {
js.set_string("response", response)?;
}
js.close()?;
Ok(())
}

#[no_mangle]
pub unsafe extern "C" fn rs_template_logger_log(
tx: *const std::os::raw::c_void, js: *mut std::os::raw::c_void,
) -> bool {
let tx = cast_pointer!(tx, TemplateTransaction);
let js = cast_pointer!(js, JsonBuilder);
log_template(tx, js).is_ok()
}
66 changes: 66 additions & 0 deletions examples/plugins/altemplate/src/parser.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/* Copyright (C) 2018 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.
*/

// same file as rust/src/applayertemplate/parser.rs except this comment

use nom7::{
bytes::streaming::{take, take_until},
combinator::map_res,
IResult,
};
use std;

fn parse_len(input: &str) -> Result<u32, std::num::ParseIntError> {
input.parse::<u32>()
}

pub fn parse_message(i: &[u8]) -> IResult<&[u8], String> {
let (i, len) = map_res(map_res(take_until(":"), std::str::from_utf8), parse_len)(i)?;
let (i, _sep) = take(1_usize)(i)?;
let (i, msg) = map_res(take(len as usize), std::str::from_utf8)(i)?;
let result = msg.to_string();
Ok((i, result))
}

#[cfg(test)]
mod tests {
use super::*;
use nom7::Err;

/// Simple test of some valid data.
#[test]
fn test_parse_valid() {
let buf = b"12:Hello World!4:Bye.";

let result = parse_message(buf);
match result {
Ok((remainder, message)) => {
// Check the first message.
assert_eq!(message, "Hello World!");

// And we should have 6 bytes left.
assert_eq!(remainder.len(), 6);
}
Err(Err::Incomplete(_)) => {
panic!("Result should not have been incomplete.");
}
Err(Err::Error(err)) | Err(Err::Failure(err)) => {
panic!("Result should not be an error: {:?}.", err);
}
}
}
}
37 changes: 37 additions & 0 deletions examples/plugins/altemplate/src/plugin.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
use super::template::rs_template_register_parser;
use crate::detect::ScDetectTemplateRegister;
use crate::log::rs_template_logger_log;
use suricata::plugin::{
SCAppLayerPlugin, SCPlugin, SCPluginRegisterAppLayer, SC_PLUGIN_API_VERSION,
};
use suricata::{SCLogError, SCLogNotice};

extern "C" fn altemplate_plugin_init() {
suricata::plugin::init();
SCLogNotice!("Initializing altemplate plugin");
let plugin = SCAppLayerPlugin {
version: SC_PLUGIN_API_VERSION, // api version for suricata compatibility
name: b"altemplate\0".as_ptr() as *const libc::c_char,
logname: b"JsonaltemplateLog\0".as_ptr() as *const libc::c_char,
confname: b"eve-log.altemplate\0".as_ptr() as *const libc::c_char,
Register: rs_template_register_parser,
Logger: rs_template_logger_log,
KeywordsRegister: ScDetectTemplateRegister,
};
unsafe {
if SCPluginRegisterAppLayer(Box::into_raw(Box::new(plugin))) != 0 {
SCLogError!("Failed to register altemplate plugin");
}
}
}

#[no_mangle]
extern "C" fn SCPluginRegister() -> *const SCPlugin {
let plugin = SCPlugin {
name: b"altemplate\0".as_ptr() as *const libc::c_char,
license: b"MIT\0".as_ptr() as *const libc::c_char,
author: b"Philippe Antoine\0".as_ptr() as *const libc::c_char,
Init: altemplate_plugin_init,
};
Box::into_raw(Box::new(plugin))
}
Loading

0 comments on commit 956a028

Please sign in to comment.