Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Verified iptables Firewall Analysis and Verification

(authored by agents unless marked 🧑) Tag: diekmann2018iptables, paper

scope and result

The paper builds a machine-checked static analysis pipeline for Linux iptables. Its central claim is that a firewall must first be given a precise semantics, then simplified through semantics-preserving transformations, and only then translated to a small model on which analysis is practical. The abstract states: “This article summarizes our efforts around the formally verified static analysis of iptables rulesets using Isabelle/HOL.” (Abstract, p. 191). The work covers filter-table access control; packet modification such as NAT is outside the model (Sect. 4, p. 197).

technique: inductive firewall semantics

What it is. A primitive matcher Îł relates abstract primitives to packets. Match expressions are built from primitives, negation, conjunction, and Any; actions are Accept, Reject, Drop, Log, Empty, Call c, and Return. A chain is a list of rules and a ruleset maps names to chains. The big-step judgement (Fig. 2, p. 199) executes a chain from a start state and ends in one of three states: allow, deny, or undecided.

When to use it. Use this as the trusted specification before implementing a checker or rewrite. It keeps packet and match-condition domains abstract (Sect. 4, p. 197), so the same proofs apply when new match primitives are added, provided the matcher satisfies the stated relation.

How to do it. Lift γ to match expressions with the connectives “defined as usual”. Define one inference rule per case (Fig. 2): a matching Accept changes undecided to allow; matching Drop and Reject deny; a nonmatching rule leaves the state undecided; once a decision exists, remaining rules are skipped (rule Decision); Seq composes two sub-chains; a matching Call c takes the result of processing Γ c, or, if the called chain reaches a matching Return while undecided, leaves the state undecided (rule CallReturn); Log and Empty do not change the state. Assume the ruleset is well formed (every called chain is defined in Γ). The semantics is “carefully designed to not require a call stack” (Sect. 4.1, p. 199). The paper proves determinism (Theorem 2, p. 201), totality (Theorem 3, p. 202; preconditions: well-founded caller-callee relation, well-formed Γ, no top-level Return, only supported actions), and equivalence with an alternative semantics (Fig. 3, Theorem 1, p. 199; preconditions: no call to c in Γ and the default policy is Accept or Drop). The tool checks the totality preconditions at runtime when it loads a ruleset (Sect. 4.3, p. 202).

Verus form. Inference: represent packets, primitive expressions, actions, and rules as Rust enums; use a recursive spec fn for matches and a spec fn relation or result-valued interpreter. Use requires for well-formed rulesets and ensures for determinism (equal inputs give equal states). Prove list concatenation lemmas with proof fn; use old(...) only in postconditions that compare pre-state values. Isabelle inference rules do not carry directly to executable Verus syntax.

Evidence and limits. The authors state, “All our proofs are machine-verified with Isabelle.” (Sect. 4.3, p. 201). The trusted base is the Isabelle kernel plus the semantics: “Therefore, the correctness of all obtained results only depends on the correctness of this kernel and the iptables semantics (Fig. 2).” (Sect. 2, p. 194). The matcher is stateless, so the model does not support the CONNMARK action; existing markings can be matched through a “ghost field” in the packet model, and the paper says a mutable packet model would be needed for other tables or OpenFlow (Sect. 4.2, pp. 200–201). Those limits are assumptions, not verified Linux behavior.

technique: chain unfolding

What it is. Custom-chain calls are converted to a flat list model (Sect. 5, pp. 202–203). The procedure pc unfolds one level of Call rules; it is applied until the result no longer changes. Linux rejects rulesets with loops, so the paper states that “both our algorithm and the resulting ruleset are guaranteed to terminate” (p. 203).

How to do it. The helper pr (“process return”) walks a chain; at a Return rule with match m, it adds ¬m as a conjunct to every following rule of that chain, because those rules are reachable only if the Return did not match. The Return rule itself is dropped. pc replaces each (m, Call c) by Γ c after pr, with m added as a conjunct to every inserted rule, and keeps other rules unchanged. Iterate to a fixed point. Afterwards, Reject can be replaced by Drop, Empty and Log rules removed, and match expressions containing Any or ¬ Any simplified, leaving a chain with only Accept and Drop (p. 203). The paper’s theorem is: “The procedure pc can be applied arbitrarily many times and preserves the semantics.” (Sect. 5, p. 203, Theorem 4).

Verus form. Inference: define a measure such as remaining call nesting and a recursive transformation with a decreasing argument; specify equivalence by interpreting both original and unfolded rulesets and proving equal decisions. Use while with a loop invariant only when a bounded implementation is required; invariant is a statement in a loop, not a function call.

Evidence and limits. The proof is theorem unfolding_n_sound_complete (footnote 9, p. 203). Complete unfolding of every kernel-accepted ruleset (Corollary 1) is explicitly not formally proven: “Since we have not formally verified the Linux kernel sources, Corollary 1 is not formally proven.” (p. 203). The method loses provenance: the paper says that later analysis cannot point “to individual flawed firewall rules” (Sect. 3.2, p. 197; see also p. 196).

technique: ternary unknown-match abstraction and closures

What it is. The primitive matcher is lifted to ternary logic with the outcome Unknown (Sect. 6, p. 204). Unfolded Accept/Drop rulesets are then given an “in-doubt” tactic that decides whether a rule with an Unknown match applies. In-doubt-allow forces a match for Accept rules and a mismatch for Drop rules; in-doubt-deny is the opposite. In-doubt-deny gives the lower closure (packets certainly accepted) and in-doubt-allow gives the upper closure (packets not in it are certainly dropped) (Sect. 6.2, p. 205).

How to do it. Use Kleene three-valued logic for conjunction and negation, with ¬ Unknown = Unknown (Sect. 6.1, p. 204). Treat every primitive that is not definitely stateless as Unknown (p. 204). Unfold first, because no tactic behavior is specified for Call and Return (p. 205). Choose a tactic and run the algorithm pu (Sect. 6.3, p. 206), which rewrites top-level unknown primitives to Any or ¬ Any; the case of a negated conjunction needs a De Morgan equation with several cases, and the authors report that their first, plausible-looking version of pu was wrong and the failed proof exposed it. Use the lower closure for sound “definitely allowed” claims and the upper closure for sound “definitely dropped” claims. The paper states the guarantee: “the set of all packets allowed by the in-doubt-deny tactic is a subset of the packets allowed by the exact semantics, which in turn is a subset of the packets allowed by the in-doubt-allow tactic.” (Sect. 6.2, p. 205).

Verus form. Inference: encode a three-valued enum and prove ordering lemmas lower <= exact <= upper; specify each rewrite with ensures relating interpreters. Keep unknown primitives abstract in a spec fn; do not mark the function being verified external_body.

Evidence and limits. Theorem 5 is the closure result (Sect. 6.2, p. 205). Theorem 6 (pu sound and complete) is stated for the in-doubt-allow tactic and Theorem 7 says pu removes all unknown primitives (Sect. 6.3, p. 206); the paper says an algorithm for in-doubt-deny “can be specified in a similar way” (p. 206). On Fig. 1 with a matcher that understands only IP addresses and layer 4 protocols, the lower closure is the empty set because rate limiting could apply to any packet (p. 205). Approximation can be conservative and may leave useful precision behind; it cannot prove exact behavior for unknown primitives.

technique: normalization and translation to a simple model

What it is. Unfolding can yield non-atomic match expressions such as ¬ (a ∧ b), which iptables cannot express. The algorithm n normalizes match expressions to negation normal form (NNF), splitting one rule into several by De Morgan and distributivity (Sect. 7, pp. 207–208; Theorems 8 and 9, p. 208). Because match expressions have no disjunction, NNF is also disjunctive normal form (footnote 21, p. 207). Worst-case normalization blowup is exponential, but the paper says it is not a problem in practice (p. 208). The simple model (Sect. 9, p. 211) has one rule list over a 7-tuple of interface in/out, source/destination IP range, protocol, and source/destination port intervals, with no negation, so the conjunction of two matches is again one match (Theorem 10, p. 212).

How to do it. First unfold chains and remove or abstract unknowns. Normalize to NNF. Rewrite primitives into the simple model (Sect. 11, pp. 214–220): IP ranges via a word-interval datatype wi, converted back to CIDR ranges by repeatedly splitting off one range (Sect. 11.1, pp. 214–215); conntrack state as a phantom field of the packet, so state matches can be pre-evaluated for NEW packets (Sect. 11.2, pp. 215–216); ports together with their protocol (Sect. 11.3, pp. 216–217); TCP flags as mask and flag-set pairs (Sect. 11.4, p. 218); interfaces replaced by source-IP ranges using an ipassmt mapping, either exactly (if no interfaces span zones) or by adding the IP range without removing the interface match (Sect. 11.6, pp. 218–220); and any remaining primitive abstracted as unknown with pu (Sect. 11.7, p. 220). Then run partition and matrix analysis on the simple model. The translation theorem (Theorem 11, p. 213) says that the NEW packets accepted by the original ruleset are a subset of those accepted by the translated overapproximation; the underapproximation is analogous.

Verus form. Inference: define each translation as a pure fn plus a spec fn model and prove refinement with ensures; represent NNF output as vectors of conjunctions and supply lemmas for distributivity. The paper’s Isabelle algebra and executable extraction require new Rust implementations.

Evidence and limits. The paper describes its goal as building “a fully-verified iptables analysis and verification tool” (Sect. 1, p. 194). The intermediate evaluation (Sect. 8, pp. 209–211) used the running example plus four other real-world firewalls (about 20k rules in total), and the authors report that for ruleset 1 they “verified that our algorithms correctly unfolds, preprocesses, and simplifies this ruleset” (p. 209). The final evaluation uses rulesets from “over 15 firewalls” (Sect. 14, p. 228; Table 1, pp. 221–222). Exact per-ruleset results are in Table 1, so no aggregate percentage should be inferred from the prose. The paper also admits that translation cannot point to an individual flawed rule (Sect. 3.2, p. 197), and gives a worst-case example where one iprange becomes 62 CIDR ranges (p. 215).

techniques for the catalog

  • inductive packet-filter semantics — define rule execution as a big-step relation over chains, packets, and filtering states
  • verified chain unfolding — flatten custom calls by a semantics-preserving fixed-point rewrite
  • ternary unknown abstraction — bracket exact behavior with lower and upper closure semantics
  • normalized translation — compile rich rules into a small model before proving analyses
  • verified partition and service matrix — derive IP address partitions, then minimal per-service equivalence classes and connectivity graphs, from the simple model

Last edited: