Ken is currently at Microsoft Research,
Principal Researcher.
Something like that.
Something like that.
[audience laughs]
Prior to that he was at
the Cadence Berkeley Labs.
Prior to that he was at Bell Labs.
And prior to that, he was a
student at Carnegie Mellon.
And I've been a fan of Ken
since his doctoral days,
simply because I didn't know him before.
[audience laughs]
And he's done some really seminal work
in the field of model checking.
I think we can confidently
say that if we have
symbolic model checking techniques,
we owe it to Ken more than anybody else,
and then he's done a number
of amazing contributions
over the years that have
been duly recognized
by number of awards that
are too many to mention.
And so, I'm sure that it
will be an exciting talk
and without further ado,
I'll leave it to you.
Thank you Fabio for that .
[audience clapping]
So I thought that it might be worthwhile
to spend a few minutes
just in introducing myself
because I think people
who aren't in the area
might not be familiar with my work.
And there are just two things
that I wanted to mention
that I've worked on
that I think are sort of the
most influential on the field.
And the first is the one
that Fabio mentioned,
which is symbolic model checking.
And I think probably, you know,
you're familiar with model
checking, which was technique
introduced by Clark and
Emerson in the 1980s.
And the idea of that is if
you have a finite state model
of some system, if you
have a specification of it
that's written in a suitable
notation for example,
the temporal logic then
you have an algorithm
that's efficient, by which you can decide
whether the system satisfies
its specification or not.
And that was very influential at the time.
And in fact, Clark and Emerson and Sifakis
eventually won the Turing Award for that.
But it had one big catch.
And that was called the
state explosion problem.
And the idea there is
that finite state model
is probably going to be
at least exponential size
in any reasonable description
of the system itself.
So I worked on that problem
in a lot of different ways.
But the thing that I did
is probably most important
what's called symbolic model checking,
and just at a very high
level, symbolic model checking
means moving from algorithms on graphs
to algorithms on formulas.
And the intuition behind that
is that very large state space
might have some regularity
in it that could be
succinctly captured in
some symbolic normal form
or some logical normal form.
Form that I originally used
in network was something
called an ordered binary decision diagram.
That's something that a lot of good work
has been done here in
Colorado, here in Colorado.
So of course, that didn't really
solve the state explosion problem.
But it allowed just enough
scaling that you could start
to have commercial
applications of model checking.
And companies like IBM
and Intel, picked that up.
And then later the electronic
design automation industry
started offering controls,
those were initially based
on DVDs and later on other things.
And there was a lot of good
work then on successive
generations of symbolic
model checking techniques
that are far more sophisticated,
and that kind of thing than I did.
And of course, one of those
is the work of Aaron Bradley
here at Colorado, which
is a technical IC3.
So now if you go to the ETA vendors
and you buy a hardware model
checker, well, that's going
to be some kind of symbolic
model checking tool.
Okay.
So the other thing that
I wanted to mention
is probably less familiar.
But it's related to the
state explosion problem.
And that's something
called Craig interpolation.
And I don't have time
to explain that here.
But this has to do with abstraction,
which is the most
important idea that we have
for dealing with complex systems,
we wanna reason about them.
And in interpolation, what you're doing
at a very high level,
is you're going from an
inference to an explanation.
And by an explanation, I mean
something rather specific.
That is, I mean, a simple
proof of some fact,
that is in a form that can be generalized.
So for example, let's say
that I'm verifying some piece of software.
And I can get a good explanation
of why a particular
behavior of that software
satisfies its specification
why it's correct.
Well, if I have that explanation
and in the form of this thing
called a Craig interplant,
then I might be able to generalize
from that explanation
to get an abstraction,
an abstraction is
essentially the raw material
of the proof of the general case.
So in other words, Craig interpolation
really means reasoning
from specific cases,
to the general case in
a logical way, okay,
not in a sort of machine
learning heuristic way.
And now interpolation is
essentially the standard technique
for doing abstraction in model checking,
especially in the domain
of software, where we have
to have abstractions to even
make the thing be finite state.
So those are just two things
that I wanted to mention
by way of introduction, but
I'm not gonna talk about
proofs at all in this talk.
Today, what I wanna talk
about is specifications.
Okay, where do they come from?
Why do you want them?
And you could say that
specifications are the unicorns
of the formal verification world.
And what I mean by that is
that we talk a lot about
specifications, but very few
people have actually seen one.
[audience laughs]
We tend to just assume in the
formal verification world,
that specs will be provided to us
by this other mythical
creature that's called a user.
By that we can give
that a suitable notation
to write the specs.
But I think specs are more
interesting than that.
And that's what I'm going
to be talking about today.
And part of the motivation
for that is some recent
adventures that I've made
into this land of formal specifications.
And one of those was coming
up with a specification
for the RISC-V Memory hierarchy.
This is a processor
architecture is being developed,
for example, at UC
Berkeley and other places.
And, you know, I'm talking
about the protocols that are
spoken between the different
components of the hardware,
and how those relate to the
high level memory semantics
that the thing is supposed
to provide to the programmer.
And I was trying to come up
with a specification of those things.
Another was replicated storage in Azure,
in cloud services,
where you have this protocol
that's being spoken amongst
all these different
servers to try to provide
this abstract view of storage to the user,
and protocols very complex,
and I was trying to write
a specification of that protocol
to relate it again to
the high level semantics.
And the third one is a
protocol called QUIC.
And this is a secure
internet transport protocol.
And I wanna use QUIC as a case study today
to talk about some of the
basic issues that arise
when you are specifying complex systems.
Okay, so here's some of the questions
that you would like to ask
about any specification.
First of all, what is its function?
In other words, what is it doing for you?
Why do you want a specification
in the first place?
Secondly, what is its form?
In other words, what
language is it written?
How do you write it?
Third what is its content?
What does it say about the system?
And what does it not say about the system?
And finally, what is its process?
In other words, how do you
or buy at that artifact?
And what I'm gonna argue today
is that you have to think
about the first question first,
what is the function
of that specification?
Before you can answer those
other three questions.
Okay, so here are some possible functions
of a specification, and
some different opinions
that people have about what
specifications are for.
So one possibility is it
could be a thinking tool.
And this is something that lesson number,
has promoted the idea
that if you can write down
what you want, in a
clear mathematical way,
that it helps you think about the problem
and come to a better design.
And that's certainly true.
Another common idea so the specification
is a contract between different designers.
So if you had an interface
between two subsystems,
and you can formally
specify that interface
then those two designers can go off
and design their two parts of the system.
And when they plug them together,
they'll know that they work.
So it could be a contract
between designers.
Another possibility is that specification
could just be a lemma in a formal proof.
In other words, I'm writing
especially that specification
of this component, because
I want to prove something
about the system as a whole.
And it will help me to do that.
So that's a common thought.
And the fourth one, which is
actually more of a recent idea
is that a specification
could be just used as a way
to evaluate artifacts,
implementations, in other words,
to test them, simulate
them, evaluate them,
and that's actually the
function of the specification
that I'm going to explore today.
So, as I said, in order to
do that, I'm going to use
this protocol called QUIC.
So I'll tell you just a little bit
about QUIC to get started.
So QUIC is a replacement
for the TLS TCP stack.
Which is the thing we use
to get secure communication
between, for example, a web
browser and a web server.
And it's been used for a long time.
But in 2013, Google
introduced this protocol
called QUIC as a replacement for it.
And the idea is you want
to get, for example,
reduce connection, latency,
better congestion control,
and generally more
responsive web applications.
So it's designed to solve a
lot of the performance problems
that come along with TCP and TLS.
So it's currently being standardized.
There's an Internet Engineering Task Force
that's working on a draft specification.
And it has been chosen as
the underlying transport
for the next version of
the worldwide web protocol.
So what that means is,
that QUIC is being used
a lot on the internet now
for things like video streaming.
In the near future, it's
going to underlie much more
of the traffic on the internet.
Whenever you talk to a web server.
Whenever you talk to a
cloud service through HTTP,
you're going to be using
QUIC under the hood.
And the thing is QUIC is very complex.
It's much more complex
than TCP for example,
I think we should be worrying about this,
especially given the
history of TLS and TCP,
since so much of our communication
and security is gonna depend on QUIC.
Okay, so let me talk a little bit about
how QUIC is structured.
So QUIC, like bottom
protocols can be thought
of as a stack of layers,
a stack of protocols layered
on top of each other.
Now at the base, you might
have a client talking
to a server, just like in TLS TCP.
And the low level communication is handled
by the User Datagram Protocol,
which is just a standard
internet protocol that allows
you to send unordered
underlying datagrams.
Now on top of that QUIC
puts a layer of protection,
which means it's encrypting the packets
so that their secret.
On top of that, it has the
so called packet protocol.
And that has a variety of functions.
But one of the primary ones
is that it's going to be used
to detect loss
and so that we can have
reliable communication.
Now on top of that, we
have another protocol
sort of arbitrarily they
call the frame protocol.
So frames are smaller
messages that are encapsulated
into these larger packets.
And they have a lot of functions.
But one of the primary ones
is sending streams of data
and making sure that
those streams are working
is that they can be reassembled
in their original order.
So that's the frame protocol.
On top of that you have a
security layer, it's based on TLS.
And it's doing authentication
and derivation of shared secrets.
So we can do encryption
and can have protection.
And finally on top of that,
we have the application layer
where hopefully you can
send multiple streams
of secure private forwarder reliable data.
Okay, so this is sort of a
common scenario where you
have all of these protocols,
each one is using services
of the layer below.
Right, so the application
layer is splitting
into two data frames, those
are getting capsulated
the packets that are being
encrypted and going up.
AUDIENCE: So one question
is that the packet is just
laid with packet loss and
somehow not worried about
the in order delivery of packets is that?
Exactly.
So in other words, the in order
delivery is entirely handled
by frame so here they've separated it.
Whereas in TCP is using the
same sequence numbers for both.
So the sequence numbers in
the packets have nothing
to do with sequence numbers,
just partially to do
with the fact that it's
multiple streams and so forth.
So especially in the head of line,
blocking of multiple streams
is one big reason for QUIC to avoid that.
Okay, so it's sort of decomposed
into these lovely layers
except not really, it because in fact,
there's loss of use of
surface at the higher layers
from the model so protection
layer has to get information
from the packet layer information
from the security layer,
packet layer also has
to transfers information
through TLS almost unbelievable.
So really this thing is kind
of a ball of spaghetti, right?
You can't take it apart into neat layers.
And that's gonna actually affect the way
that we write the formal specification.
So it's a bit of a mess.
And here's sort of a very
simple example of how it works
just to give you a flavor of the protocol.
So if you had a client
that was trying to make
a connection to a server,
the first thing it will do
is it will send a packet
and the packet will contain a frame
which contains a crypto message.
In other words, it contains
the first handshake message
for TLS which is called CRYPTO
and also will have a sequence number
for protection purposes.
The server on receiving
that is going to send back
a packet between this two
frames will have a second
security handshake
message in a crypto frame.
And it will have an acknowledgment
of the first packet.
So actually, the last detection
is a little bit shared
between traffic and the frame layers.
Okay, then the client will come back
with a third crypto message.
And at that point, we
now have authentication
between the client and the server.
And we're able to derive a shared secret
that we can use for session encryption.
So at that point, the
client can start sending
some stream frames it
contains streams of data,
and they will have perhaps a command
for the higher level application
protocol for example,
if that was HTTP command
line we get/index.html.
So if I don't get an app
for that, I might have
to retransmitted in a different packet
with a different sequence number.
They send data eventually we hope
that the server will respond.
It might be back with some stream frames
that contain that html page.
And acknowledgment of
the original request.
And that goes on until
the client is satisfied.
And eventually we'll close the connection
and end the session.
So that's the simplest case of QUIC,
but actually QUIC is much
more complicated than that,
because it's trying to
integrate a lot of different
functionality that currently
is in different protocols.
And so you have multiple
concurrent streams of data
with retransmission and flow
control and congestion control,
version negotiation, you
have migration, which means
that clients are allowed to be mobile
and move around on the internet.
To do that they kinda have
to have path validation,
which means the server has to validate
that you really own the new address.
And you're not being spoofed.
Right.
You have connection ID management.
So use lots of different
connection IDs to avoid people
tracking you on the
internet as you move around.
You have valid data with pre-shared keys,
and it just goes on and on.
So there are about 224 pages
of English language description
in the so called requests for comment
the drop requests for comments
that defines this protocol.
Okay, so that process then is going on
in the QUIC working group.
What they're doing, as I
said is they're writing
this English language description
as a potential standard,
which is basically advice
on how to implement QUIC.
So each of the members of the group
is now doing an implementation.
And the way they validate
that protocol that design
is they all get together,
everybody takes their implementations,
they plug them together,
and they see if they work.
And that's not bad.
That's a reasonable way to
design a protocol that's more
or less how it's always been done.
But there are some risks with that.
And there's some problems,
which are reasons that we might
wanna have a formal
specification of that protocol.
So the first of those is test generality.
In other words, they're using
all those implementations
to test each other,
but they don't test very
much of the protocol, right?
They don't test all the possibilities
that are allowed by the protocol.
They just have very specific behaviors.
And so when you plug the
whole system together
and test it, there's a lot
of risk that the components
won't test each other very well.
Or another way to put that
is in that testing is not
sufficiently adversarial.
Okay, the other problem with
that is that there's nothing
that actually checks
compliance to a common standard
when you test into our compatibility.
And that has a lot to
do with compatibility
with future implementations.
And a good example of
that is the story of TLS.
Which is actually a fairly sad story.
So what happened with TLS?
You know, the original
internet security protocol
was it got a lot of non
compliant implementations of TLS
that were out in the
wild out in real servers
that people were using for you
know, web services and so on.
And the result of that was
you had a lot of serious
security issues for example,
a lot of those servers
didn't implement version
negotiations correctly.
And so what happened
was, in order to be able
to talk to those legacy servers,
the browser's would
implement ad-hoc workarounds.
Those were essentially
a new ad-hoc protocol.
And those were always vulnerable.
And so you had attacks like
so called POODLE attack,
that would allow the attacker
to downgrade your security
to an older less secure cipher, right?
And you know, TLS just had a long history
of this kind of problem,
which had to do with the fact
that you had these non compliance
servers out in the wild
that you had to somehow
be compatible with.
Okay, so obviously, we
wanna avoid that with QUIC
and this is going to
drive the function now
of the formal specification, right.
So primarily, then I want
to use test specification
as a test artifact, in other words,
I want to use it for testing
these individual clients
and servers, these
implementations, that means
it needs to be able to
generate adversarial tests.
And it needs to be able
to check actual compliance
to a common standard.
So those are the two
things that I've identified
that we really want to do.
Right.
But secondarily, I also want
the specification to act as a contract.
So that future implementers
of the protocol
could use it as a reference
and implement to that.
So that's the secondary function.
So we wanna use it then
partly to capture the protocol
knowledge that's inherent
in those implementations.
It's very hard to extract from the RFC.
Okay, so then after I
decided that's the function
of my specification, everything else
is gonna follow from that.
So the form of my specification
is going to be compositional
It's going to be an assumed
guarantee specification.
And it's going to be deterministic.
And I'll explain what I mean
by all of those three things
that have to do with the function
of that I want specification to serve.
And in content, because I
want to do testing with it,
it's going to be essentially a collection
of safety properties that
have to do just with events
that are visible on the wire,
because those are the
only things that a tester
is going to be able to see.
But if I were doing a formal proof,
I could see everything on the inside,
but when I'm testing I can.
And finally, the process
is going to be to capture
those properties from testing
actual implementations.
Now I'll explain how that works.
That's not an automated process.
That's something that
I'm going to do as part
of this manual process of
developing a specification.
Okay, so, first of all,
what is compositionality?
What do I mean by compositional?
Well, they'll allow the
reverse to set it this way,
he said that a program
meets its specification
should be verified on the
basis of specifications
of its constituent components only,
without additional knowledge
of the interior construction
of their components.
So compositional specification
breaks the system
into components.
And it only talks about the interfaces
between those components.
And that's something that
we're going to require
for our particular application
for several reasons.
First of all, if I'm testing,
I can only see the external
behavior of that object.
I can't see what's going on inside.
Second, I have to break
the system into components
because when I test it
all together, you know,
with the clients and servers
talking to each other,
the testing is not
sufficiently adversarial.
I don't test enough cases.
And the third is a point
that I'll have to make later,
which is the compositional specification.
I can capture this specification knowledge
that's inherent in the implementations.
I have to explain that point later.
Okay, so compositional testing,
then means that I'm testing
individual components in
isolation using the specification.
And for a network protocol,
it works like this.
I have a network.
And I'm just thinking of that network
as being a bag of messages
processes can put messages
in and take messages out.
And then I have a bunch of processes
connected to that network,
putting messages and taking messages out.
And I want to write a global specification
of everything that happens on the network.
So I'm gonna call the specification fee.
And fee is a history dependent condition.
It looks at the history
of all the messages
that have gone into the network up to now.
And it says what messages are legal
to put into the network at this moment,
based on what's happened in the past.
What is it allowed to do now,
according to the protocol,
so very simple
and it talks about all
messages in the system.
But from that I can derive
a local specification
for each of these individual processes
as an assumed guarantee specification.
What that means is, for
example, this client
gets to assume that all messages
that it sees in the network
satisfied feed, everything
that's gone in the past
must have satisfied the specification.
But it must guarantee that
the next message that it puts
into the network also
satisfies the specification.
So basically, what that means is,
when you put it all together,
since no individual process
can ever be the first one to violate fee,
then fee must always fold in other words
from properties of these
individual processes.
I get a property of the system as a whole.
Right, and that's compositional reasoning.
So how does that work then, in testing?
Well, I want to isolate
one particular process,
let's say it's the server
that has messages going
into network and coming out.
And those are governed by my
global specification feed.
So to generate inputs for
that server at any moment,
I just have to look at that condition feed
and produce some arbitrary random message
that satisfies the specification now,
and I can treat that as
an input to the server
because it satisfies
the sort of assumption.
On the other hand, so that's generation.
On the other hand, when the
message comes out of the server,
I cannot use that
condition feed to check it
and say on according to specification
is that basically will not.
So this gives me the two
properties that I need
for my testing, right?
It's adversarial in the
sense that it doesn't reduce
the set of legal messages that I can see,
I can potentially generate
any possible message as input
to the server that's legal
according to the protocol.
Right.
On the other hand, it also
checks actual compliance
of each individual component
which wasn't done before.
So this compositional testing process
has both characteristics that I want to.
Okay, so then what would the specification
actually look like if I
wanted to use it in that way.
So you remember I said that I want to use
that specifications both
a generator and a checker,
which is a little bit of a trick,
actually to do efficiently.
And the way I'm going to do it is to write
that specification as a
collection of guarded commands.
So a guarded command looks like this,
you define some class of events E.
And those events have some parameters V.
So this event corresponds
to sending a message
that V might be for example, private
and B might correspond to
components of that message
like the source and the destination
and the sequence number.
So when I see that event, I
now have a guard called count.
The guard depends on the current
state of the specification
it depends on the event parameters,
and it tells me in the current state
is this particular event legal.
If the event is legal,
then I can execute the
update guarded command,
which says given the
current state and the event,
tell me what the next state is.
Right.
So that state then allows
me to remember history.
So I can have a history
dependent specification.
And I'm going to have one of these events,
corresponding, for example,
to each layer in that layer protocol.
And there's one more slightly
subtle point about this.
So this guard now is going to be the key
to doing generation and checking.
Because in any given state,
if I plug the state into gamma,
I get a function of the parameters,
I get a constraint on the parameters.
And I can solve that
constraint in randomized way
to get an arbitrary input,
for example for my server.
So one way that I can do that
is using something called
an SMT solver.
And I can get some approximation
of a desired distribution
over all those possible events.
Right.
So because I have this
guarded in command form,
I know what state I'm in,
so it's easy to generate a legal message.
On the other hand, if a message
comes out of the server,
I can also easily evaluate
this condition in the
current state to determine
whether that message is legal.
So specification in this form,
provided that is updated deterministic,
makes it easy to both generate and check
if this update were not deterministic.
It's a slightly technical problem.
But I wouldn't know exactly
what state I was in.
I could be in any one of a
large set of possible states
that would be checking
in trackable, right.
So this form is designed
to make sure that I can
both generate and check
and attractable one.
AUDIENCE: Question?
Yeah.
AUDIENCE: So does this mean
you have to get the state
of the protocol like completely
or is any abstraction allowed
in your specification?
Well, so the state can capture as much
or as little information as you want,
depending on how strong you
want that specification to be.
Right, so I, you know,
I'll give you a little example
of a property that's
expressed in that way.
And you can see that it's
a very weak property.
Right?
I don't know what the state
of all the individual objects is, right?
This is just an abstract
state of the protocol.
That's enough to tell
me at any given moment,
what's legal and what's not legal.
AUDIENCE: Like you still
need deterministic update
from that whatever abstraction you get
like typically you don't get that.
Well, right.
So there's this subtle
distinction between determinism
of an automaton and input output
determinism of the system.
The system is input output,
non deterministic, right?
But the specification is
a deterministic automaton.
And I don't wanna make that point here,
because It's too subtle.
AUDIENCE: Another question
on that given the state
is the set of possible
values for the parameters
finite or infinite slot?
Generally infinite, right?
Because well, I I suppose in
practice, there's a limit,
because let's say I'm
sending UDP messages,
you know, there's an
upper limit on the MTU,
the number of bytes that
can be in that message.
But in principle,
if I could send messages of
arbitrary length, for example,
the set of parameter
evaluations could be infinite.
So and that's okay, I
can still deal with this
in various ways SMT solvers
could deal with infinite spaces.
And I can also, you know,
make things finite in
various ways if I want to.
Okay, so here's an example
of a very simple
specification in that form.
So I have protocols exchanging messages.
And those messages are sequence numbers.
And all I want to specify
is that a given sender
doesn't use a given sequence number twice,
and actually QUIC has that property.
So here's what that would look like,
expressed in a language called Ivy.
So an action defined space of events,
and to call in these packet
events and they correspond
to someone sending a
packet in the protocol.
And those events have
parameters, for example,
source endpoint, destination endpoint
and some content that might be
a structure containing
different fields or anything.
Now the next thing I need,
once I have my events
is I need something to represent
the state of the protocol.
And the way I would represent that in Ivy
is the state is defined by a relation
called the relation sequence used.
And you can think of this as
a database table if you want.
And it's just recording
a set of tables S, N
where S is the sender
and N is the sequence number,
and telling you which
sequence number have been used
by which sentence.
So that's all the state
that I need for this particular property.
So I'm initializing it so that
you know at the beginning,
nothing has been used.
And then my guard condition
is expressed in like this,
I say before packet event require
that it's not the case
that the given source
has used during sequence,
according to the
information in this relation
that I stared.
So if that condition is
true, that packet is legal.
And I can execute the
update, which is expressed
in like this, after packet event,
seq_used[src, content.seq] = true;
That means I've just added
one topple to that relation,
remembering the fact that this source,
use the sequence number so I
can't use it again in future.
So it's a simple case of
[murmurs] in this case
a non finite state automaton
that expresses the property
in a deterministic way.
Now have the problem of
actually arriving at the
specification for QUIC
for events and each
layer in that protocol.
I'm gonna have one of those Ivy actions
with a precondition and an update.
So the trouble is,
it's very hard to derive
those things from the RFC.
And there are a couple
of reasons for that.
The first of course, is the
RFC is written in English.
So it's, you know, it's ambiguous.
It's in incomplete, it's
self contradictory, so forth.
And a more subtle reason is that RFC
is not actually compositional.
And I'll talk about both
of these two problems.
Okay.
So first, the question of how
do I resolve that ambiguity?
Okay, so let's say I have a
protocol that has a client
and the server that are
sending messages to each other,
AUDIENCE: I have a question.
This RFC [faintly speaking]
read these documents to be
able to get familiarized
with these, but then it's contradictory.
You can come up with a specifications
[faintly speaking]
Right, exactly.
Well, it's because it's
written in English, you know,
and those contradictions
can be hard to find.
They're actually hard to catch,
because it's no, it's 224
pages of documentation,
AUDIENCE: But these are forms not English.
In other language that is
not contradictory as well.
Hopefully it is implementable.
Yeah.
But it's hard to say,
because at this point,
we don't have an exact
definition of the protocol.
You have these 224 pages of English,
which are essentially
advice on how to implement
and but they're not really
precise enough, you know.
AUDIENCE: But then how
do your specifications
is the compatible but
they're not in English?
[faintly speaking]
And so ultimately,
you can never be sure of that.
Right.
What you need to know is that
somehow that specification
that you wrote is
consistent with the intent
of the protocol designers,
and you have to infer the
intent of the protocol designs.
There's not enough information
in the RFC to do that.
RFC is a good guy, you're
mystically speaking
if you want to implement QUIC,
but it's very far from the
definition of the protocol.
So we'll see how we go about
inferring the type of protocol.
So let's say that I have protocol
and client communicating the server.
And those messages, say
from client and server
are governed by the specification fee,
which looks at the history
and tells me is this message
or not, but of course
feed it could be wrong,
because I've just inferred
this from this English language
document that's very long.
Right?
So what am I going to do is
I'm going to start using fee
and a compositional testing framework.
So I'm going to test
the client in isolation.
I'm going to test the server in isolation.
So let's suppose that
specification fee were too strong.
In other words, it
rules out a message now,
that really ought to be legal
according to the terms
of the protocol designs.
We'll those protocol designers
have built all these clients.
Right?
At some point, the client is
going to put out a message
that violates fee.
And we're gonna have to
look at that and say, okay,
Kleinrock versus the specification wrong
at the beginning could
easily be either one.
All right, so let's say that we consult
with protocol designers, we
have a specific case, right?
And they say, no, the client
you should be able to do that.
So then we have to
weaken the specification
to allow that possibility.
On the other hand,
let's say the specifications
were too strong.
Well, the thing is, I'm using
the same specification feed
that check the clients output to generate
input for the server.
So the specification were to
meet, I'm going to eventually
generate a message going in
here, which really shouldn't
be legal and the server might fail.
It could fail by just
sending out a message
that says protocol error is very common,
or it could crash
or do something else wrong
and you find out later right
So then we've learned
that if the server's wrong
or the specifications wrong, again,
you go to the protocol designers.
And they say, oh, well actually, you know,
that message shouldn't be allowed,
you need to strengthen your specification.
So if I just monitor the client
and server talking to each
other there's no way that I can tell
when my specifications delete,
that if I close the check
and generate using that specification,
I can't, so I'm doing a little
bit of machine learning here,
without the machine to
learn this I'm doing that,
which critical reinforcement.
So gradually, what happens
is I have all these different
clients and servers that had
been designed by the protocol design
implemented protocol design,
and I'm trying to capture
the knowledge they had about
the protocol that they weren't
able to express exactly
in English, right to this
process of compositional testing.
And this says something
about assume-guarantee
specifications that's important.
That is by having an
assumed-guarantee specification.
Then the implementation pushes back
on that specification from both sides.
It tells me when it's too strong,
and it tells me when it's too weak.
Every specification is complex
enough, needs something
pushing back on correcting
in both directions.
So this is another example
of form following function.
I wrote it in this compositional way,
so that I could extract
in that information.
So I'm sorry, it's been a long day.
I'm not sure if my voice
is gonna get to the top.
Okay, so the other point I mentioned was
that the RFC is not really compositional.
Right.
And I would like to explain
that in terms of two classes
of specifications
that I'll call intentional
and extensional.
Execution we'll see if I get them.
AUDIENCE: I got a question.
Yeah.
AUDIENCE: what is been
RFC is not compositional?
[faintly speaking]
So I'm speaking informal,
but I'll explain what I mean.
So to be an extensional
specification, what I mean
is that specification is
externally falsifiable,
in other words, is it violated?
and I can prove it's violated
by external observation.
So it's just like, you know,
Karl Popper idea of a
falsifiable scientific theory,
you have to be able to
falsify it by observation.
Okay, well, it turns out
that most of the statements
in the QUIC standard are
not externally falsifiable.
In other words, there's no
way to prove from the outside
that this statement was actually finally.
Okay.
And that's actually fine,
because the RFC is really just
heuristic information, right?
It's telling me how I should implement
the thing on the inside.
And it's being written by the
people who implemented it.
Right.
So that's great information,
but from the point of
view of a specification,
it's not great.
So as a result, the specification
that we write formally
might have to be somewhat weaker
than what the RFC is trying to say.
Not everything in the RFC can be captured
as an extensional specification.
And I'll just give you a simple example.
So let's say we have a client
starting a connection to the server.
So it sends that initial
crypto message client Hello,
and includes in that packet connection ID,
which is a nonce, it's a
cryptographically random number
that's going to be used to
identify this connection.
Okay, well, the server is
going to come back and say,
Okay, I'll start a connection.
Alright.
And it says, okay, sir,
hello, acknowledgement.
And here's a new CID that I want to use.
I want you to switch to use that number
instead of your nonce.
So nonce might be used for retransmission.
But at some point, we switch
over to the server's choice.
I don't know why they do it this way.
But that's how they do it.
Alright, so the specification says,
once you receive this service ID,
you must henceforth use it
and all successive messages.
Okay, the trouble is,
let's suppose that I send
out the next message.
Well, it shouldn't have to service the ID
except, I don't know, did
this message come out?
After I received the
service CID or before,
that's an internal event,
I can't tell how things
were ordered internally.
Now, I can't tell what
delays might have occurred
in the operating system
and delivering that packet
and so forth.
So someone looking at the wire
from the outside can't talk,
in general, in this case, they can talk
and the reason is that this message
logically has to come after that.
So that's how we write the specification.
You can't write what
they wrote in the RFC.
But what we can say is
that if this message logically
must occur after that message,
then it shouldn't contain the
CID if it is non-essential.
Otherwise we have no way from the outside
of showing that specification violate.
And the RFC is just full
of statements like this
that are heuristically useful,
but there's no way to
prove from the outside
that those specifications were violated.
Okay, so we'll have to deal with that.
AUDIENCE: So quick question.
Yeah.
AUDIENCE: My basic question
is that, do you have
to model things like
the nonce is encrypted
in the public key of
the server and all that?
Right.
So well, for example, the
not there's another good
example of a non-fossil
supposed to have specification.
The specification says
that non CID should be unpredictable.
How do you test unpredictability?
Very hard.
So, we don't specify that right?
It should be connected to
a cryptographic system,
a source of randomness, you know,
not just you know, Unix
random number generator,
but I can't check that from the outside.
I have to look on the inside
to find out if you have done.,
So all right, but I'll do what I can.
I'll specify what I can
so this is what we do.
I did this work jointly
with the North souk
from the University of
Illinois at Chicago.
And we started with that draft RFC
and tried to find extensional statements
and things that I could specify
that we could specify from the outside.
And of course, we were wrong.
Okay, naturally reinterpreting
this very complex document.
So what we did was we
took four implementations
that came from the working group,
and we started testing those
components in isolation,
generating from the spec and checking..
We found lots and lots of problems
and there was we found
cases where spec was wrong.
We found cases where the
implementation was wrong.
And whenever we found
that the spec was wrong,
we would refine it, right?
We would keep improving it, you know,
we can get or strengthening
it appropriately.
there the specification was
written in this language
called, Ivy and I use the
facility of the tool Ivy
I've been working on for a
while, that can do specification
based testing, it can do
that automatic generation
randomly from the spec.
And it can do the validation of the VM.
So we wanna put a specification
that was about 3000 lines
of code in that language
that I showed you.
It's of moderate complexity.
And it's really certainly not complete.
It certainly doesn't say everything
that you would want to
say about the protocol.
But we took it to the point
where it could interact
with the real clients and servers,
without those clients and
service detecting protocol errors
and send web pages back and forth.
So essentially, the spec
became strong enough that you
could think of it as a really
inefficient implementation
of the protocol, you could play the game
with the real clients and servers.
And of course, on the way
we found lots of errors in those things.
So I'll talk about some of
the issues that we found
both in the implementations
and in the protocol itself.
So I spent about four
weeks doing this testing
of the different implementations.
In that time 27 errors were detected
in various implementations, about half
of them were just crashes.
Now they're all written
in C, and they crash.
Another half of them were
compliance violations,
things where the server
or the client admitted
a bogus message, according to
them, according to the site.
And a couple of the cases
were just cases that I
called progress failures,
where there weren't any violations.
But no data was transferred
during the test.
And so something was probably
so you know, there were things
that were coming out, like these crashes
and progress failures
that weren't actually
specification violations,
but that we nonetheless detected.
So out of those besides the crashes,
four of these were
considered to be exploitable.
Which is interesting
because we weren't looking
for security problems.
We were just trying to
validate that these things,
these things satisfied protocol,
but I'll talk about a couple
of those later that were exploitable.
Four of them resulted in
ambiguities in the standard, right?
There were things when we
look back at it, we realized
that someone implemented this way,
because the standard just wasn't complete.
It didn't have enough information.
Right.
So those things were,
you know, were corrected.
Most of these errors, once
you found the root cause,
you could say that they were discovered
because of this adversarial stimulus.
In other words, the reason
that they were found
and they weren't found
before was that we we're
just throwing random inputs.
So messages came in strange orders.
They had strange parameter
values, they hit the
corner cases that hadn't been
tested and system crashed.
And moreover, in every
case, where we could assign
a root cause which was
all before, we found
that it was either adversarial stimulus
or compliance checking
that allowed us to find
that bug that hadn't been found before.
So that sort of validates
that original intuition
that I talked about that
what was missing from the
process was adversarial stimulus,
and compliance checking.
Okay, so I'll just give
you a couple of examples
of sort of bugs that came up.
And the first one turned out to be
a denial of service scenario.
And this was a case where we
had the specification acting
the role of a client, and a
real server acting role server.
And the specification
was talking to the server
by sending packets from an IP address.
It's going along in the
protocol for a while,
and at some point, just randomly,
it sends a packet from IP address B.
And that's okay
because the protocol should
allow mobility of clients.
And the way that's dealt with
is that the server is supposed
to come back with a frame
that's called a half challenge.
To this new address,
and the client has to send
back a packet that validates
that it really owns this address.
We know that this is not a spoof
is someone's not spoofing or connection.
Okay, well, that's what would happen.
Normally, what happened in
this test was the next packet
just randomly came back from address A.
Okay, server sees that and
says, I'll abandon that probe,
and I'll start probing
this address instead.
So I think you can start to
see what's gonna go wrong here,
right, that if you keep alternating
between these addresses,
you'll never make any progress.
And this, of course, was
something that wasn't found in the
existing testing because no
actual client would do that.
And in fact, I don't even know
how well they tested ability.
So okay, so the probe never
finishes what actually happened
in practice was, those probes
were never sent, they were
on a timer, and they got
canceled before it even went out.
So it looked like the server just stopped.
It sent out nothing.
Right?
And so that was a bug in the server.
But the more interesting
thing was, when we talk to the
designers of the protocol,
they say, oh, that's a denial of service.
Because an attacker who could
replay my packets from a
different address just randomly
could prevent you from
ever making progress.
And so they put some mitigation's
into the standard for that.
So that's an example of
adversarial stimulus, if you really
like to come up with that in
an actual network scenario.
Okay, so another one was a
data leak in the sort of style
of the heartbleed things
heartbleed bug of open SSL.
And what happened there
was a server sent a
stream data frame containing
bytes that were just read
off the end of a buffer, so
just random memory contents,
which obviously is a
security problem, you know,
that might contain key
information or anything else.
So the way you could see that
in that run and protocol was,
this is the HTML page that
was supposed to be delivered.
And what we actually
saw was a piece of it.
Then a bunch of non ASCII data.
And of course that
violated the specification.
So we caught this because
it was a compliance error,
but also because of the
adversarial stimulus,
because it turned out
that this only happened,
because we had set the random
generation of flow control
information and set those
parameters in a particular way
that cut off the message
just like this, right.
And then also it happened that he randomly
did not acknowledge the data,
and was only on the reset that it read off
the end of the buffer.
So those two things had to combine
and that had not seen before.
So it was a combination of
checking against the spec,
with this adversarial randomized
stimulus that found this
and obviously, this is a serious problem
if you're trying to have a secure server.
Okay, so just to conclude,
what can we say
about specifications based
on this kind of experience?
Well, the first thing I
would say is that you should
think of a specification
as a tool with a use
and understand that use.
So in the case of QUIC,
we knew that we wanted
to generate adversarial tests.
And we knew that we wanted
to check compliance.
Okay.
And additionally, you
know, we wanted to capture
some protocol knowledge
this implementations
So given that, that told
us the form the content
and the process of the
specification, so it told us
that we wanted to have a
compositional specification
and assume guarantees, right?
And so we can do this isolated testing
and we can both generate a check.
They told us that we wanted
extensional safety properties
in other words properties
that could be falsified
in finite time, which is
all we can do with a tester.
And I said, we wanted a
deterministic guarded command form
to make generation and checking efficient,
and so we could run tests fast enough.
Okay.
And, you know, we also said
that we're going produce
this specifications by iterating
and testing individual components.
Okay, so function with where we began
and that told us form content and process.
Okay, so if I had a different
purpose for specification
for example, using it as a lemma
and approve the specification
might look very different.
Okay, another interesting
point was that a specification
need not be complete or perfect
to serve its function well.
And so what we found in
the case of QUIC was that a
modest effort put into
writing a specification
is 3 000 months of writing
paid off significant dividends
in terms of finding errors
in the implementation,
finding problems with
the protocol definition.
Right.
So we didn't wind up at
a perfect specification,
but we wound up with
one it was good enough
for the purpose at hand.
And that's important
because if we needed that
specification to be perfect,
it could take a very long time.
In fact, we probably
never would arrive at one.
Okay, so specification is
really an iterative process.
And it's a social process,
right with process of coming
to a consensus between
different designers.
And in particular, a spec
has to have corrective forces
pushing on both sides telling
you when it's too strong,
telling you when it's too weak.
Right.
And we saw that specifications and address
a significant pain points
in protocol development,
even if we're not doing
formal proofs, right.
So we can capture knowledge,
we can do unit testing
in a systematic way, unit
testing is very hard.
And it's very time consuming.
And hopefully,
we can avoid release of non
compliant implementations,
while which caused all of
those problems with TLS.
And I think that if we
can do that, you know,
that's gonna be very helpful in terms
of avoiding future issues with
QUIC ultimately, of course,
if you'd like formal
verification, which I do,
then also you can take that spec
and you can implement to it formally.
Right.
And the interesting thing about
this is that spec provides
the interface between
the formal verified world
and the wild world outside
of unverified software.
Because if we verify
implementation to the spec,
we at least know that everything else
has been tested against that spec.
So we have a good chance
of being compatible
with the rest of the world.
I think that is going to be
very helpful for formal methods,
right?
But it's ultimately,
if we can interest people
who don't do proofs
now in the value of doing
formal specifications
because of its practical benefit.
Right, then formal verification
is a lot wider application
because now the specifications
aren't unicorns, right,
you know, they actually
exist in the real world.
And then formal verification
will often be better
connected to practice.
So anyway, thank you very much.
[audience clapping]
Question?
AUDIENCE: I did a question
on that last point actually.
If you did yourself
wherever the RFC is being negotiated,
and written in disguise and so forth,
and having that group of people,
user existing specifications and tools,
or is it
that too far in the future?
Okay.
So I would like to see the case
where people who are
developing that specification,
right, could do this, you know,
could write the formal
specification along with it.
And, you know, I went and
I presented this work,
at Seedcom, you know,
and I've talked to various
networking people about that,
I think at this point, you know,
there's a lot of interest in that mainly
because, you know, of the
bugs that came out, you know,
all of the you know, the the GitHub issues
that were corrected in this process,
and everyone's very
glad to have, you know,
the testing done.
But the next phase is to say,
okay, now have a look at the spec.
Right.
try to carry it further refining.
I don't think we're ready
for that, mainly because
I don't think that specification
formalism, you know,
is yet well enough targeted to
that group of people, right,
it was something that
a formal methods person
could understand easily enough.
But the process of doing a specification,
I think, is still somewhat
alien to those people.
So what I'm hoping is,
you know, to generate
enough interest in that, you know,
so that people will want to take that up.
And one of the things that I'm doing
with Lemma actually
developing class, you know,
that starts with very
simple internet protocols,
and shows you know,
how you develop formal
specifications about them.
What you can learn about those
protocols that you can't,
you know, by other techniques.
Hopefully that formalism will
eventually get to a point
where practitioners can use
it, but we're not there yet.
AUDIENCE: There is an his
existing set of unit tests
may provide us towards
bootstrapping purposes.
They might I don't know,
you could certainly take
those unit tests, right.
And you could check them
against the specification.
Right?
You could say, do you agree or not agree?
So that could be a source of information?
We didn't use that.
And frankly, that's because, you know,
each implementer has their own framework
now in which they're doing testing.
And we were trying to do something
that was completely generic.
So it just talks to those
servers over the internet.
The other thing is, that, as always,
the unit tests are a little thing, right?
Because unit testing is so hard to do,
right that people don't
really do it sufficiently.
AUDIENCE: [faintly speaking]
Right.
So suppose you had a scenario
where people were actually
starting by formalizing their
protocols at the beginning.
Now, which is of course
what I like to happen.
Well, whenever you write a specification
is that complex, right,
you're going to be wrong.
In other words, that
specification is not really
going to capture what you intended
and you're probably not
entirely sure what you intended.
And so you remember I
made that point about
always specification needs
corrective forces on both sides,
right, it needs the supplier
and the user, so to speak.
So then we gradually arrive
at a specification right,
that we can agree satisfies our needs.
So you wanna think about developing
a complex specification as
a social process, right?
Instead of saying, oh, I the
architect will hand it down,
and then you will implement to it.
Because that won't work, Ivy the architect
have not sufficiently
clarifying, you know,
to write that's fiction and
make sure that it's right.
AUDIENCE: [faintly speaking]
Right.
So you want to formally verify that code,
that open source code actually
satisfies this specification.
Instead of just testing
it, which is right,
which is I'm gonna hop.
So the thing about that
is that code is probably
written in a language, for
example, in C, it's gonna make it
extremely laborious to do
a full formal verification.
In other words, you know,
we can do verification
of, you know, high level
properties of C code.
The current rate for doing
that is maybe 500 lines
per graduate student
per year, now of C code.
And so I think that we
wanna get to the point
where we do this, we need,
you know, maybe two orders
of magnitude higher productivity,
doing the actual formal verification.
I think that one place where
we can get that is just
not writing the implementation
in languages like C.
In other words, the most
efficient way to verify
C Programming is to translate it
into a different language and verify that.
So and you know, languages
like rust, you know,
are an example of ways in
which you can write programs
where you don't have to
think about aliasing.
And you can use higher level languages
and that so Ivy is a language
that's designed to make
formal verification of things
like distributed protocols
is easier, but still pretty insufficient.
And so that's one issue.
And the other thing is, you
really need to think hard
about methodology within a
particular domain, right?
So that you can take all these powerful
proof automation techniques,
we have like SMT,
to be able to use them in a
way that they're transparent
and stable and predictable, and so on,
which they are now so
we have a long way to go
before we can take say that
C code implementation of QUIC
and verify it, you know, and
that isn't the first thing
that I wish I would have tried.
AUDIENCE: So you think
that there's any code
for being able to generate the
specification automatically
from implementations the learning at least
close to being correct?
Yeah.
Well, as I said, I was sort of
doing reinforcement learning,
you know, manually there.
Right.
The question is, well,
could you do that reinforcement
learning automatically?
I think, to some extent,
you know, there are various
inference techniques that,
especially that apply
to finite state systems,
you know, that might be able
to infer, you know, some
finite state machine,
it's part of the protocol.
QUIC, doesn't have a lot of
finite state machines on it.
I think that we're also a
long way from being able
to do inference at that, at
that level of complexity.
Partly because think
about 3 000 lines of code.
There are a lot of programs,
think about how much data
that you would need, right
to disambiguate that in order
to learn that concept really
be an astronomical data.
So I think that there are probably ways
that you can synthesis, right,
to synthesize parts of
this, for example, in a
sketching sort of a way where
you leave some components out
and you infer those
components by observation.
And that could be one
way to help, you know,
to make that process of writing mean,
that goes to a question, right?
You know, how can you do
this in a way that people
can pick it up without
reducing the cognitive load?
On specifiers yeah,
but at this point, now, as I said,
we could do some limited sketching things,
but a specification of that complexity
would be very hard to
infer, or unlock them.
AUDIENCE: Are you able to
break the specification
down into components at all,
as part specification
doesn't really apply.
Well, okay, that would be so the
specification is broken down
into these different layers.
And all those layers are communicating
through these relations and
the specification state.
it's a little guarded command
parallel program if you will.
But the trouble is that you cannot...
It's very hard to isolate
those different players.
Okay, that's probably goes to
a previous question as well.
That's because those implementations
are not written in a layered way.
Right?
There's nothing, you can't
break them apart into pieces
and say, oh, this is the
packet layer, you know,
this is the frame layer, and so on.
There's no defined interface
between those things.
If you've implemented it
with a defined interface,
then you definitely can do that.
And you can test those different layers
much more effectively and efficiently,
and eventually get down to the point
where you could formally verify, right.
Breaking that specification
into components.
for testing purposes, it's hard
because I can't break the
implementation into components.
It's essentially one
big ball of spaghetti.
AUDIENCE: So maybe
building on Kant's question
a bit earlier, but like, so
how much do you think that sort
of foreignness of adoption
of this is just due to having
to think of idea different
sort of programming model,
versus really the concept
of having a specification.
Right.
Most of it is the concept
of having a specification.
And all of these ideas that
I was talking about saying,
what does it mean for that
specification to be extensional?
You know, when I write
that, how is it different
from the way that I write the RFC?
Right?
And some of that has to
do with the fact that,
you're trying to think abstractly
and not about specific concrete
behaviors of the thing.
And that's just something that, you know,
protocol designers are not accustomed too
hardware designers are not accustomed too.
And that's a barrier that
you have to break through,
you know, anytime that you wanna introduce
formal methods into a process.
But I think that the main
thing is actually, this idea
that, you know, a wire
specification of the thing
is really different from
what people write in terms
of the RFC, you know, and I
think the getting that idea
across is probably the main
you know, the main barrier.
So that's something that
I'm looking at, you know,
I'm working on this class
and how can you explain that.
AUDIENCE: To realize extensional part
think about what's on the wire.
Think about just what's on the wire
as opposed to what's going on.
FABIO: Well, I want to thank Ken again.
[audience clapping]
