Readit News logoReadit News
danielvf · 2 years ago
Some context on this article.

This article is targeted at proving programs that run on blockchain based virtual machines.

These VM's are deterministic to their core. This is quite different the envirnoments that than most programs run in. For example every time code on the EVM runs in production, it is being run in parallel on hundreds to hundreds of thousands of systems, and the outputs/storage writes/events all have to agree exactly after execution, or bad things things happen. The EVM is also single threaded, reasonably well defined, and with multiple different VM implementations in many different languages. So programs here are much easier to verify.

In addition, program execution costs are paid per opcode executed, so program sizes range from hundreds of lines to about thirty thousand lines (with the high side of that being considered excessive and insane). It's again quite different than the average desktop software, or even embedded, program size.

I have both used fuzzing tools (actually working on reviewing a fuzzing setup today) and formal verification in the past. I agree with the article that currently fuzzing provides a similar level of verification for considerably less effort. Current formal verification systems in the blockchain based virtual machine space are extremely difficult to write good specs for, and extremely easy to write broken specs that don't check what you think they do. On the other hand, good enough specs for fuzzing are fairly intuitive, even if great specs still take a lot of expertise.

If I had a math heavy set of code that I wanted to wring the last possible security assurances from, I'd go for formal verification on top of fuzzing. But for most other work, I'd pick fuzzing.

(Disclosure, I've been a customer of Trail of Bits, and worked with two of the three authors in the paper)

(This isn't to say that form verification will never catch up! I'm thankful for the hard work from the people who have been moving it forward - it's gotten much better over the last two years. One day maybe it will be just as easy.)

dgacmu · 2 years ago
A point they're implicitly making is: it's harder to apply formal methods retrospectively. The state of the art in formal verification involves writing the proofs jointly with the code in a language designed for verification, such as Dafny (earlier), F*, or VeRus (emerging).

Fuzzing is much easier to add to an existing system - though even there, there's a lot of benefit from designing for testability.

rurban · 2 years ago
Sorry, the state of the art of formal verification is proving C, C++ or Java code directly. Everything else is lost in translation.

You just to mark some variables as symbolized and bound the loops.

cbmc

dgacmu · 2 years ago
Not really. Cbmc is really cool but it doesn't lead to the kind of high level proof refinement that lets you say things like "this low level code correctly implements the paxos protocol".

Cmbc is -useful- and lets you check some important safety properties and it's a great tool. But it's not the state of the art in terms of how extensively you can check things with formal verification. It's closer to the state of the art of what you can do with legacy code.

Things like VeRus let you write annotated rust together with proofs that can show higher-level properties such as liveness, mutual exclusion, etc.

nanolith · 2 years ago
I don't think that fuzzing and formal methods are opposed. First, formal verification is only as good as the specifications and the extraction / equivalence proofs. Likewise, fuzzing by its very nature never reaches full coverage of a code base. However, they are complementary. Adding fuzzing to a formally verified system is a great way to find gaps in the specification or the proofs. Humans write specs and proofs. Humans are fallible. Fuzzing is a great way to explore the health of the specification and implementation.

Likewise, building toward a formal specification is a great way to convert errors found by the fuzzer into a class of error, and then build up a means by which that class of error can be eliminated entirely from the code base.

The WPA2 protocol -- not its implementation -- was partially verified using formal methods. Unfortunately, there was a flaw in this specification, which led to a poorly crafted state machine. This led to the KRACK attacks. Complementing these specifications with fuzzing could have found these attacks sooner. Researchers have since refined formal modeling of WPA2 to prove that the patches to the KRACK attacks prevent them.

https://www.usenix.org/conference/usenixsecurity20/presentat...

These tools are defense in depth for design. None of them are complete on their own.

voxl · 2 years ago
Formal methods is what happens when you try to do engineering in the traditional sense in the software space. You sit down, design, find a specification, and then build to spec. It's arguably an idealized version, even, because depending on the methods you cannot fail the spec if you succeed in building it.

Only a small group of software engineers are interested in this principled approach. Hell many software engineers are scared of recursion.

quatrefoil · 2 years ago
That's a pretty cynical take. I think a more profound problem is that formal specifications for software are fairly intractable.

For a bridge, you specify that it needs to withstand specific static and dynamic loads, plus several other things like that. Once you have the a handful of formulas sorted out, you can design thousands of bridges the same way; most of the implementation details, such as which color they paint it, don't matter much. I'm not talking out of my butt: I had a car bridge built and still have all the engineering plans. There's a lot of knowledge that goes into making them, but the resulting specification is surprisingly small.

Now try to formally specify a browser. The complexity of the specification will probably rival the complexity of implementation itself. You can break it into small state machines and work with that, but if you prove the correctness of 10,000 state machines, what did you actually prove about the big picture?

If you want to eliminate security issues, what does it even mean that a browser is secure? It runs code from the internet. It reads and writes files. I talks directly to hardware. We have some intuitive sense of what it's supposed and not supposed to do, but now write this down as math...

pydry · 2 years ago
My experience with formal specifications was that our specification ended up being more complex than the code itself.

This is a tricky problem, because your specifications can and usually does have bugs. I once measured this on a project I worked on and found that it accounted for up to ~60% of all incoming bugs - that is, 60% of bugs were due to misunderstandings or miscommunications involving a spec of some kind.

The added complexity of formal verification languages creates an opening for specification bugs to creep in. The net effect was that we might have had 0 code bugs via this automatic proving system but the number of bugs in the specification actually went up.

I'm been deeply cynical about formal verification ever since. I'm not even of the opinion that it's "maybe not good for us, but good for building code for rocket ships". I think it might be actually bad at that too.

I'm bullish on more sophisticated type systems and more sophisticated testing, but not formal verification.

jackcviers3 · 2 years ago
Browsers have been grown, not designed. The competitive pressures exuded on browsers to render ill-specified things and hacks has resulted in something essentially where what it does is what it does. That's the case with a lot of software,because we made the choice as a community of programmers to not formally verify the things that we build. Thunk of the origin of the blink tag [1]. We decided to be hackers, and doers, not thinkers.

Just as testing changes the way you structure your software, designing via formal methods changes the code that you produce as well, and it will attract a different set of people than traditional software dev, just as architecture attracts a different set of people than carpentry.

1. https://danq.me/2020/11/11/blink-and-marquee/#:~:text=Invent....

fwip · 2 years ago
A browser is possibly the most difficult application to formally specify. 99.somemorenines% of software has less complex specifications.

The formal specification for something like Redis is likely much more akin to your car bridge spec. And to continue your analogy, I imagine the specifications for big bridges (Golden Gate, etc) are much more thorough than the ones you built.

zozbot234 · 2 years ago
Type systems are "formal specifications for software" if perhaps in a fairly lightweight sense, and they work quite well. If you write a web browser in a strongly checked language such as Rust (the Servo folks are working on this, and shipping parts of their work in Firefox) you can ensure that it's not going to break memory safety, which is a start.
odyssey7 · 2 years ago
Application-level software engineering hasn't been about hand-writing for-loops for a long time, but there's a tendency for people to feel self-satisfied in what's familiar and what they worked hard to master.

Now that LLMs can write for-loops, too, I've hoped that the field will reconsider the activities that it considers to be valuable. The essential complexity is more about what you described: sit down, design, find a specification. The build to spec part, in the 21st century, can be a tooling step that takes the spec as input.

Perhaps the next step for the industry's state of the art in application-level software engineering is mastering formal spec languages, to seriously tailor the scope of work to the essential complexity of mediating between the agile world of human requirements and the formal world of computing systems. But I won't hold my breath--society is inured to software bugs as a fact of life, and programmers are used to being paid to endlessly patch their own mistakes.

knome · 2 years ago
>can be a tooling step that takes the spec as input

It's been said that a sufficiently detailed spec already has a name: a program.

asplake · 2 years ago
There is a sense also of merely moving the problem. If you can’t write a correct program, can you write a correct specification? For many problem domains it would make little sense even to try.

On the flip side, type systems bring at least some sense of proof into programming.

NovemberWhiskey · 2 years ago
>If you can’t write a correct program, can you write a correct specification? For many problem domains it would make little sense even to try.

That seems like a weird conclusion to me.

You definitely can't write a "correct" program unless you know what you expect it to do. It's easier to write a specification than a program, because a specification is at a higher level of semantic abstraction.

seeknotfind · 2 years ago
It's not just that they are afraid. No one has succeeded in building production-level systems at scale purely using "formal methods". There are a variety of ways to measure how a system compares to a spec. For instance, you can measure throughput at various points in a system. However, formal methods are much more detailed, and when they are applied today, it's usually within a limited context as a way to verify or do model checking.
colordrops · 2 years ago
Right, the tradeoff in time/resources for doing upfront formal verification vs fixing bugs after they are found is almost never worth it. Formal verification isn't even used for most aerospace software.
pphysch · 2 years ago
> Only a small group of software engineers are interested in this principled approach.

Only a small fraction of software applications can be specified up-front. Most software is enterprise software, entertainment software, etc.; software that evolves, where a priori 100% specification is a fool's errand.

thfuran · 2 years ago
That the spec can change over time just means you'll change the spec, not that you shouldn't or can't have one.
GuB-42 · 2 years ago
Traditional engineering is a lot messier than you make it look.

Things are out of specs all the time, and engineers have to deal with that all the time. It is even worse because while you can prove a program mathematically correct, reality only has a passing interest in mathematical correctness.

sabas123 · 2 years ago
You can also write a formal spec based on a reference. But it will be harder then doing it up front most likely.
jgalt212 · 2 years ago
The spec may be provably perfect, but how to you prove the code perfectly conforms to the spec? I don't think you can.

This is why I like end to end tests, UA testing, fuzzing, and property-based tests.

dspillett · 2 years ago
> how to you prove the code perfectly conforms to the spec? I don't think you can

That is actually the easy part.

From my limited knowledge of formal verification (a university module decades ago and reading the occasional article since) it works very well for individual algorithms or small libraries, where the input ranges and desired outputs can be relatively easy to pin down with some accuracy and precision.

As soon as you are working on something bigger and hairier, even under ideal circumstances the effort to produce the spec becomes exorbitant. And those ideal circumstances include end users (or other stake-holders) who know exactly what they want from the start or at least can be convinced to workshop it (at their cost) until they have bottomed out exactly what they want. More often than not, what happens instead is a PoC is produced, either as wireframes or a fuller prototype, and essentially that PoC and the issues raised against it becomes a partial spec, inconsistencies & holes included. Once you start down that track, getting back towards something formally verifiable is likely harder than starting towards that point in the first place.

IMO formal verification, in an ideal world where you have effectively infinite time for doing things properly properly, or for smaller projects a large but not as infinite time, is the ideal. Unfortunately we don't usually work in an environment like that, so we have to compromise.

NovemberWhiskey · 2 years ago
The term "formal methods" covers a wide range of stuff, though. Basically we're just talking about mathematically rigorous approaches to code.

The high-water-mark, which is stuff like formal proof of correctness or proof of worst-case runtime, gets a lot of attention; but there are plenty of valuable approaches that are less expensive.

You can do, for example, formal proof of the absence of runtime exceptions (e.g. no bounds check violations), which doesn't require a formal specification and tends to result in a lot of theorems that are easy to prove automatically.

Or you can do data and information flow analysis, which will let you verify that you don't depend on uninitialized values of variables or make sure that your expectations about how different inputs to a program should affect outputs ("hey, why doesn't the weapon release discrete depend on the weight-on-wheels sensor?")

jsenn · 2 years ago
Some spec based systems allow you to refine a high-level spec until it’s detailed enough to generate code from, where each refinement can be proved correct [1]. I doubt this is done much, but it is possible.

[1] eg https://en.m.wikipedia.org/wiki/B-Method

NovemberWhiskey · 2 years ago
>I don't think you can.

Having done it, I'm pretty sure you can. Why don't you think it can be done?

It requires a programming language (or language subset) with very well-defined semantics, and the use of theorem-proving tools but it's certainly possible.

AnimalMuppet · 2 years ago
You've done it? For how big a program?

And, for how many aspects? You can determine the absence of type problems; you can determine the absence of buffer overruns and use-after-free. Can you determine the absence of race conditions? Can you determine that it meets timing constraints with 100% certainty?

There are a huge number of different kinds of things that a specification could specify. I'm pretty sure that we can't formally verify all of them.

ykonstant · 2 years ago
If/when Lean 4 matures more and adds support for formal verification of code, I would like to see it gain high-quality fuzzers as well. Its relatively good speed (hopefully with the new compiler) could make it a good candidate for fusing these two strategies.
staunton · 2 years ago
Why do you name Lean here as opposed to the other provers? Lean is "the new shiny thing" but my impression is that the community sees its focus in math, not so much software verification.

Quite a few design decisions (e.g. focus on classical reasoning support, focus of existing libraries) suggest that software verification of Lean programs isn't seen as a major application. (Of course you can always define a framework and prove stuff about C programs, like the "software foundations" book illustrates in Coq. But that's not really something new and again would need a lot of foundational work and tooling, essentially duplicating what people do with Coq, for no obvious benefit).

ykonstant · 2 years ago
That's because it's the one I know and am using. I agree the devs are focusing on classical mathematics, but given Lean's dual purpose as a general-purpose programming language, one can imagine a workflow of writing Lean code and verifying it in the language. It's up to the community if they want to do this.
aSanchezStern · 2 years ago
Uhhhh, I'm pretty sure Lean already allows for formal verification of code. In the meta theory that tools like Lean and Coq operate, proofs and programs are very intertwined; it's not really possible to build a proving system in this metatheory without also allowing for program verification. Maybe you're talking about gaining support for formal verification of code not written in Lean? Like, being able to verify C? In that case, it's actually just a library issue. Nothing needs to be changed about the core Lean language, someone just needs to formalize the semantics of the target language semantics in Lean, and then add a verified compiler and verified extraction if you want to make the guarantees extend directly to the compiled assembly. Fuzzers have actually combined well with theorem proving in these kinds of tools in the past; Coq, the proof assistant that heavily inspired Lean and has been in constant use since the mid 80's, has a library QuickChick (based on QuickCheck from the Haskell world) which takes an arbitrary property and generates test inputs to test it, and has very mature support for extending it with new datatypes and invariants.
ykonstant · 2 years ago
Lean allows formal verification, but it does not make it easy. As staunton remarked, the devs are focusing on classical mathematics, and the ergonomics for verifying code are currently horrible. I do suspect this will change with time, though.
bluGill · 2 years ago
int Mid = (high + low) / 2;

The above line is easy to formally prove correct. However it is wrong - if high + low is greater than int_max on your system you have a bug.

I'm still in favor of formal proofs, but there are limits to what it can do.

weinzierl · 2 years ago
I don't know much about formal verification, but I had expected that it considers the most basic behaviour of the most basic data types we have at the very least. Integer overflow as one of wrap-around, saturation or invalid state (UB in the real world) is a bar so low that I do not know what formal verification that doesn't consider it would be useful for all.
temac · 2 years ago
You don't model an unconstrained "int Mid = high + low / 2;" line of e.g. C code with the math equation "Mid = high + low / 2". There are limits but this is a poor example.
bluGill · 2 years ago
This is a real world example. Code that was proved decades ago and worked has started failing now as computers are able to handle in memory datasets that large.

(I probably got the parenthesis wrong on the original example)

pfdietz · 2 years ago
Fuzzing vs. formal methods feels like another example of The Bitter Lesson.