Cache Timing Attack: Observational Determinism (NuSMV)¶
Description of the Case Study¶
The cache-attack benchmark models a three-stage protocol in which a victim process performs secret-dependent memory accesses, while an attacker observes cache misses to infer the sensitive branch. We focus on the flattened NuSMV encoding that explicitly unrolls the scheduler, counters, and control flow so every concrete interleaving is represented. The verification objective is to establish observational determinism, guaranteeing that public cache observations do not reveal differences between the high- and low-security inputs supplied to the protocol.
The NuSMV model¶
MODULE main
	VAR
		in_HIGH: boolean; -- in_HIGH-value
		in_LOW: boolean; -- in_LOW-value
		ID_one: 1..4;
		ID_two: 1..4;
		ID_three: 1..4;
		counter: 0..4;
		wait_p: 0..3;
		wait_n: 0..3;
		wait_mn: 0..7;
		-- flattened instances of programs(...)
    	proc1_PC : 0..16;   -- programs(ID_one,  in_LOW, in_HIGH,  1, counter, wait_n, wait_mn)
    	proc2_PC : 0..16;   -- programs(ID_two,  in_LOW, in_HIGH,  6, counter, wait_n, wait_mn)
    	proc3_PC : 0..16;   -- programs(ID_three,in_LOW, in_HIGH, 13, counter, wait_n, wait_mn)
	ASSIGN
		init(ID_one)   := 1;
		next(ID_one)   := 1;
		init(ID_two)   := 2;
		next(ID_two)   := 2;
		init(ID_three) := 3;
		next(ID_three) := 3;
		init(counter):= 0;
		next(counter):=
			case
				(proc2_PC=9 & (counter!=0)): counter - 1;
				(proc1_PC=3): 4;
				TRUE: counter;
			esac;
		init(wait_p):= 3;
		next(wait_p):= wait_p;
		init(wait_n):= 3;
		next(wait_n):=
			case
				((proc2_PC=7) & !(wait_n=0)): wait_n - 1;
				TRUE: wait_n;
			esac;
		init(wait_mn):= 7;
		next(wait_mn):=
			case
				((proc3_PC=14) & !(wait_mn=0)): wait_mn - 1;
				TRUE: wait_mn;
			esac;
		----------------------------------------------------------------
		-- inputs
		----------------------------------------------------------------
		init(in_HIGH) := {TRUE, FALSE};
		next(in_HIGH) := in_HIGH;
		init(in_LOW)  := {TRUE, FALSE};
		next(in_LOW)  := in_LOW;
		----------------------------------------------------------------
		-- proc1: programs(ID_one, in_LOW, in_HIGH, 1, counter, wait_n, wait_mn)
		----------------------------------------------------------------
		init(proc1_PC) := 1;
		next(proc1_PC) :=
			case
				-- program1
				(proc1_PC = 1)                          : 2;
				(proc1_PC = 2 &  (in_LOW = in_HIGH))    : 3;
				(proc1_PC = 2 & !(in_LOW = in_HIGH))    : 4;
				(proc1_PC = 3)                          : 3;
				(proc1_PC = 4)                          : 4;
				-- program2
				(proc1_PC = 6)                          : 7;
				(proc1_PC = 7 & wait_n != 0)            : 7;
				(proc1_PC = 7 & wait_n  = 0)            : 8;
				(proc1_PC = 8 & counter != 0)           : 9;
				(proc1_PC = 8 & counter  = 0)           : 10;
				(proc1_PC = 9 & counter  != 0)          : 9;
				(proc1_PC = 9 & counter   = 0)          : 10;
				(proc1_PC = 10)                         : 11;
				(proc1_PC = 11)                         : 11;
				-- program3
				(proc1_PC = 13)                         : 14;
				(proc1_PC = 14 & wait_mn != 0)          : 14;
				(proc1_PC = 14 & wait_mn  = 0)          : 15;
				(proc1_PC = 15)                         : 16;
				(proc1_PC = 16)                         : 16;
				TRUE : proc1_PC;
			esac;
		----------------------------------------------------------------
		-- proc2: programs(ID_two, in_LOW, in_HIGH, 6, counter, wait_n, wait_mn)
		----------------------------------------------------------------
		init(proc2_PC) := 6;
		next(proc2_PC) :=
			case
				-- program1
				(proc2_PC = 1)                          : 2;
				(proc2_PC = 2 &  (in_LOW = in_HIGH))    : 3;
				(proc2_PC = 2 & !(in_LOW = in_HIGH))    : 4;
				(proc2_PC = 3)                          : 3;
				(proc2_PC = 4)                          : 4;
				-- program2
				(proc2_PC = 6)                          : 7;
				(proc2_PC = 7 & wait_n != 0)            : 7;
				(proc2_PC = 7 & wait_n  = 0)            : 8;
				(proc2_PC = 8 & counter != 0)           : 9;
				(proc2_PC = 8 & counter  = 0)           : 10;
				(proc2_PC = 9 & counter  != 0)          : 9;
				(proc2_PC = 9 & counter   = 0)          : 10;
				(proc2_PC = 10)                         : 11;
				(proc2_PC = 11)                         : 11;
				-- program3
				(proc2_PC = 13)                         : 14;
				(proc2_PC = 14 & wait_mn != 0)          : 14;
				(proc2_PC = 14 & wait_mn  = 0)          : 15;
				(proc2_PC = 15)                         : 16;
				(proc2_PC = 16)                         : 16;
				TRUE : proc2_PC;
			esac;
		----------------------------------------------------------------
		-- proc3: programs(ID_three, in_LOW, in_HIGH, 13, counter, wait_n, wait_mn)
		----------------------------------------------------------------
		init(proc3_PC) := 13;
		next(proc3_PC) :=
			case
				-- program1
				(proc3_PC = 1)                          : 2;
				(proc3_PC = 2 &  (in_LOW = in_HIGH))    : 3;
				(proc3_PC = 2 & !(in_LOW = in_HIGH))    : 4;
				(proc3_PC = 3)                          : 3;
				(proc3_PC = 4)                          : 4;
				-- program2
				(proc3_PC = 6)                          : 7;
				(proc3_PC = 7 & wait_n != 0)            : 7;
				(proc3_PC = 7 & wait_n  = 0)            : 8;
				(proc3_PC = 8 & counter != 0)           : 9;
				(proc3_PC = 8 & counter  = 0)           : 10;
				(proc3_PC = 9 & counter  != 0)          : 9;
				(proc3_PC = 9 & counter   = 0)          : 10;
				(proc3_PC = 10)                         : 11;
				(proc3_PC = 11)                         : 11;
				-- program3
				(proc3_PC = 13)                         : 14;
				(proc3_PC = 14 & wait_mn != 0)          : 14;
				(proc3_PC = 14 & wait_mn  = 0)          : 15;
				(proc3_PC = 15)                         : 16;
				(proc3_PC = 16)                         : 16;
				TRUE : proc3_PC;
			esac;
	DEFINE
		halt           := (proc2_PC = 11 & proc3_PC = 16);
		obs_PRINT_ONE  := (proc2_PC = 11);
		obs_PRINT_ZERO := (proc3_PC = 16);
The HyperLTL formula¶
The property compares executions that agree on both high and low inputs across all time instants. Whenever the precondition holds,
the observations PRINT_ONE and PRINT_ZERO—representing cache-based outputs—must coincide for every timestamp, witnesses inclusive.
Forall A . Exists B . A t1 . E t2 .  
G(
    (in_HIGH[A][t1] = in_HIGH[B][t1]) &
    (in_LOW[A][t1] = in_LOW[B][t1])
)
-> 
G( 
    (obs_PRINT_ONE[A][t2] = obs_PRINT_ONE[B][t2]) &       
    (obs_PRINT_ZERO[A][t2] = obs_PRINT_ZERO[B][t2])
)