Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
7b33f50
Branch for ISA stepper. It's a bit scuffed atm, but it's there :)
RobinWebbers Oct 22, 2026
2321c11
Move stepper checks into proof-smt
windtf Oct 27, 2026
d44cb0b
Add a relational stepper
windtf Oct 27, 2026
b88f9e7
Load code from the ELF and skip the solver when values are known
windtf Oct 28, 2026
d1367c7
Check one ChaCha20 block
windtf Oct 28, 2026
acf57fb
Speed up fetching, registers, memory and jumps
windtf Oct 28, 2026
d1f3477
Check SHA-256 and BLAKE2b
windtf Oct 28, 2026
b916d25
Check the x25519 bench
windtf Oct 28, 2026
1950192
Check wolfSSL P-256 primitives at -O0 and -O3
windtf Oct 28, 2026
8aa5fc1
Use symfc's generic concrete
windtf Oct 28, 2026
ccf697a
Decode ELF code at compile time
windtf Oct 28, 2026
dcb549b
Pin symfc to stepper-sym-api
windtf Oct 29, 2026
d337e09
Refer to SymFC embeddings by name in annotations
windtf Oct 29, 2026
f965760
Update libsodium to 1.0.22
windtf Oct 30, 2026
3067a68
Update to the SymFC annotation format
windtf Oct 30, 2026
b871498
Use Z3 in CI
windtf Oct 30, 2026
a6b0080
Port the hardware proofs to SymFC
windtf Oct 30, 2026
98ea3b8
Share the shift embeddings and run CI on Bitwuzla
windtf Oct 30, 2026
bd7c3c1
Decide leaks with leakOf
windtf Oct 30, 2026
05ebdeb
Allow CI 90 minutes
windtf Oct 30, 2026
48d08be
Remove the legacy secrecy tracking
windtf Oct 30, 2026
1115d5f
Define Access as Applicative plus Comonad
windtf Oct 30, 2026
fa022d7
Run isaStep in the checker
windtf Oct 30, 2026
10334b1
Report stores into code
windtf Oct 30, 2026
0a78c3d
Check the ELF code tables against memory
windtf Oct 30, 2026
f131872
Report stores into code that wrap around address zero
windtf Oct 31, 2026
abec778
Prove that relStep keeps the invariant from a reached state
windtf Oct 31, 2026
3ba045c
Drop the two-run step proof that the invariant proof replaces
windtf Oct 31, 2026
bd5e3cb
Add a script that reruns the checker on the benchmark binaries
windtf Oct 31, 2026
1e27933
Merge remote-tracking branch 'origin/main' into isa-stepper-libsodium…
windtf Oct 31, 2026
3e41bcc
Point the evaluation script at proof/, where the merge moved the proofs
windtf Oct 31, 2026
3e6548d
Merge origin/main into isa-stepper
windtf Nov 3, 2026
b0a6a97
Pin symfc to the fix for diverging globals
windtf Nov 3, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/stack-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ on:
jobs:
build:
runs-on: ubuntu-latest
timeout-minutes: 60
timeout-minutes: 90

steps:
- name: Checkout code
Expand Down
Binary file modified benchmark/bench_blake2b
Binary file not shown.
Binary file modified benchmark/bench_chacha20
Binary file not shown.
Binary file modified benchmark/bench_sha256
Binary file not shown.
Binary file modified benchmark/bench_sodium_memcmp
Binary file not shown.
Binary file modified benchmark/bench_vuln_memcmp
Binary file not shown.
Binary file modified benchmark/bench_x25519
Binary file not shown.
106 changes: 106 additions & 0 deletions benchmark/eval.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
#!/usr/bin/env python3
"""Rerun the checker on the benchmark binaries and print its verdicts.

Each program proof under proof/Proof/SMT/Stepper passes only if the
checker returns the verdict its name states, so a green build is the result.
This script deletes those modules' compiled files so that GHC rebuilds them,
runs the build with Bitwuzla, and prints one row per checker run with its
time. GHC compares source hashes, so touching the sources would not do.

Usage: benchmark/eval.py [--bitwuzla PATH] [--csv FILE]
"""

import argparse
import csv
import os
import re
import shutil
import subprocess
import sys
from pathlib import Path

ROOT = Path(__file__).resolve().parent.parent
PROOF_SOURCES = ROOT / "proof"
CHECKER_PROOFS = {"Relational", "RelMem", "Invariant"}
PROGRAM_PROOFS = sorted(
path.relative_to(PROOF_SOURCES).with_suffix("")
for path in (PROOF_SOURCES / "Proof/SMT/Stepper").rglob("*.hs")
if path.stem not in CHECKER_PROOFS
)
COMPILED_SUFFIXES = [".hi", ".o", ".dyn_hi", ".dyn_o"]

VERDICTS = [
("NoLeak", "no leak"),
("Leaks", "leak"),
("OutOfFuel", "out of fuel"),
("OneStepShort", "out of fuel"),
]

COMPILING = re.compile(r"Compiling (Proof\.SMT\.Stepper\.\S+)")
EVALUATING = re.compile(r"SymFC evaluating '([^']+)'")
ELAPSED = re.compile(r"\*\*\* SymFC: Elapsed time: ([0-9.]+)")


def verdict_of(proof):
for marker, verdict in VERDICTS:
if marker in proof:
return verdict
return None


def run_build(bitwuzla):
env = dict(os.environ, SBV_Z3=bitwuzla, SBV_Z3_OPTIONS="--produce-models")
for build_dir in ROOT.glob(".stack-work/dist/*/*/build"):
for module in PROGRAM_PROOFS:
for suffix in COMPILED_SUFFIXES:
build_dir.joinpath(module).with_suffix(suffix).unlink(missing_ok=True)
build = subprocess.Popen(
["stack", "build", "--test", "--no-run-tests", "--force-dirty"],
cwd=ROOT,
env=env,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
text=True,
errors="replace",
)
rows = []
module = proof = None
for line in build.stdout:
if found := COMPILING.search(line):
module = found.group(1).removeprefix("Proof.SMT.Stepper.")
if module in CHECKER_PROOFS:
module = None
elif found := EVALUATING.search(line):
proof = found.group(1)
elif (found := ELAPSED.search(line)) and proof:
verdict = verdict_of(proof)
if verdict and module:
rows.append((module, proof, verdict, float(found.group(1))))
proof = None
return build.wait(), rows


def main():
parser = argparse.ArgumentParser(description=__doc__.splitlines()[0])
parser.add_argument("--bitwuzla", default=shutil.which("bitwuzla"))
parser.add_argument("--csv", type=Path)
args = parser.parse_args()
if not args.bitwuzla:
sys.exit("bitwuzla not found; pass --bitwuzla PATH")

status, rows = run_build(args.bitwuzla)
rows.sort()
width = max((len(proof) for _, proof, _, _ in rows), default=10)
for module, proof, verdict, seconds in rows:
print(f"{module:14} {proof:{width}} {verdict:12} {seconds:8.1f} s")
if args.csv:
with args.csv.open("w", newline="") as out:
writer = csv.writer(out)
writer.writerow(["program", "check", "verdict", "seconds"])
writer.writerows(rows)
if status != 0:
sys.exit("the build failed, so at least one verdict is not the one its proof states")


if __name__ == "__main__":
main()
2 changes: 1 addition & 1 deletion benchmark/libsodium
Submodule libsodium updated 264 files
Binary file modified benchmark/secure_memory.o
Binary file not shown.
45 changes: 39 additions & 6 deletions package.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ description: Please see the README on GitHub at <https://github.com/gith

# The modules listed under the smt-proof flag in the library discharge their
# properties through an SMT solver while GHC compiles them, so building them
# needs a solver on PATH and takes as long as the solver does. Pantomime goes through SBV, and SBV's Z3 backend is
# needs a solver on PATH and takes as long as the solver does. SymFC goes
# through SBV, and SBV's Z3 backend is
# pointed at a different binary with SBV_Z3; Z3 itself does not finish these in
# useful time, while bitwuzla discharges each in under a minute:
#
Expand Down Expand Up @@ -82,10 +83,12 @@ dependencies:
- exceptions
- entropy
- binary
- pantomime
- pantomime-clash
- pantomime-base
- sym-fc
- sym-fc-clash
- sym-fc-base
- template-haskell
- constraints
- comonad

ghc-options:
- -Wall
Expand All @@ -99,7 +102,7 @@ ghc-options:
- -Wredundant-constraints
- -fexpose-all-unfoldings
- -fplugin GHC.TypeLits.Normalise
- -fplugin Pantomime
- -fplugin SymFC

library:
source-dirs:
Expand All @@ -120,9 +123,26 @@ library:
- Proof.Memory.Cache4K.SMT.Array
- Proof.Memory.Cache4K.SMT.Axioms
- Proof.Memory.Cache4K.SMT.Coherent
- Proof.SMT.Logged
- Proof.SMT.Sanity
- Proof.Core.Functional.System
- Proof.SMT.Concrete
- Proof.SMT.Stepper
- Proof.SMT.Stepper.Blake2b
- Proof.SMT.Stepper.Chacha
- Proof.SMT.Stepper.X25519.Call
- Proof.SMT.Stepper.X25519.Cswap
- Proof.SMT.Stepper.X25519.Ladder
- Proof.SMT.Stepper.X25519.Mul
- Proof.SMT.Stepper.Memcmp
- Proof.SMT.Stepper.P256Call
- Proof.SMT.Stepper.P256Field
- Proof.SMT.Stepper.P256Multiply
- Proof.SMT.Stepper.P256Recode
- Proof.SMT.Stepper.RelMem
- Proof.SMT.Stepper.Relational
- Proof.SMT.Stepper.Invariant
- Proof.SMT.Stepper.Sha256
- Proof.SMT.Symbolic
cpp-options: -DSMT_PROOF
# Listed explicitly, with no automatic discovery: the solver modules above
# live in proof/ too, and must only be built with the smt-proof flag.
Expand Down Expand Up @@ -156,6 +176,7 @@ library:
- Proof.Core.Leakage.Driver
- Proof.Core.Leakage.Invariant
- Proof.Core.Leakage.Obligation
- Access
- Elf.ElfLoader
- Elf.Syscall
- HardwareSim
Expand All @@ -173,6 +194,18 @@ library:
- Pretty
- Core.RegFile
- Simulate
- Stepper
- Stepper.AddrMap
- Stepper.Concrete
- Stepper.ElfCode
- Stepper.Embedding
- Stepper.Memory
- Stepper.Rel
- Stepper.RelMem
- Stepper.RelRegs
- Stepper.RelState
- Stepper.Relational
- Stepper.Shift
- Types
- Util

Expand Down
2 changes: 1 addition & 1 deletion proof/Proof/Core/Functional/Invariant.hs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
-- * 'invCases' represents each case as a named list of conjuncts, so a
-- failing QuickCheck run can report which clause broke ('explain');
-- * 'invAt' is the identical predicate built from '&&' and '||' with no
-- lists, because Pantomime's evaluator diverges on recursion it cannot
-- lists, because SymFC's evaluator diverges on recursion it cannot
-- prove terminating -- even over a fully concrete list. Symbolic execution
-- uses this one.
--
Expand Down
Loading
Loading