Upload
joshua-oneal
View
214
Download
0
Embed Size (px)
Citation preview
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;
heapcell version;
proc main()
{
locals q;
[version] := 1;
q := new 0;
call mkQueue(q);
[loop_h] := loop(_);
eval [loop_h](q)
}
proc loop(q)
{
locals e;
e := new 0;
call getEvent(q,e);
call handleEvent(e);
call maybe_update();
eval [loop_h](q)
}
proc handleEvent(e) {...}
proc getEvent(q,e) {...}
proc mkQueue(q) {...}
This slide: initial version of model web server
heapcell loop_h;
heapcell version;
proc main()
{
locals q;
[version] := 1;
q := new 0;
call mkQueue(q);
[loop_h] := loop(_);
eval [loop_h](q)
}
proc loop(q)
{
locals e;
e := new 0;
call getEvent(q,e);
call handleEvent(e);
call maybe_update();
eval [loop_h](q)
}
proc handleEvent(e) {...}
proc getEvent(q,e) {...}
proc mkQueue(q) {...}
heapcell loop_h;
heapcell version;
proc main()
{
locals q;
[version] := 1; q := new 0;
call mkQueue(q);
[loop_h] := loop(_);
eval [loop_h](q)
}
proc loop(q)
{
locals e;
e := new 0;
call getEvent(q,e);
call handleEvent(e);
call maybe_update();
eval [loop_h](q)
}
proc handleEvent(e) {...}
proc getEvent(q,e) {...}
proc mkQueue(q) {...}
Record that we start at version 1
heapcell loop_h;
heapcell version;
proc main()
{
locals q;
[version] := 1;
q := new 0;
call mkQueue(q); [loop_h] := loop(_);
eval [loop_h](q)
}
proc loop(q)
{
locals e;
e := new 0;
call getEvent(q,e);
call handleEvent(e);
call maybe_update();
eval [loop_h](q)
}
proc handleEvent(e) {...}
proc getEvent(q,e) {...}
proc mkQueue(q) {...}
Create a queue for events
heapcell loop_h;
heapcell version;
proc main()
{
locals q;
[version] := 1;
q := new 0;
call mkQueue(q);
[loop_h] := loop(_); eval [loop_h](q)
}
proc loop(q)
{
locals e;
e := new 0;
call getEvent(q,e);
call handleEvent(e);
call maybe_update();
eval [loop_h](q)
}
proc handleEvent(e) {...}
proc getEvent(q,e) {...}
proc mkQueue(q) {...}
‘loop’ procedure kept on theheap, so we can update it later
heapcell loop_h;
heapcell version;
proc main()
{
locals q;
[version] := 1;
q := new 0;
call mkQueue(q);
[loop_h] := loop(_);
eval [loop_h](q)}
proc loop(q)
{
locals e;
e := new 0;
call getEvent(q,e);
call handleEvent(e);
call maybe_update();
eval [loop_h](q)
}
proc handleEvent(e) {...}
proc getEvent(q,e) {...}
proc mkQueue(q) {...}
heapcell loop_h;
heapcell version;
proc main()
{
locals q;
[version] := 1;
q := new 0;
call mkQueue(q);
[loop_h] := loop(_);
eval [loop_h](q)
}
proc loop(q)
{
locals e;
e := new 0;
call getEvent(q,e);
call handleEvent(e); call maybe_update();
eval [loop_h](q)
}
proc handleEvent(e) {...}
proc getEvent(q,e) {...}
proc mkQueue(q) {...}
heapcell loop_h;
heapcell version;
proc main()
{
locals q;
[version] := 1;
q := new 0;
call mkQueue(q);
[loop_h] := loop(_);
eval [loop_h](q)
}
proc loop(q)
{
locals e;
e := new 0;
call getEvent(q,e);
call handleEvent(e);
call maybe_update(); eval [loop_h](q)
}
proc handleEvent(e) {...}
proc getEvent(q,e) {...}
proc mkQueue(q) {...}
heapcell loop_h;
heapcell version;
proc main()
{
locals q;
[version] := 1;
q := new 0;
call mkQueue(q);
[loop_h] := loop(_);
eval [loop_h](q)
}
proc loop(q)
{
locals e;
e := new 0;
call getEvent(q,e);
call handleEvent(e);
call maybe_update();
eval [loop_h](q)}
proc handleEvent(e) {...}
proc getEvent(q,e) {...}
proc mkQueue(q) {...}
heapcell loop_h;
heapcell version;
proc main()
{
locals q;
[version] := 1;
q := new 0;
call mkQueue(q);
[loop_h] := loop(_);
eval [loop_h](q)
}
proc loop(q)
{
locals e;
e := new 0;
call getEvent(q,e);
call handleEvent(e);
call maybe_update();
eval [loop_h](q)
}
proc handleEvent(e) {...}
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;
heapcell logEvent_h;
heapcell mkEmptyLog_h;
proc mkEmptyLog_2(log) {...}
proc logEvent_2(e,log) {...}
proc loop_2(q)
{
locals log;
log := new 0;
eval [mkEmptyLog_h](log);
eval [loopPrime_h](q,log)
}
proc loopPrime_2(q,log)
{
locals e;
e := new 0;
call getEvent(q,e);
eval [logEvent_h](e,log);
call handleEvent(e);
call maybe_update();
eval [loopPrime_h](q,log)
}
heapcell loopPrime_h;
heapcell logEvent_h;
heapcell mkEmptyLog_h;
proc mkEmptyLog_2(log) {...}
proc logEvent_2(e,log) {...}
proc loop_2(q){
locals log;
log := new 0;
eval [mkEmptyLog_h](log);
eval [loopPrime_h](q,log)
}
proc loopPrime_2(q,log)
{
locals e;
e := new 0;
call getEvent(q,e);
eval [logEvent_h](e,log);
call handleEvent(e);
call maybe_update();
eval [loopPrime_h](q,log)
}
Replacement for ‘loop’:“transitional function” to take system into new version
heapcell loopPrime_h;
heapcell logEvent_h;
heapcell mkEmptyLog_h;
proc mkEmptyLog_2(log) {...}
proc logEvent_2(e,log) {...}
proc loop_2(q)
{
locals log;
log := new 0;
eval [mkEmptyLog_h](log); eval [loopPrime_h](q,log)
}
proc loopPrime_2(q,log)
{
locals e;
e := new 0;
call getEvent(q,e);
eval [logEvent_h](e,log);
call handleEvent(e);
call maybe_update();
eval [loopPrime_h](q,log)
}
Set up an empty Log structure
heapcell loopPrime_h;
heapcell logEvent_h;
heapcell mkEmptyLog_h;
proc mkEmptyLog_2(log) {...}
proc logEvent_2(e,log) {...}
proc loop_2(q)
{
locals log;
log := new 0;
eval [mkEmptyLog_h](log);
eval [loopPrime_h](q,log)}
proc loopPrime_2(q,log)
{
locals e;
e := new 0;
call getEvent(q,e);
eval [logEvent_h](e,log);
call handleEvent(e);
call maybe_update();
eval [loopPrime_h](q,log)
}
Pass control to the newevent handling loop
heapcell loopPrime_h;
heapcell logEvent_h;
heapcell mkEmptyLog_h;
proc mkEmptyLog_2(log) {...}
proc logEvent_2(e,log) {...}
proc loop_2(q)
{
locals log;
log := new 0;
eval [mkEmptyLog_h](log);
eval [loopPrime_h](q,log)
}
proc loopPrime_2(q,log){
locals e;
e := new 0;
call getEvent(q,e);
eval [logEvent_h](e,log); call handleEvent(e);
call maybe_update();
eval [loopPrime_h](q,log)
}
new event handling loop
The new behaviour:Log every eventbefore processing
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 }
}
Non-deterministically decide whether to update or not
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 }
}
Increment version number
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 newprocedures 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