diff --git a/rust/Cargo.lock.in b/rust/Cargo.lock.in index 3fe1f16a5d7e..7fc8c5076f48 100644 --- a/rust/Cargo.lock.in +++ b/rust/Cargo.lock.in @@ -414,6 +414,12 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "either" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e" + [[package]] name = "endian-type" version = "0.1.2" @@ -489,6 +495,12 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" +[[package]] +name = "fixedbitset" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" + [[package]] name = "flate2" version = "1.0.35" @@ -500,6 +512,12 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + [[package]] name = "foldhash" version = "0.2.0" @@ -637,6 +655,15 @@ dependencies = [ "polyval", ] +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "foldhash 0.1.5", +] + [[package]] name = "hashbrown" version = "0.16.1" @@ -645,7 +672,7 @@ checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" dependencies = [ "allocator-api2", "equivalent", - "foldhash", + "foldhash 0.2.0", ] [[package]] @@ -700,7 +727,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4b0f83760fb341a774ed326568e19f5a863af4a952def8c39f9ab92fd95b88e5" dependencies = [ "equivalent", - "hashbrown", + "hashbrown 0.16.1", ] [[package]] @@ -722,6 +749,15 @@ dependencies = [ "rusticata-macros", ] +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.18" @@ -792,7 +828,7 @@ version = "0.16.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f66e8d5d03f609abc3a39e6f08e4164ebf1447a732906d39eb9b99b7919ef39" dependencies = [ - "hashbrown", + "hashbrown 0.16.1", ] [[package]] @@ -1071,6 +1107,18 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" +[[package]] +name = "petgraph" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8701b58ea97060d5e5b155d383a69952a60943f0e6dfe30b04c287beb0b27455" +dependencies = [ + "fixedbitset", + "hashbrown 0.15.5", + "indexmap", + "serde", +] + [[package]] name = "phf" version = "0.10.1" @@ -1546,6 +1594,7 @@ dependencies = [ "hkdf", "humantime", "ipsec-parser", + "itertools", "kerberos-parser", "lazy_static", "ldap-parser", @@ -1560,11 +1609,14 @@ dependencies = [ "num", "num-derive", "num-traits 0.2.19", + "petgraph", "psl", "regex", "sawp", "sawp-modbus", "sawp-pop3", + "serde", + "serde_json", "sha1", "sha2", "snmp-parser", @@ -1944,6 +1996,9 @@ name = "uuid" version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" +dependencies = [ + "serde", +] [[package]] name = "valuable" diff --git a/rust/Cargo.toml.in b/rust/Cargo.toml.in index 8c2ae734d836..461d5f1a2636 100644 --- a/rust/Cargo.toml.in +++ b/rust/Cargo.toml.in @@ -42,7 +42,7 @@ nom7 = { version="7.1", package="nom" } nom8 = { version="8.0", package="nom" } bitflags = "~1.3.2" byteorder = "~1.4.3" -uuid = "~0.8.2" +uuid = { version = "~0.8.2", features = ["serde"] } crc = "~1.8.1" lzma-rs = { version = "~0.2.0", features = ["stream"] } memchr = "~2.7.4" @@ -60,6 +60,9 @@ lru = "~0.16.3" der-parser = { version = "~9.0.0", default-features = false } kerberos-parser = { version = "~0.8.0", default-features = false } +petgraph = "~0.8.2" +itertools = "~0.14.0" + sawp-modbus = "~0.13.1" sawp-pop3 = "~0.13.1" sawp = "~0.13.1" @@ -84,6 +87,8 @@ hex = "~0.4.3" psl = "2" time = "=0.3.41" +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" suricata-derive = { path = "./derive", version = "@PACKAGE_VERSION@" } suricata-sys = { path = "./sys", version = "@PACKAGE_VERSION@" } diff --git a/rust/src/utils/flowbits_resolver.rs b/rust/src/utils/flowbits_resolver.rs new file mode 100644 index 000000000000..9dd204400dfa --- /dev/null +++ b/rust/src/utils/flowbits_resolver.rs @@ -0,0 +1,339 @@ +/* Copyright (C) 2026 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. + */ + +// Author: Shivani Bhardwaj + +use crate::conf::*; +use crate::jsonbuilder::*; +use petgraph::algo::{is_cyclic_directed, tarjan_scc, toposort}; +use petgraph::graph::{GraphError, NodeIndex}; +use petgraph::stable_graph::StableDiGraph; +use petgraph::visit::EdgeRef; +use petgraph::Direction; +use std::collections::{HashMap, HashSet}; +use std::os::raw::c_void; + +/// Special Graph Node storing flowbit or signature +#[derive(Debug, Copy, Clone)] +struct SCGNode { + iid: u32, + sid: u32, + nidx: NodeIndex, /* Graph's internal node index */ +} + +/// Edge object to determine a genuine cycle breaker +#[derive(Debug, Copy, Clone)] +struct SCGEdge { + cmd: u8, + fb_id: u32, +} + +#[derive(Debug)] +struct FlowbitSidStore { + graph: StableDiGraph, + iid_map: HashMap, +} + +/// Function to create an empty directed Graph +#[no_mangle] +pub unsafe extern "C" fn SCCreateDirectedGraph() -> *mut c_void { + // StableDiGraph is the ideal choice here for there is removal of + // nodes in the line later and this type of graph guarantees to + // not re-use any existing node indices + let fb_sid_store = FlowbitSidStore { + graph: StableDiGraph::new(), + iid_map: HashMap::new(), + }; + + /* Make an opaque pointer for C as nothing is changed there */ + return Box::into_raw(Box::new(fb_sid_store)) as *mut c_void; +} + +/// Drop the directed Graph. Called from C. +#[no_mangle] +pub unsafe extern "C" fn SCFreeDirectedGraph(store: *mut c_void) { + let _ = Box::from_raw(store as *mut FlowbitSidStore); +} + +#[no_mangle] +pub unsafe extern "C" fn SCGetOrCreateNodeGraph(graph: *mut c_void, iid: u32, sid: u32) -> i64 { + let g = &mut *(graph as *mut FlowbitSidStore); + + let Some(node_idx) = get_or_create_node(g, iid, sid) else { + SCLogError!("Error adding node; Graph is at full capacity"); + return -2; + }; + + node_idx.index() as i64 +} + +/// Function to get or create a node and add an appropriate directed +/// edge based on its type +#[no_mangle] +pub unsafe extern "C" fn SCCreateNodeEdgeDirectedGraph( + fss_void: *mut c_void, from: u32, to: u32, cmd: u8, fb_id: u32, +) -> i64 { + let fss = &mut *(fss_void as *mut FlowbitSidStore); + + let from_idx = NodeIndex::from(from); + let to_idx = NodeIndex::from(to); + + match fss + .graph + .try_update_edge(from_idx, to_idx, SCGEdge { cmd, fb_id }) + { + /* edge from a flowbit setter to a flowbit reader */ + Ok(_) => { + SCLogDebug!( + "Created an edge from {:?} -> {:?} for flowbit {:?} with command: {:?}", + from, + to, + fb_id, + cmd + ); + } + Err(GraphError::EdgeIxLimit) => { + SCLogError!("Error adding edge; Graph is at full capacity"); + return -2; + } + Err(GraphError::NodeOutBounds) => { + SCLogError!("Error adding edge; node does not exist"); + return -2; + } + Err(_) => { + SCLogError!("Error adding edge to the Graph"); + return -2; + } + } + + return 0; +} + +fn log_graph( + js: &mut JsonBuilder, graph: &mut StableDiGraph, +) -> Result<(), JsonError> { + SCLogDebug!("Starting the logging.."); + for node in graph.node_weights() { + SCLogDebug!("{:?}", node.nidx.index()); + js.open_object(&node.sid.to_string())?; + js.open_array("in")?; + for edge in graph.edges_directed(node.nidx, Direction::Incoming) { + js.start_object()?; + js.set_uint("id", edge.source().index() as u64)?; + js.set_uint("weight", edge.weight().cmd as u64)?; + js.set_uint("sid", graph[edge.source()].sid as u64)?; + js.close()?; + } + js.close()?; + js.open_array("out")?; + for edge in graph.edges_directed(node.nidx, Direction::Outgoing) { + js.start_object()?; + js.set_uint("id", edge.target().index() as u64)?; + js.set_uint("weight", edge.weight().cmd as u64)?; + js.set_uint("sid", graph[edge.target()].sid as u64)?; + js.close()?; + } + js.close()?; + js.close()?; + } + Ok(()) +} + +#[no_mangle] +pub unsafe extern "C" fn SCDebugLogFlowbitGraph( + jsb: &mut JsonBuilder, fss_void: *mut c_void, +) -> bool { + let fss = &mut *(fss_void as *mut FlowbitSidStore); + log_graph(jsb, &mut fss.graph).is_ok() +} + +fn check_cycle_update_graph(graph: &mut StableDiGraph) -> i8 { + let mut max_stack_size: usize = 100; + if let Some(val) = conf_get("detect.flowbits.max-cycle-resolution") { + if let Ok(v) = val.parse::() { + if v > u8::MAX as usize { + SCLogError!("Invalid value for max-cycle-resolution"); + } else { + max_stack_size = v; + } + } else { + SCLogError!("Invalid value for max-cycle-resolution"); + } + } + + for i in 0..=max_stack_size { + /* Check graph for any cycles */ + if !is_cyclic_directed(&*graph) { + SCLogDebug!("no cycles after {} tries", i); + return 0; + } + + SCLogDebug!("Found a cycle in i {}. Checking if it's valid..", i); + if i == max_stack_size { + break; + } + + if !try_resolve_one_cycle(graph) { + /* If we can't resolve any cycle, we're stuck */ + SCLogError!("Unable to resolve cycles after {} tries", i); + return -1; + } + } + + SCLogError!( + "Maximum tries ({}) reached while trying to resolve cycles", + max_stack_size + ); + return -1; +} + +fn try_resolve_one_cycle(graph: &mut StableDiGraph) -> bool { + let sccs = tarjan_scc(&*graph); + let mut edge_map: HashMap = HashMap::new(); + + /* Find the first multi-node SCC */ + for scc in sccs { + if scc.len() == 1 { + let self_loop_edges: Vec<_> = graph + .edges(scc[0]) + .filter(|edge| edge.target() == scc[0]) + .map(|edge| edge.id()) + .collect(); + + if let Some(edge) = self_loop_edges.into_iter().next() { + graph.remove_edge(edge); + return true; + } + continue; + } + + SCLogDebug!("Current scc: {:?}", scc); + let scc_set: HashSet<_> = scc.iter().copied().collect(); + let edge_indices: Vec<_> = graph.edge_indices().collect(); + + for edge_idx in edge_indices { + let Some((_src, tgt)) = graph.edge_endpoints(edge_idx) else { + continue; + }; + if !(scc_set.contains(&_src) && scc_set.contains(&tgt)) { + continue; + } + let SCGEdge { cmd, fb_id } = graph[edge_idx]; + let same_bit_setters = graph + .edges_directed(tgt, Direction::Incoming) + .filter(|e| e.weight().fb_id == fb_id) + .count(); + if same_bit_setters > 1 { + graph.remove_edge(edge_idx); + return true; + } + /* store cycle edges to inspect their commands (weights) later */ + edge_map.insert(edge_idx, cmd); + } + /* Find if the cycle causing edges are made up of differing weights */ + let distinct_weights = edge_map.values().copied().collect::>().len(); + debug_validate_bug_on!(distinct_weights == 0); + if distinct_weights > 1 { + /* Find and remove the edge with highest weight (lowest priority) */ + if let Some((cur_e, _)) = edge_map.iter().max_by_key(|(_, &weight)| weight) { + graph.remove_edge(*cur_e); + return true; + } + } else { + /* Valid cycle with same weights -- can't resolve */ + let sids: Vec<_> = scc_set.into_iter().map(|a| graph[a].sid).collect(); + SCLogError!( + "Cyclic dependency found between flowbits from signatures: {:?}", + sids + ); + return false; + } + break; + } + + /* couldn't resolve */ + false +} + +/// Wrapper function to resolve flowbit dependencies +#[no_mangle] +pub unsafe extern "C" fn SCResolveFlowbitDependencies( + fss_void: *mut c_void, sorted_iid_list: *mut u32, sorted_iid_list_len: u32, +) -> i8 { + SCLogDebug!("Attempting to resolve flowbit dependencies"); + let fss = &mut *(fss_void as *mut FlowbitSidStore); + let r = check_cycle_update_graph(&mut fss.graph); + if r == -1 { + SCLogError!("Couldn't do anything to fix the graph. Retreating.."); + return -1; + } + + debug_validate_bug_on!(fss.graph.node_count() == 0); + + let sorted_iid_list = + std::slice::from_raw_parts_mut(&mut *sorted_iid_list, sorted_iid_list_len as usize); + + /* No need for all the extra work if there's just one node */ + if fss.graph.node_count() == 1 { + debug_validate_bug_on!(sorted_iid_list_len != 1); + sorted_iid_list[0] = fss.graph[NodeIndex::from(0)].iid; + return 0; + } + + /* At this point, it must be a DAG, so perform a topological sort to find + * out the correct order of signatures */ + return toposort_dag(&fss.graph, sorted_iid_list); +} + +fn get_or_create_node(fss: &mut FlowbitSidStore, iid: u32, sid: u32) -> Option { + if let Some(&nidx) = fss.iid_map.get(&iid) { + return Some(nidx); /* O(1) */ + } + let nd = SCGNode { + iid, + sid, + nidx: NodeIndex::from(u32::MAX), + }; + if let Ok(idx) = fss.graph.try_add_node(nd) { + /* O(1) */ + fss.graph[idx].nidx = idx; + fss.iid_map.insert(iid, idx); + SCLogDebug!("Created node: {:?}", fss.graph[idx]); + return Some(idx); + } + + None +} + +/// Produce a topological ordering of the DAG +fn toposort_dag(graph: &StableDiGraph, sorted_iid_list: &mut [u32]) -> i8 { + match toposort(graph, None) { + Ok(order) => { + debug_validate_bug_on!(order.len() != sorted_iid_list.len()); + for (i, idx) in order.into_iter().enumerate() { + SCLogDebug!("[{:?}]: {:?}", i, graph[idx]); + sorted_iid_list[i] = graph[idx].iid; + } + 0 + } + Err(_) => { + /* Unreachable in practice: cycles are resolved before this point */ + SCLogError!("Graph still contains a cycle; cannot produce an order"); + -1 + } + } +} diff --git a/rust/src/utils/mod.rs b/rust/src/utils/mod.rs index 542aa7791fb2..36aea88ec49b 100644 --- a/rust/src/utils/mod.rs +++ b/rust/src/utils/mod.rs @@ -1,4 +1,4 @@ -/* Copyright (C) 2024 Open Information Security Foundation +/* Copyright (C) 2024-2026 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 @@ -17,4 +17,5 @@ pub mod base64; pub mod datalink; +pub mod flowbits_resolver; pub mod ipaddr; diff --git a/src/detect-engine-sigorder.c b/src/detect-engine-sigorder.c index 98012cf9d3f2..426a74a78092 100644 --- a/src/detect-engine-sigorder.c +++ b/src/detect-engine-sigorder.c @@ -1,4 +1,4 @@ -/* Copyright (C) 2007-2024 Open Information Security Foundation +/* Copyright (C) 2007-2026 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 @@ -37,9 +37,12 @@ #include "util-unittest-helper.h" #include "util-debug.h" #include "util-action.h" +#include "util-conf.h" #include "action-globals.h" #include "flow-util.h" #include "util-validate.h" +#include "suricata.h" +#include "rust.h" #define DETECT_FLOWVAR_NOT_USED 1 #define DETECT_FLOWVAR_TYPE_READ 2 @@ -66,6 +69,9 @@ #define DETECT_XBITS_TYPE_SET_READ 3 #define DETECT_XBITS_TYPE_SET 4 +extern bool rule_engine_analysis_set; +SCMutex g_flowbits_graph_dump_write_m = SCMUTEX_INITIALIZER; + /** * \brief Different kinds of helper data that can be used by the signature * ordering module. Used by the "user" field in SCSigSignatureWrapper @@ -535,6 +541,14 @@ static int SCSigLessThan(SCSigSignatureWrapper *sw1, { SCSigOrderFunc *funcs = cmp_func_list; + // Special handling for flowbits, do not touch the order if the comparison fn + // calculates two signatures to be equal + if ((sw1->user[DETECT_SIGORDER_FLOWBITS] > DETECT_FLOWBITS_NOT_USED) && + (sw1->user[DETECT_SIGORDER_FLOWBITS] == DETECT_FLOWBITS_TYPE_SET_READ) && + (sw1->user[DETECT_SIGORDER_FLOWBITS] == sw2->user[DETECT_SIGORDER_FLOWBITS])) { + return 1; + } + while (funcs != NULL) { int delta = funcs->SWCompare(sw1, sw2); if (delta > 0) @@ -544,6 +558,7 @@ static int SCSigLessThan(SCSigSignatureWrapper *sw1, funcs = funcs->next; } + // They are equal, so use sid as the final decider. return sw1->sig->id < sw2->sig->id; } @@ -555,38 +570,50 @@ static SCSigSignatureWrapper *SCSigOrder(SCSigSignatureWrapper *sw, { DEBUG_VALIDATE_BUG_ON(sw == NULL); + if (sw->next == NULL) { + /* only one element in the list */ + return sw; + } + SCSigSignatureWrapper *subA = NULL; SCSigSignatureWrapper *subB = NULL; - SCSigSignatureWrapper *first; - SCSigSignatureWrapper *second; SCSigSignatureWrapper *result = NULL; SCSigSignatureWrapper *last = NULL; SCSigSignatureWrapper *new = NULL; - /* Divide input list into two sub-lists. */ - while (sw != NULL) { - first = sw; - sw = sw->next; - /* Push the first element onto sub-list A */ - first->next = subA; - subA = first; - - if (sw == NULL) - break; - second = sw; - sw = sw->next; - /* Push the second element onto sub-list B */ - second->next = subB; - subB = second; - } - if (subB == NULL) { - /* Only zero or one element on the list. */ - return subA; + /* Divide input list into two sub-lists using Tortoise and Hare algorithm */ + SCSigSignatureWrapper *slow = sw; + SCSigSignatureWrapper *fast = sw; /* 2x faster pointer */ + SCSigSignatureWrapper *prev = NULL; + + while (fast != NULL && fast->next != NULL) { + prev = slow; + slow = slow->next; + fast = fast->next->next; } + + prev->next = NULL; /* Cut the first half */ + subA = sw; /* First half */ + subB = slow; /* Second half */ + + SCLogDebug("subA is: %d; subB is: %d", subA->sig->id, subB->sig->id); + DEBUG_VALIDATE_BUG_ON(subA == NULL); + DEBUG_VALIDATE_BUG_ON(subB == NULL); - /* Now sort each list */ +#ifdef DEBUG + SCLogDebug("subA:"); + for (SCSigSignatureWrapper *sigw = subA; sigw != NULL; sigw = sigw->next) { + SCLogDebug("sig_id: %d", sigw->sig->id); + } +#endif subA = SCSigOrder(subA, cmp_func_list); +#ifdef DEBUG + SCLogDebug("subB:"); + for (SCSigSignatureWrapper *sigw = subB; sigw != NULL; sigw = sigw->next) { + SCLogDebug("sig_id: %d", sigw->sig->id); + } +#endif subB = SCSigOrder(subB, cmp_func_list); DEBUG_VALIDATE_BUG_ON(subA == NULL); DEBUG_VALIDATE_BUG_ON(subB == NULL); @@ -619,6 +646,233 @@ static SCSigSignatureWrapper *SCSigOrder(SCSigSignatureWrapper *sw, return result; } +static inline void SCLogFlowbitsGraph(void *graph) +{ + SCJsonBuilder *jb = SCJbNewObject(); + if (jb == NULL) + return; + + SCDebugLogFlowbitGraph(jb, graph); + SCJbClose(jb); + const char *filename = "flowbits_dependency_graph.json"; + const char *log_dir = SCConfigGetLogDirectory(); + char log_path[PATH_MAX] = ""; + snprintf(log_path, sizeof(log_path), "%s/%s", log_dir, filename); + + SCMutexLock(&g_flowbits_graph_dump_write_m); + FILE *fp = fopen(log_path, "w"); + if (fp != NULL) { + fwrite(SCJbPtr(jb), SCJbLen(jb), 1, fp); + fprintf(fp, "\n"); + fclose(fp); + } + SCMutexUnlock(&g_flowbits_graph_dump_write_m); + SCJbFree(jb); +} + +static int CreateGraphFromFlowbitAnalyzer( + void *graph, SCSigSignatureWrapper *sw, uint32_t max_fb_id) +{ + SCSigSignatureWrapper *tmp = sw; + if (max_fb_id == 0) + return 0; + + struct FBAnalyzer fba = { .array = NULL, .array_size = 0 }; + const uint32_t array_size = max_fb_id + 1; + struct FBAnalyze *array = SCCalloc(array_size, sizeof(struct FBAnalyze)); + if (array == NULL) { + SCLogError("Unable to allocate flowbit analyze array"); + return -1; + } + fba.array = array; + fba.array_size = array_size; + + SCLogDebug( + "fb analyzer array size: %" PRIu64, (uint64_t)(array_size * sizeof(struct FBAnalyze))); + + /* fill flowbit array, updating counters per sig */ + while (tmp != NULL) { + const Signature *s = tmp->sig; + + int64_t node = SCGetOrCreateNodeGraph(graph, s->iid, s->id); + if (node < 0) { + FatalErrorOnInit("Couldn't create or get a node in the graph for sid: %d", s->id); + } + SCLogDebug("added node: %ld", node); + + int r = DetectFlowbitsAnalyzeSignature(s, &fba); + if (r < 0) { + FBAnalyzerFree(&fba); + return -1; + } + tmp = tmp->next; + } + + // varnamestore ids start at 1 + for (uint32_t x = 1; x < array_size; x++) { + for (uint32_t i = 0; i < fba.array[x].isset_iids_idx; i++) { + SCLogDebug("Getting or creating node for sid: %d", fba.array[x].isset_iids[i].sid); + int64_t to = SCGetOrCreateNodeGraph( + graph, fba.array[x].isset_iids[i].iid, fba.array[x].isset_iids[i].sid); + if (to < 0) { + FatalErrorOnInit("Couldn't create or get a node in the graph for sid: %d", + fba.array[x].isset_iids[i].sid); + } + SCLogDebug("added node: %ld", to); + for (uint32_t y = 0; y < fba.array[x].set_iids_idx; y++) { + SCLogDebug("Getting or creating node for iid: %d", fba.array[x].set_iids[y].sid); + int64_t from = SCGetOrCreateNodeGraph( + graph, fba.array[x].set_iids[y].iid, fba.array[x].set_iids[y].sid); + if (from < 0) { + FatalErrorOnInit("Couldn't create or get a node in the graph for sid: %d", + fba.array[x].set_iids[y].sid); + } + SCLogDebug("added node: %ld", from); + if (SCCreateNodeEdgeDirectedGraph( + graph, (uint32_t)from, (uint32_t)to, DETECT_FLOWBITS_CMD_SET, x) < 0) { + if (rule_engine_analysis_set) { + SCLogFlowbitsGraph(graph); + } + FatalErrorOnInit( + "Flowbits signatures that are unsatisfiable at runtime found: %d, %d", + fba.array[x].isset_iids[i].sid, fba.array[x].set_iids[y].sid); + } + } + } + } + + for (uint32_t x = 1; x < array_size; x++) { + for (uint32_t i = 0; i < fba.array[x].isnotset_iids_idx; i++) { + int64_t to = SCGetOrCreateNodeGraph( + graph, fba.array[x].isnotset_iids[i].iid, fba.array[x].isnotset_iids[i].sid); + if (to < 0) { + FatalErrorOnInit("Couldn't create or get a node in the graph for sid: %d", + fba.array[x].isnotset_iids[i].sid); + } + SCLogDebug("added node: %ld", to); + for (uint32_t y = 0; y < fba.array[x].unset_iids_idx; y++) { + int64_t from = SCGetOrCreateNodeGraph( + graph, fba.array[x].unset_iids[y].iid, fba.array[x].unset_iids[y].sid); + if (from < 0) { + FatalErrorOnInit("Couldn't create or get a node in the graph for sid: %d", + fba.array[x].unset_iids[y].sid); + } + SCLogDebug("added node: %ld", from); + if (SCCreateNodeEdgeDirectedGraph(graph, (uint32_t)from, (uint32_t)to, + DETECT_FLOWBITS_CMD_UNSET, x) < 0) { + if (rule_engine_analysis_set) { + SCLogFlowbitsGraph(graph); + } + FatalErrorOnInit( + "Flowbits signatures that are unsatisfiable at runtime found: %d, %d", + fba.array[x].isnotset_iids[i].sid, fba.array[x].unset_iids[y].sid); + } + } + } + } + + FBAnalyzerFree(&fba); + return 0; +} + +/** + * \brief Function to Resolve dependencies among flowbits + * + * \param arg_sw Signature Wrapper containing all flowbits of SET_READ type + * + * \return SCSigSignatureWrapper list post dependency resolution + */ +static SCSigSignatureWrapper *SCSigResolveFlowbitDependencies( + SCSigSignatureWrapper *head, uint32_t max_fb_id) +{ + uint32_t sig_cnt = 0; + uint32_t *sorted_iids = NULL; + SCSigSignatureWrapper *tmp = NULL; + + SCLogInfo("Signatures with flowbits found"); + + if (head == NULL) { + return NULL; + } + if (head->next == NULL) { + return head; + } + tmp = head; + while (tmp != NULL) { + ++sig_cnt; + /* The dependency-resolved order overrides any explicit priority for + * SET_READ flowbit rules. Warn once per such rule that set a priority. */ + if (tmp->user[DETECT_SIGORDER_FLOWBITS] == DETECT_FLOWBITS_TYPE_SET_READ && + (tmp->sig->init_data->init_flags & SIG_FLAG_INIT_PRIO_EXPLICIT)) { + SCLogWarning("sid %u: explicitly set priority is overridden by flowbits " + "dependency resolution ordering", + tmp->sig->id); + } + tmp = tmp->next; + } + SCLogDebug("sig_cnt: %d", sig_cnt); + + DEBUG_VALIDATE_BUG_ON(max_fb_id == 0); + DEBUG_VALIDATE_BUG_ON(sig_cnt == 0); + void *graph = SCCreateDirectedGraph(); + if (CreateGraphFromFlowbitAnalyzer(graph, head, max_fb_id) < 0) { + FatalErrorOnInit("Could not create Flowbit Analyzer Graph"); + } + + sorted_iids = SCCalloc(sig_cnt, sizeof(uint32_t)); + if (sorted_iids == NULL) { + goto error; + } + if (rule_engine_analysis_set) { + SCLogFlowbitsGraph(graph); + } + int ret = SCResolveFlowbitDependencies(graph, sorted_iids, sig_cnt); + if (ret < 0) { + goto error; + } + + SCSigSignatureWrapper *fin = NULL; + tmp = NULL; + + for (uint32_t i = 0; i < sig_cnt; i++) { + SCSigSignatureWrapper *prev = NULL; + SCSigSignatureWrapper *cur = head; + + while (cur != NULL) { + if (sorted_iids[i] == cur->sig->iid) { + if (prev == NULL) { + head = cur->next; + } else { + prev->next = cur->next; + } + + cur->next = NULL; + if (tmp != NULL) { + tmp->next = cur; + } else { + fin = cur; + } + tmp = cur; + break; + } + prev = cur; + cur = cur->next; + } + } + + SCFree(sorted_iids); /* No longer needed */ + SCFreeDirectedGraph(graph); + return fin; + +error: + SCLogError("Error resolving flowbit dependencies"); + if (sorted_iids != NULL) { + SCFree(sorted_iids); + } + SCFreeDirectedGraph(graph); + return NULL; +} + /** * \brief Orders an incoming Signature based on its action * @@ -809,12 +1063,17 @@ int SCSigOrderSignatures(DetectEngineCtx *de_ctx) int retval = 0; SCLogDebug("ordering signatures in memory"); SCSigSignatureWrapper *sigw = NULL; - SCSigSignatureWrapper *td_sigw_list = NULL; /* unified td list */ + + SCSigSignatureWrapper *td_sigw_list_start = NULL; /* unified td list start */ + SCSigSignatureWrapper *td_sigw_list_end = NULL; /* unified td list end */ + + SCSigSignatureWrapper *fb_sigw_list_start = NULL; /* flowbits list start */ SCSigSignatureWrapper *fw_pf_sigw_list = NULL; /* hook: packet_filter */ SCSigSignatureWrapper *fw_af_sigw_list = NULL; /* hook: app_filter */ Signature *sig = de_ctx->sig_list; + while (sig != NULL) { sigw = SCSigAllocSignatureWrapper(sig); if (sigw == NULL) { @@ -834,11 +1093,26 @@ int SCSigOrderSignatures(DetectEngineCtx *de_ctx) fw_af_sigw_list = sigw; } } else { - sigw->next = td_sigw_list; - td_sigw_list = sigw; + /* Flowbit specific handling. Store the three types of flowbit signatures + * separately for an easy merge of the lists by priority later on */ + if (sigw->user[DETECT_SIGORDER_FLOWBITS] > DETECT_FLOWBITS_NOT_USED) { + sigw->next = fb_sigw_list_start; + fb_sigw_list_start = sigw; + } else { + sigw->next = td_sigw_list_start; + if (!td_sigw_list_end) + td_sigw_list_end = sigw; + td_sigw_list_start = sigw; + } } sig = sig->next; } +#if DEBUG + SCLogDebug("Initial list:"); + for (sigw = fb_sigw_list_start; sigw != NULL; sigw = sigw->next) { + SCLogDebug("sig id: %d, iid: %d", sigw->sig->id, sigw->sig->iid); + } +#endif /* despite having Append in the name, the new Sig/Rule funcs actually prepend with some special * logic around bidir sigs. So to respect the firewall rule order, we sort this part of the list @@ -851,10 +1125,41 @@ int SCSigOrderSignatures(DetectEngineCtx *de_ctx) SCSigOrderFunc OrderFn = { .SWCompare = SCSigOrderByAppFirewall, .next = NULL }; fw_af_sigw_list = SCSigOrder(fw_af_sigw_list, &OrderFn); } - if (td_sigw_list) { - /* Sort the list */ - td_sigw_list = SCSigOrder(td_sigw_list, de_ctx->sc_sig_order_funcs); + if (fb_sigw_list_start) { + /* Resolve any complex dependencies, if possible, or roll back to the original ruleset */ + SCSigSignatureWrapper *tmp = + SCSigResolveFlowbitDependencies(fb_sigw_list_start, de_ctx->max_fb_id); + if (tmp == NULL) { + SC_ATOMIC_EXTERN(unsigned int, engine_stage); + if (SC_ATOMIC_GET(engine_stage) == SURICATA_INIT) { + FatalErrorOnInit( + "Flowbits have circular dependencies that cannot be met at runtime"); + } else { + SCLogWarning("Applying the existing ruleset as flowbit dependencies could not be " + "resolved"); + } + } else { + fb_sigw_list_start = tmp; + } } + +#if DEBUG + SCLogDebug("Right BEFORE SigOrder:"); + for (sigw = fb_sigw_list_start; sigw != NULL; sigw = sigw->next) { + SCLogDebug("sig id: %d", sigw->sig->id); + } +#endif + + if (td_sigw_list_end) { + td_sigw_list_end->next = fb_sigw_list_start; + } else if (fb_sigw_list_start) { + DEBUG_VALIDATE_BUG_ON(td_sigw_list_start != NULL); + td_sigw_list_start = fb_sigw_list_start; + } + if (td_sigw_list_start) { + td_sigw_list_start = SCSigOrder(td_sigw_list_start, de_ctx->sc_sig_order_funcs); + } + /* Recreate the sig list in order */ de_ctx->sig_list = NULL; @@ -894,7 +1199,7 @@ int SCSigOrderSignatures(DetectEngineCtx *de_ctx) SCFree(sigw_to_free); } /* threat detect list for hook app_td */ - for (sigw = td_sigw_list; sigw != NULL;) { + for (sigw = td_sigw_list_start; sigw != NULL;) { sigw->sig->next = NULL; if (de_ctx->sig_list == NULL) { /* First entry on the list */ diff --git a/src/detect-flowbits.c b/src/detect-flowbits.c index a9671f9bbee8..9876912bb10d 100644 --- a/src/detect-flowbits.c +++ b/src/detect-flowbits.c @@ -492,32 +492,6 @@ void DetectFlowbitFree (DetectEngineCtx *de_ctx, void *ptr) SCFree(fd); } -struct FBAnalyzer { - struct FBAnalyze *array; - uint32_t array_size; -}; - -struct FBAnalyze { - uint16_t cnts[DETECT_FLOWBITS_CMD_MAX]; - uint16_t state_cnts[DETECT_FLOWBITS_CMD_MAX]; - - uint32_t *set_sids; - uint32_t set_sids_idx; - uint32_t set_sids_size; - - uint32_t *isset_sids; - uint32_t isset_sids_idx; - uint32_t isset_sids_size; - - uint32_t *isnotset_sids; - uint32_t isnotset_sids_idx; - uint32_t isnotset_sids_size; - - uint32_t *unset_sids; - uint32_t unset_sids_idx; - uint32_t unset_sids_size; -}; - extern bool rule_engine_analysis_set; static void DetectFlowbitsAnalyzeDump(const DetectEngineCtx *de_ctx, struct FBAnalyze *array, uint32_t elements); @@ -526,16 +500,16 @@ static void FBAnalyzerArrayFree(struct FBAnalyze *array, const uint32_t array_si { if (array) { for (uint32_t i = 0; i < array_size; i++) { - SCFree(array[i].set_sids); - SCFree(array[i].unset_sids); - SCFree(array[i].isset_sids); - SCFree(array[i].isnotset_sids); + SCFree(array[i].set_iids); + SCFree(array[i].unset_iids); + SCFree(array[i].isset_iids); + SCFree(array[i].isnotset_iids); } SCFree(array); } } -static void FBAnalyzerFree(struct FBAnalyzer *fba) +void FBAnalyzerFree(struct FBAnalyzer *fba) { if (fba && fba->array) { FBAnalyzerArrayFree(fba->array, fba->array_size); @@ -544,23 +518,23 @@ static void FBAnalyzerFree(struct FBAnalyzer *fba) } } -#define MAX_SIDS 8 -static bool CheckExpand(const uint32_t sids_idx, uint32_t **sids, uint32_t *sids_size) +#define MAX_IIDS 8 +static bool CheckExpand(const uint32_t iids_idx, SigIdentifier **iids, uint32_t *iids_size) { - if (sids_idx >= *sids_size) { - const uint32_t old_size = *sids_size; - const uint32_t new_size = MAX(2 * old_size, MAX_SIDS); + if (iids_idx >= *iids_size) { + const uint32_t old_size = *iids_size; + const uint32_t new_size = MAX(2 * old_size, MAX_IIDS); - void *ptr = SCRealloc(*sids, new_size * sizeof(uint32_t)); + void *ptr = SCRealloc(*iids, new_size * sizeof(SigIdentifier)); if (ptr == NULL) return false; - *sids_size = new_size; - *sids = ptr; + *iids_size = new_size; + *iids = ptr; } return true; } -static int DetectFlowbitsAnalyzeSignature(const Signature *s, struct FBAnalyzer *fba) +int DetectFlowbitsAnalyzeSignature(const Signature *s, struct FBAnalyzer *fba) { struct FBAnalyze *array = fba->array; if (array == NULL) @@ -582,16 +556,18 @@ static int DetectFlowbitsAnalyzeSignature(const Signature *s, struct FBAnalyzer fa->state_cnts[fb->cmd] += has_state; if (fb->cmd == DETECT_FLOWBITS_CMD_ISSET) { - if (!CheckExpand(fa->isset_sids_idx, &fa->isset_sids, &fa->isset_sids_size)) + if (!CheckExpand(fa->isset_iids_idx, &fa->isset_iids, &fa->isset_iids_size)) return -1; - fa->isset_sids[fa->isset_sids_idx] = s->iid; - fa->isset_sids_idx++; + fa->isset_iids[fa->isset_iids_idx].iid = s->iid; + fa->isset_iids[fa->isset_iids_idx].sid = s->id; + fa->isset_iids_idx++; } else if (fb->cmd == DETECT_FLOWBITS_CMD_ISNOTSET) { if (!CheckExpand( - fa->isnotset_sids_idx, &fa->isnotset_sids, &fa->isnotset_sids_size)) + fa->isnotset_iids_idx, &fa->isnotset_iids, &fa->isnotset_iids_size)) return -1; - fa->isnotset_sids[fa->isnotset_sids_idx] = s->iid; - fa->isnotset_sids_idx++; + fa->isnotset_iids[fa->isnotset_iids_idx].iid = s->iid; + fa->isnotset_iids[fa->isnotset_iids_idx].sid = s->id; + fa->isnotset_iids_idx++; } } if (fb->or_list_size == 0) { @@ -600,16 +576,18 @@ static int DetectFlowbitsAnalyzeSignature(const Signature *s, struct FBAnalyzer fa->state_cnts[fb->cmd] += has_state; if (fb->cmd == DETECT_FLOWBITS_CMD_ISSET) { - if (!CheckExpand(fa->isset_sids_idx, &fa->isset_sids, &fa->isset_sids_size)) + if (!CheckExpand(fa->isset_iids_idx, &fa->isset_iids, &fa->isset_iids_size)) return -1; - fa->isset_sids[fa->isset_sids_idx] = s->iid; - fa->isset_sids_idx++; + fa->isset_iids[fa->isset_iids_idx].iid = s->iid; + fa->isset_iids[fa->isset_iids_idx].sid = s->id; + fa->isset_iids_idx++; } else if (fb->cmd == DETECT_FLOWBITS_CMD_ISNOTSET) { if (!CheckExpand( - fa->isnotset_sids_idx, &fa->isnotset_sids, &fa->isnotset_sids_size)) + fa->isnotset_iids_idx, &fa->isnotset_iids, &fa->isnotset_iids_size)) return -1; - fa->isnotset_sids[fa->isnotset_sids_idx] = s->iid; - fa->isnotset_sids_idx++; + fa->isnotset_iids[fa->isnotset_iids_idx].iid = s->iid; + fa->isnotset_iids[fa->isnotset_iids_idx].sid = s->id; + fa->isnotset_iids_idx++; } } } @@ -624,15 +602,17 @@ static int DetectFlowbitsAnalyzeSignature(const Signature *s, struct FBAnalyzer fa->state_cnts[fb->cmd] += has_state; if (fb->cmd == DETECT_FLOWBITS_CMD_SET) { - if (!CheckExpand(fa->set_sids_idx, &fa->set_sids, &fa->set_sids_size)) + if (!CheckExpand(fa->set_iids_idx, &fa->set_iids, &fa->set_iids_size)) return -1; - fa->set_sids[fa->set_sids_idx] = s->iid; - fa->set_sids_idx++; + fa->set_iids[fa->set_iids_idx].iid = s->iid; + fa->set_iids[fa->set_iids_idx].sid = s->id; + fa->set_iids_idx++; } else if (fb->cmd == DETECT_FLOWBITS_CMD_UNSET) { - if (!CheckExpand(fa->unset_sids_idx, &fa->unset_sids, &fa->unset_sids_size)) + if (!CheckExpand(fa->unset_iids_idx, &fa->unset_iids, &fa->unset_iids_size)) return -1; - fa->unset_sids[fa->unset_sids_idx] = s->iid; - fa->unset_sids_idx++; + fa->unset_iids[fa->unset_iids_idx].iid = s->iid; + fa->unset_iids[fa->unset_iids_idx].sid = s->id; + fa->unset_iids_idx++; } } return 0; @@ -679,10 +659,10 @@ int DetectFlowbitsAnalyze(DetectEngineCtx *de_ctx) if (array[i].cnts[DETECT_FLOWBITS_CMD_ISSET] && array[i].cnts[DETECT_FLOWBITS_CMD_SET] == 0) { - const Signature *s = de_ctx->sig_array[array[i].isset_sids[0]]; + const Signature *s = de_ctx->sig_array[array[i].isset_iids[0].iid]; SCLogWarning("flowbit '%s' is checked but not " "set. Checked in %u and %u other sigs", - varname, s->id, array[i].isset_sids_idx - 1); + varname, s->id, array[i].isset_iids_idx - 1); } if (array[i].state_cnts[DETECT_FLOWBITS_CMD_ISSET] && array[i].state_cnts[DETECT_FLOWBITS_CMD_SET] == 0) @@ -709,26 +689,26 @@ int DetectFlowbitsAnalyze(DetectEngineCtx *de_ctx) array[i].state_cnts[DETECT_FLOWBITS_CMD_UNSET], array[i].state_cnts[DETECT_FLOWBITS_CMD_ISNOTSET], array[i].state_cnts[DETECT_FLOWBITS_CMD_ISSET]); - for (uint32_t x = 0; x < array[i].set_sids_idx; x++) { - SCLogDebug("SET flowbit %s/%u: SID %u", varname, i, - de_ctx->sig_array[array[i].set_sids[x]]->id); + for (uint32_t x = 0; x < array[i].set_iids_idx; x++) { + SCLogDebug("SET flowbit %s/%u: sid %u", varname, i, + de_ctx->sig_array[array[i].set_iids[x].iid]->id); } if (to_state) { - for (uint32_t x = 0; x < array[i].isset_sids_idx; x++) { - Signature *s = de_ctx->sig_array[array[i].isset_sids[x]]; - SCLogDebug("GET flowbit %s/%u: SID %u", varname, i, s->id); + for (uint32_t x = 0; x < array[i].isset_iids_idx; x++) { + Signature *s = de_ctx->sig_array[array[i].isset_iids[x].iid]; + SCLogDebug("GET flowbit %s/%u: sid %u", varname, i, s->id); s->init_data->init_flags |= SIG_FLAG_INIT_STATE_MATCH; s->init_data->is_rule_state_dependant = true; - const uint32_t sids_array_size = array[i].set_sids_idx; - if (sids_array_size == 0) + const uint32_t iids_array_size = array[i].set_iids_idx; + if (iids_array_size == 0) continue; // save information about flowbits that affect this rule's state if (s->init_data->rule_state_dependant_sids_array == NULL) { s->init_data->rule_state_dependant_sids_array = - SCCalloc(sids_array_size, sizeof(uint32_t)); + SCCalloc(iids_array_size, sizeof(uint32_t)); if (s->init_data->rule_state_dependant_sids_array == NULL) { SCLogError("Failed to allocate memory for rule_state_dependant_ids"); goto error; @@ -740,14 +720,14 @@ int DetectFlowbitsAnalyze(DetectEngineCtx *de_ctx) SCLogError("Failed to allocate memory for rule_state_variable_idx"); goto error; } - s->init_data->rule_state_dependant_sids_size = sids_array_size; + s->init_data->rule_state_dependant_sids_size = iids_array_size; SCLogDebug("alloc'ed array for rule dependency and fbs idx array, sid %u, " "sizes are %u and %u", s->id, s->init_data->rule_state_dependant_sids_size, s->init_data->rule_state_flowbits_ids_size); } else { uint32_t new_array_size = - s->init_data->rule_state_dependant_sids_size + sids_array_size; + s->init_data->rule_state_dependant_sids_size + iids_array_size; void *tmp_ptr = SCRealloc(s->init_data->rule_state_dependant_sids_array, new_array_size * sizeof(uint32_t)); if (tmp_ptr == NULL) { @@ -772,10 +752,10 @@ int DetectFlowbitsAnalyze(DetectEngineCtx *de_ctx) s->init_data->rule_state_flowbits_ids_size = new_fb_array_size; } for (uint32_t idx = 0; idx < s->init_data->rule_state_dependant_sids_size; idx++) { - if (idx < array[i].set_sids_idx) { + if (idx < array[i].set_iids_idx) { s->init_data->rule_state_dependant_sids_array [s->init_data->rule_state_dependant_sids_idx] = - de_ctx->sig_array[array[i].set_sids[idx]]->id; + de_ctx->sig_array[array[i].set_iids[idx].iid]->id; s->init_data->rule_state_dependant_sids_idx++; } } @@ -785,8 +765,9 @@ int DetectFlowbitsAnalyze(DetectEngineCtx *de_ctx) s->init_data->rule_state_flowbits_ids_size += 1; // flowbit info saving for rule made stateful rule work finished - SCLogDebug("made SID %u stateful because it depends on " - "stateful rules that set flowbit %s", s->id, varname); + SCLogDebug("made sid %u stateful because it depends on " + "stateful rules that set flowbit %s", + s->id, varname); } } } @@ -865,17 +846,17 @@ static struct FBAnalyzer DetectFlowbitsAnalyzeForGroup( array[i].state_cnts[DETECT_FLOWBITS_CMD_UNSET], array[i].state_cnts[DETECT_FLOWBITS_CMD_ISNOTSET], array[i].state_cnts[DETECT_FLOWBITS_CMD_ISSET]); - for (uint32_t x = 0; x < array[i].set_sids_idx; x++) { - SCLogDebug("SET flowbit %s/%u: SID %u", varname, i, - de_ctx->sig_array[array[i].set_sids[x]]->id); + for (uint32_t x = 0; x < array[i].set_iids_idx; x++) { + SCLogDebug("SET flowbit %s/%u: sid %u", varname, i, + de_ctx->sig_array[array[i].set_iids[x].iid]->id); } - for (uint32_t x = 0; x < array[i].isset_sids_idx; x++) { - Signature *s = de_ctx->sig_array[array[i].isset_sids[x]]; - SCLogDebug("GET flowbit %s/%u: SID %u", varname, i, s->id); + for (uint32_t x = 0; x < array[i].isset_iids_idx; x++) { + Signature *s = de_ctx->sig_array[array[i].isset_iids[x].iid]; + SCLogDebug("GET flowbit %s/%u: sid %u", varname, i, s->id); if (to_state) { s->init_data->init_flags |= SIG_FLAG_INIT_STATE_MATCH; - SCLogDebug("made SID %u stateful because it depends on " + SCLogDebug("made sid %u stateful because it depends on " "stateful rules that set flowbit %s", s->id, varname); } @@ -912,8 +893,8 @@ static void DetectFlowbitsAnalyzeDump(const DetectEngineCtx *de_ctx, // sets if (e->cnts[DETECT_FLOWBITS_CMD_SET]) { SCJbOpenArray(js, "sets"); - for (uint32_t i = 0; i < e->set_sids_idx; i++) { - const Signature *s = de_ctx->sig_array[e->set_sids[i]]; + for (uint32_t i = 0; i < e->set_iids_idx; i++) { + const Signature *s = de_ctx->sig_array[e->set_iids[i].iid]; SCJbAppendUint(js, s->id); } SCJbClose(js); @@ -921,8 +902,8 @@ static void DetectFlowbitsAnalyzeDump(const DetectEngineCtx *de_ctx, // gets if (e->cnts[DETECT_FLOWBITS_CMD_ISSET]) { SCJbOpenArray(js, "isset"); - for (uint32_t i = 0; i < e->isset_sids_idx; i++) { - const Signature *s = de_ctx->sig_array[e->isset_sids[i]]; + for (uint32_t i = 0; i < e->isset_iids_idx; i++) { + const Signature *s = de_ctx->sig_array[e->isset_iids[i].iid]; SCJbAppendUint(js, s->id); } SCJbClose(js); @@ -930,8 +911,8 @@ static void DetectFlowbitsAnalyzeDump(const DetectEngineCtx *de_ctx, // isnotset if (e->cnts[DETECT_FLOWBITS_CMD_ISNOTSET]) { SCJbOpenArray(js, "isnotset"); - for (uint32_t i = 0; i < e->isnotset_sids_idx; i++) { - const Signature *s = de_ctx->sig_array[e->isnotset_sids[i]]; + for (uint32_t i = 0; i < e->isnotset_iids_idx; i++) { + const Signature *s = de_ctx->sig_array[e->isnotset_iids[i].iid]; SCJbAppendUint(js, s->id); } SCJbClose(js); @@ -939,8 +920,8 @@ static void DetectFlowbitsAnalyzeDump(const DetectEngineCtx *de_ctx, // unset if (e->cnts[DETECT_FLOWBITS_CMD_UNSET]) { SCJbOpenArray(js, "unset"); - for (uint32_t i = 0; i < e->unset_sids_idx; i++) { - const Signature *s = de_ctx->sig_array[e->unset_sids[i]]; + for (uint32_t i = 0; i < e->unset_iids_idx; i++) { + const Signature *s = de_ctx->sig_array[e->unset_iids[i].iid]; SCJbAppendUint(js, s->id); } SCJbClose(js); @@ -1180,15 +1161,15 @@ static uint32_t NextMultiple(const uint32_t v, const uint32_t m) /** \internal * \brief adds sids for 'isset' prefilter flowbits - * \retval int 1 if we added sid(s), 0 if we didn't, -1 on error */ + * \retval int 1 if we added iid(s), 0 if we didn't, -1 on error */ // TODO skip sids that aren't set by this sgh // TODO skip sids that doesn't have a isset in the same direction -static int AddIssetSidsForBit(const DetectEngineCtx *de_ctx, const struct FBAnalyzer *fba, +static int AddIssetIidsForBit(const DetectEngineCtx *de_ctx, const struct FBAnalyzer *fba, const DetectFlowbitsData *fb, PrefilterFlowbit *add) { int added = 0; - for (uint32_t i = 0; i < fba->array[fb->idx].isset_sids_idx; i++) { - const uint32_t sig_iid = fba->array[fb->idx].isset_sids[i]; + for (uint32_t i = 0; i < fba->array[fb->idx].isset_iids_idx; i++) { + const uint32_t sig_iid = fba->array[fb->idx].isset_iids[i].iid; const Signature *s = de_ctx->sig_array[sig_iid]; SCLogDebug("flowbit: %u => considering sid %u (iid:%u)", fb->idx, s->id, s->iid); @@ -1264,15 +1245,15 @@ static int AddBitSet(const DetectEngineCtx *de_ctx, struct FBAnalyzer *fba, return -1; add->id = fb->idx; - add->rule_id_size = NextMultiple(fba->array[fb->idx].isset_sids_idx, BLOCK_SIZE); + add->rule_id_size = NextMultiple(fba->array[fb->idx].isset_iids_idx, BLOCK_SIZE); add->rule_id = SCCalloc(1, add->rule_id_size * sizeof(uint32_t)); if (add->rule_id == NULL) { SCFree(add); return -1; } - if (AddIssetSidsForBit(de_ctx, fba, fb, add) != 1) { - SCLogDebug("no sids added"); + if (AddIssetIidsForBit(de_ctx, fba, fb, add) != 1) { + SCLogDebug("no iids added"); SCFree(add->rule_id); SCFree(add); return 0; @@ -1283,11 +1264,11 @@ static int AddBitSet(const DetectEngineCtx *de_ctx, struct FBAnalyzer *fba, } else { SCLogDebug("found! pfb %p id %u", pfb, pfb->id); - int r = AddIssetSidsForBit(de_ctx, fba, fb, pfb); + int r = AddIssetIidsForBit(de_ctx, fba, fb, pfb); if (r < 0) { return -1; } else if (r == 0) { - SCLogDebug("no sids added"); + SCLogDebug("no iids added"); return 0; } } @@ -1340,8 +1321,8 @@ static int PrefilterSetupFlowbits(DetectEngineCtx *de_ctx, SigGroupHead *sgh) continue; } - if (fb_analysis.array[fb->idx].isnotset_sids_idx || - fb_analysis.array[fb->idx].unset_sids_idx) { + if (fb_analysis.array[fb->idx].isnotset_iids_idx || + fb_analysis.array[fb->idx].unset_iids_idx) { SCLogDebug("flowbit %u not supported: unset in use", fb->idx); continue; } @@ -1373,8 +1354,8 @@ static int PrefilterSetupFlowbits(DetectEngineCtx *de_ctx, SigGroupHead *sgh) } const DetectFlowbitsData *fb = (DetectFlowbitsData *)s->init_data->prefilter_sm->ctx; - if (fb_analysis.array[fb->idx].isnotset_sids_idx || - fb_analysis.array[fb->idx].unset_sids_idx) { + if (fb_analysis.array[fb->idx].isnotset_iids_idx || + fb_analysis.array[fb->idx].unset_iids_idx) { SCLogDebug("flowbit %u not supported: unset in use", fb->idx); s->init_data->prefilter_sm = NULL; s->flags &= ~SIG_FLAG_PREFILTER; diff --git a/src/detect-flowbits.h b/src/detect-flowbits.h index 55b11375008b..98057e47e295 100644 --- a/src/detect-flowbits.h +++ b/src/detect-flowbits.h @@ -31,6 +31,37 @@ #define DETECT_FLOWBITS_CMD_ISSET 3 #define DETECT_FLOWBITS_CMD_MAX 4 +struct FBAnalyzer { + struct FBAnalyze *array; + uint32_t array_size; +}; + +typedef struct SigIdentifier_ { + uint32_t iid; + uint32_t sid; +} SigIdentifier; + +struct FBAnalyze { + uint16_t cnts[DETECT_FLOWBITS_CMD_MAX]; + uint16_t state_cnts[DETECT_FLOWBITS_CMD_MAX]; + + SigIdentifier *set_iids; + uint32_t set_iids_idx; + uint32_t set_iids_size; + + SigIdentifier *isset_iids; + uint32_t isset_iids_idx; + uint32_t isset_iids_size; + + SigIdentifier *isnotset_iids; + uint32_t isnotset_iids_idx; + uint32_t isnotset_iids_size; + + SigIdentifier *unset_iids; + uint32_t unset_iids_idx; + uint32_t unset_iids_size; +}; + typedef struct DetectFlowbitsData_ { uint32_t idx; uint8_t cmd; @@ -44,4 +75,6 @@ typedef struct DetectFlowbitsData_ { /* prototypes */ void DetectFlowbitsRegister (void); +int DetectFlowbitsAnalyzeSignature(const Signature *, struct FBAnalyzer *); +void FBAnalyzerFree(struct FBAnalyzer *); #endif /* SURICATA_DETECT_FLOWBITS_H */ diff --git a/suricata.yaml.in b/suricata.yaml.in index 2e417d94b7cc..7f2260b311bf 100644 --- a/suricata.yaml.in +++ b/suricata.yaml.in @@ -1897,6 +1897,12 @@ detect: #flowbits: # max-per-signature: 8 # maximum number of flowbits keyword usage per signature, minimum value allowed is 1 + # In order to determine the correct order of signatures when flowbits are present + # and may have inter dependencies, Suricata makes a dependency graph of the + # signatures with flowbits and attempts to resolve any cycles that can be + # fixed to give a plausible order of signatures. This value determines how many + # times Suricata will try to resolve cycles before it gives up. + #max-cycle-resolution: 100 # Select the multi pattern algorithm you want to run for scan/search the # in the engine.