Formal Verification
Formal verification in Ironclad is done with the goals of ensuring Abscence of Runtime Errors (AoRTE) and program correctness.
Ironclad is not 100% verified code. A majority of its architecture-independent code lies somewhere within SPARK's spectrum, and a big chunk of it is verified to high SPARK verification levels, while architectural code is much less verified. Non-architectural code is prioritized due to easier and more rewarding verification.
How much is formally verified?
Ironclad is divided on different subsystems, which can be seen in the source code as subdirectories and Ada packages. Here is an overview of the project:
Subsystem | SPARK's stone level progress | SPARK's silver/gold level progress | Verification quality* |
---|---|---|---|
arch | Done | Peding on arch ports | Peding on arch ports |
arch/riscv64-limine | Not started | Pending on stone level | Pending on stone level |
arch/x86_64-limine | Not started | Pending on stone level | Pending on stone level |
cryptography | Done | Done | Full AoRTE |
devices | Done | Mostly done | Full AoRTE with sporadic small errors |
ipc | Done | Mostly done | Full AoRTE when applicable |
lib | Done | Done | Full AoRTE |
memory | Done | Not started | Peding on verification |
networking | Done | Done | Full AoRTE |
userland | Done | Mostly done | Full AoRTE mostly, syscall bodies have errors |
vfs | Done | Partial | Full AoRTE on registry, drivers unverified |
*Past silver level, SPARK provides no objective measures to quantify verification coverage. Thus, this field is just a statement on quality from the developers.
Obtaining factual results
If you would like some hard numbers to back up the claims, Ironclad is checkable locally using gnatprove with:
make prove
Further instructions are available on the project's README
.
How it is done
Ironclad uses SPARK for formal verification. SPARK works with contracts specified in Ada code itself. Which take the form of preconditions, postconditions, and relationships between types and variables. When not specified, SPARK infers them.
These verification mechanisms may appear in the source as:
procedure Substract
(Seconds1, Nanoseconds1 : in out Unsigned_64;
Seconds2, Nanoseconds2 : Unsigned_64)
with Pre =>
Is_Normalized (Nanoseconds1) and then
Is_Normalized (Nanoseconds2)
Post => Is_Normalized (Nanoseconds1);
By checking these constructs across the whole codebase, SPARK checkers, like GNATProve, by using a process called deductive verification, are able to establish checkable mathematical proofs that the code is checked against.