diff --git a/rust/Cargo.lock.in b/rust/Cargo.lock.in index 6056e14f9315..e0c1f49b7f07 100644 --- a/rust/Cargo.lock.in +++ b/rust/Cargo.lock.in @@ -414,6 +414,12 @@ dependencies = [ "syn 2.0.106", ] +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + [[package]] name = "endian-type" version = "0.1.2" @@ -489,6 +495,12 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ced73b1dacfc750a6db6c0a0c3a3853c8b41997e2e2c563dc90804ae6867959" +[[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" @@ -638,6 +656,17 @@ dependencies = [ "polyval", ] +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash 0.1.5", +] + [[package]] name = "hashbrown" version = "0.16.1" @@ -646,7 +675,7 @@ checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" dependencies = [ "allocator-api2", "equivalent", - "foldhash", + "foldhash 0.2.0", ] [[package]] @@ -701,7 +730,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4b0f83760fb341a774ed326568e19f5a863af4a952def8c39f9ab92fd95b88e5" dependencies = [ "equivalent", - "hashbrown", + "hashbrown 0.16.1", ] [[package]] @@ -723,6 +752,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.15" @@ -793,7 +831,7 @@ version = "0.16.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1dc47f592c06f33f8e3aea9591776ec7c9f9e4124778ff8a3c3b87159f7e593" dependencies = [ - "hashbrown", + "hashbrown 0.16.1", ] [[package]] @@ -1072,6 +1110,18 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" +[[package]] +name = "petgraph" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54acf3a685220b533e437e264e4d932cfbdc4cc7ec0cd232ed73c08d03b8a7ca" +dependencies = [ + "fixedbitset", + "hashbrown 0.15.5", + "indexmap", + "serde", +] + [[package]] name = "phf" version = "0.10.1" @@ -1559,6 +1609,7 @@ dependencies = [ "hkdf", "humantime", "ipsec-parser", + "itertools", "kerberos-parser", "lazy_static", "ldap-parser", @@ -1573,6 +1624,7 @@ dependencies = [ "num", "num-derive", "num-traits 0.2.19", + "petgraph", "psl", "regex", "sawp", diff --git a/rust/Cargo.toml.in b/rust/Cargo.toml.in index 92b3daff6ab3..dc6ef20a987f 100644 --- a/rust/Cargo.toml.in +++ b/rust/Cargo.toml.in @@ -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" diff --git a/rust/src/utils/flowbits_resolver.rs b/rust/src/utils/flowbits_resolver.rs new file mode 100644 index 000000000000..b6e17ce41ef5 --- /dev/null +++ b/rust/src/utils/flowbits_resolver.rs @@ -0,0 +1,330 @@ +/* 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. + */ + +// Author: Shivani Bhardwaj + +use itertools::iproduct; +use petgraph::algo::{is_cyclic_directed, tarjan_scc}; +use petgraph::graph::{GraphError, NodeIndex}; +use petgraph::stable_graph::StableDiGraph; +use petgraph::visit::Bfs; +use petgraph::Direction; +use std::collections::HashMap; +use std::os::raw::c_void; + + +/// Special Graph Node storing flowbit or signature +#[derive(Debug, Copy, Clone)] +struct SCGNode { + iid: u32, /* signature iid or flowbits iid (in VarNameStore) */ + ntype: bool, /* node type: Signature (0), Flowbit (1) */ + nidx: NodeIndex, /* Graph's internal node index */ +} + +/// 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 graph: StableDiGraph = StableDiGraph::new(); + let boxed_graph = Box::new(graph); + + /* Make an opaque pointer for C as nothing is changed there */ + return Box::into_raw(boxed_graph) as *mut c_void; +} + +/// Drop the directed Graph. Called from C. +#[no_mangle] +pub unsafe extern "C" fn SCFreeDirectedGraph(graph: *mut c_void) { + let _ = Box::from_raw(graph as *mut StableDiGraph); +} + +/// 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( + graph: *mut c_void, iid: u32, ntype: bool, write_cmd: bool, cmd: u8, sig_gid: u32, +) -> i64 { + let g = &mut *(graph as *mut StableDiGraph); + + let node_idx; + if let Some(nidx) = get_or_create_node(g, iid, ntype) { + node_idx = nidx; + } else { + SCLogError!("Error adding node; Graph is at full capacity"); + return -2; + } + + let sidx = NodeIndex::from(sig_gid); + + if !ntype { + SCLogNotice!("Node type is not flowbit"); + return node_idx.index() as i64; + } + + /* flowbit type node */ + if write_cmd { + /* WRITE command */ + match g.try_update_edge(sidx, node_idx, cmd) { + /* edge from signature to flowbit */ + Ok(_) => { + SCLogNotice!("Created an edge from {:?} -> {:?}", sidx, node_idx); + } + 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; + } + } + } else { + match g.try_update_edge(node_idx, sidx, cmd) { + /* edge from flowbit to signature */ + Ok(_) => { + SCLogNotice!("Created an edge from {:?} -> {:?}", sidx, node_idx); + } + 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; + } + } + } + + SCLogNotice!("node count: {:?}", g.node_count()); + node_idx.index() as i64 +} + +/// Recursive fn to find a valid cycle and update the graph +/// STODO what is the time complexity of this entire algorithm now with so +/// much of fluff? +fn check_cycle_update_graph(graph: &mut StableDiGraph) -> i8 +{ + let mut nodes: Vec> = Vec::new(); + /* Check graph for any cycles */ + if !is_cyclic_directed(&graph.clone()) { + SCLogNotice!("no cycles"); + return 0; + } else { + SCLogNotice!("Found a cycle. Checking if its valid.."); + let sccs = tarjan_scc(&*graph); + // find all strongly connected components of the graph + for scc in sccs.iter().filter(|scc| scc.len() > 1) { + SCLogNotice!("Cycle nodes: {:?}", scc); + nodes.push(scc.to_vec()); + // STODO what if it's a big cycle? can that happen? + } + } + for scc in nodes { + for np in scc.windows(2) { // STODO make a test with cycle formed b/w 5 nodes + if let [a, b] = np { + // STODO BEWARE, this is only correct for cycle b/w two nodes + if let Some(e1) = graph.find_edge(*a, *b) { + if let Some(e2) = graph.find_edge(*b, *a) { + let w1 = graph.edge_weight(e1).unwrap(); // STODO is this safe? + let w2 = graph.edge_weight(e2).unwrap(); // STODO is this safe? + if w1 == w2 { + debug_validate_bug_on!(*w1 == (1 << 7)); + // It's a cycle of same commands and must be rejected + return -1; + } else { + // Remove the edge with higher weight (so lower priority) + if *w1 > *w2 { + graph.remove_edge(e1); + } else { + graph.remove_edge(e2); + } + } + } + } + + } + } + } + + // Call the fn again for multiple cycles STODO add tests + check_cycle_update_graph(graph) +} + + +/// Wrapper function to resolve flowbit dependencies +#[no_mangle] +pub unsafe extern "C" fn SCResolveFlowbitDependencies( + graph: *mut c_void, sorted_sid_list: *mut u32, sorted_sid_list_len: u32, +) -> i8 { + let g = &mut *(graph as *mut StableDiGraph); + + debug_validate_bug_on!(g.node_count() == 0); + + /* Create a signature only directed graph */ + normalize_graph(g); + + let sorted_sid_list = + std::slice::from_raw_parts_mut(&mut *sorted_sid_list, sorted_sid_list_len as usize); + + /* No need for all the extra work if there's just one node */ + if g.node_count() == 1 { + debug_validate_bug_on!(sorted_sid_list_len != 1); + sorted_sid_list[0] = g[NodeIndex::from(0)].iid; + /* Given that first a signature is added, it is guaranteed + * that 0th node must always be a signature node */ + debug_validate_bug_on!(g[NodeIndex::from(0)].ntype); + return 0; + } + + if check_cycle_update_graph(g) == -1 { + // Couldn't do anything to fix the graph, it's a legit cycle + return -1; + } + + /* At this point, it must be a DAG, so perform a BFS on the tree to find + * out the correct order of signatures */ + return bfs_tree_dag(g, sorted_sid_list); +} + +fn get_or_create_node( + g: &mut StableDiGraph, iid: u32, ntype: bool, +) -> Option { + for node in g.node_weights() { + if node.iid == iid && node.ntype == ntype { + return Some(node.nidx); + } + } + let nd = SCGNode { + iid, + ntype, + nidx: NodeIndex::from(u32::MAX), + }; + if let Ok(idx) = g.try_add_node(nd) { + g[idx].nidx = idx; + SCLogNotice!("Created node: {:?}", g[idx]); + return Some(idx); + } + + None +} + +/// Function to create a dependency graph among signatures +/// A map of all the edges is created and then flowbit nodes +/// are eliminated while creating a direct directed edge between +/// the signature nodes connected by the flowbit node +fn normalize_graph(g: &mut StableDiGraph) { + let mut map_new_edges: Vec<(NodeIndex, Vec<(NodeIndex, NodeIndex)>)> = Vec::new(); + let mut fb_nodes_list: Vec = Vec::new(); + + for nd in g.node_weights() { + if nd.ntype { + let in_edges: Vec = + g.neighbors_directed(nd.nidx, Direction::Incoming).collect(); + + let out_edges: Vec = + g.neighbors_directed(nd.nidx, Direction::Outgoing).collect(); + + let map_edges_curnode: Vec<(NodeIndex, NodeIndex)> = + iproduct!(in_edges, out_edges).collect(); + map_new_edges.push((nd.nidx, map_edges_curnode)); + fb_nodes_list.push(*nd); + } + } + + for (fb_nd, sig_nds) in map_new_edges { + SCLogNotice!("map_new_edges -- from: {:?}; to: {:?}", fb_nd, sig_nds); + for nd in sig_nds { + let mut nweight = 0; + if let Some(ei) = g.find_edge(fb_nd, nd.0) { + if let Some(w) = g.edge_weight(ei) { + nweight |= 1 << w; + } + } + if let Some(ei) = g.find_edge(fb_nd, nd.1) { + if let Some(w) = g.edge_weight(ei) { + nweight |= 1 << w; + } + } + debug_validate_bug_on!(nweight == 0); + g.add_edge(nd.0, nd.1, nweight); + } + } + + for node in fb_nodes_list { + SCLogNotice!("Removing node {:?}", node); + /* Only flowbit nodes must be removed from the graph */ + debug_validate_bug_on!(!node.ntype); + g.remove_node(node.nidx); + } +} + +fn calculate_in_degree_nodes( + g: &mut StableDiGraph, in_degrees: &mut HashMap, +) { + for node_idx in g.node_indices() { + let in_degree = g.neighbors_directed(node_idx, Direction::Incoming).count(); + SCLogNotice!("in_degree for node {:?}: {:?}", g[node_idx], in_degree); + in_degrees.insert(node_idx, in_degree); + } +} + +/// Perform a BFS (Breadth First Search) of the DAG (Directed Acyclic Graph) +fn bfs_tree_dag(g: &mut StableDiGraph, sorted_sid_list: &mut [u32]) -> i8 { + let mut in_degrees: HashMap = HashMap::new(); + calculate_in_degree_nodes(g, &mut in_degrees); + + let nidx; + if let Some(idx) = get_or_create_node(g, u32::MAX, false) { + nidx = idx; + } else { + SCLogError!("Error adding node; Graph is at full capacity"); + return -1; + } + + /* Connect all the loner nodes to the dummy node so as to create a discoverable + * path and a connected tree to perform a BFS */ + for (node_idx, in_degree) in in_degrees { + if in_degree == 0 { + SCLogNotice!("added edge from {:?} -> {:?}", nidx, node_idx); + g.add_edge(nidx, node_idx, 1 << 7); + } + } + + let mut bfs = Bfs::new(&*g, nidx); + let mut i = 0; + + SCLogNotice!("BFS of the graph:"); + while let Some(idx) = bfs.next(&*g) { + /* Don't add dummy node to the graph */ + if idx != nidx { + SCLogNotice!("[{:?}]: {:?}", i, g[idx]); + sorted_sid_list[i] = g[idx].iid; + i += 1; + } + } + 0 +} diff --git a/rust/src/utils/mod.rs b/rust/src/utils/mod.rs index 882126ca50fa..d372ffc9b51f 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-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 @@ -17,3 +17,4 @@ pub mod base64; pub mod datalink; +pub mod flowbits_resolver; diff --git a/src/detect-engine-sigorder.c b/src/detect-engine-sigorder.c index b6a3ba85a55d..f5575df8938a 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-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 @@ -40,6 +40,7 @@ #include "action-globals.h" #include "flow-util.h" #include "util-validate.h" +#include "rust.h" #define DETECT_FLOWVAR_NOT_USED 1 #define DETECT_FLOWVAR_TYPE_READ 2 @@ -546,6 +547,13 @@ static int SCSigLessThan(SCSigSignatureWrapper *sw1, funcs = funcs->next; } + + // 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] == sw2->user[DETECT_SIGORDER_FLOWBITS])) + return 0; + // They are equal, so use sid as the final decider. return sw1->sig->id < sw2->sig->id; } @@ -621,6 +629,119 @@ static SCSigSignatureWrapper *SCSigOrder(SCSigSignatureWrapper *sw, return result; } +/** + * \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 sig_cnt = 0; + uint32_t *sorted_siids = NULL; + SCSigSignatureWrapper *sw = head; + + void *graph = SCCreateDirectedGraph(); + + while (sw != NULL) { + DEBUG_VALIDATE_BUG_ON(sw->user[DETECT_SIGORDER_FLOWBITS] != DETECT_FLOWBITS_TYPE_SET_READ); + + Signature *s = sw->sig; + int64_t sidx = SCCreateNodeEdgeDirectedGraph(graph, s->iid, false /* Signature type node */, + false /* does not matter */, 7 /* max allowed bit shift*/, + UINT32_MAX /* no signature index in graph yet */); + if (sidx < 0) { + goto error; + } + DEBUG_VALIDATE_BUG_ON(sidx > UINT32_MAX); + SigMatch *sm = s->init_data->smlists[DETECT_SM_LIST_MATCH]; + DetectFlowbitsData *fbd = NULL; + int64_t nidx = 0; + + while (sm != NULL) { + if (sm->type != DETECT_FLOWBITS) { + sm = sm->next; + continue; + } + fbd = (DetectFlowbitsData *)sm->ctx; + nidx = SCCreateNodeEdgeDirectedGraph(graph, fbd->idx, true /* Flowbit type node */, + false /* READ cmd */, fbd->cmd, (uint32_t)sidx); + if (nidx < 0) { + goto error; + } + sm = sm->next; + } + + sm = s->init_data->smlists[DETECT_SM_LIST_POSTMATCH]; + + while (sm != NULL) { + if (sm->type != DETECT_FLOWBITS) { + sm = sm->next; + continue; + } + fbd = (DetectFlowbitsData *)sm->ctx; + nidx = SCCreateNodeEdgeDirectedGraph(graph, fbd->idx, true /* Flowbit type node */, + true /* WRITE cmd */, fbd->cmd, (uint32_t)sidx); + if (nidx < 0) { + goto error; + } + sm = sm->next; + } + + sw = sw->next; + sig_cnt++; + } + + DEBUG_VALIDATE_BUG_ON(sig_cnt == 0); + + sorted_siids = SCCalloc(sig_cnt, sizeof(uint32_t)); + if (sorted_siids == NULL) { + goto error; + } + if (SCResolveFlowbitDependencies(graph, sorted_siids, sig_cnt) < 0) { + goto error; + } + + SCSigSignatureWrapper *fin = NULL; + SCSigSignatureWrapper *tmp = NULL; + + for (uint32_t i = 0; i < sig_cnt; i++) { + sw = head; + SCSigSignatureWrapper *prev = NULL; + while (sw != NULL) { + if (sorted_siids[i] == sw->sig->iid) { + if (tmp) { + tmp->next = sw; + tmp = tmp->next; + } else { + tmp = sw; + fin = tmp; + } + if (!prev) { + head = sw->next; + } else { + prev->next = sw->next; + } + sw->next = NULL; + } + prev = sw; + sw = sw->next; + } + } + SCFree(sorted_siids); /* No longer needed */ + SCFreeDirectedGraph(graph); + return fin; + +error: + SCLogError("Error resolving flowbit dependencies; rolling back to original order"); + if (sorted_siids != NULL) { + SCFree(sorted_siids); + } + SCFreeDirectedGraph(graph); + return head; +} + /** * \brief Orders an incoming Signature based on its action * @@ -810,12 +931,22 @@ void SCSigOrderSignatures(DetectEngineCtx *de_ctx) 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 *s_fb_sigw_list_start = NULL; /* SET flowbits list start */ + SCSigSignatureWrapper *r_fb_sigw_list_start = NULL; /* READ flowbits list start */ + SCSigSignatureWrapper *sr_fb_sigw_list_start = NULL; /* SET_READ flowbits list start */ + + SCSigSignatureWrapper *s_fb_sigw_list_end = NULL; /* SET flowbits list end */ + SCSigSignatureWrapper *sr_fb_sigw_list_end = NULL; /* SET_READ flowbits list end */ 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); /* Push signature wrapper onto a list, order doesn't matter here. */ @@ -829,8 +960,29 @@ void 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) { + if (sigw->user[DETECT_SIGORDER_FLOWBITS] == DETECT_FLOWBITS_TYPE_SET_READ) { + sigw->next = sr_fb_sigw_list_start; + sr_fb_sigw_list_start = sigw; + } else if (sigw->user[DETECT_SIGORDER_FLOWBITS] == DETECT_FLOWBITS_TYPE_READ) { + sigw->next = r_fb_sigw_list_start; + r_fb_sigw_list_start = sigw; + /* READ flowbits are the last to be placed in the final list so no need + * to store the end */ + } else if (sigw->user[DETECT_SIGORDER_FLOWBITS] == DETECT_FLOWBITS_TYPE_SET) { + sigw->next = s_fb_sigw_list_start; + if (!s_fb_sigw_list_end) + s_fb_sigw_list_end = sigw; + s_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; } @@ -846,10 +998,47 @@ void 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 (sr_fb_sigw_list_start) { + /* Resolve any complex dependencies, if possible, or roll back to the original ruleset */ + sr_fb_sigw_list_start = SCSigResolveFlowbitDependencies(sr_fb_sigw_list_start); + SCSigSignatureWrapper *sorted_sr_fb_sigw_list = sr_fb_sigw_list_start; + + /* Find the end of the list after dependency resolution */ + while (sorted_sr_fb_sigw_list != NULL) { + sr_fb_sigw_list_end = sorted_sr_fb_sigw_list; + sorted_sr_fb_sigw_list = sorted_sr_fb_sigw_list->next; + } } + + /* Merge the flowbit lists into the generic threat detection list. The ideal order is: + * Threat Detection -> SET flowbits -> SET_READ flowbits -> READ flowbits */ + SCSigSignatureWrapper *sorted_sr_fb_sigw_list = NULL; + if (s_fb_sigw_list_end) { + s_fb_sigw_list_end->next = sr_fb_sigw_list_start; + sorted_sr_fb_sigw_list = s_fb_sigw_list_start; + } + if (sr_fb_sigw_list_end) { + sr_fb_sigw_list_end->next = r_fb_sigw_list_start; + if (!sorted_sr_fb_sigw_list) + sorted_sr_fb_sigw_list = sr_fb_sigw_list_start; + } + if (r_fb_sigw_list_start) { + if (!sr_fb_sigw_list_start && s_fb_sigw_list_end) + s_fb_sigw_list_end->next = r_fb_sigw_list_start; + if (!sorted_sr_fb_sigw_list) + sorted_sr_fb_sigw_list = r_fb_sigw_list_start; + } + + if (td_sigw_list_end) { + td_sigw_list_end->next = sorted_sr_fb_sigw_list; + /* Sort the list by the registered ordering functions */ + td_sigw_list_start = SCSigOrder(td_sigw_list_start, de_ctx->sc_sig_order_funcs); + } else if (sorted_sr_fb_sigw_list) { + td_sigw_list_start = sorted_sr_fb_sigw_list; + /* Sort the list by the registered ordering functions */ + 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; @@ -888,7 +1077,7 @@ void 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 */