Observed behaviour doesn't fall within accepted range
| Vulnerability potential | Low |
| DDoS potential | None |
Real behaviour doesn’t match expectation
Impact
A complete behaviours clause asserts that the named behaviours of a function
cover every admissible input: the disjunction of their assumes guards is a
tautology over the precondition. A violation means the analyzer found an input
that satisfies the precondition yet matches none of the declared behaviours —
there is a hole in the specification. Concretely, the function will be exercised
in a situation the author enumerated no contract for, so its behaviour there is
unspecified and unverified. This is a specification-completeness defect: the
guarantees you proved hold only on the cases you listed, and the missing case is
exactly where surprises (wrong results, unhandled states, latent crashes) tend to
hide.
Vulnerability potential
On its own an incompleteness finding is a verification-coverage gap, not an exploitable flaw. The security relevance is indirect: the uncovered case is an input class that was specified and tested less, or not at all, so if a memory-safety or validation bug exists it is most likely to live there. An attacker who can steer input into the unhandled region reaches the least-vetted part of the function. Severity is therefore low by itself but should be resolved, because completing the behaviours is what forces the missing case to be analyzed.
Technical details
In ACSL (the specification language Frama-C consumes) a function contract may be
split into named behavior blocks, each with an assumes guard selecting
the inputs it describes and ensures/assigns clauses for that case. Two
meta-clauses constrain the set:
complete behaviors b1, b2, ...;— at least one guard holds for every input allowed by the precondition (the guards cover the input space).disjoint behaviors b1, b2, ...;— at most one guard holds for any input (the cases do not overlap).
This finding is a failure of the complete obligation. The verifier reduces it
to proving requires ==> (assumes_1 || assumes_2 || ...) and finds a model
where the precondition holds but no assumes does. The usual cause is an
overlooked boundary or equality case (e.g. covering x < 0 and x > 0 but
forgetting x == 0), or guards written with the wrong relational operators.
The fix is to add or widen a behaviour so the guards exhaust the precondition.
Catching the issue
Run Frama-C/WP (or another ACSL-aware verifier) with the complete behaviors
clause present; the tool emits a dedicated completeness goal and reports it
unproven when a gap exists, often with a counterexample input. Treat that goal
like any other and fail CI if it does not discharge. When enumerating cases by
hand, cover the relational trichotomy explicitly (<, ==, >) and the
domain endpoints, since the omitted boundary is the most common hole. Pairing
complete with disjoint is good practice: together they prove the
behaviours form an exact partition of the input space, so neither gaps nor
overlaps slip through.
How to reproduce
Observe that the two behaviours cover x < 0 and x > 0 but not x == 0;
frama-c -wp reports the complete behaviors goal as unproven.
/*@ behavior negative:
@ assumes x < 0;
@ ensures \result == -1;
@ behavior positive:
@ assumes x > 0;
@ ensures \result == 1;
@ complete behaviors negative, positive; // gap: x == 0 is uncovered
@ disjoint behaviors negative, positive;
@*/
int sign(int x) {
if (x < 0) return -1;
if (x > 0) return 1;
return 0; /* the unspecified case */
}