Concurrent Leakage Variants (NuSMV)¶
Description of the Case Study¶
The concurrent leakage suite models how different schedulers and input manipulations influence information flow between three
processes that communicate via shared Boolean registers x and y. The attacker observes the outputs generated by the
second process. We analyse three variants: a baseline with two processes, a three-process refinement that preserves security,
and a buggy three-process model where the third process forces low inputs to leak the secret. All instances are encoded as
flattened NuSMV models that explicitly capture the scheduler and loop counters.
The NuSMV model(s)¶
MODULE main
VAR
-- globals (unchanged)
in_HIGH : boolean; -- in_HIGH-value
LOW : boolean; -- low-value
var_X : boolean; -- X
var_Y : boolean; -- Y
SCHEDULE : 0..1; -- 0: p1 moves, 1: p2 moves
ID_one : 0..2;
ID_two : 0..2;
ID_three : 0..2;
-- halt : boolean;
-- flattened state for proc1: programs(ID_one, SCHEDULE, LOW, in_HIGH, var_X, var_Y, 0)
proc1_line : 0..8;
proc1_loop_count : 0..2;
-- flattened state for proc2: programs(ID_two, SCHEDULE, LOW, in_HIGH, var_X, var_Y, 6)
proc2_line : 0..8;
proc2_loop_count : 0..2;
ASSIGN
----------------------------------------------------------------
-- IDs (as in your original)
----------------------------------------------------------------
init(ID_one) := 0;
next(ID_one) := 0;
init(ID_two) := 1;
next(ID_two) := 1;
----------------------------------------------------------------
-- inputs / other globals (updated references)
----------------------------------------------------------------
init(in_HIGH) := {TRUE, FALSE};
next(in_HIGH) :=
case
TRUE : in_HIGH;
esac;
init(LOW) := FALSE;
next(LOW) :=
case
TRUE : LOW;
esac;
init(var_X) := FALSE;
next(var_X) :=
case
(proc1_line = 0) : {FALSE};
(proc1_line = 2 | proc1_line = 5) : {TRUE};
TRUE : var_X;
esac;
init(var_Y) := FALSE;
next(var_Y) :=
case
(proc1_line = 0) : {FALSE};
(proc1_line = 3 | proc1_line = 3) : {TRUE}; -- (same as your original)
TRUE : var_Y;
esac;
init(SCHEDULE) := {0,1};
next(SCHEDULE) :=
case
(proc1_loop_count = 2) : 1;
(proc2_loop_count = 2) : 0;
TRUE : {0,1};
esac;
-- init(halt) := FALSE;
-- next(halt) :=
-- case
-- ((proc1_loop_count = 2) & (proc2_loop_count = 2)) : TRUE;
-- TRUE : halt;
-- esac;
----------------------------------------------------------------
-- proc1: programs(ID_one, SCHEDULE, LOW, in_HIGH, var_X, var_Y, 0) — flattened
----------------------------------------------------------------
init(proc1_loop_count) := 0;
next(proc1_loop_count) :=
case
(proc1_loop_count = 2) : 2;
(ID_one = 0 & proc1_line = 0 & (SCHEDULE = ID_one)) : proc1_loop_count + 1;
(ID_one = 1 & proc1_line = 6 & (SCHEDULE = ID_one)) : proc1_loop_count + 1;
TRUE : proc1_loop_count;
esac;
init(proc1_line) := 0;
next(proc1_line) :=
case
(!(SCHEDULE = ID_one)) : proc1_line;
-- program1
(proc1_line = 0) : {1}; -- x=0, y=0
(proc1_line = 1 & in_HIGH = LOW) : {2}; -- if (h == l)
(proc1_line = 2) : {3}; -- x=1
(proc1_line = 3) : {0}; -- y=1
(proc1_line = 1 & !(in_HIGH = LOW)) : {4};
(proc1_line = 4) : {5}; -- y=1
(proc1_line = 5) : {0}; -- x=1
-- program2
(proc1_line = 6) : {7};
(proc1_line = 7) : {8}; -- output x
(proc1_line = 8) : {6}; -- output y
TRUE : proc1_line;
esac;
----------------------------------------------------------------
-- proc2: programs(ID_two, SCHEDULE, LOW, in_HIGH, var_X, var_Y, 6) — flattened
----------------------------------------------------------------
init(proc2_loop_count) := 0;
next(proc2_loop_count) :=
case
(proc2_loop_count = 2) : 2;
(ID_two = 0 & proc2_line = 0 & (SCHEDULE = ID_two)) : proc2_loop_count + 1;
(ID_two = 1 & proc2_line = 6 & (SCHEDULE = ID_two)) : proc2_loop_count + 1;
TRUE : proc2_loop_count;
esac;
init(proc2_line) := 6;
next(proc2_line) :=
case
(!(SCHEDULE = ID_two)) : proc2_line;
-- program1
(proc2_line = 0) : {1}; -- x=0, y=0
(proc2_line = 1 & in_HIGH = LOW) : {2}; -- if (h == l)
(proc2_line = 2) : {3}; -- x=1
(proc2_line = 3) : {0}; -- y=1
(proc2_line = 1 & !(in_HIGH = LOW)) : {4};
(proc2_line = 4) : {5}; -- y=1
(proc2_line = 5) : {0}; -- x=1
-- program2
(proc2_line = 6) : {7};
(proc2_line = 7) : {8}; -- output x
(proc2_line = 8) : {6}; -- output y
TRUE : proc2_line;
esac;
DEFINE
-- observables (updated to flattened names)
obs_X_is_zero := (proc2_line = 7) & (in_HIGH = FALSE);
obs_X_is_one := (proc2_line = 7) & (in_HIGH = TRUE);
obs_Y_is_zero := (proc2_line = 8) & (in_HIGH = FALSE);
obs_Y_is_one := (proc2_line = 8) & (in_HIGH = TRUE);
-- halting condition
halt := ((proc1_line=8) & (proc2_line=8));
MODULE main
VAR
-- top-level shared state
in_HIGH : boolean; -- in_HIGH-value
in_LOW : boolean; -- in_LOW-value
var_X : boolean; -- X
var_Y : boolean; -- Y
SCHEDULE : 0..2; -- 0:p1 moves, 1:p2 moves, 2:p3 moves
ID_one : 0..2;
ID_two : 0..2;
ID_three : 0..2;
-- inlined programs(ID_one, SCHEDULE, ..., initline=1) as proc1
proc1_line : 0..12;
proc1_loop_count : 0..2;
-- inlined programs(ID_two, SCHEDULE, ..., initline=6) as proc2
proc2_line : 0..12;
proc2_loop_count : 0..2;
-- inlined programs(ID_three, SCHEDULE, ..., initline=9) as proc3
proc3_line : 0..12;
proc3_loop_count : 0..2;
ASSIGN
-- IDs are fixed to 0,1,2
init(ID_one) := 0;
next(ID_one) := 0;
init(ID_two) := 1;
next(ID_two) := 1;
init(ID_three) := 2;
next(ID_three) := 2;
-- inputs toggled by proc3
init(in_HIGH) := FALSE;
next(in_HIGH) :=
case
(proc3_line = 10) : {TRUE, FALSE};
TRUE : in_HIGH;
esac;
init(in_LOW) := FALSE;
next(in_LOW) :=
case
(proc3_line = 11) : {TRUE, FALSE};
TRUE : in_LOW;
esac;
-- X updated by proc1
init(var_X) := FALSE;
next(var_X) :=
case
(proc1_line = 0) : {FALSE};
(proc1_line = 2 | proc1_line = 5) : {TRUE};
TRUE : var_X;
esac;
-- Y updated by proc1
init(var_Y) := FALSE;
next(var_Y) :=
case
(proc1_line = 0) : {FALSE};
(proc1_line = 3 | proc1_line = 4) : {TRUE};
TRUE : var_Y;
esac;
-- round-robin-ish schedule updates after each program finishes 2 loops
init(SCHEDULE) := {0, 1, 2};
next(SCHEDULE) :=
case
(proc1_loop_count = 2) : {1, 2};
(proc2_loop_count = 2) : {0, 2};
(proc3_loop_count = 2) : {0, 1};
TRUE : SCHEDULE;
esac;
----------------------------------------------------------------------------
-- Inlined programs(ID_one, SCHEDULE, in_LOW, in_HIGH, var_X, var_Y, initline=1)
----------------------------------------------------------------------------
init(proc1_loop_count) := 0;
next(proc1_loop_count) :=
case
(proc1_loop_count = 2) : 2;
(proc1_line = 0 & (SCHEDULE = ID_one)) : proc1_loop_count + 1;
TRUE : proc1_loop_count;
esac;
init(proc1_line) := 1;
next(proc1_line) :=
case
-- scheduler gate: if not this program’s turn, it stutters
!(SCHEDULE = ID_one) : proc1_line;
-- program 1 (lines 0..5)
(proc1_line = 0) : {1}; -- x=0, y=0
(proc1_line = 1 & (in_HIGH = in_LOW)) : {2}; -- if (h == l)
(proc1_line = 2) : {3}; -- x=1
(proc1_line = 3) : {0}; -- y=1
(proc1_line = 1 & !(in_HIGH = in_LOW)) : {4};
(proc1_line = 4) : {5}; -- y=1
(proc1_line = 5) : {0}; -- x=1
-- program 2 (lines 6..8)
(proc1_line = 6) : {7};
(proc1_line = 7) : {8}; -- output x
(proc1_line = 8) : {6}; -- output y
-- program 3 (lines 9..11)
(proc1_line = 9) : {10};
(proc1_line = 10) : {11}; -- in_HIGH := {0,1} at top-level
(proc1_line = 11) : {9}; -- in_LOW := {0,1} at top-level
TRUE : proc1_line;
esac;
----------------------------------------------------------------------------
-- Inlined programs(ID_two, SCHEDULE, in_LOW, in_HIGH, var_X, var_Y, initline=6)
----------------------------------------------------------------------------
init(proc2_loop_count) := 0;
next(proc2_loop_count) :=
case
(proc2_loop_count = 2) : 2;
(proc2_line = 6 & (SCHEDULE = ID_two)) : proc2_loop_count + 1;
TRUE : proc2_loop_count;
esac;
init(proc2_line) := 6;
next(proc2_line) :=
case
!(SCHEDULE = ID_two) : proc2_line;
(proc2_line = 0) : {1};
(proc2_line = 1 & (in_HIGH = in_LOW)) : {2};
(proc2_line = 2) : {3};
(proc2_line = 3) : {0};
(proc2_line = 1 & !(in_HIGH = in_LOW)) : {4};
(proc2_line = 4) : {5};
(proc2_line = 5) : {0};
(proc2_line = 6) : {7};
(proc2_line = 7) : {8};
(proc2_line = 8) : {6};
(proc2_line = 9) : {10};
(proc2_line = 10) : {11};
(proc2_line = 11) : {9};
TRUE : proc2_line;
esac;
----------------------------------------------------------------------------
-- Inlined programs(ID_three, SCHEDULE, in_LOW, in_HIGH, var_X, var_Y, initline=9)
----------------------------------------------------------------------------
init(proc3_loop_count) := 0;
next(proc3_loop_count) :=
case
(proc3_loop_count = 2) : 2;
(proc3_line = 9 & (SCHEDULE = ID_three)) : proc3_loop_count + 1;
TRUE : proc3_loop_count;
esac;
init(proc3_line) := 9;
next(proc3_line) :=
case
!(SCHEDULE = ID_three) : proc3_line;
(proc3_line = 0) : {1};
(proc3_line = 1 & (in_HIGH = in_LOW)) : {2};
(proc3_line = 2) : {3};
(proc3_line = 3) : {0};
(proc3_line = 1 & !(in_HIGH = in_LOW)) : {4};
(proc3_line = 4) : {5};
(proc3_line = 5) : {0};
(proc3_line = 6) : {7};
(proc3_line = 7) : {8};
(proc3_line = 8) : {6};
(proc3_line = 9) : {10};
(proc3_line = 10) : {11};
(proc3_line = 11) : {9};
TRUE : proc3_line;
esac;
DEFINE
-- observables (now refer to inlined proc2 state)
obs_X_is_zero := ((proc2_line = 7) & (var_X = FALSE));
obs_X_is_one := ((proc2_line = 7) & (var_X = TRUE));
obs_Y_is_zero := ((proc2_line = 8) & (var_Y = FALSE));
obs_Y_is_one := ((proc2_line = 8) & (var_Y = TRUE));
-- global halt: each inlined program finished two loops
halt := (proc1_loop_count = 2 & proc2_loop_count = 2 & proc3_loop_count = 2);
MODULE main
VAR
-- top-level shared state
HIGH : boolean; -- high-value
LOW : boolean; -- low-value
var_X : boolean; -- X
var_Y : boolean; -- Y
SCHEDULE : 0..2; -- 0:p1 moves, 1:p2 moves, 2:p3 moves
ID_one : 0..2;
ID_two : 0..2;
ID_three : 0..2;
-- inlined programs(ID_one, SCHEDULE, LOW, HIGH, var_X, var_Y, initline=1) as proc1
proc1_line : 0..12;
-- inlined programs(ID_two, SCHEDULE, LOW, HIGH, var_X, var_Y, initline=6) as proc2
proc2_line : 0..12;
-- inlined programs(ID_three, SCHEDULE, LOW, HIGH, var_X, var_Y, initline=9) as proc3
proc3_line : 0..12;
ASSIGN
-- IDs are fixed
init(ID_one) := 0;
next(ID_one) := 0;
init(ID_two) := 1;
next(ID_two) := 1;
init(ID_three) := 2;
next(ID_three) := 2;
-- inputs toggled by proc3
init(HIGH) := FALSE;
next(HIGH) :=
case
(proc3_line = 10) : {TRUE, FALSE};
TRUE : HIGH;
esac;
init(LOW) := FALSE;
next(LOW) :=
case
(proc3_line = 11) : {TRUE}; -- sequence LOW: 0 -> 1
TRUE : LOW;
esac;
-- X/Y updated by proc1
init(var_X) := FALSE;
next(var_X) :=
case
(proc1_line = 0) : {FALSE};
(proc1_line = 2 | proc1_line = 5) : {TRUE};
TRUE : var_X;
esac;
init(var_Y) := FALSE;
next(var_Y) :=
case
(proc1_line = 0) : {FALSE};
(proc1_line = 3 | proc1_line = 4) : {TRUE};
TRUE : var_Y;
esac;
--------------------------------------------------------------------------
-- Inlined programs(..., initline=1) -> proc1_line
--------------------------------------------------------------------------
init(proc1_line) := 1;
next(proc1_line) :=
case
-- scheduler gate: if not this program’s turn, it stutters
!(SCHEDULE = ID_one) : proc1_line;
-- program1 (lines 0..5)
(proc1_line = 0) : {1}; -- x=0, y=0
(proc1_line = 1 & (HIGH = LOW)) : {2}; -- if (h == l)
(proc1_line = 2) : {3}; -- x=1
(proc1_line = 3) : {0}; -- y=1
(proc1_line = 1 & !(HIGH = LOW)) : {4};
(proc1_line = 4) : {5}; -- y=1
(proc1_line = 5) : {0}; -- x=1
-- program2 (lines 6..8)
(proc1_line = 6) : {7};
(proc1_line = 7) : {8}; -- output x
(proc1_line = 8) : {6}; -- output y
-- program3 (lines 9..11)
(proc1_line = 9) : {10};
(proc1_line = 10) : {11}; -- HIGH := {0,1} at top level
(proc1_line = 11) : {9}; -- LOW := 1 at top level (per your case)
TRUE : proc1_line;
esac;
--------------------------------------------------------------------------
-- Inlined programs(..., initline=6) -> proc2_line
--------------------------------------------------------------------------
init(proc2_line) := 6;
next(proc2_line) :=
case
!(SCHEDULE = ID_two) : proc2_line;
(proc2_line = 0) : {1};
(proc2_line = 1 & (HIGH = LOW)) : {2};
(proc2_line = 2) : {3};
(proc2_line = 3) : {0};
(proc2_line = 1 & !(HIGH = LOW)) : {4};
(proc2_line = 4) : {5};
(proc2_line = 5) : {0};
(proc2_line = 6) : {7};
(proc2_line = 7) : {8}; -- output x
(proc2_line = 8) : {6}; -- output y
(proc2_line = 9) : {10};
(proc2_line = 10) : {11};
(proc2_line = 11) : {9};
TRUE : proc2_line;
esac;
--------------------------------------------------------------------------
-- Inlined programs(..., initline=9) -> proc3_line
--------------------------------------------------------------------------
init(proc3_line) := 9;
next(proc3_line) :=
case
!(SCHEDULE = ID_three) : proc3_line;
(proc3_line = 0) : {1};
(proc3_line = 1 & (HIGH = LOW)) : {2};
(proc3_line = 2) : {3};
(proc3_line = 3) : {0};
(proc3_line = 1 & !(HIGH = LOW)) : {4};
(proc3_line = 4) : {5};
(proc3_line = 5) : {0};
(proc3_line = 6) : {7};
(proc3_line = 7) : {8};
(proc3_line = 8) : {6};
(proc3_line = 9) : {10};
(proc3_line = 10) : {11}; -- HIGH := {0,1} at top level
(proc3_line = 11) : {9}; -- LOW := 1 at top level
TRUE : proc3_line;
esac;
DEFINE
-- the observables (now referencing inlined proc2)
obs_X_is_zero := ((proc2_line = 7) & (var_X = FALSE));
obs_X_is_one := ((proc2_line = 7) & (var_X = TRUE));
obs_Y_is_zero := ((proc2_line = 8) & (var_Y = FALSE));
obs_Y_is_one := ((proc2_line = 8) & (var_Y = TRUE));
-- halt condition from original DEFINE
halt := ((proc1_line = 5) & (proc2_line = 8) & (proc3_line = 11));
The HyperLTL formula¶
All variants are checked against the same observational determinism property. As long as the high and low inputs agree across
traces, the observable events obs_X_is_zero, obs_X_is_one, obs_Y_is_zero, and obs_Y_is_one must coincide. The buggy
model violates this requirement because the forced low input reveals the secret.
Forall A . Exists B . E t .
G(
(in_HIGH[A][t] = in_HIGH[B][t])
)
->
G(
(obs_X_is_zero[A][t] = obs_X_is_zero[B][t]) &
(obs_X_is_one[A][t] = obs_X_is_one[B][t]) &
(obs_Y_is_zero[A][t] = obs_Y_is_zero[B][t]) &
(obs_Y_is_one[A][t] = obs_Y_is_one[B][t])
)