Skip to content

SAT back end: asserted forall handed over from an ensures is very expensive #9387

Open

Description

@mkannwischer

In mldsa-native I am adding CaDiCaL as a second solver for some CBMC proofs. With CBMC 6.11.0, several top-level proofs fail with it on the 32 GB CI machines; for example, the ML-DSA-87 attempt_signature_generation proof needs 50 GB and almost an hour with CaDiCaL. The cause is a common contract pattern: one function's ensures bounds every element of an array, and the next function's requires asks for the same bound.

In CBMC, symex_assert skolemizes every asserted forall (rewrite_quantifiers), while the SAT back end expands assumed foralls over their constant range (eager_quantifier_instantiation). The requires check thus becomes one read at a symbolic index that the solver has to relate to every expanded instance of the ensures.

Reproducer

#include <stdint.h>
typedef int32_t mat[4][4][256];

#define BOUND(m, i, j, k)                                       \
  __CPROVER_forall { uint32_t i; i < 4 ==>                      \
  __CPROVER_forall { uint32_t j; j < 4 ==>                      \
  __CPROVER_forall { uint32_t k; k < 256 ==>                    \
    (0 <= (*m)[i][j][k] && (*m)[i][j][k] < 8380417) } } }

void produce(mat *m)
__CPROVER_requires(__CPROVER_is_fresh(m, sizeof(mat)))
__CPROVER_assigns(__CPROVER_object_whole(m))
__CPROVER_ensures(BOUND(m, i, j, k));

void consume(mat *m)
__CPROVER_requires(__CPROVER_is_fresh(m, sizeof(mat)))
__CPROVER_requires(BOUND(m, x, y, z));

void target(mat *m)
__CPROVER_requires(__CPROVER_is_fresh(m, sizeof(mat)))
__CPROVER_assigns(__CPROVER_object_whole(m))
{
  produce(m);
  consume(m);
}

void harness(void) { mat *m; target(m); }
goto-cc --function harness repro.c -o a.goto
goto-instrument --add-library a.goto b.goto
goto-instrument --dfcc harness --enforce-contract target \
  --replace-call-with-contract produce --replace-call-with-contract consume b.goto c.goto
cbmc c.goto --function harness --object-bits 12 --slice-formula \
  --sat-solver cadical [--no-forall-skolemization]
CBMC 6.11.0, CaDiCaL Time Memory Clauses
6.11.0 130 s 2.9 GB 6.6M
with --no-forall-skolemization (patch below) 0.7 s 0.15 GB 0.55M

Patch

no-forall-skolemization.patch

The attached patch (against CBMC 6.11.0, also used in pq-code-package/mldsa-native#1521) adds --no-forall-skolemization: asserted quantifiers are passed to the back end unchanged and expanded like assumed ones. It also treats an empty instantiation range as true (forall) / false (exists) instead of leaving the quantifier unconstrained. With it, I can verify all top-level functions of mldsa-native with CaDiCaL.

I would prefer CBMC to decide this internally rather than through a flag, but I don't quite know how to get that to work.

Activity

  1. mkannwischer commented on Nov 3, 2026

    @mkannwischer
    ContributorAuthor

    I found a (nasty) workaround that needs no CBMC change: wrapping each forall in a single-value exists keeps CBMC from skolemizing it when it is asserted, so the SAT back end expands it like the assumed one. In the reproducer, this means replacing BOUND by:

    #define NS(w, P) __CPROVER_exists { uint32_t w; (w < 1u) && (P) }
    
    #define BOUND(m, i, j, k)                                                  \
      NS(i##w, __CPROVER_forall { uint32_t i; i < 4 ==>                        \
      NS(j##w, __CPROVER_forall { uint32_t j; j < 4 ==>                        \
      NS(k##w, __CPROVER_forall { uint32_t k; k < 256 ==>                      \
        (0 <= (*m)[i][j][k] & (*m)[i][j][k] < 8380417) }) }) })
    CBMC 6.11.0, CaDiCaL Time Memory Clauses
    stock 130 s 2.9 GB 6.6M
    stock, exists wrapper with & 2.0 s 0.32 GB 0.57M
    stock, exists wrapper with && 15.4 s 2.4 GB 5.8M
    with --no-forall-skolemization 0.7 s 0.15 GB 0.55M

    The wrapper only helps this much together with & instead of && in the bound; I don't know why && makes such a difference here.

  2. added
    awsBugs or features of importance to AWS CBMC users
    on Nov 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

Code ContractsFunction and loop contractsawsBugs or features of importance to AWS CBMC users

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions