here - (Nathaniel) Charlton`s page

Formal reasoning about
runtime code update
Billiejoe (Nathaniel) Charlton
Ben Horsfall
Bernhard Reus
HotSWUp 2011
Outline
•
Discuss how to do formal proofs about safety of runtime code updates
- Using a (relatively) new variant of Hoare logic
- specifically, Hoare logic with nested Hoare triples
Outline
•
Discuss how to do formal proofs about safety of runtime code updates
- Using a (relatively) new variant of Hoare logic
- specifically, Hoare logic with nested Hoare triples
•
Example of formally specifying safety of a runtime update
- for a model of an updateable web server from:
“Formalizing Dynamic Software Updating”
(Gavin Bierman, Michael Hicks, Peter Sewell, Gareth Stoyle)
Outline
•
Discuss how to do formal proofs about safety of runtime code updates
- Using a (relatively) new variant of Hoare logic
- specifically, Hoare logic with nested Hoare triples
•
Example of formally specifying safety of a runtime update
- for a model of an updateable web server from:
“Formalizing Dynamic Software Updating”
(Gavin Bierman, Michael Hicks, Peter Sewell, Gareth Stoyle)
- (time permitting) glimpse of this proof done in Crowfoot,
our semi-automated verification tool
Hoare logic
•
A formal logic for proving things - triples - about programs, e.g.
•
Meaning: if we run the program in a state where the precondition holds
- then the program doesn’t crash
- and if it terminates, the postcondition will hold
Hoare logic
•
A formal logic for proving things - triples - about programs, e.g.
•
Meaning: if we run the program in a state where the precondition holds
- then the program doesn’t crash
- and if it terminates, the postcondition will hold
[ ] = heap access
(indirection)
Hoare logic
•
A formal logic for proving things - triples - about programs, e.g.
•
Meaning: if we run the program in a state where the precondition holds
- then the program doesn’t crash
- and if it terminates, the postcondition will hold
•
BUT: Conventional Hoare logic assumes that program’s code is fixed
- because pre- and post-condition talk only about data, not code
- so how can one reason about dynamic software updates? 
Key idea: nested Hoare triples
•
Writing code onto the heap:
Key idea: nested Hoare triples
•
Writing code onto the heap:
Key idea: nested Hoare triples
•
Writing code onto the heap:
Key idea: nested Hoare triples
•
Writing code onto the heap:
•
Invoking code stored on the heap:
Key idea: nested Hoare triples
•
Writing code onto the heap:
•
Invoking code stored on the heap:
Key idea: nested Hoare triples
•
Writing code onto the heap:
•
Invoking code stored on the heap:
Nested Hoare triples
•
Only understood recently
- because underlying mathematics is complicated
- started with [Honda, Yoshida, Berger - LICS 05]
- further developed by others since then
Nested Hoare triples
•
Only understood recently
- because underlying mathematics is complicated
- started with [Honda, Yoshida, Berger - LICS 05]
- further developed by others since then
•
Now we have the theory, can we use it to reason about programs?
- We thought: let’s give it a go 
Nested Hoare triples
•
Only understood recently
- because underlying mathematics is complicated
- started with [Honda, Yoshida, Berger - LICS 05]
- further developed by others since then
•
Now we have the theory, can we use it to reason about programs?
- We thought: let’s give it a go 
•
We borrowed an example from the literature: a model of an updateable
web server
- One particular runtime update: adding logging to the web server
- Code on slides is mercilessly pruned compared to our paper
heapcell loop_h;
proc loop(q)
heapcell version;
{
locals e;
proc main()
e := new 0;
{
call getEvent(q,e);
locals q;
call handleEvent(e);
call maybe_update();
[version] := 1;
q := new 0;
eval [loop_h](q)
}
call mkQueue(q);
}
[loop_h] := loop(_);
proc handleEvent(e) {...}
eval [loop_h](q)
proc getEvent(q,e) {...}
proc mkQueue(q) {...}
This slide: initial version of model web server
heapcell loop_h;
proc loop(q)
heapcell version;
{
locals e;
proc main()
e := new 0;
{
call getEvent(q,e);
call handleEvent(e);
locals q;
call maybe_update();
eval [loop_h](q)
[version] := 1;
q := new 0;
}
call mkQueue(q);
}
[loop_h] := loop(_);
proc handleEvent(e) {...}
eval [loop_h](q)
proc getEvent(q,e) {...}
proc mkQueue(q) {...}
heapcell loop_h;
proc loop(q)
heapcell version;
{
locals e;
proc main()
{
Record that we
start at version 1
e := new 0;
call getEvent(q,e);
locals q;
call handleEvent(e);
call maybe_update();
[version] := 1;
q := new 0;
eval [loop_h](q)
}
call mkQueue(q);
}
[loop_h] := loop(_);
proc handleEvent(e) {...}
eval [loop_h](q)
proc getEvent(q,e) {...}
proc mkQueue(q) {...}
heapcell loop_h;
proc loop(q)
heapcell version;
{
locals e;
proc main()
{
locals q;
e := new 0;
call getEvent(q,e);
Create a
queue
for events
call handleEvent(e);
call maybe_update();
[version] := 1;
q := new 0;
eval [loop_h](q)
}
call mkQueue(q);
}
[loop_h] := loop(_);
proc handleEvent(e) {...}
eval [loop_h](q)
proc getEvent(q,e) {...}
proc mkQueue(q) {...}
heapcell loop_h;
proc loop(q)
heapcell version;
{
locals e;
proc main()
e := new 0;
{
call getEvent(q,e);
locals q;
call handleEvent(e);
call maybe_update();
[version] := 1;
q := new 0;
eval [loop_h](q)
}
call mkQueue(q);
}
[loop_h] := loop(_);
proc handleEvent(e) {...}
eval [loop_h](q)
proc getEvent(q,e) {...}
proc mkQueue(q) {...}
‘loop’ procedure kept on the
heap, so we can update it later
heapcell loop_h;
proc loop(q)
heapcell version;
{
locals e;
proc main()
e := new 0;
{
call getEvent(q,e);
locals q;
call handleEvent(e);
call maybe_update();
[version] := 1;
q := new 0;
eval [loop_h](q)
}
call mkQueue(q);
}
[loop_h] := loop(_);
proc handleEvent(e) {...}
eval [loop_h](q)
proc getEvent(q,e) {...}
proc mkQueue(q) {...}
heapcell loop_h;
proc loop(q)
heapcell version;
{
locals e;
proc main()
e := new 0;
{
call getEvent(q,e);
locals q;
call handleEvent(e);
call maybe_update();
[version] := 1;
q := new 0;
eval [loop_h](q)
}
call mkQueue(q);
}
[loop_h] := loop(_);
proc handleEvent(e) {...}
eval [loop_h](q)
proc getEvent(q,e) {...}
proc mkQueue(q) {...}
heapcell loop_h;
proc loop(q)
heapcell version;
{
locals e;
proc main()
e := new 0;
{
call getEvent(q,e);
locals q;
call handleEvent(e);
call maybe_update();
[version] := 1;
q := new 0;
eval [loop_h](q)
}
call mkQueue(q);
}
[loop_h] := loop(_);
proc handleEvent(e) {...}
eval [loop_h](q)
proc getEvent(q,e) {...}
proc mkQueue(q) {...}
heapcell loop_h;
proc loop(q)
heapcell version;
{
locals e;
proc main()
e := new 0;
{
call getEvent(q,e);
locals q;
call handleEvent(e);
call maybe_update();
[version] := 1;
q := new 0;
eval [loop_h](q)
}
call mkQueue(q);
}
[loop_h] := loop(_);
proc handleEvent(e) {...}
eval [loop_h](q)
proc getEvent(q,e) {...}
proc mkQueue(q) {...}
heapcell loop_h;
proc loop(q)
heapcell version;
{
locals e;
proc main()
e := new 0;
{
call getEvent(q,e);
locals q;
call handleEvent(e);
call maybe_update();
[version] := 1;
q := new 0;
eval [loop_h](q)
}
call mkQueue(q);
}
[loop_h] := loop(_);
proc handleEvent(e) {...}
eval [loop_h](q)
proc getEvent(q,e) {...}
proc mkQueue(q) {...}
What does our update do?
•
Add logging to server – every event will be logged
•
Overwrite ‘loop’ code with a new version
•
Add three new procedures:
- loopPrime – the new event-handling loop
- logEvent – creates a log entry for a given event
- mkEmptyLog – used during transition to create an empty log
heapcell loopPrime_h;
proc loopPrime_2(q,log)
heapcell logEvent_h;
{
heapcell mkEmptyLog_h;
locals e;
proc mkEmptyLog_2(log) {...}
e := new 0;
call getEvent(q,e);
proc logEvent_2(e,log) {...}
eval [logEvent_h](e,log);
proc loop_2(q)
call maybe_update();
{
locals log;
log := new 0;
eval [mkEmptyLog_h](log);
eval [loopPrime_h](q,log)
}
call handleEvent(e);
eval [loopPrime_h](q,log)
}
heapcell loopPrime_h;
proc loopPrime_2(q,log)
heapcell logEvent_h;
{
heapcell mkEmptyLog_h;
locals e;
proc mkEmptyLog_2(log) {...}
e := new 0;
call getEvent(q,e);
proc logEvent_2(e,log) {...}
eval [logEvent_h](e,log);
call handleEvent(e);
proc loop_2(q)
call maybe_update();
{
eval [loopPrime_h](q,log)
locals log;
}
log := new 0;
eval [mkEmptyLog_h](log);
eval [loopPrime_h](q,log)
}
Replacement for ‘loop’:
“transitional function” to take
system into new version
heapcell loopPrime_h;
proc loopPrime_2(q,log)
heapcell logEvent_h;
{
heapcell mkEmptyLog_h;
locals e;
proc mkEmptyLog_2(log) {...}
e := new 0;
call getEvent(q,e);
proc logEvent_2(e,log) {...}
eval [logEvent_h](e,log);
call handleEvent(e);
proc loop_2(q)
call maybe_update();
{
eval [loopPrime_h](q,log)
locals log;
}
log := new 0;
eval [mkEmptyLog_h](log);
eval [loopPrime_h](q,log)
}
Set up an empty
Log structure
heapcell loopPrime_h;
proc loopPrime_2(q,log)
heapcell logEvent_h;
{
heapcell mkEmptyLog_h;
locals e;
proc mkEmptyLog_2(log) {...}
e := new 0;
call getEvent(q,e);
proc logEvent_2(e,log) {...}
eval [logEvent_h](e,log);
call handleEvent(e);
proc loop_2(q)
call maybe_update();
{
eval [loopPrime_h](q,log)
locals log;
}
log := new 0;
eval [mkEmptyLog_h](log);
eval [loopPrime_h](q,log)
}
Pass control to the new
event handling loop
heapcell loopPrime_h;
proc loopPrime_2(q,log)
heapcell logEvent_h;
{
heapcell mkEmptyLog_h;
locals e;
proc mkEmptyLog_2(log) {...}
e := new 0;
new event
handling loop
call getEvent(q,e);
proc logEvent_2(e,log) {...}
eval [logEvent_h](e,log);
call handleEvent(e);
proc loop_2(q)
call maybe_update();
{
eval [loopPrime_h](q,log)
locals log;
}
log := new 0;
eval [mkEmptyLog_h](log);
eval [loopPrime_h](q,log)
}
The new behaviour:
Log every event
before processing
proc maybe_update()
{
if [version] = 1 then
{
if nondet then { skip }
else
Non-deterministically
decide whether to
update or not
{
[version] := 2;
[loop_h]
:= loop_2(_);
[loopPrime_h] := loopPrime_2(_,_);
[logEvent_h]
:= logEvent_2(_,_);
[mkEmptyLog_h] := mkEmptyLog_2(_)
}
}
else { skip }
}
proc maybe_update()
{
if [version] = 1 then
{
if nondet then { skip }
else
{
Increment
version
number
[version] := 2;
[loop_h]
:= loop_2(_);
[loopPrime_h] := loopPrime_2(_,_);
[logEvent_h]
:= logEvent_2(_,_);
[mkEmptyLog_h] := mkEmptyLog_2(_)
}
}
else { skip }
}
proc maybe_update()
{
if [version] = 1 then
{
if nondet then { skip }
else
{
[version] := 2;
[loop_h]
:= loop_2(_);
[loopPrime_h] := loopPrime_2(_,_);
[logEvent_h]
:= logEvent_2(_,_);
[mkEmptyLog_h] := mkEmptyLog_2(_)
}
}
else { skip }
}
Overwrite ‘loop’
with new version
proc maybe_update()
{
if [version] = 1 then
{
if nondet then { skip }
else
{
[version] := 2;
[loop_h]
:= loop_2(_);
[loopPrime_h] := loopPrime_2(_,_);
[logEvent_h]
:= logEvent_2(_,_);
[mkEmptyLog_h] := mkEmptyLog_2(_)
}
}
else { skip }
}
Load new
procedures
onto heap
What are we trying to prove?
We need to say what we are trying to prove!
- For each procedure we give a specification e.g. for ‘getEvent’:
- Concentrate on memory safety
- i.e. the right kind of data structures are present in the right place
- We won’t go into details of Queue, Event, Log predicates...
To specify the effect of maybe_update(),
we need this monster definition
Code(v) describes the kind of code
present on the heap in version v
Code(v) is nested
inside itself!
Specifications
For ‘loop’:
Specifications
For ‘loop’:
For ‘logEvent_2’:
Specifications
For ‘loop’:
For ‘logEvent_2’:
For ‘maybe_update’:
•
With these specifications we can prove the update safe
•
Proof done semi-automatically by our verification tool
Summary
•
Discussed how to do formal proofs about safety of runtime code
updates
- using Hoare logic with nested triples
•
Talked through how to formally specify safety of an update
- for a model of an updateable web server from:
“Formalizing Dynamic Software Updating”
(Bierman, Hicks, Sewell, Stoyle)
•
(maybe) glimpsed Crowfoot, our semi-automated verification tool for
such safety proofs
The End