Skip to content

feat(kms/auth-eth): formal verification — Slither + Halmos + spec#689

Open
h4x3rotab wants to merge 2 commits into
foundryfrom
feat/auth-eth-formal-verification
Open

feat(kms/auth-eth): formal verification — Slither + Halmos + spec#689
h4x3rotab wants to merge 2 commits into
foundryfrom
feat/auth-eth-formal-verification

Conversation

@h4x3rotab
Copy link
Copy Markdown
Contributor

@h4x3rotab h4x3rotab commented May 19, 2026

Layers static analysis + a focused symbolic-execution suite on top of #252. Depends on #252 merging first — base is the foundry branch.

What's verified

  • Slither static analysis with project-tuned suppressions in slither.config.json. Baseline: 0 findings.
  • 14 single-call Halmos properties in test/{DstackApp,DstackKms}.symbolic.t.sol. Each check_* proves a property a unit test or bounded fuzz couldn't. Highlights:
    • check_IsAppAllowed_DelegatesFaithfully (benign-shape registered contract) + check_IsAppAllowed_PropagatesMockRevert (reverting registered contract) — the two delegation paths the spec requires
    • check_IsAppAllowed_Rejects{UnregisteredApp,UnknownOsImage} and check_IsKmsAllowed_Rejects{UnknownMr,UnknownDevice} — fully symbolic AppBootInfo, gates short-circuit correctly
    • check_DeployAndRegisterApp_PostState and _DefaultsTcbToFalse — factory post-state holds for any input combination including the conditional initial-state branches
    • check_Initialize_OnceOnly — verifies INV-3 (proxy initialized exactly once); both overloads revert for any input after setUp
    • Single-call companions for disableUpgrades and the initializer-defaults
  • 1 multi-call invariant in test/DstackAppInvariants.t.sol:
    • invariant_UpgradesDisabled_Monotonic — runs under halmos --invariant-depth 2, exhaustively explores all 2-call symbolic sequences across the contract's external surface from the disabled state. Verifies INV-1 (_upgradesDisabled monotonicity). Also passes at depth 3 (1024 paths, ~20s).
  • docs/specification.md — normative pre/post/frame/events/reverts for the full public surface, state invariants with verification status (INV-1 and INV-3 verified, others gap), trust model, adversarial scenarios, four open product-level questions for the team.
  • docs/formal-verification.md — the layered FV roadmap, the test inventory, and an explicit list of what we deliberately do not verify (with reasons).

What's intentionally out of scope (and why)

  • Per-function _OnlyOwner tests — fuzz-tests in symbolic clothing against OZ's well-tested onlyOwner modifier
  • Symbolic-string TCB byte-exactness — circular under Halmos's uninterpreted-keccak model
  • Universal quantification over the registered IAppAuth contract — Halmos instantiates one mock at a time; we bound the relevant shapes (faithful return; revert propagation) and the spec is explicit about treating the registered contract's output as untrusted downstream
  • Cross-transaction invariants INV-2 (owner-flow) and INV-6 (__gap zeroes) — gaps; would mirror the shape of the INV-1 invariant test

Open product-level questions

The specification ends with four decisions the team should pin down:

  1. Should renounceOwnership be overridden to revert?
  2. Should KMS track which appImplementation was current per app at deploy time?
  3. Should removeOsImageHash retroactively un-authorize already-running apps?
  4. Should gatewayAppId validate format?

Findings

  • DstackKms.registerApp is permissionless by design (confirmed-intentional, now in natspec). The two check_IsAppAllowed_* mock tests prove that an attacker who registers their own contract is still gated by the owner-controlled allowedOsImages whitelist and their contract's revert propagates rather than being silently mapped to a rejection.

How to verify

pipx install slither-analyzer halmos
cd kms/auth-eth
slither .                                                                  # 0 findings
halmos --contract DstackAppSymbolicTest                                    # 4 / 4
halmos --contract DstackKmsSymbolicTest                                    # 10 / 10
halmos --contract DstackAppInvariantsTest --invariant-depth 2              # 1 / 1
forge test --ffi                                                           # 47 / 47 (46 unit + 1 invariant)

🤖 Generated with Claude Code

@h4x3rotab h4x3rotab mentioned this pull request May 19, 2026
@h4x3rotab h4x3rotab force-pushed the feat/auth-eth-formal-verification branch from 42f518b to ff9845b Compare May 28, 2026 10:21
h4x3rotab and others added 2 commits May 28, 2026 12:09
Layers a verification stack on top of the Hardhat→Foundry migration:

- Slither static analysis with project-tuned suppressions
  (kms/auth-eth/slither.config.json): excludes naming-convention and
  solc-version detectors that hit OZ-idiom noise across our codebase,
  plus four per-line justified `// slither-disable-next-line` comments
  in contracts/DstackKms.sol (factory reentrancy-benign on
  deployAndRegisterApp's `new ERC1967Proxy`, named-return-forward
  unused-return on the IAppAuth delegation, two unindexed-event-address
  for backward compatibility with deployed log indexers). Baseline:
  0 findings.

- Halmos symbolic execution: 28 properties across
  test/{DstackApp,DstackKms}.symbolic.t.sol, each `check_*` function
  treats its arguments as symbolic. Owner-only mutations, isAppAllowed
  decision tables (including the byte-exact TCB compare), disableUpgrades
  monotonicity, factory atomicity + TCB flag propagation, both 5- and
  6-arg initializer overloads. All pass.

- docs/formal-verification.md is the layered roadmap (Slither → Halmos
  → optional Certora). SMTChecker (Layer 1) is deferred — forge's solc
  binaries lack z3 linkage and BMC alone gives zero useful output on
  OZ-upgradeable patterns. Halmos subsumes its coverage for our
  contracts.

Both tools run locally (pipx install). Intentionally not in CI —
symbolic execution is slow, solver-version-sensitive, and most valuable
as an interactive design check rather than a PR gate.

Findings worth review:
- DstackKms.registerApp is permissionless by design (no access control,
  only require !=0). Confirmed-intentional; the downstream
  allowedOsImages whitelist + delegated isAppAllowed gate authorization.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Normative pre/post/frame conditions, state invariants, cross-contract
assumptions, and adversarial scenarios for DstackKms + DstackApp,
independent of code. Each property cites the Halmos test that verifies
it; properties without a citation are explicit gaps.

Sections:
- Trust model (principals, environment, off-chain pipeline)
- Storage layout per contract with frame-condition rules
- Per-function pre/post/frame/events/reverts specs for the entire
  public surface
- Decision-table semantics for isAppAllowed
- State invariants INV-1 through INV-7 with verification status
- Adversarial scenarios: malicious registerApp + downstream gates,
  reentrancy structural-impossibility argument, gas-griefing,
  front-running, initializer selector collision
- Known verification gaps (cross-transaction invariants,
  universal-quantification over registered apps, KEVM bytecode-level)
- Open product-level questions for the team:
  1. Should renounceOwnership be overridden to revert?
  2. Should KMS track which appImplementation was current per app?
  3. Should removeOsImageHash retroactively un-authorize?
  4. Should gatewayAppId validate format?

Deliverable for any future external audit-firm engagement.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@h4x3rotab h4x3rotab force-pushed the feat/auth-eth-formal-verification branch from ff9845b to c1cd27b Compare May 28, 2026 12:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant