theme | class | highlighter | lineNumbers | drawings | |
---|---|---|---|---|---|
apple-basic |
text-center |
prism |
false |
|
how formal verification and Magmide could make
provably correct code tractable for practicing engineers
- What is it?
- Core ideas
- Magmide
- Prerequisites: type systems, especially Rust
- Security breaches
- Ransomware attacks
- Operational failures
- Safety critical failures
- $1.56 trillion in operational failures
- $1.31 trillion in technical debt
- $945 billion monetary loss from cybercrime in 2020
- $145 billion global spending on cybersecurity in 2020
- up from $522.5 billion in 2018
- Software underpins critical social infrastructure.
- Broken software hurts people.
- Broken software slows down human progress.
- Software is just information that represents pure logic.
- Relies on hardware assumptions, but can itself be literally mathematically perfect.
- Provably correct code is possible with formal verification.
- DARPA funded team.
- Quadcopter control software.
- Red team of world-class hackers.
- Logical proof of security.
- Tools purpose-built by and for academics.
- Bring formal verification out of the ivory tower to practicing engineers.
- Proof language as a verified bare metal program.
- Foundation for verified programs for any architecture or environment.
- Understanding makes it easier to believe.
- Dependent Types
- Type Checking is Proof Checking
- Separation Logic
- More powerful than normal types.
fn is_one(n: u64) -> bool {
n == 1
}
(u64) -> bool
Same type as is_one
, very different behaviors.
fn is_zero(n: u64) -> bool {
n == 0
}
fn always_true(_: u64) -> bool {
true
}
fn always_false(_: u64) -> bool {
false
}
// ... many other possible functions
- Testing can only guarantee correctness with small finite functions.
- Real guarantees need the type system.
- Normal types can only reference other types.
- Dependent types can reference values.
Program Definition is_one n: {b | b = true <-> n = 1} :=
match n with
| 1 => true
| _ => false
end.
Solve All Obligations with (simpl; lia).
forall (n: nat), {b: bool | b = true <-> n = 1}
Coq makes this painful, but it's possible!
Program Definition is_one n: {b | b = true <-> n = 1} :=
match n with
| 0 => true // <- mistake!
| _ => false
end.
Solve All Obligations with (simpl; lia).
// ❌ unable to unify!
- The type system checks that everything is consistent.
- All type systems represent some logic.
- Type checkers basically find proofs.
Inductive Even: nat -> Prop :=
| Even_0: Even 0
| Even_plus_2: forall n, Even n -> Even (S (S n)).
Even
takesnat
and gives logical proposition.Even_0
has type declaring0
is even.Even_plus_2
has type that if anyn
is even, so isn + 2
Inductive Even: nat -> Prop :=
| Even_0: Even 0
| Even_plus_2: forall n, Even n -> Even (S (S n)).
Coq is lisp-like, not C-like.
(foo a b)
rather thanfoo(a, b)
.(S (S n))
same asn + 1 + 1
.
Inductive Even: nat -> Prop :=
| Even_0: Even 0
| Even_plus_2: forall n, Even n -> Even (S (S n)).
Definition four_is_even: Even 4 :=
(Even_plus_2 2 (Even_plus_2 0 Even_0)).
Definition four_is_even: Even 4.
Proof.
repeat constructor.
Qed.
Fixpoint double (n: nat) :=
match n with
| O => O
| S sub_n => S (S (double sub_n))
end.
Definition even_double: forall n, Even (double n).
Proof.
induction n; constructor; assumption.
Qed.
even_double
is a proof transforming function.even_double
is literally infinitely better than a unit test.- Didn't have to change definition of
double
.
- Software Foundations
- Certified Programming with Dependent Types
- and many others...
Dependently typed proof languages are extremely powerful:
- Research Debt
- Pure Functional Dogma
Research Debt - Chris Olah, Shan Carter
There's a tradeoff between the energy put into explaining an idea, and the energy needed to understand it. On one extreme, the explainer can painstakingly craft a beautiful explanation, leading their audience to understanding without even realizing it could have been difficult. On the other extreme, the explainer can do the absolute minimum and abandon their audience to struggle. This energy is called interpretive labor.
People expect the climb to be hard. It reflects the tremendous progress and cumulative effort that's gone into mathematics. The climb is seen as an intellectual pilgrimage, the labor a rite of passage. But the climb could be massively easier. It's entirely possible to build paths and staircases into these mountains. The climb isn't something to be proud of.
The climb isn't progress: the climb is a mountain of debt.
The insidious thing about research debt is that it's normal. Everyone takes it for granted, and doesn't realize that things could be different.
Academia has bad incentives to
properly explain and expose their work.
- No mutation or side effects.
- But it's always a lie!
- Computers are just big chunks of mutable state.
- Have to reason about real computation.
- Logical framework for reasoning about mutable state.
- Can be defined and used within a pure logic like Coq.
- Surprisingly uncommon.
P ∧ Q
P ∧ Q
equivalent to:
(P ∧ Q) ∧ P
Perfectly reasonable for pure facts.
Even(4) ∧ (1 + 1 = 2)
equivalent to:
(Even(4) ∧ (1 + 1 = 2)) ∧ Even(4)
What about mutable facts?
a --> 1
memory location a
points to 1
// {program assertions}
// {a --> 1}
let a_value = *a;
// {a --> 1}
assert(a_value == 1);
// {a --> 1}
// {a --> 1}
some_function(a);
// {a --> 1 ∧ a --> ???}
let a_value = *a;
assert(a_value == 1); // ❌
(a --> 1) ∧ (a --> 2)
∧
operator too lenient for mutable facts.- Difficult to scale program reasoning.
- Had to reason about entire program rather than composable pieces.
// Not Allowed! ❌
(a --> 1) * (a --> 1)
- pronounced "and separately"
- called separating conjunction
- requires state assertions to be disjoint, separate
- doesn't allow duplicating assertions
// Okay! ✅
(a --> 1) * (b --> 2)
// {a --> 1}
let a_value = *a;
assert(a_value == 1);
// {a --> 1}
some_function(a);
// {}
// let a_value = *a;
// ❌ no longer own a!
- Must give away ownership.
- Allows small-scale composable proofs.
- Directly inspired Rust.
- More concepts, can be more complex.
- Represents a decidable subset of a fractional separation logic.
- Like a "proof finder", sometimes called a certified decision procedure.
- Can always determine correctness of safe Rust.
- Can't figure out correctness of
unsafe
code. - Rust needs
unsafe
to actually be realistic and useful.
- Built to verify arbitrarily complex Rust programs.
- Written in Coq. Researchers write proofs rather than relying on a decidable algorithm.
- Iris from the ground up
- RustBelt: Securing the Foundations of the Rust Programming Language
- Type system, ownership and lifetimes,
Send
andSync
. Arc
,Rc
,Cell
,RefCell
,Mutex
,RwLock
,mem::swap
,thread::spawn
,rayon::join
,take_mut
- Iris/Iron powerful enough to verify no resource leaks!
- Research debt.
- Not a directly usable language.
- Not built into any other tools.
- Only built for "on the side" proofs.
funrec option_as_mut(x) ret ret :=
let r = new(2) in
letcont k() := delete(1, x); jump ret(r) in
let y = ∗x in case ∗y of
− r :=={inj 0} (); jump k()
− r :=={inj 1} y.1; jump k()
Necessities to achieve goal:
- Capable of arbitrary logic
- Capable of bare metal performance
- Gradually verifiable
- Fully reusable
- Practical and usable
- Taught effectively
- Max out logical power with full type theory.
- Able to formalize any assertion.
- Decidable subsets still possible.
- Start at the lowest level, use Iris.
- Less performant abstractions still possible.
- LLVM-like abstract assembly language, hardware axioms at bottom.
- Trackable effects, for tracking safety and correctness conditions.
- Knowledge of program safety is absolute but flexible.
- Incremental adoption and realistic iteration.
- Converge toward correctness.
- Verification pyramid.
- Foundations pass on provably safe abstractions.
- Metaprogramming and query-based compiler.
- Higher level languages can "lift" full power, have escape hatches, reuse Magmide compiler functions.
- Don't have to write proofs for everything.
- Rust/Cargo prove we can have nice things.
- Remove incidental complexity, focus essential complexity.
- This research needs to be applied.
- Respect user's time.
- Assume reader is trying to get something done.
- Concrete examples before formal definitions.
- Call out true prerequisites.
- Use graspable words not opaque and unsearchable non-ascii symbols.
- Syntax should make it easy to find definitions.
Separates pure Logic from computational Host.
represents and
implements
+--------------------+--------------------+
| | |
| | |
v | |
Logic Magmide +-------------> Host Magmide
| ^
| |
| |
+-----------------------------------------+
logically defines
and verifies
- C -> Rust
- Coq/LLVM -> Magmide
- Currently bootstrapping prototype
- Coq for initial proofs and LLVM for compilation
- Proof carrying code.
- Provably secure operating systems, firmware, network drivers, browsers, voting software...
- Safe package ecosystems.
- More advanced borrow checking algorithms.
- Approachable logic coach in many more hands.