1 Introduction

Hyperproperties [10] are widely studied in (but not limited to) the context of secure information flow control. They generalize trace properties in that they not only check the correctness of individual computation traces in isolation, but relate multiple computation traces to each other. Examples include information flow policies, such as observational determinism [33, 34, 42], (quantitative) non-interference [33, 36, 41] as well as symmetry [26] and spurious dependencies in hardware designs [23], error correcting codes [26], and anti-doping of automotive software [14].

In this article, we present the monitoring approach implemented in the latest version of \(\text {RVHyper}\), an automata-based monitoring tool for hyperproperties [24]. In dynamic verification of hyperproperties, efficient and light-weight monitoring techniques are instrumented in systems, which are usually far beyond the scope of static verification approaches. By doing so, countermeasures are enacted before, for example, irreparable information leaks happen. A runtime verification tool for hyperproperties is in particular useful if the implementation of a security critical system is not available. Even without access to the source code, monitoring the observable execution traces still detects insecure information flow. \(\text {RVHyper}\) also supports the verification workflow by providing a method to test and develop specifications: Specifications can be checked on sample traces without the need for a complete model. Based on the feedback of \(\text {RVHyper}\), the specification can be refined until it matches the intended meaning.

The input of \(\text {RVHyper}\) is given in the temporal logic \(\text {HyperLTL}\) [9], which expresses temporal hyperproperties by extending linear-time temporal logic with explicit trace quantification (see [11] for a recent study of hyperlogics). HyperLTL has been used extensively to specify hyperproperties of practical interest (e.g. [14, 21, 23,24,25,26]). For example, observational determinism is expressed as the following formula:

$$\begin{aligned} \forall \pi . \forall \pi '. (o_\pi = o_{\pi '}) {\mathcal {W}} (i_\pi \ne i_{\pi '}), \end{aligned}$$

stating that every trace pair \(\pi ,\pi '\) has to agree on the output as long as it agrees on the inputs as well. When detecting a violation, \(\text {RVHyper}\) outputs a counterexample, which is a set of traces that does not satisfy the input formula.

Efficient model checking, synthesis, and satisfiability checking tools for HyperLTL already exist [12, 19,20,21,22, 25, 26]. Implementing an efficient runtime verification tool for HyperLTL specifications is, despite recent theoretical progress [1, 5,6,7, 24, 28, 29, 37], difficult: In principle, the monitor not only needs to process every observed trace, but must also store every trace observed so far, so that future traces can be compared with the traces seen so far.

The previous version of \(\text {RVHyper}\) tackles this challenging problem by implementing two optimizations [23, 24]: a specification analysis to detect exploitable properties of a hyperproperty, such as symmetry and a trace analysis, which detects all redundant traces that can be omitted during the monitoring process. A limitation of the trace analysis, which is based on a language inclusion check, is that only entire traces can be analyzed and pruned. For example, consider the traces \(t_1 = \{a\}\{a\}\{\}\) and \(t_2 = \{a\}\{\}\{a\}\) of length 3 and the HyperLTL formula \(\forall \pi . \forall \pi '. \square ( a_\pi \rightarrow \lnot b_{\pi '} )\). Neither \(t_1\) nor \(t_2\) is dominated by the other trace, in the sense of the trace analysis, i.e., that one of the traces poses strictly less requirements on future traces [24]. The traces, however, are equal on the first position. This provides an opportunity for optimization, which our new approach exploits. We introduce a novel trace storage technique (that also has massive impact on the running time), such that \(\text {RVHyper}\) can also handle partially equal traces by storing them in a tree structure.

We evaluate \(\text {RVHyper}\) on existing benchmarks such as classical information flow security by checking for violations of non-interference or monitoring error-resistant encoder. \(\text {HyperLTL}\) is, however, not limited to security policies. As an example of such an application beyond security, we show how \(\text {RVHyper}\) can be used to detect spurious dependencies in hardware designs.

Structure of this article The remainder of this article is structured as follows. We begin by giving preliminaries on \(\text {HyperLTL}\), its finite trace semantics and notation in Sect. 2. In Sect. 3, we present automata-based monitoring approach implemented in \(\text {RVHyper}\), before discussing optimizations in Sect. 4 that make the monitoring feasible in practice. In Sect. 5, we evaluate \(\text {RVHyper}\) with a focus on the novel storage optimization technique using our tree data structure.

This is a revised and extended version of a paper that appeared at TACAS 2018 [23]. Our contribution and extension compared to [23] is the inclusion of a new trace storage optimization technique presented in Sect. 4.2 and an extended evaluation in Sect. 5.

Related work The temporal logic \(\text {HyperLTL}\) was introduced to model check security properties of reactive systems [9, 26]. For one of its predecessors, \(\text {SecLTL}\) [16], there has been a proposal for a white-box monitoring approach [17] based on alternating automata. A recent survey on algorithms for monitoring hyperproperties is given in [28]. Agrawal and Bonakdarpour [1] were the first to study the monitoring problem of HyperLTL for the sequential model. They give a syntactic characterization of monitorable \(\text {HyperLTL}\) formulas. They present a first monitoring algorithm based on a progression logic expressing trace interdependencies and the composition of an LTL\(_3\) monitor. A first constraint-based approach has been outlined in [7], which works for a subclass of HyperLTL specifications. The idea is to identify a set of propositions of interest and store corresponding constraints. A constraint-based algorithm for the complete fragment of \(\forall ^2\) HyperLTL formulas has been proposed in [29]. The algorithms rewrite a HyperLTL formula and an incoming event into a constraint composed of a plain LTL requirement as well as a HyperLTL requirement. An constraint system is built incrementally: The HyperLTL part is encoded with variables, which will be incrementally defined with more incoming events of a trace. Like with our monitoring algorithm, they do not have access to the implementation (black box), but in contrast to our work, they do not provide witnessing traces as a monitor verdict.

In [5], the authors study the complexity of monitoring hyperproperties. They show that the form and size of the input as well as the formula have a significant impact on the feasibility of the monitoring process. They differentiate between several input forms and study their complexity: a set of linear traces, tree-shaped Kripke structures, and acyclic Kripke structures. For acyclic structures and alternation-free HyperLTL formulas, the problems complexity gets as low as NC.In [6, 37], the authors study where static analysis can be combined with runtime verification techniques to monitor HyperLTL formulas beyond the alternation-free fragment.

For certain information flow policies, like non-interference and some extensions, dynamic enforcement mechanisms have been proposed. Techniques for the enforcement of information flow policies include tracking dependencies at the hardware level [38], language-based monitors [2,3,4, 35, 40], and abstraction-based dependency tracking [8, 27, 30]. Secure multi-execution [15] is a technique that can enforce non-interference by executing a program multiple times in different security levels. To enforce non-interference, the inputs are replaced by default values whenever a program tries to read from a higher security level.

2 Preliminaries

Let AP be a finite set of atomic propositions, and let \(\Sigma = 2^\text {AP}\) be the corresponding alphabet. An infinite trace \(t \in \Sigma ^\omega \) is an infinite sequence over the alphabet. A subset \(T \subseteq \Sigma ^\omega \) is called a trace property. A hyperproperty \(H \subseteq 2^{(\Sigma ^\omega )}\) is a generalization of a trace property. A finite trace \(t \in \Sigma ^+\) is a finite sequence over \(\Sigma \). In the case of finite traces, |t| denotes the length of a trace. We use the following notation to access and manipulate traces: Let t be a trace and i be a natural number. t[i] denotes the ith element of t. Therefore, t[0] represents the first element of the trace. Let j be natural number. If \(j \ge i\) and \(i\le |t|\), then t[ij] denotes the sequence \(t[i] t[i+1] \cdots t[\mathrm{min}(j,|t|-1)]\). Otherwise, it denotes the empty trace \(\epsilon \). \(t[i\rangle \) denotes the suffix of t starting at position i. For two finite traces s and t, we denote their concatenation by \(s \cdot t\).

HyperLTL syntax HyperLTL [9] extends LTL with trace variables and trace quantifiers. Let \({\mathcal {V}}\) be a finite set of trace variables. The syntax of HyperLTL is given by the grammar

$$\begin{aligned} \varphi&{}:=~ \forall \pi . \; \varphi \mid \exists \pi . \; \varphi \mid \psi \\ \psi&{}:=~ a_{\pi } \mid \psi \wedge \psi \mid \lnot \psi \mid \bigcirc \psi \mid \psi \; {\mathcal {U}} \; \psi , \end{aligned}$$

where \(a \in \text {AP}\) is an atomic proposition and \(\pi \in {\mathcal {V}}\) is a trace variable. Atomic propositions are indexed by trace variables. The explicit trace quantification enables us to express properties like “on all traces \(\varphi \) must hold,” expressed by \(\forall \pi \mathpunct {.}\varphi \). Dually, we can express “there exists a trace such that \(\varphi \) holds,” expressed by \(\exists \pi \mathpunct {.}\varphi \). We use the standard derived operators release \(\varphi {\mathcal {R}} \psi :=\lnot (\lnot \varphi {\mathcal {U}} \lnot \psi )\), eventually \(\diamondsuit \varphi :=\text {true} {\mathcal {U}} \varphi \), globally \(\square \varphi :=\lnot \diamondsuit \lnot \varphi \), and weak until \(\varphi _1 {\mathcal {W}} \varphi _2 :=(\varphi _1 {\mathcal {U}} \varphi _2) \vee \square \varphi _1\). As we use the finite trace semantics, \(\bigcirc \varphi \) denotes the strong version of the next operator, i.e., if a trace ends before the satisfaction of \(\varphi \) can be determined, the satisfaction relation, defined below, evaluates to false. To enable duality in the finite trace setting, we additionally use the weak next operator \(\tilde{\bigcirc }\varphi \) which evaluates to true if a trace ends before the satisfaction of \(\varphi \) can be determined and is defined as \(\tilde{\bigcirc }\varphi :=\lnot \bigcirc \lnot \varphi \). We call \(\psi \) of a HyperLTL formula \(\mathbf {Q}. \psi \), with an arbitrary quantifier prefix \(\mathbf {Q}\), the body of the formula. A HyperLTL formula \(\mathbf {Q}. \psi \) is in the alternation-free fragment if either \(\mathbf {Q}\) consists solely of universal quantifiers or solely of existential quantifiers. We also denote the respective alternation-free fragments as the \(\forall ^n\) fragment and the \(\exists ^n\) fragment, with n being the number of quantifiers in the prefix.

Finite trace semantics We recap the finite trace semantics for HyperLTL [7], which is itself based on the finite trace semantics of \(\text {LTL}\) [32]. Let \(\Pi _\text {fin}: {\mathcal {V}}\rightarrow \Sigma ^+\) be a partial function mapping trace variables to finite traces. We define \(\epsilon [0]\) as the empty set. \(\Pi _\text {fin}[i\rangle \) denotes the trace assignment that is equal to \(\Pi _\text {fin}(\pi )[i\rangle \) for all \(\pi \in \mathrm{dom}(\Pi _\text {fin})\). By slight abuse of notation, we write \(t \in \Pi _\text {fin}\) to access traces t in the image of \(\Pi _\text {fin}\). The satisfaction of a HyperLTL formula \(\varphi \) over a finite trace assignment \(\Pi _\text {fin}\) and a set of finite traces T, denoted by \(\Pi _\text {fin}\vDash _T \varphi \), is defined as follows:

$$\begin{aligned} \begin{array}{ll} \Pi _\text {fin}\vDash _T a_\pi &{} \quad \text {if } a \in \Pi _\text {fin}(\pi )[0] \\ \Pi _\text {fin}\vDash _T \lnot \varphi &{}\quad \text {if } \Pi _\text {fin}\nvDash _T \varphi \\ \Pi _\text {fin}\vDash _T \varphi \vee \psi &{} \quad \text {if } \Pi _\text {fin}\vDash _T \varphi \text { or } \Pi _\text {fin}\vDash _T \psi \\ \Pi _\text {fin}\vDash _T \bigcirc \varphi &{}\quad \text {if } \forall t \in \Pi _\text {fin}\mathpunct {.}|t|>1 \text { and } \Pi _\text {fin}[1\rangle \vDash _T \varphi \\ \Pi _\text {fin}\vDash _T \varphi {\mathcal {U}}\psi &{}\quad \text {if } \exists i< \min \nolimits _{t \in \Pi _\text {fin}} |t| \mathpunct {.}\Pi _\text {fin}[i\rangle \vDash _T \psi \\ &{}\quad \wedge \forall j < i \mathpunct {.}\Pi _\text {fin}[j\rangle \vDash _T \varphi \\ \Pi _\text {fin}\vDash _T \exists \pi \mathpunct {.}\varphi &{}\quad \text {if } \exists t \in T \text { such that } \Pi _\text {fin}[\pi \mapsto t] \vDash _T \varphi \\ \Pi _\text {fin}\vDash _T \forall \pi \mathpunct {.}\varphi &{}\quad \text {if } \forall t \in T \text { holds that } \Pi _\text {fin}[\pi \mapsto t] \vDash _T \varphi \end{array} \end{aligned}$$

The hyperproperty represented by a \(\text {HyperLTL}\) formula \(\varphi \), denoted by \(\mathcal {H}(\varphi )\), is the set \(\{{ T \subseteq \Sigma ^\omega \mid T \vDash \varphi }\}\).

3 Runtime verification of hyperproperties with \(\text {RVHyper}\)

In this section, we present an overview over \(\text {RVHyper}\), before describing the implementation setup, present the monitoring algorithm, and discuss our optimization techniques.

The input of \(\text {RVHyper}\) is given as a universally quantified HyperLTL formula and, in addition, the observed behavior of the system under consideration. The observed behavior is represented as a trace set T, where each \(t \in T\) represents a previously observed execution of the system to monitor. \(\text {RVHyper}\) can therefore detect violations of every monitorable k-safety hyperproperty (see [24] for an extensive study of monitorability of hyperproperties). If \(\text {RVHyper}\) detects that the system violates the hyperproperty, it outputs a counterexample, i.e., a k-ary tuple of traces, where k is the number of quantifiers in the HyperLTL formula.

3.1 Implementation details

\(\text {RVHyper}\)Footnote 1 is written in C++. We use spot [18] for building the deterministic monitor automata and the Buddy BDD library for handling symbolic constraints. We use the \(\text {HyperLTL}\) satisfiability solver \(\text {EAHyper}\) [19, 22] to determine whether the input formula is reflexive, symmetric, or transitive. Depending on those results, we omit redundant tuples in the monitoring algorithm.

3.2 Online monitoring algorithm

For the online algorithm, we use standard techniques for building LTL monitoring automata and use this to instantiate this monitor by the traces as specified by the \(\text {HyperLTL}\) formula. Let \(\text {AP}\) be a set of atomic propositions and \({\mathcal {V}}= \{\pi _1, \ldots , \pi _n\}\) a set of trace variables. A deterministic monitor template \(\mathcal {M}= (\Sigma , Q, \delta , q_0, F)\) is a tuple of a finite alphabet \(\Sigma = 2^{(\text {AP}\times {\mathcal {V}})}\), a non-empty set of states Q, a partial transition function \(\delta : Q \times \Sigma \hookrightarrow Q\), a designated initial state \(q_0 \in Q\), and a set of accepting states \(F \subseteq Q\). The instantiated automaton runs in parallel over traces in \((2^\text {AP})^*\), and thus, we define a run with respect to a n-ary tuple \(N \in ((2^\text {AP})^*)^n\) of finite traces. A run of N is a sequence of states \(q_0 q_1 \cdots q_m \in Q^*\), where m is the length of the smallest trace in N, starting in the initial state \(q_0\) such that for all i with \(0 \le i < m\), it holds that

$$\begin{aligned} \delta \left( q_i, \bigcup _{j=1}^n \bigcup _{a \in N(j)(i)} \{{(a,\pi _j)}\} \right) = q_{i+1} . \end{aligned}$$

A tuple N is accepted if there is a run on \(\mathcal {M}\) that ends in an accepting state. For LTL, such a deterministic monitor can be constructed in doubly exponential time in the size of the formula [13, 39].

Example 1

As an example formula, consider again the observational determinism formula introduced in the introduction:

$$\begin{aligned} \forall \pi . \forall \pi '. (o_\pi = o_{\pi '}) {\mathcal {W}} (i_\pi \ne i_{\pi '}), \end{aligned}$$

The corresponding monitor template is depicted in Fig. 1.

Fig. 1
figure 1

Visualization of a monitor template corresponding to the formula given in Example 1. We use a symbolic representation of the transition function \(\delta \). We depict a sink rejecting state \(q_\bot \)

The algorithm for monitoring \(\text {HyperLTL}\) formulas when traces are given sequentially to the monitor is presented as Algorithm 1. After building the deterministic monitoring automaton \(\mathcal {M}_\varphi \), the algorithm accepts new traces and afterward proceeds with the pace of the incoming stream. We have a variable S that maps tuples of traces to states of the deterministic monitor. Whenever the current trace t progresses, we progress every tuple \((t_1,\ldots ,t_n)\) that contains t with one of the following outcomes:

  1. 1.

    One of the traces \(t_1,\ldots ,t_n\) may have ended, and thus, we check if the monitor is in an accepting state and report a violation if this is not the case.

  2. 2.

    There is a successor state in the monitor, and thus, we update S.

  3. 3.

    There is no successor state, and hence, we report a violation.

When a new trace t starts, only new tuples are considered for S that are tuples \(\mathbf {t} \in (T \cup \{{t}\})^n\) containing the new trace t.

figure a

Example 2

We continue Example 1 by showing how the algorithm progresses on the given formula. Assume for the sake of readability that we have a single input proposition i and a single output proposition o. Furthermore, assume that we have already seen the trace \(t_0 = \{{i}\}\{{i,o}\}\{{o}\}\), that is, \(T = \{{t_0}\}\) and \(S(t_0,t_0) = q_0\). We now show how the algorithm continues with a fresh trace \(t_1\). In lines 6–8, we add the pairs \((t_0,t_1)\), \((t_0,t_1)\), and \((t_1,t_1)\) with the initial state \(q_0\) to S. Let \(p = \{{i}\}\) be the first input proposition; thus, \(t_1 = \{{i}\}\). Since \(t_0[0] = t_1[0]\), the monitor remains in \(q_0\) for every tuple. Let \(p = \{{i}\}\) be the next input proposition; thus, \(t_1 = \{{i}\}\{{i}\}\). Consider the tuple \((t_0,t_1)\). As \(t_0[1]\) and \(t_1[1]\) are equal with respect to i but differ in o, the monitor progresses to the rejecting state \(q_\bot \) and the algorithm terminates by reporting the violation. If the previous input proposition is \(p = \{{}\}\), both tuples \((t_0,t_1)\) and \((t_1,t_0)\) would progress to the accepting sink state \(q_\top \) as the input proposition is different to \(t_0[1]\). Assume two more inputs, e.g., \(t_1 = \{{i}\}\{{i}\}\{{}\}\{{}\}\); then the pairs \((t_0,t_0)\), \((t_0,t_1)\), and \((t_1,t_0)\) are removed from S as \(t_1\) is strict longer than \(t_0\).

4 Optimizations

In this section, we present three optimizations implemented in \(\text {RVHyper}\), which, as we will see in the evaluation section, are necessary to make the automata-based monitoring approach feasible in practice. We begin by explaining a specification analysis, which is a preprocessing step that exploits properties of the specification to reduce the algorithmic workload. In the subsequent section, we show how \(\text {RVHyper}\) tackles the problem of potentially unbounded memory consumption: We recap the trace analysis, which was so far the only storage optimization implemented in \(\text {RVHyper}\). We then provide a novel storage optimization technique based on prefix trees, so-called tries, to exploit partial equality of the given traces.

4.1 Specification analysis

In the example execution in Example 2, we have seen that the algorithm had to do more work than necessary to monitor observational determinism. For example, a tuple (tt) for some trace t cannot violate observational determinism as the traces are equal. Further, from the pairs \((t,t')\) and \((t',t)\) for some traces t and \(t'\), we only need to check one of them as a violation in one of them implies the violation in the other pair. We can automatically check for such conditions and, thus, omit unnecessary work.

\(\text {RVHyper}\) implements the specification analysis with the HyperLTL satisfiability solver EAHyper [19, 22]. The specification analysis is a preprocessing step that analyzes the HyperLTL formula under consideration. EAHyper can detect whether a formula is (1) symmetric, i.e., we halve the number of instantiated monitors, (2) transitive, i.e., we reduce the number of instantiated monitors to two, or (3) reflexive, i.e., we can omit the self-comparison of traces.

Definition 1

[24] Let \(\psi \) be the quantifier-free part of some \(\text {HyperLTL}\) formula \(\varphi \) over trace variables \({\mathcal {V}}\). We say \(\varphi \) is invariant under trace variable permutation \(\sigma : {\mathcal {V}}\rightarrow {\mathcal {V}}\), if for any set of traces \(T \subseteq \Sigma ^{\omega }\) and any assignment \(\Pi : {\mathcal {V}}\rightarrow T\), \((\emptyset , \Pi , 0) \vDash \psi \Leftrightarrow (\emptyset , \Pi \circ \sigma , 0) \vDash \psi \). We say \(\varphi \) is symmetric, if it is invariant under every trace variable permutation in \({\mathcal {V}}\).

Symmetry is particularly interesting since many information flow policies satisfy this property. Consider, for example, observational determinism: \( \forall \pi \mathpunct {.}\forall \pi '\mathpunct {.}(o_\pi = o_{\pi '}) {\mathcal {W}}(i_\pi \ne i_{\pi '})\). \(\text {RVHyper}\) detects symmetry by translating this formula to a formula that is unsatisfiable if there exists no set of traces for which every trace pair violates the symmetry condition: \( \exists \pi \mathpunct {.}\exists \pi '\mathpunct {.}\big ((o_\pi = o_{\pi '}) {\mathcal {W}}(i_\pi \ne i_{\pi '})\big ) \nleftrightarrow \big ((o_\pi = o_{\pi '}) {\mathcal {W}}(i_\pi \ne i_{\pi '})\big ) \). If the resulting formula turns out to be unsatisfiable, \(\text {RVHyper}\) omits the symmetric instantiations of the monitor automaton.

Definition 2

[24] Let \(\psi \) be the quantifier-free part of some \(\text {HyperLTL}\) formula \(\varphi \) over trace variables \(\{{\pi _1, \pi _2}\}\). Let \(T = \{{t_1, t_2, t_3}\} \in \Sigma ^{\omega }\) be a three-elemented set of traces. We define the assignment \(\Pi _{i,j} : {\mathcal {V}}\rightarrow \Sigma ^{\omega }\) by \(\Pi _{i,j} :=\{\pi _1 \mapsto t_i, \pi _2 \mapsto t_j\}\). We say \(\varphi \) is transitive, if for all three-elemented sets T it holds that \((\emptyset , \Pi _{1,2}, 0) \vDash \psi \wedge (\emptyset , \Pi _{2,3}, 0) \vDash \psi \rightarrow (\emptyset , \Pi _{1,3}, 0) \vDash \psi \).

While symmetric HyperLTL formulas allow us to prune half of the monitor instances, transitivity of a HyperLTL formula has an even larger impact on the required memory. Equality, i.e., \(\forall \pi . \forall \pi ' \mathpunct {.}\square (a_\pi \leftrightarrow a_{\pi '})\), for example, is transitive and symmetric and allows us to reduce the number of monitor instances to one, since we can check equality against any reference trace.

Definition 3

[24] Let \(\psi \) be the quantifier-free part of some \(\text {HyperLTL}\) formula \(\varphi \) over trace variables \({\mathcal {V}}\). We say \(\varphi \) is reflexive, if for any trace \(t \in \Sigma ^{\omega }\) and the corresponding assignment \(\Pi : {\mathcal {V}}\rightarrow \{t\}\), \((\emptyset , \Pi , 0) \vDash \psi \).

Lastly, if a formula is reflexive, \(\text {RVHyper}\) omits the composition of a trace with itself during the monitoring process. For example, equality and observational determinism have reflexive HyperLTL formulas.

Example 3

Consider again the observational determinism formula from Example 1. We have seen that this formula is both, reflexive and symmetric; thus, we can omit those instances in the algorithm.

4.2 Optimizing trace storage

The main obstacle in monitoring hyperproperties is the potentially unbounded space consumption. Previously, RVHyper employed a trace analysis technique to detect redundant traces, with respect to a given HyperLTL formula, i.e., traces that can be safely discarded without losing any information and without losing the ability to return a counterexample.

Definition 4

[24] Given a HyperLTL formula \(\varphi \), a trace set T, and an arbitrary \(t \in \textit{TR}\), we say that t is \((T,\varphi )\)-redundant if T is a model of \(\varphi \) if and only if \(T \cup \{{t}\}\) is a model of \(\varphi \) as well, formally

$$\begin{aligned} \forall T' \supseteq T \mathpunct {.}T' \in \mathcal {H}(\varphi ) \Leftrightarrow T' \cup \{t\} \in \mathcal {H}(\varphi ) . \end{aligned}$$

Definition 5

[24] Given \(t,t' \in \Sigma ^\omega \), we say t dominates \(t'\) with respect to \(\varphi \) (or simply t dominates \(t'\) if it is clear from the context) if \(t'\) is (\(\{t\},\varphi \))-redundant.

Example 4

For observational determinism, a trace t is dominated by a trace \(t'\) if \({|{t}|} < {|{t'}|}\) and both traces agree on the input propositions.

This is efficiently implemented in \(\text {RVHyper}\) (cf. Algorithm 2) and is guaranteed to catch all redundant traces. In our experiments [23, 24], we made the observation that traces often share the same prefixes, leading to a lot of redundant monitor automaton instantiations, repetitive computations, and duplicated information when those traces get stored.

The trace analysis, as it is based on a language inclusion check of the entire traces, cannot handle partial redundancy, for example, in the case that traces have redundant prefix requirements. This leaves room for optimization, which we address by implementing a trie data structure for managing the storage of incoming traces.

figure b

Tries, also known as prefix trees, are a tree data structure, which can represent a set of words over an alphabet in a compact manner. The root of a trie is identified with the empty word \(\epsilon \); additionally, each node can have several child nodes, each of which corresponds to a unique letter getting appended to the representing word of the parent node. So the set of words of a trie is identified with the set of words the leaf nodes represent.

Definition 6

A trie is a four tuple \((\Sigma , \mathcal {T}, \longrightarrow , \tau _0)\) consisting of

  • A finite alphabet \(\Sigma \),

  • A non-empty set of states \(\mathcal {T}\),

  • A transition function \(\longrightarrow : \mathcal {T}\times \Sigma \rightarrow \mathcal {T}\),

  • And a designated initial state \(\tau _0 \in \mathcal {T}\) called the root.

Instead of \(((\tau ,a),\tau ') \in \longrightarrow \), we will write \(\tau \overset{a}{\longrightarrow } \tau '\) in the following. For a trie to be of valid form, we restrict \(\longrightarrow \) such that, \(\forall \tau ,\tau ' \in \mathcal {T}. |\{\tau \overset{a}{\longrightarrow } \tau '| a \in \Sigma \}| \le 1\).

In our case, the alphabet would be the set of propositions used in the specification, and the word built by the trie represents the traces. Instead of storing each trace individually, we store all of them in one trie structure, branching only in case of deviation. This means equal prefixes only have to be stored once. Besides the obvious benefits for memory, we also can make use of the maintained trie data structure to improve the runtime of our monitoring algorithms. As traces with same prefixes end up corresponding to the same path in the trie, we only have to instantiate the monitor automaton as much as the trie contains branches.

Example 5

Consider the following traces of length 6 over the alphabet \(2^{\{i,o\}}\):

  • \(t_1: \{\{{i}\},\{{i,o}\},\{{i}\},\{{i}\},\{{i}\},\{{i,o}\} \}\)

  • \(t_2: \{\{{i}\}, \{{i,o}\}, \{{i}\}, \{{i}\} , \{{i}\}, \{{i}\}\}\)

  • \(t_3: \{\{{i}\}, \{{i}\}, \{{i}\}, \{{i}\} , \{{i}\}, \{{i}\}\}\)

  • \(t_4: \{\{{i}\}, \{{i}\}, \{{}\}, \{{}\} , \{{}\}, \{{}\}\}\)

After processing the traces sequentially, the resulting trie looks as follows:

figure c

4.3 Trie-based monitoring algorithm

We depict a trie-based offline monitoring algorithm in Fig. 3. For the sake of readability, we assume that there are as many traces as universal quantifiers that we progress through all traces in parallel, and that all traces have the same length. This is merely a simplification in the presentation, and one can build the trie in a sequential fashion for online monitoring by a slight modification of the presented algorithm.

Without using tries, our monitoring algorithm was based on instantiating the deterministic monitor template \(\mathcal {M}_\varphi \) with tuples of traces. Now, we instantiate \(\mathcal {M}_\varphi \) with tuples of tries. Initially, we only have to create the single instance having the root of our trie.

figure d

The trie-based algorithm has much in common with its previously discussed trace-based counterpart. Initially, we have to build the deterministic monitor automaton \(\mathcal {M}_\varphi = (\Sigma _{\mathcal {V}}, Q, q_0, \delta , F)\). We instantiate the monitor with a fresh trie root \(\tau _0\). A mapping from trie instantiations to a state in \(\mathcal {M}_\varphi \) \(S: \mathcal {T}^n \rightarrow Q\) stores the current state of every active branch of the trie, stored in the set I. For each of the incoming traces, we provide an entry in a tuple of tries \({\tau }\), and each entry gets initialized to \(\tau _0\). During the run of our algorithm, these entries are updated such that they always correspond to the word built by the traces up to this point. For as long as there are traces left, which have not yet ended, and we have not yet detected a violation, we will proceed updating the entries in \(\mathbf {i}\) as follows. Having entry \(\tau \) and the correspond trace sequence proceeds with a, if \(\exists \tau ' \in \mathcal {T}. \tau \overset{a}{\longrightarrow } \tau '\), we update the entry to \(\tau '\); otherwise, we create such a child node of \(\tau \) (add_child in line 8). Creating a new node in the trie always occurs when the prefix of the incoming trace starts to differ from already seen prefixes. After having moved one step in our traces sequences, we have to reflect this step in our trie structure, in order for the trie-instantiated automata to correctly monitor the new propositions. As a trie node can branch to multiple child nodes, each monitor instantiation is replaced by the set of instantiations, where all possible child combinations of the different assigned tries are existent (update of I in line 11). Afterward, we update S in the same way as in Algorithm 2; thus, we omit algorithmic details here. If a violation is detected here, that is, there is no transition in the monitor corresponding to \(\mathbf {i}\), we will return the corresponding counterexample as a tuple of traces, as those can get reconstructed by stepping upwards in the tries of \(\mathbf {i}\). If the traces end, we check if every open branch \(\mathbf {i} \in I\) is in an accepting state.

5 Evaluation

In the following, we evaluate the new version of \(\text {RVHyper}\), especially the novel trace storage optimization. We use several benchmarks: an encoder that guarantees a Hamming distance of 2, violations of non-interference on randomly generated traces, and a symmetry property on an implementation of the Bakery protocol. As an example how \(\text {RVHyper}\) can be used outside security runtime verification, we give a case study on detecting spurious dependencies in hardware designs.

5.1 Error correcting codes

We monitored whether an encoder preserves a Hamming distance of 2. We randomly built traces of length 50. In each position of the trace, the corresponding bit had a 1% chance to be flipped. The specification can be encoded as the following \(\text {HyperLTL}\) formula [26]:

$$\begin{aligned}&\forall \pi \pi '. (\diamondsuit (i_\pi \nleftrightarrow i_{\pi '}) \rightarrow ((o_\pi \leftrightarrow o_{\pi '}) \\ {\mathcal {U}} ((o_{\pi } \nleftrightarrow o_{\pi '})&\quad \wedge \bigcirc ((o_\pi \leftrightarrow o_{\pi '}) {\mathcal {U}} (o_{\pi } \nleftrightarrow o_{\pi '}))))). \end{aligned}$$

The right plot of Fig. 2 shows the results of our experiments. We compared the naive monitoring approach to different combinations of \(\text {RVHyper}\)’s optimizations. The specification analysis returns in under one second with the result that the formula is symmetric and reflexive. Hence, as expected, this preprocessing step has a major impact on the running time of the monitoring process as more than half of the, in general necessary, monitor instantiations can be omitted. A combination of the specification and trace analysis performs nearly equally well as naively storing the traces in our trie data structure. Combining the trie data structure with the specification analysis performs best and results in a tremendous speedup compared to the naive approach.

Fig. 2
figure 2

Left: monitoring of black box circuits (mux example). Right: hamming-distance preserving encoder; runtime comparison of naive monitoring approach with different optimizations and a combination thereof

5.2 Checking non-interference

Table 1 Non-interference benchmark: monitored 2000 traces of length 50 with an increasing input size

Non-interference [33] is an important information flow policy demanding that an observer of a system cannot infer any high security input of a system by observing only low security input and output. Formally, we specify that all low security outputs \(\mathbf {o}^{\mathrm{low}}\) have to be equal on all system executions as long as the low security inputs \(\mathbf {i}^{\mathrm{low}}\) of those executions are the same: \(\forall \pi , \pi ' \mathpunct {.}({\mathbf {o}^{\mathrm{low}}_\pi } \leftrightarrow {\mathbf {o}^{\mathrm{low}}_{\pi '}}) {\mathcal {W}} ({\mathbf {i}^{\mathrm{low}}_\pi } \nleftrightarrow {\mathbf {i}^{\mathrm{low}}_{\pi '}}).\) This class of benchmarks has previously been used to evaluate RVHyper [23]. We repeated the experiments, to show that using the trie data structure is a valid optimization. The results are depicted in Table 1. We chose a trace length of 50 and monitored non-interference on 2000 randomly generated traces, where we distinguish between an input range of 8 to 64 bits. The results show that the trie optimization has an enormous impact compared to a naive approach that solely relies on the specification analysis. As expected, the difference in runtime is especially high on experiments where traces collapse heavily in the trie data structure, i.e., producing almost no instances that must be considered during the monitoring process.

5.3 Symmetry in mutual exclusion protocols

In this benchmark (introduced as a case study in [26]), we monitor whether a Verilog implementation of the Bakery protocol [31] from the VIS verification benchmark satisfies a symmetry property. Symmetry violations indicate that certain clients are privileged. The Bakery protocol is a classical protocol implementing mutual exclusion, working as follows: Every process that wishes to access a critical resource draws a ticket, which is consecutively numbered. The process with the smallest number may access the resource first. If two processes draw a ticket concurrently, i.e., obtaining the same number, the process with the smaller process ID may access the resource first. We monitored the following HyperLTL formula [26]:

$$\begin{aligned}&\forall \pi . \forall \pi '. \square (\text {sym}(\text {select}_\pi ,\text {select}_{\pi '}) \wedge \text {pause}_\pi = \text {pause}_{\pi '})\\&\quad \rightarrow \square (\text {pc}(0)_\pi = \text {pc}(1)_{\pi '} \wedge \text {pc}(1)_\pi = \text {pc}(0)_{\pi '}) , \end{aligned}$$

where \(\text {select}\) indicates the process ID that runs in the next step and \(\text {pause}\) indicates whether the step is stuttering. Each process i has a program counter \(\text {pc}(i)\) and when process i is selected, \(\text {pc}(i)\) is executed. \(\text {sym}(\text {select}_\pi ,\text {select}_{\pi '})\) states that process 0 is selected on trace \(\pi \) and process 1 is selected on trace \(\pi '\). Unsurprisingly, the implementation violates the specification, as it is provably impossible to implement a mutual exclusion protocol that is entirely symmetric [32]. Figure 3 shows the results of our experiment. In this benchmark, we can observe that the language inclusion check, on which the trace optimization is based on, produces an overhead during the monitoring. Since the traces differ a lot, the trace analysis cannot prune enough traces to be valuable. As there are only a few instances (in this case 4), the trie optimization outperforms the previous version of \(\text {RVHyper}\) massively on such a low instance count. The specification analysis, however, is always a valuable optimization.

Fig. 3
figure 3

Experiment of monitoring symmetry on an implementation of the Bakery protocol

5.4 Case study: detecting spurious dependencies in hardware designs

While HyperLTL has been applied to a range of domains, including security and information flow properties, we focus in the following on a classical verification problem, the independence of signals in hardware designs. We demonstrate how \(\text {RVHyper}\) can automatically detect such dependencies from traces generated from hardware designs.

Input and output The input to \(\text {RVHyper}\) is a set of traces and a \(\text {HyperLTL}\) formula. For the following experiments, we generate a set of traces from the Verilog description of several example circuits by random simulation. If a set of traces violates the specification, \(\text {RVHyper}\) returns a counterexample.

Specification We consider the problem of detecting whether input signals influence output signals in hardware designs. We write \(\mathbf {i} \not \leadsto \mathbf {o}\) to denote that the inputs \(\mathbf {i}\) do not influence the outputs \(\mathbf {o}\). Formally, we specify this property as the following \(\text {HyperLTL}\) formula:

$$\begin{aligned} \forall \pi _1 \forall \pi _2 \mathpunct {.}(\mathbf {o}_{\pi _1} = \mathbf {o}_{\pi _2}) {\mathcal {W}} (\overline{\mathbf {i}}_{\pi _1} \ne \overline{\mathbf {i}}_{\pi _2}), \end{aligned}$$

where \(\overline{\mathbf {i}}\) denotes all inputs except \(\mathbf {i}\). Intuitively, the formula asserts that for every two pairs of execution traces \((\pi _1,\pi _2)\), the value of \(\mathbf {o}\) has to be the same until there is a difference between \(\pi _1\) and \(\pi _2\) in the input vector \(\overline{\mathbf {i}}\), i.e., the inputs on which \(\mathbf {o}\) may depend.

Sample hardware designs We apply \(\text {RVHyper}\) to traces generated from the following hardware designs. Note that, since \(\text {RVHyper}\) observes traces and treats the system that generates the traces as a black box, the performance of \(\text {RVHyper}\) does not depend on the size of the circuit.

Example 6

(xor) As a first example, consider the xor function \(\mathbf {o} = \mathbf {i} \oplus \mathbf {i}'\). In the corresponding circuit, every jth output bit \(o_j\) is only influenced by the jth input bits \(i_j\) and \(i'_j\).

Fig. 4
figure 4

mux circuit with black box

Fig. 5
figure 5

Verilog description of Example 8 (counter)

Table 2 Results of \(\text {RVHyper}\) on traces generated from circuit instances

Example 7

(mux) This example circuit is depicted in Fig. 4. There is a black box combinatorial circuit, guarded by a multiplexer that selects between the two input vectors \(\mathbf {i}\) and \(\mathbf {i}'\) and an inverse multiplexer that forwards the output of the black box either toward \(\mathbf {o}\) or \(\mathbf {o}'\). Despite there being a syntactic dependency between \(\mathbf {o}\) and \(\mathbf {i}'\), there is no semantic dependency, i.e., the output \(\mathbf {o}\) does solely depend on \(\mathbf {i}\) and the selector signal.

When using the same example, but with a sequential circuit as black box, there may be information flow from the input vector \(\mathbf {i}'\) to the output vector \(\mathbf {o}\) because the state of the latches may depend on it. We construct such a circuit that leaks information about \(\mathbf {i}'\) via its internal state.

The left part of Fig. 2 shows the total runtime of \(\text {RVHyper}\) with the different optimizations and a combination thereof. As observed in our previous experiments, the specification analysis, if applicable as in this case, is a valuable optimization consistently reducing the runtime and does so also when combined with the trace analysis. As expected, the runtime is halved by exploiting symmetry and reflexivity in the formula. From the plot, we can also infer that the trace analysis is effective in a context with a majority of redundant traces. For such a highly redundant setup, the trace analysis reduces the overall runtime of the monitoring algorithm by several magnitudes. With a decrease in similarity and redundancy in the traces, the positive effect of the trace analysis steadily decreases up until the overhead of the trace analysis itself gets noticeable. The decrease in runtime for configurations without trace analysis, which comes with reduced traces similarity, is explained by the fact that the more the input of the monitored traces is different, the earlier trace tuples can get pruned as they satisfy the specification and thereby reduce the computational burden of the algorithm. This is also the reason why the configurations with trace analysis show decreasing runtime behavior again as soon as the aforementioned effects dominate the runtime characteristics of the monitoring approach. In contrast to that, the trie optimization provides a stable improvement on the running time.

Example 8

(counter) Our last example is a binary counter with two input control bits \(\text {incr}\) and \(\text {decr}\) that increments and decrements the counter. The corresponding Verilog design is shown in Fig. 5. The counter has a single output, namely a signal that is set to one when the counter value overflows. Both inputs influence the output, but timing of the overflow depends on the number of counter bits.

Results The results of multiple random simulations are given in Table 2. Even the previous version of \(\text {RVHyper}\) was able to scale up to thousands of input traces with millions of monitor instantiations. The novel implemented optimization of \(\text {RVHyper}\), i.e., storing the traces in a prefix tree data structure combined with our specification analysis, results in a remarkable speedup. Particularly interesting is the reduction in the number of instances in the counterexample. As there is only one input, the traces collapse in our trie data structure. For the two instances, where the property is satisfied (xor and mux), \(\text {RVHyper}\) has not found a violation for any of the runs. For instance, where the property is violated, \(\text {RVHyper}\) was able to find counterexamples.

6 Conclusion

\(\text {RVHyper}\) monitors a running system for violations of a HyperLTL specification. We have introduced a novel trace storage optimization, based on a prefix tree data structure, to existing optimizations implemented in \(\text {RVHyper}\).

We demonstrated the impact of the optimizations on \(\text {RVHyper}\)s performance on several benchmarks of runtime verification problems. By providing a use case on how \(\text {RVHyper}\) can be used to detect spurious dependencies in hardware design, we showed how \(\text {RVHyper}\) can be used outside of classical security monitoring problems. The functionality of \(\text {RVHyper}\) thus complements model checking tools for HyperLTL, like MCHyper [26], tools for satisfiability checking, like EAHyper [22], and tools for synthesis, like BoSyHyper [21].

\(\text {RVHyper}\) is in particular useful during the development of a \(\text {HyperLTL}\) specification, where it can be used to check the HyperLTL formula on sample traces without the need for a complete model. Based on the feedback of the tool, the user can refine the HyperLTL formula until it captures the intended policy.

In our current approach, the trace analysis and the trie representation are separate optimizations that cannot be applied at the same time. The integration of the two optimization is an interesting challenge for future work.