Security: Information Flow
Write "the secret never reaches this pin" as two copies of the design with equal public inputs, find a key bit on a debug port, and keep the check from passing for nothing.
- Lesson
- F5.8
- Time
- 15 min
- Runs on
- SymbiYosys
- Check
- 4 questions
A crypto block's debug mux carried state and counters onto eight pins. A late change widened one internal bus and shifted a bit, so one select now carried the low bit of the key. Functional tests passed, because the debug port was not functional, and the only answer to "can the key reach the pins?" was a diagram.
Measured with smtbmc yices at depth 20. The solver chose keys 01 and 00: they differ in bit 0, the bit that leaks. Sampled at step 3, reported at 4.
Influence Is a Relation Between Runs
Read the diagram as text
- Public inputs: din, start, key_ld, dbg_sel: one wire to both copies
- k_a (anyconst): secret for copy A
- k_b (anyconst): secret for copy B
- sec_dbg copy A: the design, untouched
- sec_dbg copy B: the same design again
- assert(dbg_a == dbg_b): the sink that must not see the key
- dout_a, dout_b: the ciphertext: allowed to differ, so not asserted
- Public inputs → sec_dbg copy A
- Public inputs → sec_dbg copy B
- k_a (anyconst) → sec_dbg copy A
- k_b (anyconst) → sec_dbg copy B
- sec_dbg copy A → assert(dbg_a == dbg_b)
- sec_dbg copy B → assert(dbg_a == dbg_b)
- sec_dbg copy B → dout_a, dout_b
The property never mentions the key's value. It asks whether two runs that differ only in the key can be told apart at the sink.
The claim that the key never influences the debug port is not true or false of one run: no single trace shows influence. It is a statement about two runs. Take two copies of the design, give them every public input in common and different secrets, and assert that the debug outputs stay equal.
Here the copies share their input wires, which is the same as assuming the inputs equal every step. The two keys are anyconst, chosen once by the solver (F1.3). With the correct mux the equality gives BMC PASS to depth 20.
The Leak, Two Copies Stacked
| Sink | May depend on the key? | Property |
|---|---|---|
dbg, the debug port | no | assert(dbg_a == dbg_b) |
dout, the ciphertext | yes, by design | none; cover(dout_a != dout_b): Cover reached, reported step 3 |
| a checksum the spec lets out | only through that value | assume(sum_a == sum_b): an assumption on a derived value, reviewed as a declassification |
The cover on the allowed sink is the vacuity check: if the keys could not make the copies differ anywhere, every equality would pass for nothing.
With the shifted bus the run gives FAIL at reported step 4. The solver chose two keys that differ in bit 0, loaded them, and set the select to 2. Nothing else differs between the copies until that step.
This is a safety property: it has a failing step. It is also complete for non-interference at that sink: if no pair of runs differing only in the secret ever differs there, the sink carries nothing about the secret. A timing leak shows up the same way if the sink's valid signal is in the equality.
Proving It, and the Shortcut Tools Take
| Correct mux | Measured result | Time |
|---|---|---|
| bmc, depth 20 | BMC PASS to depth 20 | 555 ms |
| prove, k-induction | UNKNOWN at k = 4 (base case passed, induction step failed) | 538 ms |
| prove, abc pdr | Prove PASS (abc pdr) | 581 ms |
Engine smtbmc yices unless named; reset assumed in step 0.
k-induction fails because an unreachable start state may have the two copies already out of step in their counters. PDR closes it without help. The invariant it needs is that every key-free register is equal in both copies. Writing that as a helper is the manual route, but this engine refuses the hierarchical names (a.cnt) it would take.
Commercial security apps usually track a taint mark from the secret through the logic. That is a one-copy approximation of the two-copy definition, and it can flag paths where the value cannot actually leak. Two copies are the definition.
Asked in Interviews
| Question | Answer |
|---|---|
| What is information-flow verification? | Proving a secret cannot influence a sink: two copies, equal public inputs, different secrets, equal sinks. This is self-composition. |
| How is it a safety property? | The equality has a failing step and a trace. |
| What is taint tracking? | A one-copy approximation that follows a mark from the secret; the two-copy form is the definition. |
| What is declassification? | An allowed flow, written as an assumption on a derived value and reviewed as such. |
Try It
The rule in SVA
assert property (@(posedge clk) disable iff (rst) dbg_a == dbg_b);
cover property (@(posedge clk) !rst && dout_a != dout_b);
One equality per forbidden sink, one cover on an allowed one.
The composition as this engine runs it
module sec_top(input clk, rst, start, key_ld, input [7:0] din, input [1:0] dbg_sel);
(* anyconst *) reg [7:0] k_a, k_b; // two secrets, chosen once by the solver
wire [7:0] dout_a, dout_b, dbg_a, dbg_b;
sec_dbg a(.clk(clk), .rst(rst), .start(start), .key_ld(key_ld), .din(din), .key_in(k_a), .dbg_sel(dbg_sel), .dout(dout_a), .dbg(dbg_a));
sec_dbg b(.clk(clk), .rst(rst), .start(start), .key_ld(key_ld), .din(din), .key_in(k_b), .dbg_sel(dbg_sel), .dout(dout_b), .dbg(dbg_b));
`ifdef FORMAL
always @(posedge clk) begin
if ($initstate) begin assume(rst); end else begin assume(!rst); end
end
always @(posedge clk) if (!rst) begin
assert(dbg_a == dbg_b); // the sink must not see the secret
end
`endif
endmodule
Two instances, shared inputs, two anyconst keys.
prep -top sec_top flattens both copies into one model.[options]
mode bmc
depth 20
[engines]
smtbmc yices
[script]
read_verilog -formal -sv design.sv
prep -top sec_top
[files]
design.svCheck the bench is alive
Run the cover on
doutfirst: Cover reached, reported step 3. The keys can make the copies differ.Run the leak
Run as it is: FAIL at reported step 4. Find the step where the copies split and the keys' differing bit.
Mask the spare
Drive the spare select with zero: BMC PASS to depth 20.
Prove it
Switch to
mode prove: k = 4 gives UNKNOWN at k = 4 (base case passed, induction step failed);abc pdrgives Prove PASS (abc pdr).Break it on purpose
Add
always @(*) assume(k_a == k_b);and rerun the cover. Why does every equality now pass (F3.2)?
design.sv
props.sv
Use assert property (@(posedge clk) ...);. Leave blank for immediate assertions in the design.
Inputs are free unless constrained. Run to find a counterexample or check a proof.
Model: one positive-edge clock, binary data. Sampled history requires declaration-initialized variables; primary-input history is unsupported.
SBY summary and log
Check Yourself
Remember
- Information flow is a property of two runs: two copies, shared public inputs, different secrets, equal forbidden sinks.
- The leak has a failing step, so it is a safety check; a proof means the sink carries nothing about the secret.
- Cover an allowed sink to show the secrets can make a difference, or the equalities may pass for nothing.
- Taint tracking approximates the two-copy definition; declassification is an assumption to review.