Path: blob/main/cranelift/isle/veri/README.md
1692 views
Crocus: An SMT-based ISLE verification tool
This directory contains Crocus, a tool for verifying instruction lowering and transformation rules written in ISLE. Crocus uses an underlying SMT solver to model values in ISLE rules in as logical bitvectors, searching over all possible inputs to find potential soundness counterexamples. The motivation and context project are described in detail in our ASPLOS 2024 paper: Lightweight, Modular Verification for WebAssembly-to-Native Instruction Selection.
Currently[^1], Crocus requires every ISLE term uses within a rule to have a user-provided specification, or spec
, that provides the logical preconditions and effects of the term (require
and provide
blocks). The syntax for these specs is embedded as an optional extension to ISLE itself: specs are written in the ISLE source files.
[^1]: We have work in progress to lower this annotation burden.
Running on an individual rule
The easiest way to run Crocus on an individual ISLE rule is to give that rule a name.
For example, to verify the following aarch64
rule:
We can add a name (before the priority):
We also require that the relevant (outermost) CLIF term on the left hand side has a "type instantiation" to specify the types, e.g. bitwidths, we are interested in verifying. In this case, this is provided with:
We can then invoke the rule with the following, using -t
or --term
to specify the relevant CLIF instruction and --names
to specify the name of the rule:
With the expected output:
If the rule was unsound, this will report counterexamples. For instance, if we change the rule to the following:
Then the output would include counterexamples, like so:
The annotation language
The annotation maps closely to SMT-LIB theories of bitvectors and booleans, with a several added conveniences.
Top-level constructs
We extend the ISLE parser with the following top-level constructs:
model
specifies how an ISLE type maps to an SMT type. For example, the follow ISLE type definitions along with their models specify how booleans andu8
s are modeled:
Models can be Bool
, Int
, or (bv)
with or without a specific bitwidth. If the bitwidth is not provided, Crocus type inference will verify the rule with all possible inferred widths
As in the example above,
instantiate
andform
specify what type instantiations should be considered for a verification.spec
terms provide specifications for ISLE declarations, which can correspond to ISLE instructions, ISA instructions, external constructors/extractors defined in Rust, or transient, ISLE-only terms. Specs take the form(spec (term arg1 ... argN) (provide p1 ... pM) (require r1 ... rO))
, providing theterm
termname (must be a defined ISLE decl), fresh variablesarg1 ... argN
to refer to the arguments, and zero or more provide and require expressionsp1, ..., pN, r1, ..., RN
that take the form of expressions with operations as described below.spec
terms use the keywordresult
to constrain the return value of the term.
General SMT-LIB operations
The following terms exactly match their general SMT-LIB meaning:
=
: equalityand
: boolean andor
: boolean ornot
: boolean negation=>
: boolean implication
We additionally support variadic uses of the and
and or
operations (these desugar to the binary SMT-LIB versions as expected).
Integer operations
The following terms exactly match the SMT-LIB theories Int
.
<
<=
>
>=
In specs, integer operations are primarily used for comparing the number of bits in an ISLE type.
Bitvector operations
The following terms exactly match SMT-LIB theory FixedSizeBitVectors
.
There operations are typically used in specs for any operations on ISLE Value
s.
bvnot
bvand
bvor
bvxor
bvneg
bvadd
bvsub
bvmul
bvudiv
bvurem
bvsdiv
bvsrem
bvshl
bvlshr
bvashr
bvsaddo
bvule
bvult
bvugt
bvuge
bvslt
bvsle
bvsgt
bvsge
Custom bitvector operations
int2bv
: equivalent to SMT-LIBnat2bv
.bv2int
: equivalent to SMT-LIBbv2nat
.extract
:(extract h l e)
whereh
andl
are integer literals ande
is a bitvector is equivalent to SMT-LIB((_ extract h l) e)
.zero_ext
:(zero_ext w e)
wherew : Int
ande : (bv N)
is equivalent to SMT-LIB((_ zero_extend M) e))
whereM = w - N
.sign_ext
:(sign_ext w e)
wherew : Int
ande : (bv N)
is equivalent to SMT-LIB((_ sign_extend M) e))
whereM = w - N
.rotr
:(rotr e1 e2)
wheree1, e2: (bv N)
resolves to(bvor (bvlshr e1 e3) (bvshl e1 (bvsub (nat2bv N N) e3)))
, wheree3 = (bvurem e2 (nat2bv N N))
. Bitvector rotate right.rotl
:(rotl e1 e2)
wheree1, e2: (bv N)
resolves to(bvor (bvshl e1 e3) (bvlshr e1 (bvsub (nat2bv N N) e3)))
, wheree3 = (bvurem e2 (nat2bv N N))
. Bitvector rotate left.concat
:(concat e_1... e_N)
resolves to(concat e_1 (concat e_2 (concat ... e_N)))
. That is, this is a variadic version of the SMT-LIBconcat
operation.widthof
:(widthof e)
wheree : (bv N)
resolves toN
. That is, returns the bitwidth of a supplied bitvector as an integer.subs
:(subs e1 e2)
returns the results of a subtraction with flags.popcnt
:(popcnt e)
wheree : (bv N)
returns the count of non-zero bits ine
.rev
:(rev e)
wheree : (bv N)
reverses the order of bits ine
.cls
:(cls e)
wheree : (bv N)
returns the count of leading sign bits ine
.clz
:(clz e)
wheree : (bv N)
returns the count of leading zero bits ine
.convto
:(convto w e)
wherew : Int
ande : (bv N)
converts the bitvectore
to the widthw
, leaving the upper bits unspecified in the case of a extension. That is, there are 3 cases:w = N
: resolves toe
.w < N
: resolves to((_ extract M 0) e)
whereM = N - 1
.w > N
: resolves to(concat e2 e)
wheree2
is a fresh bitvector withw - N
unspecified bits.
Custom memory operations
load_effect
:(load_effect flags size address)
whereflags : (bv 16)
,size: Int
, andaddress : (bv 64)
models a load ofsize
bits from addressaddress
with flagsflags
. Only 1load_effect
may be used per left hand and right hand side of a rule.store_effect
:(store_effect flags size val address)
whereflags : (bv 16)
,size: Int
, andval : (bv size)
,address : (bv 64)
models a store ofval
(withsize
bits) to addressaddress
with flagsflags
. Only 1store_effect
may be used per left hand and right hand side of a rule.
Custom control operation
if
: equivalent to SMT-LIBite
.switch
:(switch c (m1 e1) ... (mN eN))
resolves to a series of nestedite
expressions,(ite(= c m1) e1 (ite (= c m2) e2 (ite ...eN)))
. It additionally adds a verification condition that some case must match, that is,(or (= c m1) (or (= c m2)...(= c mN)))
.
Example
Continuing the band_fits_in_64
example from before, the full required specifications are places in the relevant ISLE files.
In inst_specs.isle
:
In prelude_lower.isle
:
In aarch64/lower.isle
:
In aarch64/inst.isle
:
Testing
To see an all of our current output, run tests without capturing standard out:
To run a specific test, you can provide the test name (most rules are tested in cranelift/isle/veri/veri_engine/tests/veri.rs
). Set RUST_LOG=DEBUG
to see more detailed output on test cases that expect success.
To see the x86-64 CVE repro, run:
To see the x86-64 CVE variant with a 32-bit address, run: