Specifying and Verifying Device Drivers
description
Transcript of Specifying and Verifying Device Drivers
Specifying and VerifyingDevice Drivers
Wes Weimer
George Necula
Gregoire Sutre
Overview
• Background and Motivation
• Safety Properties
• Events and History Registers
• Driver States
• Specifications
• Verification
• Implementation
Background and Motivation
• Why bother?• PCC-style background
– Want a sound analysis
• Decided on an all-paths analysis– Similar to PREfix, ESC, Slam, Magick– Check pre- and post-conditions, kernel API– But also specify resource management, etc.
• Greg will recast as model checking later
Safety Properties
• We aim to verify that …– Allocated resources are freed
• Over the lifetime of the driver
• Enforce acquire order: no deadlocks
– Kernel functions are called correctly• Also includes ordering requirements and
obligations, like schedule() and signal_pending().
– Certain minimum actions are performed
Events
• Three broad types:+ allocation, - deallocation, ! event
• Applied to uninterpreted constructors:irq, dma, memory, schedule
• And given program values as arguments:+irq(5), -dma(3), !schedule
• Function call and return may be treated as events
History Register
• Abstract register used to verify programs– Like the “memory register” in PCC/VCGen– Never appears in user-written code
• Keeps an ever-increasing list of events– E.g., [ +irq(7) ; !schedule ; +misc(&qpmouse) ]
• May refer to it in conditions– E.g., PRE(!historyContains(+irq(i)))
Driver States
S0: Uninitialized
S1: Initialized
S2: Opened
init_module=0cleanup_module=0
misc_open=0 misc_release=0
misc_read=*
misc_write=*Inv(S0) = trueInv(S1) = historyContains(+misc(any))
Driver States (cont’d)
• Kernel API guarantees a certain state-like behavior for device drivers– E.g., misc_open will only be called if the driver is in S1
• We associate an invariant with each state– Captures history register values– And global variables in the driver
• Must check all paths from S0 to S0.– For resource leaks, etc. – Similar to data-flow analysis
Specifications
• Write spec in C, just like driver– May use data and control non-determinism– May use pre-conditions: like assert(), but will not fail
• Spec is minimal– List only things that must be done– Looks like a “template” device driver– Normal device drivers often written by copying others
• Side-conditions govern optional behavior– Like matching alloc/free– Or checking signals after sleeping
Specification Example
int init_module() { /* user code must look like this */PRE(!historyContains(+misc(any)));switch (__rand_int_range(0,3)) {
case 0: misc_register(any); /* use kernel API */ return 0;
case 1: return –5; /* EIO */case 2: return –6; /* ENXIO */case 3: return –19; /* ENODEV */
}}
Specification Example (2)
int misc_fasync(void *fd, void *filp, int on) {PRE(fd != 0 && filp && (on == 1 || on == 0)
&& current->state == TASK_RUNNING&& historyCount(!misc_open) >
historyCount(!misc_release));int retval = fasync_helper(fd, filp, on, any);if (retval < 0) return retval;else return 0;}
Kernel API Spec
• Model behavior of kernel functions– Like kmalloc(), schedule(), kfree()– May modify history register
• Example:int request_irq(int i, void *f, …) {PRE(historyCount(+irq(i)) == historyCount(-irq(i)) &&
implements(f, irq_handler) && …);if (__rand_int_range(0,1)) { return –5; } else { historyAdd(+irq(i)); return 0; }}
Kernel API Spec (2)int kmalloc(int size, int prio) {PRE(size > 0 && size < (128*1024) && (prio & GFP_KERNEL || prio & GFP_ATOMIC)
&& !(prio & GFP_KERNEL && prio & GFP_ATOMIC));if (prio & GFP_KERNEL) {
int old_state = current->state;current->state = TASK_INTERRUPTIBLE;schedule();current->state = old_state;historyAdd(!signal_pending);
}if (__rand_int_range(0,1) {
void *retval = __rand_int_range(1, MAX_INT/4)*4;historyAdd(+memory(retval));return retval;
} else return NULL;}
Side Conditions
• Used to verify optional behavior– Like resource allocation, scheduling– Conditions on the history register
• Examples:– Every !schedule must match a !signal_pending– After !must_return(-512), must return –512– All +irq(…) before any +dma(…)– All +irq(…) occur after !misc_open
Story so far …
• We have the user source code
• We have the history register abstraction– Keep track of the past, etc.
• We have a spec for the user code– Minimal, lists things that must be done
• We have a spec for the kernel API
• We have side conditions
Verification
• When does the user code meet the spec?– Each user function F “matches up” (defined
next) with our spec for function F– If the user calls a kernel function with a pre-
condition, the condition will always be satisfied– All of the side conditions hold over the life of
the device driver
“Matching Up”
• Can be phrased as a trace inclusion problem– More on that with the next speaker
• We’ll do it as an all-paths analysis
• Symbolically execute all paths in user code
• Keep track of history register
• Remove +resource(X) and –resource(X)
• Make sure other important events match
How to Verify
• Symbolically execute all paths in user code– End up with a set of final states U– Verify pre-conditions, use theorem prover
• Symbolically execute all paths in spec– End up with a set of final states S
• For every uU, find an sS such that– They have the same return value– Their history registers match up– If s proves T then u proves T (for a few global T’s)
How to Verify: States
• Start in S0, then check init_module()– Match each final state against the spec– Check all side-conditions in each final state
• Gather all states R with return value 0– Their LUB becomes the invariant for S1– Analyze all paths out of S1 (= symbolically
execute those functions with that invariant)
• Repeat until this terminates.
What can go wrong?
• User code can call a kernel function and fail to meet the pre-condition
• User code can fail to meet a side condition
• User code can fail to match up to the spec
• If so, report an error!
• And hope that it’s not a false positive.
Loop Invariants
• Used in symbolic execution to model loops
• Currently, we guess “true”– or use any other heuristic (e.g., on “for” loops)
• Except for the history register
• Gather all history changes in the loop body
• Add a special history element:• X_copies_of(history_element_list)
Does it work?
• Sample implementation
• Tested on init_module() in 46 misc device drivers (about 300k post-processed each)
• 30 successful terminations (25 meet spec)– Others look fine, spec needs to be refined
• 16 “no answer after 45 seconds” – All-paths analysis is expensive: O(2n)
Conclusions and Future Work
• Method for specifying and verifying– Spec looks like original code– Sound, incomplete, expensive– Uses abstract events
• Captures allocation, function ordering, other API and behavioral restrictions
• Next steps: – folding similar paths– handling concurrency (e.g., irq handlers)