Formal Verification in Ironclad

Formal verification in Ironclad is done with the goals of ensuring Absence 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 Pending on arch ports Pending 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 Sporadic small errors
ipc Done Mostly done Full AoRTE selectively
lib Done Done Full AoRTE
memory Done Not started Pending on gold level
networking Done Done Full AoRTE
userland Done Mostly done Syscalls not verified
vfs Done Partial Full AoRTE at places

Obtaining results

If you would like to analyze the formal verification status and information yourself, Ironclad is checkable locally using gnatprove with make prove.

Further instructions are available on the project’s README.md.

How is it 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 Subtract
   (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.