<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Story on An evolving mindspace</title><link>https://shivanibhardwaj.com/categories/story/</link><description>Recent content in Story on An evolving mindspace</description><generator>Hugo -- gohugo.io</generator><language>en-us</language><lastBuildDate>Wed, 08 Apr 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://shivanibhardwaj.com/categories/story/index.xml" rel="self" type="application/rss+xml"/><item><title>Suricata flowbits don't always flow</title><link>https://shivanibhardwaj.com/p/suricata-flowbits-dont-always-flow/</link><pubDate>Wed, 08 Apr 2026 00:00:00 +0000</pubDate><guid>https://shivanibhardwaj.com/p/suricata-flowbits-dont-always-flow/</guid><description>&lt;img src="https://shivanibhardwaj.com/p/suricata-flowbits-dont-always-flow/cover.jpg" alt="Featured image of post Suricata flowbits don't always flow" />&lt;h1 id="suricata-flowbits-dont-always-flow">Suricata flowbits don&amp;rsquo;t always flow
&lt;/h1>&lt;p>Welcome, reader!&lt;/p>
&lt;p>I&amp;rsquo;m assuming if you&amp;rsquo;ve landed on my blog you know what Suricata is, but, if you don&amp;rsquo;t, please check
out &lt;a class="link" href="https://github.com/OISF/suricata" target="_blank" rel="noopener"
>https://github.com/OISF/suricata&lt;/a>. As a part of my work at Suricata, I was tasked with what looked
like a not so hard (solvable) problem. This problem entailed fixing how Suricata internally orders
the signatures with flowbits for correct and efficient matching against the real world traffic.
Let&amp;rsquo;s first get an idea of what a flowbit is. Learn about Flow &lt;a class="link" href="https://inliniac.net/blog/2014/07/28/suricata-flow-logging/" target="_blank" rel="noopener"
>here&lt;/a>.
The post is a bit outdated but most of the stuff is still the same. &lt;code>flowbits&lt;/code> are special kinds of bits
that can be set, tracked and modified per flow. These help with:&lt;/p>
&lt;ul>
&lt;li>State tracking&lt;/li>
&lt;li>Controlling the rule evaluation order&lt;/li>
&lt;li>Progress tracking
per flow.&lt;/li>
&lt;/ul>
&lt;p>Examples of flowbits usage from the popular ET Open ruleset:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-diff" data-lang="diff">&lt;span class="line">&lt;span class="cl">alert tcp any any -&amp;gt; $HOME_NET 3389 (msg:&amp;#34;ET DOS Microsoft Remote Desktop (RDP) Session Established Flowbit Set&amp;#34;; flow:established,to_server; flowbits:isset,ms.rdp.synack; flowbits:set,ms.rdp.established; flowbits:unset,ms.rdp.synack; flowbits:noalert; reference:cve,2012-0152; classtype:not-suspicious; sid:2014386; rev:5; metadata:created_at 2012_03_15, signature_severity Major, updated_at 2024_03_14;)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">alert tcp any any -&amp;gt; $HOME_NET 3389 (msg:&amp;#34;ET DOS Microsoft Remote Desktop (RDP) Syn then Reset 30 Second DoS Attempt&amp;#34;; flags:R; flow:to_server; flowbits:isnotset,ms.rdp.established; flowbits:isset,ms.rdp.synack; flowbits:unset,ms.rdp.synack; reference:cve,2012-0152; classtype:attempted-dos; noalert; sid:2014384; rev:9; metadata:created_at 2012_03_13, signature_severity Major, updated_at 2024_03_14;)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">alert tcp $HOME_NET 3389 -&amp;gt; any any (msg:&amp;#34;ET DOS Microsoft Remote Desktop (RDP) Syn/Ack Outbound Flowbit Set&amp;#34;; flow:from_server; flowbits:isnotset,ms.rdp.synack; flowbits:set,ms.rdp.synack; flowbits:noalert; flags:SA; reference:cve,2012-0152; classtype:not-suspicious; sid:2014385; rev:6; metadata:created_at 2012_03_15, cve CVE_2012_0152, signature_severity Major, updated_at 2024_03_14;)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Now, these rules are ordered internally by Suricata in a way that they are evaluated correctly. There
are several properties at play but for the sake of this blog post, we&amp;rsquo;re only concerned with flowbits.
Flowbit rules are ordered as follows:&lt;/p>
&lt;ol>
&lt;li>Write operation on flowbits. Rules that SET, UNSET or TOGGLE a flowbit i.e. a state change happens.&lt;/li>
&lt;li>Write + Read operations on flowbits. Rules that do at least one Write operation and at least one Read operation
like ISSET or ISNOTSET.&lt;/li>
&lt;li>Read operation on flowbits. Rules that check if one or more flowbits ISSET or ISNOTSET i.e. no state
change happens, the existing state is only read.&lt;/li>
&lt;/ol>
&lt;p>This has worked fine all this time until we hit an edge case and found that there can exist rules with
complex ordering that is not easily determined by the internal sorting conditionals. The bug report can
be seen &lt;a class="link" href="https://redmine.openinfosecfoundation.org/issues/7638" target="_blank" rel="noopener"
>here&lt;/a>.&lt;/p>
&lt;p>The idea is that in order to be able to &amp;ldquo;read&amp;rdquo; a flowbit, it must first be written. So, the rules should
be ordered such that this criteria is preserved for all read and write states.&lt;/p>
&lt;p>At first, it looked like a problem about finding the correct sorting order but soon I started to see
edge cases one after the other including &lt;a class="link" href="https://redmine.openinfosecfoundation.org/issues/7771" target="_blank" rel="noopener"
>one&lt;/a> &amp;ldquo;unsatisfiable&amp;rdquo; at runtime.
So, I went on a quest to find how to resolve dependencies and..&lt;/p>
&lt;p>&lt;img src="https://media1.tenor.com/m/JdTGkFCQQ8YAAAAd/spongebob-squarepants-spongebob.gif"
loading="lazy"
alt="Alt Text"
>&lt;/p>
&lt;p>I landed on something called Boolean Satisfiability Problems and it was quite a topic. It took a long time
to understand what it is. I had to.. in order to determine if what I was dealing with also fell in the
same category. Let&amp;rsquo;s understand it in a simplified way with basic examples.&lt;/p>
&lt;h2 id="what-are-boolean-satisfiability-problems">What are Boolean Satisfiability Problems?
&lt;/h2>&lt;p>Every problem that can be reduced to a boolean expression such that the said expression can then be
verified to be satisfiable for all inputs.&lt;/p>
&lt;p>e.g. &lt;code>A ∧ ¬B&lt;/code> &amp;ndash; this is a basic boolean expression (read as A &lt;em>and&lt;/em> &lt;em>not&lt;/em> B)&lt;/p>
&lt;p>You understand it well. Your computer understands it well.
Here, A and B are what we call &amp;ldquo;literals&amp;rdquo;.&lt;/p>
&lt;p>An expression can also look like &lt;code>(A ∧ ¬B) ∨ (¬A ∧ B)&lt;/code> &amp;ndash; a sub-expression like &lt;code>(A ∧ ¬B)&lt;/code> is called a &amp;ldquo;clause&amp;rdquo;.&lt;/p>
&lt;p>Simple enough, isn&amp;rsquo;t it? Actually, it&amp;rsquo;s an NP-Complete problem with certain specialized cases even
classified as NP-Hard. Another blog post is due to explain NP-Complete and NP-Hard.
But, are all SAT problems this hard? Not really. As long as the literals stay &amp;lt;= 2, or the clauses
are less, the SAT problem is verifiable in polynomial time with even a scope of finding a solution
in &lt;code>O(n + m)&lt;/code>&lt;/p>
&lt;p>where, &lt;code>n&lt;/code> = number of literals&lt;/p>
&lt;p>and, &lt;code>m&lt;/code> = number of clauses&lt;/p>
&lt;p>Let&amp;rsquo;s say our problem at hand is finding if the expression &lt;code>(A ∧ ¬B)&lt;/code> is satisfiable at runtime.&lt;/p>
&lt;p>&lt;strong>Truth table for &lt;code>(A ∧ ¬B)&lt;/code>&lt;/strong>&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>A&lt;/th>
&lt;th>B&lt;/th>
&lt;th>Output&lt;/th>
&lt;th>Satisfiable&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>0&lt;/td>
&lt;td>1&lt;/td>
&lt;td>0&lt;/td>
&lt;td>No&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>0&lt;/td>
&lt;td>0&lt;/td>
&lt;td>0&lt;/td>
&lt;td>No&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>1&lt;/td>
&lt;td>1&lt;/td>
&lt;td>0&lt;/td>
&lt;td>No&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>1&lt;/td>
&lt;td>0&lt;/td>
&lt;td>1&lt;/td>
&lt;td>Yes!&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>It&amp;rsquo;s an easy problem. So easy in fact that it is easily verifiable using a simple Truth Table.
But, how was this reduced to a SAT problem? Let&amp;rsquo;s now see how SAT problems are represented.&lt;/p>
&lt;h3 id="representation-of-a-sat-problem">Representation of a SAT problem
&lt;/h3>&lt;p>SAT problems are represented in Conjunctive Normal Form (CNF).&lt;/p>
&lt;h4 id="cnf">CNF
&lt;/h4>&lt;p>Simply put, CNF is a way of expressing a boolean formula using conjunctions of clauses.
So, instead of having an expression like &lt;code>A ∨ (B ∧ C)&lt;/code>, a SAT solver would use an algorithm like
&lt;a class="link" href="https://people.cs.umass.edu/~marius/class/h250/lec2.pdf" target="_blank" rel="noopener"
>Tseitin encoding&lt;/a> to convert this into a CNF by introducing an auxiliary variable.
It is probably not needed for small expressions but for when there are larger expressions at hand.&lt;/p>
&lt;p>The idea is to introduce an auxiliary variable, say &lt;code>X&lt;/code> where &lt;code>X ≡ (B ∧ C)&lt;/code> and add clauses that would
result in the same outcome as the original expression but all AND&amp;rsquo;ed. Here, the &lt;code>(B ∧ C)&lt;/code> was the
complex clause whereas &lt;code>A&lt;/code> a standalone literal so &lt;code>(B ∧ C)&lt;/code> was chosen. However, the choice is not that
simple when multiple clauses are present and an &lt;a class="link" href="https://en.wikipedia.org/wiki/Abstract_syntax_tree" target="_blank" rel="noopener"
>Abstract Syntax Tree&lt;/a> is used as a part of the
algorithms like &lt;em>Tseitin encoding&lt;/em> to determine which expressions must act as the auxiliary variables
so as to easily reduce the expression to CNF.&lt;/p>
&lt;p>The given expression with the new auxiliary expression &lt;code>X&lt;/code> comes out to be:&lt;/p>
&lt;p>&lt;code>(A ∨ X) ∧ (¬X ∨ B) ∧ (¬X ∨ C)&lt;/code>&lt;/p>
&lt;p>You can see that the outputs remain the same for any values of &lt;code>A&lt;/code>, &lt;code>B&lt;/code> and &lt;code>C&lt;/code> as in the original
expression to verify the correctness.&lt;/p>
&lt;p>&lt;strong>Truth Table for &lt;code>A ∨ (B ∧ C)&lt;/code>&lt;/strong>&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>A&lt;/th>
&lt;th>B&lt;/th>
&lt;th>C&lt;/th>
&lt;th>Output&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>0&lt;/td>
&lt;td>0&lt;/td>
&lt;td>0&lt;/td>
&lt;td>0&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>0&lt;/td>
&lt;td>1&lt;/td>
&lt;td>0&lt;/td>
&lt;td>0&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>0&lt;/td>
&lt;td>0&lt;/td>
&lt;td>1&lt;/td>
&lt;td>0&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>0&lt;/td>
&lt;td>1&lt;/td>
&lt;td>1&lt;/td>
&lt;td>1&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>1&lt;/td>
&lt;td>0&lt;/td>
&lt;td>0&lt;/td>
&lt;td>1&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>1&lt;/td>
&lt;td>1&lt;/td>
&lt;td>0&lt;/td>
&lt;td>1&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>1&lt;/td>
&lt;td>0&lt;/td>
&lt;td>1&lt;/td>
&lt;td>1&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>1&lt;/td>
&lt;td>1&lt;/td>
&lt;td>1&lt;/td>
&lt;td>1&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>&lt;strong>Truth Table for &lt;code>(A ∨ X) ∧ (¬X ∨ B) ∧ (¬X ∨ C)&lt;/code>&lt;/strong>&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>A&lt;/th>
&lt;th>B&lt;/th>
&lt;th>C&lt;/th>
&lt;th>X&lt;/th>
&lt;th>Output&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>0&lt;/td>
&lt;td>0&lt;/td>
&lt;td>0&lt;/td>
&lt;td>0&lt;/td>
&lt;td>0&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>0&lt;/td>
&lt;td>1&lt;/td>
&lt;td>0&lt;/td>
&lt;td>0&lt;/td>
&lt;td>0&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>0&lt;/td>
&lt;td>0&lt;/td>
&lt;td>1&lt;/td>
&lt;td>0&lt;/td>
&lt;td>0&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>0&lt;/td>
&lt;td>1&lt;/td>
&lt;td>1&lt;/td>
&lt;td>1&lt;/td>
&lt;td>1&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>1&lt;/td>
&lt;td>0&lt;/td>
&lt;td>0&lt;/td>
&lt;td>0&lt;/td>
&lt;td>1&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>1&lt;/td>
&lt;td>1&lt;/td>
&lt;td>0&lt;/td>
&lt;td>0&lt;/td>
&lt;td>1&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>1&lt;/td>
&lt;td>0&lt;/td>
&lt;td>1&lt;/td>
&lt;td>0&lt;/td>
&lt;td>1&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>1&lt;/td>
&lt;td>1&lt;/td>
&lt;td>1&lt;/td>
&lt;td>1&lt;/td>
&lt;td>1&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>These auxiliary variables are invisible to the final outcome and are introduced for the convenient
conversion to CNF.&lt;/p>
&lt;h5 id="why-should-cnf-be-used">Why should CNF be used?
&lt;/h5>&lt;ul>
&lt;li>Any expression is reducible to CNF in polynomial time&lt;/li>
&lt;li>Allows for easy conflict analysis; faster exit if any clause were unsatisfiable&lt;/li>
&lt;li>DNF (Distributive Conjunction Form) &amp;ndash; the opposite of CNF can lead to exponential blow up of the formula&lt;/li>
&lt;li>It is complete and standardized distributive formulation&lt;/li>
&lt;/ul>
&lt;h2 id="applications-of-sat">Applications of SAT
&lt;/h2>&lt;p>Satisfiability problems are a part of the regular computer science everyday problems like&lt;/p>
&lt;ul>
&lt;li>
&lt;p>CPU scheduling&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Network routing&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Machine Learning decision making&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Package dependency resolutions &amp;ndash; this is something that most of us have fought.&lt;/p>
&lt;p>You may say what&amp;rsquo;s the problem here? My code works fine as it is and my package manager takes
care of all that needs to be taken care of. At this point, either you&amp;rsquo;re lying to yourself or
you&amp;rsquo;ve broken the internet already. Well, your package manager has.. with its algorithm.&lt;/p>
&lt;p>I&amp;rsquo;ve already disclosed that this is a boolean satisfiability problem so why are the
package managers still working fine? Are they, really?
Package A depends on Package B &amp;gt;= 0.5 depends on Package C == 2.3 depends on Package D == 7.9.0
depends on Package B &amp;lt; 0.4 and BAM!&lt;/p>
&lt;p>Sound familiar? 😄&lt;/p>
&lt;p>Package managers work because yes they use incredibly intelligent algorithms that are usually
backtracking based (you&amp;rsquo;re able to go back to the last good decision &amp;ndash; unlike your life 🙃 )&lt;/p>
&lt;p>but they can always run into &amp;ldquo;failure&amp;rdquo; scenarios.&lt;/p>
&lt;p>This is where &lt;strong>you&lt;/strong> come in.&lt;/p>
&lt;p>You either upgrade the entire main package, or pin a sub package to a version or decide to keep multiple versions of a
package, etc and with such assistances, it mostly works out because there&amp;rsquo;s a definite, relatively
small list of clauses.&lt;/p>
&lt;p>This can however become an NP-Hard problem by increasing the difficulty by a bit (NOT BYTE) 😉
See: &lt;a class="link" href="https://stackoverflow.com/questions/28099683/algorithm-for-dependency-resolution" target="_blank" rel="noopener"
>https://stackoverflow.com/questions/28099683/algorithm-for-dependency-resolution&lt;/a>&lt;/p>
&lt;p>[YES I GAVE YOU A STACKOVERFLOW LINK AND NOT AN AI PROMPT, DEAL WITH IT!]&lt;/p>
&lt;p>There are excellent package managers out there today like one I briefly analyzed in a hope to find
solution to my problems 😭: &lt;a class="link" href="https://pubgrub-rs-guide.pages.dev" target="_blank" rel="noopener"
>https://pubgrub-rs-guide.pages.dev&lt;/a>&lt;/p>
&lt;p>But, in the end, none of them can guarantee to find you a solution.&lt;/p>
&lt;/li>
&lt;/ul>
&lt;p>If it&amp;rsquo;s NP-Complete or worse, why do I still have a working version of all applications mentioned
above? Great question! (Feeling like sycophant AI at this point 🥲)&lt;/p>
&lt;p>Because with some assistance and limited variables, there is a chance of finding solutions. However, all
SAT solvers come with a timeout for obvious reasons.&lt;/p>
&lt;h2 id="sat-solvers">SAT solvers
&lt;/h2>&lt;p>Most SAT solvers use either of the following algorithms:&lt;/p>
&lt;ol>
&lt;li>&lt;strong>DPLL (Davis–Putnam–Logemann–Loveland)&lt;/strong> - A backtracking based search algorithm.&lt;/li>
&lt;li>&lt;strong>CDCL (Conflict-Driven Clause Learning)&lt;/strong> - A smarter backtracking algorithm. It does &amp;ldquo;backjumping&amp;rdquo; in
a non-chronological fashion and maintains a database of learnings about conflicts.&lt;/li>
&lt;/ol>
&lt;p>Note that in the worst case scenarios, the time complexity is exponential! Since a binary tree is used
to track the states, it comes out to &lt;code>O(2ⁿ)&lt;/code>.&lt;/p>
&lt;p>We just talked about &lt;code>pubgrub&lt;/code> and turns out it is based on CDCL as well with more things to make it fast,
flexible but as the author (of the algorithm, not just the blog post!) Natalie Weizenbaum explains her
awesome work here: &lt;a class="link" href="https://nex3.medium.com/pubgrub-2fb6470504f" target="_blank" rel="noopener"
>https://nex3.medium.com/pubgrub-2fb6470504f&lt;/a> the problem remains NP-Hard.&lt;/p>
&lt;h3 id="read-more">Read more
&lt;/h3>&lt;ol>
&lt;li>&lt;a class="link" href="https://inria.hal.science/hal-03589602/file/ClementinTayou-mai2021.pdf" target="_blank" rel="noopener"
>https://inria.hal.science/hal-03589602/file/ClementinTayou-mai2021.pdf&lt;/a>&lt;/li>
&lt;li>&lt;a class="link" href="https://www.geeksforgeeks.org/dsa/2-satisfiability-2-sat-problem/" target="_blank" rel="noopener"
>https://www.geeksforgeeks.org/dsa/2-satisfiability-2-sat-problem/&lt;/a>&lt;/li>
&lt;li>Wikipedia forever &amp;lt;3 &lt;a class="link" href="https://en.wikipedia.org/wiki/Boolean_satisfiability_problem" target="_blank" rel="noopener"
>https://en.wikipedia.org/wiki/Boolean_satisfiability_problem&lt;/a>&lt;/li>
&lt;li>There are international SAT competitions! &lt;a class="link" href="https://satcompetition.github.io" target="_blank" rel="noopener"
>https://satcompetition.github.io&lt;/a>&lt;/li>
&lt;/ol>
&lt;h1 id="suricata-and-flowbits-continued">Suricata and Flowbits continued&amp;hellip;
&lt;/h1>&lt;p>Now, getting back to the problem at hand. It indeed started to look like a satisfiability problem
but I saw how 2-SAT problems are solved and went head-in to see if I can reduce the problem in a way that
even graphs can solve. And, I found a solution! &amp;hellip;or so I had thought.&lt;/p>
&lt;h2 id="failed-solution-attempt-1">Failed Solution Attempt #1
&lt;/h2>&lt;p>My target was to find the correct dependency order for complex flowbit ordering, reject any cycles (see graph felt
like a plausible solution if only I could convert the problem) and deal with some inefficient cases of
command combinations that just did not make sense but can exist in theory.&lt;/p>
&lt;p>The information available at hand at once is a signature and the flowbits inside this signature along with the
commands that they are used with. So, I decided to make both signatures and flowbits as nodes in the
graph. Now, the challenge was to represent the commands, so it made sense to use directions as the indicator
of READ or WRITE. Quickly, I had an ugly but working &lt;a class="link" href="https://github.com/inashivb/blog-code-snippets/blob/master/notebooks/Flowbits%20Dependency%20Resolver%20%7C%20DEMO.ipynb" target="_blank" rel="noopener"
>proof of concept&lt;/a> of an algorithm that passed the existing
tests and the bug that started this quest!&lt;/p>
&lt;p>Rough idea was:&lt;/p>
&lt;ol>
&lt;li>Bring out the signatures that had Write + Read operations on flowbits.&lt;/li>
&lt;li>For each signature, add a node of type &amp;ldquo;signature&amp;rdquo;
i. For each flowbit in the signature, add a node of type &amp;ldquo;flowbit&amp;rdquo;
ii. Add a directed edge from the flowbit node to the signature node if the flowbit had a Read command on it
iii. Add a directed edge from the signature node to the flowbit node if the flowbit had a Write command on it&lt;/li>
&lt;li>Normalize the graph i.e. remove flowbit nodes as they&amp;rsquo;re inconsequential in the final solution.&lt;/li>
&lt;li>Check for cycles.&lt;/li>
&lt;li>Connect any loner nodes (that had no dependency with others) with a dummy node to make them all reachable.&lt;/li>
&lt;li>If no cycles were found, perform a BFS (Breadth First Search) of what should now be a DAG (Directed Acyclic Graph).&lt;/li>
&lt;/ol>
&lt;p>and I hit the first issue soon enough.&lt;/p>
&lt;p>I had over generalized the &amp;ldquo;Read&amp;rdquo; and &amp;ldquo;Write&amp;rdquo; definitions. That meant that a good set of rules like the following:&lt;/p>
&lt;pre tabindex="0">&lt;code>flowbits:isset,A; flowbits:set,B; sid:1;
flowbits:isset,B; flowbits:unset,A; flowbits:unset,B; sid:2;
&lt;/code>&lt;/pre>&lt;video width="640" height="480" controls>
&lt;source src="flowbits_cycle.mp4" type="video/mp4">
&lt;/video>
&lt;p>was now rejected for having a cyclic dependency!&lt;/p>
&lt;h2 id="failed-solution-attempt-2">Failed Solution Attempt #2
&lt;/h2>&lt;p>Now, I had to figure out a way to make sure the commands are respected and not just generalized as Read or
Write. My mentor asked if we could use weights on edges somehow and it seemed like a good thing to try
out. So, now, the algorithm remained mostly the same with some changes:&lt;/p>
&lt;ul>
&lt;li>each edge had weight of the command that was in use&lt;/li>
&lt;li>if a cycle was formed by edges of varying weights, the edge with higher weight (lower priority command)
was removed&lt;/li>
&lt;/ul>
&lt;p>This was all done in Rust and I used the &lt;a class="link" href="https://github.com/petgraph/petgraph" target="_blank" rel="noopener"
>Petgraph&lt;/a> library&amp;rsquo;s StableDiGraph.
I don&amp;rsquo;t know if I was using good Rust practices but well.. it compiled.&lt;/p>
&lt;p>&lt;img src="https://shivanibhardwaj.com/p/suricata-flowbits-dont-always-flow/sad-mouse-big-eyes.png"
width="382"
height="450"
srcset="https://shivanibhardwaj.com/p/suricata-flowbits-dont-always-flow/sad-mouse-big-eyes_hu_402faf46802e3439.png 480w, https://shivanibhardwaj.com/p/suricata-flowbits-dont-always-flow/sad-mouse-big-eyes_hu_f11d4dbe2b4b3a24.png 1024w"
loading="lazy"
class="gallery-image"
data-flex-grow="84"
data-flex-basis="203px"
>&lt;/p>
&lt;p>I still was not confident and wondered if I was missing more cases and indeed there were. New questions arose:&lt;/p>
&lt;ol>
&lt;li>
&lt;p>I felt that &lt;code>isnotset&lt;/code> command was an independent command as per its usage. It was something that was
used to track the state until the first time something was set. However, my mentor argued that it is a
dependent command and should be affected by the presence of &lt;code>unset&lt;/code> and &lt;code>toggle&lt;/code> commands. So, now the dependency
states were:&lt;/p>
&lt;p>&lt;img src="https://shivanibhardwaj.com/p/suricata-flowbits-dont-always-flow/flowbits-dependency.jpg"
width="694"
height="246"
srcset="https://shivanibhardwaj.com/p/suricata-flowbits-dont-always-flow/flowbits-dependency_hu_7e7b112aa1eb69f4.jpg 480w, https://shivanibhardwaj.com/p/suricata-flowbits-dont-always-flow/flowbits-dependency_hu_5aa28244ce423da5.jpg 1024w"
loading="lazy"
class="gallery-image"
data-flex-grow="282"
data-flex-basis="677px"
>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;code>unset&lt;/code> seemed invalid to be used without a tie-up with a state.
e.g.&lt;/p>
&lt;pre tabindex="0">&lt;code>flowbits:isset,A; flowbits:unset,A; sid:1;
&lt;/code>&lt;/pre>&lt;p>seemed more correct even if there&amp;rsquo;s an extra operation instead of the open&lt;/p>
&lt;pre tabindex="0">&lt;code>flowbits:unset,A; sid:2;
&lt;/code>&lt;/pre>&lt;p>which indicates a reset operation. Now, in the beginning of a flow, the flowbit should anyway be unset, in the
middle, if there are going to be rules using it, it&amp;rsquo;s probably better to ensure that the flowbit is in fact set
before unsetting it. The obvious difference is that the &lt;code>sid:2&lt;/code> is going to match unconditionally however,
&lt;code>sid:1&lt;/code> is going to fail the match in case the flowbit &lt;code>A&lt;/code> was not set.&lt;/p>
&lt;p>After all these failures, it was time for something that should have been done much earlier. Remember the big
lesson on SAT? Let&amp;rsquo;s put that to use to try and see if this problem can be reduced to SAT and what it might look
like.&lt;/p>
&lt;/li>
&lt;/ol>
&lt;h2 id="an-attempt-at-converting-this-problem-to-sat">An attempt at converting this problem to SAT
&lt;/h2>&lt;p>It turned out to be harder than I had thought. But, going back to the basics, we at least need&lt;/p>
&lt;ul>
&lt;li>literals&lt;/li>
&lt;li>clauses&lt;/li>
&lt;li>the boolean expression&lt;/li>
&lt;/ul>
&lt;p>So, let&amp;rsquo;s turn to the most basic of flowbits rules at first.&lt;/p>
&lt;pre tabindex="0">&lt;code>flowbits:set,A; sid:1;
flowbits:isset,A; sid:2;
&lt;/code>&lt;/pre>&lt;p>Here, sid:1 (&lt;code>s1&lt;/code> from here on) must come before sid:2 (&lt;code>s2&lt;/code> from here on). This is our dependency criteria.&lt;/p>
&lt;p>This can be made into the statement: &lt;strong>s2 depends on s1&lt;/strong> or, &lt;strong>if s2 then s1&lt;/strong>.
The boolean expression for this statement can be expressed as: &lt;code>¬s2 ∨ s1&lt;/code> (NOT s2 OR s1)&lt;/p>
&lt;p>To be sure, our favorite:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>s1&lt;/th>
&lt;th>s2&lt;/th>
&lt;th>Output&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>0&lt;/td>
&lt;td>0&lt;/td>
&lt;td>1&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>0&lt;/td>
&lt;td>1&lt;/td>
&lt;td>0&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>1&lt;/td>
&lt;td>0&lt;/td>
&lt;td>1&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>1&lt;/td>
&lt;td>1&lt;/td>
&lt;td>1&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>The expression is satisfiable in all cases except when s2 exists without s1. This is correct. &lt;code>isset&lt;/code> without &lt;code>set&lt;/code>
is invalid and would never result in a match. This is accepted by Suricata but a warning is issued.&lt;/p>
&lt;p>Let&amp;rsquo;s use slightly more complex flowbits rules now.&lt;/p>
&lt;pre tabindex="0">&lt;code>flowbits:set,A; sid:1;
flowbits:isset,B; flowbits:set,C; sid:3;
flowbits:isset,A; flowbits:set,B; sid:2;
flowbits:isset,C; sid:4;
&lt;/code>&lt;/pre>&lt;p>This is exactly the flowbit and rule structure in the ticket that started this work.
Here, the dependencies look like the following.&lt;/p>
&lt;p>Flowbit &lt;code>A&lt;/code>: s2 depends on s1 : &lt;code>¬s2 ∨ s1&lt;/code>&lt;/p>
&lt;p>Flowbit &lt;code>B&lt;/code>: s3 depends on s2 : &lt;code>¬s3 ∨ s2&lt;/code>&lt;/p>
&lt;p>Flowbit &lt;code>C&lt;/code>: s4 depends on s3 : &lt;code>¬s4 ∨ s3&lt;/code>&lt;/p>
&lt;p>Notice how we had to split things out per flowbit to make them legible.&lt;/p>
&lt;p>Now, in order to convert it into CNF, we must simply AND all the individual clauses that we were able to come
up with because they all must exist at once for the ruleset containing these 4 rules to be considered satisfiable.&lt;/p>
&lt;p>CNF := &lt;code>(¬s2 ∨ s1) ∧ (¬s3 ∨ s2) ∧ (¬s4 ∨ s3)&lt;/code>&lt;/p>
&lt;p>The truth table for this expression will be:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>s1&lt;/th>
&lt;th>s2&lt;/th>
&lt;th>s3&lt;/th>
&lt;th>s4&lt;/th>
&lt;th>Output&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>0&lt;/td>
&lt;td>0&lt;/td>
&lt;td>0&lt;/td>
&lt;td>0&lt;/td>
&lt;td>1&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>0&lt;/td>
&lt;td>1&lt;/td>
&lt;td>0&lt;/td>
&lt;td>0&lt;/td>
&lt;td>0&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>0&lt;/td>
&lt;td>0&lt;/td>
&lt;td>1&lt;/td>
&lt;td>0&lt;/td>
&lt;td>0&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>0&lt;/td>
&lt;td>1&lt;/td>
&lt;td>1&lt;/td>
&lt;td>0&lt;/td>
&lt;td>0&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>1&lt;/td>
&lt;td>0&lt;/td>
&lt;td>0&lt;/td>
&lt;td>0&lt;/td>
&lt;td>1&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>1&lt;/td>
&lt;td>1&lt;/td>
&lt;td>0&lt;/td>
&lt;td>0&lt;/td>
&lt;td>1&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>1&lt;/td>
&lt;td>0&lt;/td>
&lt;td>1&lt;/td>
&lt;td>0&lt;/td>
&lt;td>0&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>1&lt;/td>
&lt;td>1&lt;/td>
&lt;td>1&lt;/td>
&lt;td>0&lt;/td>
&lt;td>1&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>0&lt;/td>
&lt;td>0&lt;/td>
&lt;td>0&lt;/td>
&lt;td>1&lt;/td>
&lt;td>0&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>0&lt;/td>
&lt;td>1&lt;/td>
&lt;td>0&lt;/td>
&lt;td>1&lt;/td>
&lt;td>0&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>0&lt;/td>
&lt;td>0&lt;/td>
&lt;td>1&lt;/td>
&lt;td>1&lt;/td>
&lt;td>0&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>0&lt;/td>
&lt;td>1&lt;/td>
&lt;td>1&lt;/td>
&lt;td>1&lt;/td>
&lt;td>0&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>1&lt;/td>
&lt;td>0&lt;/td>
&lt;td>0&lt;/td>
&lt;td>1&lt;/td>
&lt;td>0&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>1&lt;/td>
&lt;td>1&lt;/td>
&lt;td>0&lt;/td>
&lt;td>1&lt;/td>
&lt;td>0&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>1&lt;/td>
&lt;td>0&lt;/td>
&lt;td>1&lt;/td>
&lt;td>1&lt;/td>
&lt;td>0&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>1&lt;/td>
&lt;td>1&lt;/td>
&lt;td>1&lt;/td>
&lt;td>1&lt;/td>
&lt;td>1&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>So, this expression is correctly satisfiable in the following cases:&lt;/p>
&lt;p>i. None of the rules exist&lt;/p>
&lt;p>ii: s1 exists alone&lt;/p>
&lt;p>iii: s1 and s2 exist&lt;/p>
&lt;p>iv: s1, s2 and s3 exist&lt;/p>
&lt;p>v: all signatures exist&lt;/p>
&lt;p>Since we are willing to find the exact order of the signatures, our key takeaway is point &lt;strong>v&lt;/strong> that states that there
does exist a way for the dependencies to be satisfiable at runtime indeed with all the rules present.
Note that SAT equations are &amp;ldquo;verifiable&amp;rdquo; in polynomial time.
This time, I decided to verify my analysis using a SAT solver. One popular and easy choice is &lt;a class="link" href="https://github.com/Z3Prover/z3" target="_blank" rel="noopener"
>Z3&lt;/a>
because of the easy Python scripting syntax it offers.&lt;/p>
&lt;p>A trivial Z3 script to find the solution to the given expression can be written as:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-Python" data-lang="Python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">z3&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="o">*&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">s1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">s2&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">s3&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">s4&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">Bools&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;s1 s2 s3 s4&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">solver&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">Solver&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># CNF: (¬s2 ∨ s1) ∧ (¬s3 ∨ s2) ∧ (¬s4 ∨ s3)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">solver&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">add&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">Or&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">Not&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">s2&lt;/span>&lt;span class="p">),&lt;/span> &lt;span class="n">s1&lt;/span>&lt;span class="p">))&lt;/span> &lt;span class="c1"># ¬s2 ∨ s1&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">solver&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">add&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">Or&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">Not&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">s3&lt;/span>&lt;span class="p">),&lt;/span> &lt;span class="n">s2&lt;/span>&lt;span class="p">))&lt;/span> &lt;span class="c1"># ¬s3 ∨ s2&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">solver&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">add&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">Or&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">Not&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">s4&lt;/span>&lt;span class="p">),&lt;/span> &lt;span class="n">s3&lt;/span>&lt;span class="p">))&lt;/span> &lt;span class="c1"># ¬s4 ∨ s3&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">if&lt;/span> &lt;span class="n">solver&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">check&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="n">sat&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;Satisfiable equation :)&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;The solution is:&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">res&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">solver&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">model&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">model&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">else&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;Unsatisfiable at runtime :(&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>I found out that there was problem - Z3 only finds one solution at a time so, we have to &amp;ldquo;guide&amp;rdquo; it by blocking
the solutions it has already found (ring a bell?) or use other ways to get &lt;em>all&lt;/em> the solutions. If you run the above
script, Z3 finds you the easiest solution that is satisfiable: all values set to False.&lt;/p>
&lt;p>The script with blocking clause looks like:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-Python" data-lang="Python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">z3&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="o">*&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">find_all_solutions&lt;/span>&lt;span class="p">():&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">s1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">s2&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">s3&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">s4&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">Bools&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;s1 s2 s3 s4&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">solver&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">Solver&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">solver&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">add&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">Or&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">Not&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">s2&lt;/span>&lt;span class="p">),&lt;/span> &lt;span class="n">s1&lt;/span>&lt;span class="p">))&lt;/span> &lt;span class="c1"># ¬s2 ∨ s1&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">solver&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">add&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">Or&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">Not&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">s3&lt;/span>&lt;span class="p">),&lt;/span> &lt;span class="n">s2&lt;/span>&lt;span class="p">))&lt;/span> &lt;span class="c1"># ¬s3 ∨ s2&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">solver&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">add&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">Or&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">Not&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">s4&lt;/span>&lt;span class="p">),&lt;/span> &lt;span class="n">s3&lt;/span>&lt;span class="p">))&lt;/span> &lt;span class="c1"># ¬s4 ∨ s3&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">solutions&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">while&lt;/span> &lt;span class="n">solver&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">check&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="n">sat&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">model&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">solver&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">model&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">solution&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="n">var&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="n">s1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">s2&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">s3&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">s4&lt;/span>&lt;span class="p">]:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">solution&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">var&lt;/span>&lt;span class="p">)]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">is_true&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">model&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">evaluate&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">var&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">solutions&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">append&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">solution&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;Solution #&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="nb">len&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">solutions&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">solution&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">blocking_clause&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="n">var&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="n">s1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">s2&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">s3&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">s4&lt;/span>&lt;span class="p">]:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">is_true&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">model&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">evaluate&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">var&lt;/span>&lt;span class="p">)):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">blocking_clause&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">append&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">Not&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">var&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">else&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">blocking_clause&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">append&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">var&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">solver&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">add&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">Or&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">blocking_clause&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">solutions&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">all_solutions&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">find_all_solutions&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>and the output is:&lt;/p>
&lt;pre tabindex="0">&lt;code>Solution #1: {&amp;#39;s1&amp;#39;: False, &amp;#39;s2&amp;#39;: False, &amp;#39;s3&amp;#39;: False, &amp;#39;s4&amp;#39;: False}
Solution #2: {&amp;#39;s1&amp;#39;: True, &amp;#39;s2&amp;#39;: False, &amp;#39;s3&amp;#39;: False, &amp;#39;s4&amp;#39;: False}
Solution #3: {&amp;#39;s1&amp;#39;: True, &amp;#39;s2&amp;#39;: True, &amp;#39;s3&amp;#39;: False, &amp;#39;s4&amp;#39;: False}
Solution #4: {&amp;#39;s1&amp;#39;: True, &amp;#39;s2&amp;#39;: True, &amp;#39;s3&amp;#39;: True, &amp;#39;s4&amp;#39;: False}
Solution #5: {&amp;#39;s1&amp;#39;: True, &amp;#39;s2&amp;#39;: True, &amp;#39;s3&amp;#39;: True, &amp;#39;s4&amp;#39;: True}
&lt;/code>&lt;/pre>&lt;p>so, the manual solution checking and equation creation checks out!**&lt;/p>
&lt;p>Now, how can I automate the creation of the constraints? Notice how in order to create the SAT equation, I had to
break things down per flowbit, so, at least that looks like a good route. This information is not available at the signature
ordering time but it would have to be retrieved. The logical flow requires a map of all sids for each flowbit
per command. This is already somewhat done in Suricata to check inconsistencies among the commands. But, this happens at
end of the entire ordering and grouping and a warning is issued in such a case. We need more though to solve the
ordering problem.
Moving forward, each flowbit will have 5 arrays, let&amp;rsquo;s say we&amp;rsquo;re dealing with the same flowbits in the above example.
There are 3 distinct flowbits.&lt;/p>
&lt;pre tabindex="0">&lt;code>Flowbit A:
set_sids: [1]
isset_sids: [2]
isnotset_sids: []
toggle_sids: []
unset_sids: []
Flowbit B:
set_sids: [2]
isset_sids: [3]
isnotset_sids: []
toggle_sids: []
unset_sids: []
Flowbit C:
set_sids: [3]
isset_sids: [4]
isnotset_sids: []
toggle_sids: []
unset_sids: []
&lt;/code>&lt;/pre>&lt;p>Now, we must define the dependency order among the commands. So, far, it seems safe to assume that the following
is acceptable as shown earlier:&lt;/p>
&lt;p>&lt;img src="https://shivanibhardwaj.com/p/suricata-flowbits-dont-always-flow/flowbits-dependency.jpg"
width="694"
height="246"
srcset="https://shivanibhardwaj.com/p/suricata-flowbits-dont-always-flow/flowbits-dependency_hu_7e7b112aa1eb69f4.jpg 480w, https://shivanibhardwaj.com/p/suricata-flowbits-dont-always-flow/flowbits-dependency_hu_5aa28244ce423da5.jpg 1024w"
loading="lazy"
class="gallery-image"
data-flex-grow="282"
data-flex-basis="677px"
>&lt;/p>
&lt;p>Generic constraint evaluation then becomes:&lt;/p>
&lt;ol>
&lt;li>
&lt;p>for each flowbit:&lt;/p>
&lt;p>create a one to many map of isset_sids and (set_sids and toggle_sids)&lt;/p>
&lt;p>create a one to many map of isnotset_sids and (unset_sids and toggle_sids)&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Create a consolidated map of all flowbit maps.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>for each entry (dependent_sid, dependee_sid) in the map&lt;/p>
&lt;p>add a clause &lt;code>c&lt;/code>: Or(Not(dependent_sid), dependee_sid)&lt;/p>
&lt;p>add the clause to SAT solver: solver.add(&lt;code>c&lt;/code>)&lt;/p>
&lt;/li>
&lt;/ol>
&lt;p>and that should be our final boolean expression**.&lt;/p>
&lt;p>Now, running this boolean expression through a SAT solver should tell you if an arrangement is possible among
the given set of signatures.
But, we just learned that SAT solvers give one solution at a time. Good news is that we can freeze the literals
to a certain value. So, in Z3, for example, we could force any variable to be true:&lt;/p>
&lt;pre tabindex="0">&lt;code>solver.add(s == True)
&lt;/code>&lt;/pre>&lt;p>By forcing this constraint, I am basically asking SAT solver if a solution exists if &lt;strong>all&lt;/strong> the flowbits rules
were enabled. If there&amp;rsquo;s no solution, we can always go to another possible solution using blocking clauses.&lt;/p>
&lt;p>Now, because I know for sure which rules should be enabled to make the ruleset satisfiable at runtime, I can go
back to directed graphs for the actual final solution. Some corrections on the last algorithm based on how I was
able to reduce the problem to SAT are:&lt;/p>
&lt;ol>
&lt;li>Node types should only be signatures but all flowbit signatures not just the complex ones like before&lt;/li>
&lt;li>The dependent-dependee map should be used to create relationships &amp;ndash; Problem that stays: we could
still run into &amp;ldquo;unreachable&amp;rdquo; nodes which is OK but they make it hard to define the order. e.g.&lt;/li>
&lt;/ol>
&lt;p>&lt;img src="https://shivanibhardwaj.com/p/suricata-flowbits-dont-always-flow/fb-unreachable-node.jpg"
width="962"
height="602"
srcset="https://shivanibhardwaj.com/p/suricata-flowbits-dont-always-flow/fb-unreachable-node_hu_3faa96847298d271.jpg 480w, https://shivanibhardwaj.com/p/suricata-flowbits-dont-always-flow/fb-unreachable-node_hu_e31e41ff4f29f54f.jpg 1024w"
loading="lazy"
class="gallery-image"
data-flex-grow="159"
data-flex-basis="383px"
>&lt;/p>
&lt;p>If there&amp;rsquo;s a working algorithm, it could, in theory be used without a SAT solver while rejecting any ruleset that
has unsatisfiable rule paths (cycles).
Ofc the downside is that Suricata would simply reject the ruleset as impossible to match at runtime without any
helpful directions on what needs to be done. SAT solvers can help with a possible solution quickly.&lt;/p>
&lt;p>Let&amp;rsquo;s also see what the SAT equation would look like in case of an unsatisfiable ruleset.&lt;/p>
&lt;pre tabindex="0">&lt;code>flowbits:set,A; flowbits:isset,B; sid:1;
flowbits:isset,A; flowbits:set,B; sid:2;
&lt;/code>&lt;/pre>&lt;p>Flowbit &lt;code>A&lt;/code>: s2 depends on s1 : &lt;code>¬s2 ∨ s1&lt;/code>
Flowbit &lt;code>B&lt;/code>: s1 depends on s2 : &lt;code>¬s1 ∨ s2&lt;/code>&lt;/p>
&lt;p>CNF := &lt;code>(¬s2 ∨ s1) ∧ (¬s1 ∨ s2)&lt;/code>&lt;/p>
&lt;p>Truth table for this expression:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>s1&lt;/th>
&lt;th>s2&lt;/th>
&lt;th>Output&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>0&lt;/td>
&lt;td>0&lt;/td>
&lt;td>1&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>0&lt;/td>
&lt;td>1&lt;/td>
&lt;td>0&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>1&lt;/td>
&lt;td>0&lt;/td>
&lt;td>0&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>1&lt;/td>
&lt;td>1&lt;/td>
&lt;td>1&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>Wut.&lt;/p>
&lt;p>SAT solver says that cyclic dependency rules are solvable in two scenarios: Neither rule exists - OK.
Both the rules exist - What?&lt;/p>
&lt;p>This, my dear reader was another blip in what was looking like a promising solution. Even SAT solvers and ASP
solvers use directed graphs or ordered clauses to detect cycles.&lt;/p>
&lt;p>&lt;img src="https://shivanibhardwaj.com/p/suricata-flowbits-dont-always-flow/crying_cat.jpeg"
width="219"
height="148"
srcset="https://shivanibhardwaj.com/p/suricata-flowbits-dont-always-flow/crying_cat_hu_ed6b385d4d18ecb9.jpeg 480w, https://shivanibhardwaj.com/p/suricata-flowbits-dont-always-flow/crying_cat_hu_f232e526131e79d4.jpeg 1024w"
loading="lazy"
class="gallery-image"
data-flex-grow="147"
data-flex-basis="355px"
>&lt;/p>
&lt;h3 id="outcomes-of-the-sat-solver-approach">Outcomes of the SAT solver approach
&lt;/h3>&lt;ul>
&lt;li>While promising, SAT solvers also need guidance for special conditions.&lt;/li>
&lt;li>With correct equations in SAT solvers, if a solution does not exist for all True values, we can remove that constraint
and ask the solver to give a solution, any solution.&lt;/li>
&lt;/ul>
&lt;p>**Noticed the stars above? They indicate that the equation although working for the cases at hand are incomplete.
All equations need &lt;strong>ALL&lt;/strong> possible clauses which is where Tseitin encoding like algorithms come in. For example, if
A depends on B and B depends on C then apart from these clauses, one must add a transitive clause stating that
A must also depend on C.&lt;/p>
&lt;h2 id="conclusion">Conclusion
&lt;/h2>&lt;p>The flowbits ordering issue was successfully reduced to a SAT problem.
Can this all be done in polynomial time? For most practical cases, yes. If one were to stress test it, I don&amp;rsquo;t know.
The complexity goes up with the increase in literals (signatures) and clauses (dependencies created by each flowbit usage).&lt;/p>
&lt;p>There are alternatives like &lt;a class="link" href="https://www.cs.utexas.edu/~vl/papers/wiasp.pdf" target="_blank" rel="noopener"
>ASP&lt;/a> that look promising. I don&amp;rsquo;t know if I
will explore it but if I do, you&amp;rsquo;ll see in a blog post.&lt;/p>
&lt;p>Is this a solution to the problem then?
I don&amp;rsquo;t think so. While this research helped with the direction and clarifying what may or may not work, there are still
edge cases, one of which I mentioned earlier in finding the actual solution.
Stay tuned for the final solution, if any. For now, the battle continues..&lt;/p>
&lt;h3 id="mandatory-note">Mandatory Note
&lt;/h3>&lt;p>I assure you I&amp;rsquo;m a human and therefore, I am bound to make mistakes. Please feel free to point any obvious mistakes in
the analysis or the content. I decided to write this blog post despite there being no clear indication if this
work is close to a finished state or will be accepted just because this took on and off a lot of research,
failures, trials at different solutions and understanding several new and interesting concepts over a long period of time.&lt;/p>
&lt;p>Thanks for reading! 😺&lt;/p></description></item></channel></rss>